summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/CMakeLists.txt93
-rw-r--r--server/FreeRDP-ServerConfig.cmake.in13
-rw-r--r--server/Mac/CMakeLists.txt78
-rw-r--r--server/Mac/ModuleOptions.cmake4
-rw-r--r--server/Mac/mf_audin.c64
-rw-r--r--server/Mac/mf_audin.h33
-rw-r--r--server/Mac/mf_event.c219
-rw-r--r--server/Mac/mf_event.h75
-rw-r--r--server/Mac/mf_info.c231
-rw-r--r--server/Mac/mf_info.h49
-rw-r--r--server/Mac/mf_input.c511
-rw-r--r--server/Mac/mf_input.h36
-rw-r--r--server/Mac/mf_interface.c0
-rw-r--r--server/Mac/mf_interface.h106
-rw-r--r--server/Mac/mf_mountain_lion.c269
-rw-r--r--server/Mac/mf_mountain_lion.h38
-rw-r--r--server/Mac/mf_peer.c485
-rw-r--r--server/Mac/mf_peer.h33
-rw-r--r--server/Mac/mf_rdpsnd.c200
-rw-r--r--server/Mac/mf_rdpsnd.h58
-rw-r--r--server/Mac/mf_types.h33
-rw-r--r--server/Mac/mfreerdp.c108
-rw-r--r--server/Mac/mfreerdp.h28
-rw-r--r--server/Sample/CMakeLists.txt86
-rw-r--r--server/Sample/ModuleOptions.cmake4
-rw-r--r--server/Sample/sf_ainput.c92
-rw-r--r--server/Sample/sf_ainput.h37
-rw-r--r--server/Sample/sf_audin.c112
-rw-r--r--server/Sample/sf_audin.h35
-rw-r--r--server/Sample/sf_encomsp.c43
-rw-r--r--server/Sample/sf_encomsp.h31
-rw-r--r--server/Sample/sf_rdpsnd.c62
-rw-r--r--server/Sample/sf_rdpsnd.h31
-rw-r--r--server/Sample/sfreerdp.c1440
-rw-r--r--server/Sample/sfreerdp.h76
-rw-r--r--server/Sample/test_icon.bmpbin0 -> 5770 bytes
-rw-r--r--server/Sample/test_icon.jpgbin0 -> 4096 bytes
-rw-r--r--server/Sample/test_icon.pngbin0 -> 6140 bytes
-rw-r--r--server/Sample/test_icon.ppm5572
-rw-r--r--server/Sample/test_icon.webpbin0 -> 2122 bytes
-rw-r--r--server/Windows/CMakeLists.txt128
-rw-r--r--server/Windows/ModuleOptions.cmake4
-rw-r--r--server/Windows/cli/CMakeLists.txt60
-rw-r--r--server/Windows/cli/wfreerdp.c171
-rw-r--r--server/Windows/cli/wfreerdp.h25
-rw-r--r--server/Windows/wf_directsound.c219
-rw-r--r--server/Windows/wf_directsound.h13
-rw-r--r--server/Windows/wf_dxgi.c486
-rw-r--r--server/Windows/wf_dxgi.h41
-rw-r--r--server/Windows/wf_info.c402
-rw-r--r--server/Windows/wf_info.h46
-rw-r--r--server/Windows/wf_input.c223
-rw-r--r--server/Windows/wf_input.h36
-rw-r--r--server/Windows/wf_interface.c341
-rw-r--r--server/Windows/wf_interface.h140
-rw-r--r--server/Windows/wf_mirage.c361
-rw-r--r--server/Windows/wf_mirage.h219
-rw-r--r--server/Windows/wf_peer.c414
-rw-r--r--server/Windows/wf_peer.h29
-rw-r--r--server/Windows/wf_rdpsnd.c152
-rw-r--r--server/Windows/wf_rdpsnd.h33
-rw-r--r--server/Windows/wf_settings.c102
-rw-r--r--server/Windows/wf_settings.h28
-rw-r--r--server/Windows/wf_update.c251
-rw-r--r--server/Windows/wf_update.h37
-rw-r--r--server/Windows/wf_wasapi.c333
-rw-r--r--server/Windows/wf_wasapi.h15
-rw-r--r--server/common/CMakeLists.txt77
-rw-r--r--server/common/server.c236
-rw-r--r--server/freerdp-server.pc.in15
-rw-r--r--server/proxy/CMakeLists.txt131
-rw-r--r--server/proxy/FreeRDP-ProxyConfig.cmake.in10
-rw-r--r--server/proxy/channels/CMakeLists.txt17
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.c711
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.h26
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.c2017
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.h47
-rw-r--r--server/proxy/channels/pf_channel_smartcard.c397
-rw-r--r--server/proxy/channels/pf_channel_smartcard.h39
-rw-r--r--server/proxy/cli/CMakeLists.txt60
-rw-r--r--server/proxy/cli/freerdp-proxy.1.in85
-rw-r--r--server/proxy/cli/freerdp_proxy.c161
-rw-r--r--server/proxy/config.ini53
-rw-r--r--server/proxy/freerdp-proxy.pc.in16
-rw-r--r--server/proxy/modules/CMakeLists.txt33
-rw-r--r--server/proxy/modules/README.md66
-rw-r--r--server/proxy/modules/bitmap-filter/CMakeLists.txt60
-rw-r--r--server/proxy/modules/bitmap-filter/bitmap-filter.cpp453
-rw-r--r--server/proxy/modules/demo/CMakeLists.txt53
-rw-r--r--server/proxy/modules/demo/demo.cpp422
-rw-r--r--server/proxy/modules/dyn-channel-dump/CMakeLists.txt58
-rw-r--r--server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp436
-rw-r--r--server/proxy/pf_channel.c355
-rw-r--r--server/proxy/pf_channel.h64
-rw-r--r--server/proxy/pf_client.c1102
-rw-r--r--server/proxy/pf_client.h31
-rw-r--r--server/proxy/pf_config.c1348
-rw-r--r--server/proxy/pf_context.c394
-rw-r--r--server/proxy/pf_input.c206
-rw-r--r--server/proxy/pf_input.h29
-rw-r--r--server/proxy/pf_modules.c633
-rw-r--r--server/proxy/pf_server.c1073
-rw-r--r--server/proxy/pf_server.h43
-rw-r--r--server/proxy/pf_update.c629
-rw-r--r--server/proxy/pf_update.h34
-rw-r--r--server/proxy/pf_utils.c95
-rw-r--r--server/proxy/pf_utils.h43
-rw-r--r--server/proxy/proxy_modules.h100
-rw-r--r--server/shadow/CMakeLists.txt245
-rw-r--r--server/shadow/FreeRDP-ShadowConfig.cmake.in14
-rw-r--r--server/shadow/Mac/CMakeLists.txt31
-rw-r--r--server/shadow/Mac/mac_shadow.c680
-rw-r--r--server/shadow/Mac/mac_shadow.h64
-rw-r--r--server/shadow/Win/CMakeLists.txt20
-rw-r--r--server/shadow/Win/win_dxgi.c794
-rw-r--r--server/shadow/Win/win_dxgi.h61
-rw-r--r--server/shadow/Win/win_rdp.c440
-rw-r--r--server/shadow/Win/win_rdp.h56
-rw-r--r--server/shadow/Win/win_shadow.c560
-rw-r--r--server/shadow/Win/win_shadow.h89
-rw-r--r--server/shadow/Win/win_wds.c850
-rw-r--r--server/shadow/Win/win_wds.h48
-rw-r--r--server/shadow/X11/CMakeLists.txt72
-rw-r--r--server/shadow/X11/x11_shadow.c1513
-rw-r--r--server/shadow/X11/x11_shadow.h114
-rw-r--r--server/shadow/freerdp-shadow-cli.1.in85
-rw-r--r--server/shadow/freerdp-shadow.pc.in15
-rw-r--r--server/shadow/shadow.c179
-rw-r--r--server/shadow/shadow.h44
-rw-r--r--server/shadow/shadow_audin.c104
-rw-r--r--server/shadow/shadow_audin.h39
-rw-r--r--server/shadow/shadow_capture.c263
-rw-r--r--server/shadow/shadow_capture.h52
-rw-r--r--server/shadow/shadow_channels.c66
-rw-r--r--server/shadow/shadow_channels.h45
-rw-r--r--server/shadow/shadow_client.c2612
-rw-r--r--server/shadow/shadow_client.h35
-rw-r--r--server/shadow/shadow_encoder.c520
-rw-r--r--server/shadow/shadow_encoder.h81
-rw-r--r--server/shadow/shadow_encomsp.c129
-rw-r--r--server/shadow/shadow_encomsp.h39
-rw-r--r--server/shadow/shadow_input.c114
-rw-r--r--server/shadow/shadow_input.h35
-rw-r--r--server/shadow/shadow_lobby.c85
-rw-r--r--server/shadow/shadow_lobby.h40
-rw-r--r--server/shadow/shadow_mcevent.c355
-rw-r--r--server/shadow/shadow_mcevent.h56
-rw-r--r--server/shadow/shadow_rdpgfx.c55
-rw-r--r--server/shadow/shadow_rdpgfx.h39
-rw-r--r--server/shadow/shadow_rdpsnd.c87
-rw-r--r--server/shadow/shadow_rdpsnd.h39
-rw-r--r--server/shadow/shadow_remdesk.c50
-rw-r--r--server/shadow/shadow_remdesk.h39
-rw-r--r--server/shadow/shadow_screen.c163
-rw-r--r--server/shadow/shadow_screen.h55
-rw-r--r--server/shadow/shadow_server.c1028
-rw-r--r--server/shadow/shadow_subsystem.c286
-rw-r--r--server/shadow/shadow_subsystem.h47
-rw-r--r--server/shadow/shadow_subsystem_builtin.c75
-rw-r--r--server/shadow/shadow_surface.c104
-rw-r--r--server/shadow/shadow_surface.h45
161 files changed, 39308 insertions, 0 deletions
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
new file mode 100644
index 0000000..895b7a4
--- /dev/null
+++ b/server/CMakeLists.txt
@@ -0,0 +1,93 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Servers
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+# Servers
+option(WITH_SHADOW "Compile with shadow server" ON)
+option(WITH_PROXY "Compile with proxy server" ON)
+option(WITH_PLATFORM_SERVER "Compile with platform server" ON)
+
+add_subdirectory(common)
+if (WITH_SHADOW)
+ add_subdirectory(shadow)
+endif()
+if (WITH_PROXY)
+ add_subdirectory(proxy)
+endif()
+
+if(WITH_SAMPLE)
+ add_subdirectory(Sample)
+endif()
+
+if (WITH_PLATFORM_SERVER)
+ if(NOT WIN32)
+ if(APPLE AND (NOT IOS))
+ add_subdirectory(Mac)
+ endif()
+ else()
+ add_subdirectory(Windows)
+ endif()
+
+ if(NOT DEFINED WITH_FREERDS)
+ set(WITH_FREERDS 1)
+ endif()
+
+ if(WITH_FREERDS AND (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/FreeRDS"))
+ add_subdirectory("FreeRDS")
+ endif()
+endif()
+
+# Pick up other servers
+
+set(FILENAME "ModuleOptions.cmake")
+file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}")
+
+foreach(FILEPATH ${FILEPATHS})
+ if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}")
+ string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" FREERDP_SERVER ${FILEPATH})
+ set(FREERDP_SERVER_ENABLED 0)
+ include(${FILEPATH})
+ if(FREERDP_SERVER_ENABLED)
+ if(NOT (${FREERDP_SERVER_VENDOR} MATCHES "FreeRDP"))
+ list(APPEND FREERDP_EXTRA_SERVERS ${FREERDP_SERVER})
+ endif()
+ endif()
+ endif()
+endforeach()
+
+foreach(FREERDP_SERVER ${FREERDP_EXTRA_SERVERS})
+ add_subdirectory(${FREERDP_SERVER})
+endforeach()
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-server.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-server)
+
+SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Server${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ServerConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake
+ DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ServerTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
diff --git a/server/FreeRDP-ServerConfig.cmake.in b/server/FreeRDP-ServerConfig.cmake.in
new file mode 100644
index 0000000..db7cb44
--- /dev/null
+++ b/server/FreeRDP-ServerConfig.cmake.in
@@ -0,0 +1,13 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Server_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Server_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Server_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Server_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ServerTargets.cmake")
diff --git a/server/Mac/CMakeLists.txt b/server/Mac/CMakeLists.txt
new file mode 100644
index 0000000..9dddb38
--- /dev/null
+++ b/server/Mac/CMakeLists.txt
@@ -0,0 +1,78 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Mac OS X Server cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+set(MODULE_NAME "mfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_MAC")
+
+FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
+FIND_LIBRARY(CORE_AUDIO CoreAudio)
+FIND_LIBRARY(CORE_VIDEO CoreVideo)
+FIND_LIBRARY(APP_SERVICES ApplicationServices)
+FIND_LIBRARY(IOKIT IOKit)
+FIND_LIBRARY(IOSURFACE IOSurface)
+FIND_LIBRARY(CARBON Carbon)
+
+set(${MODULE_PREFIX}_SRCS
+ mfreerdp.c
+ mfreerdp.h
+ mf_interface.c
+ mf_interface.h
+ mf_event.c
+ mf_event.h
+ mf_peer.c
+ mf_peer.h
+ mf_info.c
+ mf_info.h
+ mf_input.c
+ mf_input.h
+ mf_mountain_lion.c
+ mf_mountain_lion.h)
+
+if(CHANNEL_AUDIN_SERVER)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS}
+ mf_audin.c
+ mf_audin.h)
+endif()
+
+if(CHANNEL_RDPSND_SERVER)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS}
+ mf_rdpsnd.c
+ mf_rdpsnd.h)
+
+endif()
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS}
+ freerdp-server
+ ${AUDIO_TOOL}
+ ${CORE_AUDIO}
+ ${CORE_VIDEO}
+ ${APP_SERVICES}
+ ${IOKIT}
+ ${IOSURFACE}
+ ${CARBON})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Mac")
diff --git a/server/Mac/ModuleOptions.cmake b/server/Mac/ModuleOptions.cmake
new file mode 100644
index 0000000..bfd36cb
--- /dev/null
+++ b/server/Mac/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "mfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "X11")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Mac/mf_audin.c b/server/Mac/mf_audin.c
new file mode 100644
index 0000000..aaabafa
--- /dev/null
+++ b/server/Mac/mf_audin.c
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 "mfreerdp.h"
+
+#include "mf_audin.h"
+#include "mf_interface.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static UINT mf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ WLog_WARN(TAG, "not implemented");
+ WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
+ return CHANNEL_RC_OK;
+}
+
+BOOL mf_peer_audin_init(mfPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->audin = audin_server_context_new(context->vcm);
+ context->audin->rdpcontext = &context->_p;
+ context->audin->userdata = context;
+
+ context->audin->Data = mf_peer_audin_data;
+
+ return audin_server_set_formats(context->audin, -1, NULL);
+}
+
+void mf_peer_audin_uninit(mfPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ audin_server_context_free(context->audin);
+ context->audin = NULL;
+}
diff --git a/server/Mac/mf_audin.h b/server/Mac/mf_audin.h
new file mode 100644
index 0000000..31edffa
--- /dev/null
+++ b/server/Mac/mf_audin.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Corey Clayton <can.of.tuna@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_MAC_AUDIN_H
+#define FREERDP_SERVER_MAC_AUDIN_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "mf_types.h"
+#include "mfreerdp.h"
+
+BOOL mf_peer_audin_init(mfPeerContext* context);
+void mf_peer_audin_uninit(mfPeerContext* context);
+
+#endif /* FREERDP_SERVER_MAC_AUDIN_H */
diff --git a/server/Mac/mf_event.c b/server/Mac/mf_event.c
new file mode 100644
index 0000000..e30b9ac
--- /dev/null
+++ b/server/Mac/mf_event.c
@@ -0,0 +1,219 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mf_event.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+int mf_is_event_set(mfEventQueue* event_queue)
+{
+ fd_set rfds;
+ int num_set;
+ struct timeval time = { 0 };
+
+ FD_ZERO(&rfds);
+ FD_SET(event_queue->pipe_fd[0], &rfds);
+ num_set = select(event_queue->pipe_fd[0] + 1, &rfds, 0, 0, &time);
+
+ return (num_set == 1);
+}
+
+void mf_signal_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = write(event_queue->pipe_fd[1], "sig", 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_signal_event: error");
+}
+
+void mf_set_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = write(event_queue->pipe_fd[1], "sig", 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_set_event: error");
+}
+
+void mf_clear_events(mfEventQueue* event_queue)
+{
+ int length;
+
+ while (mf_is_event_set(event_queue))
+ {
+ length = read(event_queue->pipe_fd[0], &length, 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_clear_event: error");
+ }
+}
+
+void mf_clear_event(mfEventQueue* event_queue)
+{
+ int length;
+
+ length = read(event_queue->pipe_fd[0], &length, 4);
+
+ if (length != 4)
+ WLog_ERR(TAG, "mf_clear_event: error");
+}
+
+void mf_event_push(mfEventQueue* event_queue, mfEvent* event)
+{
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count >= event_queue->size)
+ {
+ event_queue->size *= 2;
+ event_queue->events =
+ (mfEvent**)realloc((void*)event_queue->events, sizeof(mfEvent*) * event_queue->size);
+ }
+
+ event_queue->events[(event_queue->count)++] = event;
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ mf_set_event(event_queue);
+}
+
+mfEvent* mf_event_peek(mfEventQueue* event_queue)
+{
+ mfEvent* event;
+
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count < 1)
+ event = NULL;
+ else
+ event = event_queue->events[0];
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ return event;
+}
+
+mfEvent* mf_event_pop(mfEventQueue* event_queue)
+{
+ mfEvent* event;
+
+ pthread_mutex_lock(&(event_queue->mutex));
+
+ if (event_queue->count < 1)
+ return NULL;
+
+ /* remove event signal */
+ mf_clear_event(event_queue);
+
+ event = event_queue->events[0];
+ (event_queue->count)--;
+
+ memmove(&event_queue->events[0], &event_queue->events[1], event_queue->count * sizeof(void*));
+
+ pthread_mutex_unlock(&(event_queue->mutex));
+
+ return event;
+}
+
+mfEventRegion* mf_event_region_new(int x, int y, int width, int height)
+{
+ mfEventRegion* event_region = malloc(sizeof(mfEventRegion));
+
+ if (event_region != NULL)
+ {
+ event_region->x = x;
+ event_region->y = y;
+ event_region->width = width;
+ event_region->height = height;
+ }
+
+ return event_region;
+}
+
+void mf_event_region_free(mfEventRegion* event_region)
+{
+ free(event_region);
+}
+
+mfEvent* mf_event_new(int type)
+{
+ mfEvent* event = malloc(sizeof(mfEvent));
+ if (!event)
+ return NULL;
+ event->type = type;
+ return event;
+}
+
+void mf_event_free(mfEvent* event)
+{
+ free(event);
+}
+
+mfEventQueue* mf_event_queue_new()
+{
+ mfEventQueue* event_queue = malloc(sizeof(mfEventQueue));
+
+ if (event_queue != NULL)
+ {
+ event_queue->pipe_fd[0] = -1;
+ event_queue->pipe_fd[1] = -1;
+
+ event_queue->size = 16;
+ event_queue->count = 0;
+ event_queue->events = (mfEvent**)malloc(sizeof(mfEvent*) * event_queue->size);
+
+ if (pipe(event_queue->pipe_fd) < 0)
+ {
+ free(event_queue);
+ return NULL;
+ }
+
+ pthread_mutex_init(&(event_queue->mutex), NULL);
+ }
+
+ return event_queue;
+}
+
+void mf_event_queue_free(mfEventQueue* event_queue)
+{
+ if (event_queue->pipe_fd[0] != -1)
+ {
+ close(event_queue->pipe_fd[0]);
+ event_queue->pipe_fd[0] = -1;
+ }
+
+ if (event_queue->pipe_fd[1] != -1)
+ {
+ close(event_queue->pipe_fd[1]);
+ event_queue->pipe_fd[1] = -1;
+ }
+
+ pthread_mutex_destroy(&(event_queue->mutex));
+}
diff --git a/server/Mac/mf_event.h b/server/Mac/mf_event.h
new file mode 100644
index 0000000..197cfd7
--- /dev/null
+++ b/server/Mac/mf_event.h
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_EVENT_H
+#define FREERDP_SERVER_MAC_EVENT_H
+
+typedef struct mf_event mfEvent;
+typedef struct mf_event_queue mfEventQueue;
+typedef struct mf_event_region mfEventRegion;
+
+#include <pthread.h>
+#include "mfreerdp.h"
+
+//#include "mf_peer.h"
+
+enum mf_event_type
+{
+ FREERDP_SERVER_MAC_EVENT_TYPE_REGION,
+ FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK
+};
+
+struct mf_event
+{
+ int type;
+};
+
+struct mf_event_queue
+{
+ int size;
+ int count;
+ int pipe_fd[2];
+ mfEvent** events;
+ pthread_mutex_t mutex;
+};
+
+struct mf_event_region
+{
+ int type;
+
+ int x;
+ int y;
+ int width;
+ int height;
+};
+
+void mf_event_push(mfEventQueue* event_queue, mfEvent* event);
+mfEvent* mf_event_peek(mfEventQueue* event_queue);
+mfEvent* mf_event_pop(mfEventQueue* event_queue);
+
+mfEventRegion* mf_event_region_new(int x, int y, int width, int height);
+void mf_event_region_free(mfEventRegion* event_region);
+
+mfEvent* mf_event_new(int type);
+void mf_event_free(mfEvent* event);
+
+mfEventQueue* mf_event_queue_new(void);
+void mf_event_queue_free(mfEventQueue* event_queue);
+
+#endif /* FREERDP_SERVER_MAC_EVENT_H */
diff --git a/server/Mac/mf_info.c b/server/Mac/mf_info.c
new file mode 100644
index 0000000..ab523e1
--- /dev/null
+++ b/server/Mac/mf_info.c
@@ -0,0 +1,231 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "mf_info.h"
+#include "mf_mountain_lion.h"
+
+#define MF_INFO_DEFAULT_FPS 30
+#define MF_INFO_MAXPEERS 32
+
+static mfInfo* mfInfoInstance = NULL;
+
+int mf_info_lock(mfInfo* mfi)
+{
+ int status = pthread_mutex_lock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+int mf_info_try_lock(mfInfo* mfi, UINT32 ms)
+{
+ int status = pthread_mutex_trylock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ case EBUSY:
+ return FALSE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+int mf_info_unlock(mfInfo* mfi)
+{
+ int status = pthread_mutex_unlock(&mfi->mutex);
+
+ switch (status)
+ {
+ case 0:
+ return TRUE;
+ break;
+
+ default:
+ return -1;
+ break;
+ }
+
+ return 1;
+}
+
+mfInfo* mf_info_init()
+{
+ mfInfo* mfi;
+
+ mfi = (mfInfo*)calloc(1, sizeof(mfInfo));
+
+ if (mfi != NULL)
+ {
+ pthread_mutex_init(&mfi->mutex, NULL);
+
+ mfi->peers = (freerdp_peer**)calloc(MF_INFO_MAXPEERS, sizeof(freerdp_peer*));
+ if (!mfi->peers)
+ {
+ free(mfi);
+ return NULL;
+ }
+
+ mfi->framesPerSecond = MF_INFO_DEFAULT_FPS;
+ mfi->input_disabled = FALSE;
+ }
+
+ return mfi;
+}
+
+mfInfo* mf_info_get_instance()
+{
+ if (mfInfoInstance == NULL)
+ mfInfoInstance = mf_info_init();
+
+ return mfInfoInstance;
+}
+
+void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context)
+{
+ if (mf_info_lock(mfi) > 0)
+ {
+ int peerId;
+
+ if (mfi->peerCount == MF_INFO_MAXPEERS)
+ {
+ mf_info_unlock(mfi);
+ return;
+ }
+
+ context->info = mfi;
+
+ if (mfi->peerCount == 0)
+ {
+ mf_mlion_display_info(&mfi->servscreen_width, &mfi->servscreen_height, &mfi->scale);
+ mf_mlion_screen_updates_init();
+ mf_mlion_start_getting_screen_updates();
+ }
+
+ peerId = 0;
+
+ for (int i = 0; i < MF_INFO_MAXPEERS; ++i)
+ {
+ // empty index will be our peer id
+ if (mfi->peers[i] == NULL)
+ {
+ peerId = i;
+ break;
+ }
+ }
+
+ mfi->peers[peerId] = ((rdpContext*)context)->peer;
+ mfi->peers[peerId]->pId = peerId;
+ mfi->peerCount++;
+
+ mf_info_unlock(mfi);
+ }
+}
+
+void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context)
+{
+ if (mf_info_lock(mfi) > 0)
+ {
+ int peerId;
+
+ peerId = ((rdpContext*)context)->peer->pId;
+ mfi->peers[peerId] = NULL;
+ mfi->peerCount--;
+
+ if (mfi->peerCount == 0)
+ mf_mlion_stop_getting_screen_updates();
+
+ mf_info_unlock(mfi);
+ }
+}
+
+BOOL mf_info_have_updates(mfInfo* mfi)
+{
+ if (mfi->framesWaiting == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+void mf_info_update_changes(mfInfo* mfi)
+{
+}
+
+void mf_info_find_invalid_region(mfInfo* mfi)
+{
+ mf_mlion_get_dirty_region(&mfi->invalid);
+}
+
+void mf_info_clear_invalid_region(mfInfo* mfi)
+{
+ mf_mlion_clear_dirty_region();
+ mfi->invalid.height = 0;
+ mfi->invalid.width = 0;
+}
+
+void mf_info_invalidate_full_screen(mfInfo* mfi)
+{
+ mfi->invalid.x = 0;
+ mfi->invalid.y = 0;
+ mfi->invalid.height = mfi->servscreen_height;
+ mfi->invalid.width = mfi->servscreen_width;
+}
+
+BOOL mf_info_have_invalid_region(mfInfo* mfi)
+{
+ if (mfi->invalid.width * mfi->invalid.height == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch)
+{
+ *width = mfi->invalid.width / mfi->scale;
+ *height = mfi->invalid.height / mfi->scale;
+ *pitch = mfi->servscreen_width * mfi->scale * 4;
+
+ mf_mlion_get_pixelData(mfi->invalid.x / mfi->scale, mfi->invalid.y / mfi->scale, *width,
+ *height, pBits);
+
+ *pBits = *pBits + (mfi->invalid.x * 4) + (*pitch * mfi->invalid.y);
+}
diff --git a/server/Mac/mf_info.h b/server/Mac/mf_info.h
new file mode 100644
index 0000000..8f08982
--- /dev/null
+++ b/server/Mac/mf_info.h
@@ -0,0 +1,49 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_INFO_H
+#define FREERDP_SERVER_MAC_INFO_H
+
+#define FREERDP_SERVER_MAC_INFO_DEFAULT_FPS 1
+#define FREERDP_SERVER_MAC_INFO_MAXPEERS 1
+
+#include <winpr/wtypes.h>
+#include <freerdp/codec/rfx.h>
+
+#include "mf_interface.h"
+
+int mf_info_lock(mfInfo* mfi);
+int mf_info_try_lock(mfInfo* mfi, UINT32 ms);
+int mf_info_unlock(mfInfo* mfi);
+
+mfInfo* mf_info_get_instance(void);
+void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context);
+void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context);
+
+BOOL mf_info_have_updates(mfInfo* mfi);
+void mf_info_update_changes(mfInfo* mfi);
+void mf_info_find_invalid_region(mfInfo* mfi);
+void mf_info_clear_invalid_region(mfInfo* mfi);
+void mf_info_invalidate_full_screen(mfInfo* mfi);
+BOOL mf_info_have_invalid_region(mfInfo* mfi);
+void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch);
+// BOOL CALLBACK mf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM
+// dwData);
+
+#endif /* FREERDP_SERVER_MAC_INFO_H */
diff --git a/server/Mac/mf_input.c b/server/Mac/mf_input.c
new file mode 100644
index 0000000..fd4af85
--- /dev/null
+++ b/server/Mac/mf_input.c
@@ -0,0 +1,511 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Input)
+ *
+ * Copyright 2013 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Carbon/Carbon.h>
+
+#include <winpr/windows.h>
+
+#include "mf_input.h"
+#include "mf_info.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static const CGKeyCode keymap[256] = {
+ 0xFF, // 0x0
+ kVK_Escape, // 0x1
+ kVK_ANSI_1, // 0x2
+ kVK_ANSI_2, // 0x3
+ kVK_ANSI_3, // 0x4
+ kVK_ANSI_4, // 0x5
+ kVK_ANSI_5, // 0x6
+ kVK_ANSI_6, // 0x7
+ kVK_ANSI_7, // 0x8
+ kVK_ANSI_8, // 0x9
+ kVK_ANSI_9, // 0xa
+ kVK_ANSI_0, // 0xb
+ kVK_ANSI_Minus, // 0xc
+ kVK_ANSI_Equal, // 0xd
+ kVK_Delete, // 0xe
+ kVK_Tab, // 0xf
+ kVK_ANSI_Q, // 0x10
+ kVK_ANSI_W, // 0x11
+ kVK_ANSI_E, // 0x12
+ kVK_ANSI_R, // 0x13
+ kVK_ANSI_T, // 0x14
+ kVK_ANSI_Y, // 0x15
+ kVK_ANSI_U, // 0x16
+ kVK_ANSI_I, // 0x17
+ kVK_ANSI_O, // 0x18
+ kVK_ANSI_P, // 0x19
+ kVK_ANSI_LeftBracket, // 0x1a
+ kVK_ANSI_RightBracket, // 0x1b
+ kVK_Return, // 0x1c
+ kVK_Control, // 0x1d
+ kVK_ANSI_A, // 0x1e
+ kVK_ANSI_S, // 0x1f
+ kVK_ANSI_D, // 0x20
+ kVK_ANSI_F, // 0x21
+ kVK_ANSI_G, // 0x22
+ kVK_ANSI_H, // 0x23
+ kVK_ANSI_J, // 0x24
+ kVK_ANSI_K, // 0x25
+ kVK_ANSI_L, // 0x26
+ kVK_ANSI_Semicolon, // 0x27
+ kVK_ANSI_Quote, // 0x28
+ kVK_ANSI_Grave, // 0x29
+ kVK_Shift, // 0x2a
+ kVK_ANSI_Backslash, // 0x2b
+ kVK_ANSI_Z, // 0x2c
+ kVK_ANSI_X, // 0x2d
+ kVK_ANSI_C, // 0x2e
+ kVK_ANSI_V, // 0x2f
+ kVK_ANSI_B, // 0x30
+ kVK_ANSI_N, // 0x31
+ kVK_ANSI_M, // 0x32
+ kVK_ANSI_Comma, // 0x33
+ kVK_ANSI_Period, // 0x34
+ kVK_ANSI_Slash, // 0x35
+ kVK_Shift, // 0x36
+ kVK_ANSI_KeypadMultiply, // 0x37
+ kVK_Option, // 0x38
+ kVK_Space, // 0x39
+ kVK_CapsLock, // 0x3a
+ kVK_F1, // 0x3b
+ kVK_F2, // 0x3c
+ kVK_F3, // 0x3d
+ kVK_F4, // 0x3e
+ kVK_F5, // 0x3f
+ kVK_F6, // 0x40
+ kVK_F7, // 0x41
+ kVK_F8, // 0x42
+ kVK_F9, // 0x43
+ kVK_F10, // 0x44
+ 0xFF, // 0x45 -- numlock
+ 0xFF, // 0x46 -- scroll lock
+ kVK_ANSI_Keypad7, // 0x47
+ kVK_ANSI_Keypad8, // 0x48
+ kVK_ANSI_Keypad9, // 0x49
+ kVK_ANSI_KeypadMinus, // 0x4a
+ kVK_ANSI_Keypad4, // 0x4b
+ kVK_ANSI_Keypad5, // 0x4c
+ kVK_ANSI_Keypad6, // 0x4d
+ kVK_ANSI_KeypadPlus, // 0x4e
+ kVK_ANSI_Keypad1, // 0x4f
+ kVK_ANSI_Keypad2, // 0x50
+ kVK_ANSI_Keypad3, // 0x51
+ kVK_ANSI_Keypad0, // 0x52
+ kVK_ANSI_KeypadDecimal, // 0x53
+ 0xFF, // 0x54
+ 0xFF, // 0x55
+ 0xFF, // 0x56
+ kVK_F11, // 0x57
+ kVK_F12, // 0x58
+ 0xFF, // 0x59 -- pause
+ 0xFF, // 0x5a
+ kVK_Control, // 0x5b
+ kVK_Control, // 0x5c
+ 0xFF, // 0x5d -- application
+ 0xFF, // 0x5e -- power
+ 0xFF, // 0x5f -- sleep
+ 0xFF, // 0x60
+ 0xFF, // 0x61
+ 0xFF, // 0x62
+ 0xFF, // 0x63 -- wake
+ 0xFF, // 0x64
+ 0xFF, // 0x65
+ 0xFF, // 0x66
+ 0xFF, // 0x67
+ 0xFF, // 0x68
+ 0xFF, // 0x69
+ 0xFF, // 0x6a
+ 0xFF, // 0x6b
+ 0xFF, // 0x6c
+ 0xFF, // 0x6d
+ 0xFF, // 0x6e
+ 0xFF, // 0x6f
+ 0xFF, // 0x70
+ 0xFF, // 0x71
+ 0xFF, // 0x72
+ 0xFF, // 0x73
+ 0xFF, // 0x74
+ 0xFF, // 0x75
+ 0xFF, // 0x76
+ 0xFF, // 0x77
+ 0xFF, // 0x78
+ 0xFF, // 0x79
+ 0xFF, // 0x7a
+ 0xFF, // 0x7b
+ 0xFF, // 0x7c
+ 0xFF, // 0x7d
+ 0xFF, // 0x7e
+ 0xFF, // 0x7f
+ 0xFF, // 0x80
+ 0xFF, // 0x81
+ 0xFF, // 0x82
+ 0xFF, // 0x83
+ 0xFF, // 0x84
+ 0xFF, // 0x85
+ 0xFF, // 0x86
+ 0xFF, // 0x87
+ 0xFF, // 0x88
+ 0xFF, // 0x89
+ 0xFF, // 0x8a
+ 0xFF, // 0x8b
+ 0xFF, // 0x8c
+ 0xFF, // 0x8d
+ 0xFF, // 0x8e
+ 0xFF, // 0x8f
+ 0xFF, // 0x90
+ 0xFF, // 0x91
+ 0xFF, // 0x92
+ 0xFF, // 0x93
+ 0xFF, // 0x94
+ 0xFF, // 0x95
+ 0xFF, // 0x96
+ 0xFF, // 0x97
+ 0xFF, // 0x98
+ 0xFF, // 0x99
+ 0xFF, // 0x9a
+ 0xFF, // 0x9b
+ 0xFF, // 0x9c
+ 0xFF, // 0x9d
+ 0xFF, // 0x9e
+ 0xFF, // 0x9f
+ 0xFF, // 0xa0
+ 0xFF, // 0xa1
+ 0xFF, // 0xa2
+ 0xFF, // 0xa3
+ 0xFF, // 0xa4
+ 0xFF, // 0xa5
+ 0xFF, // 0xa6
+ 0xFF, // 0xa7
+ 0xFF, // 0xa8
+ 0xFF, // 0xa9
+ 0xFF, // 0xaa
+ 0xFF, // 0xab
+ 0xFF, // 0xac
+ 0xFF, // 0xad
+ 0xFF, // 0xae
+ 0xFF, // 0xaf
+ 0xFF, // 0xb0
+ 0xFF, // 0xb1
+ 0xFF, // 0xb2
+ 0xFF, // 0xb3
+ 0xFF, // 0xb4
+ 0xFF, // 0xb5
+ 0xFF, // 0xb6
+ 0xFF, // 0xb7
+ 0xFF, // 0xb8
+ 0xFF, // 0xb9
+ 0xFF, // 0xba
+ 0xFF, // 0xbb
+ 0xFF, // 0xbc
+ 0xFF, // 0xbd
+ 0xFF, // 0xbe
+ 0xFF, // 0xbf
+ 0xFF, // 0xc0
+ 0xFF, // 0xc1
+ 0xFF, // 0xc2
+ 0xFF, // 0xc3
+ 0xFF, // 0xc4
+ 0xFF, // 0xc5
+ 0xFF, // 0xc6
+ 0xFF, // 0xc7
+ 0xFF, // 0xc8
+ 0xFF, // 0xc9
+ 0xFF, // 0xca
+ 0xFF, // 0xcb
+ 0xFF, // 0xcc
+ 0xFF, // 0xcd
+ 0xFF, // 0xce
+ 0xFF, // 0xcf
+ 0xFF, // 0xd0
+ 0xFF, // 0xd1
+ 0xFF, // 0xd2
+ 0xFF, // 0xd3
+ 0xFF, // 0xd4
+ 0xFF, // 0xd5
+ 0xFF, // 0xd6
+ 0xFF, // 0xd7
+ 0xFF, // 0xd8
+ 0xFF, // 0xd9
+ 0xFF, // 0xda
+ 0xFF, // 0xdb
+ 0xFF, // 0xdc
+ 0xFF, // 0xdd
+ 0xFF, // 0xde
+ 0xFF, // 0xdf
+ 0xFF, // 0xe0
+ 0xFF, // 0xe1
+ 0xFF, // 0xe2
+ 0xFF, // 0xe3
+ 0xFF, // 0xe4
+ 0xFF, // 0xe5
+ 0xFF, // 0xe6
+ 0xFF, // 0xe7
+ 0xFF, // 0xe8
+ 0xFF, // 0xe9
+ 0xFF, // 0xea
+ 0xFF, // 0xeb
+ 0xFF, // 0xec
+ 0xFF, // 0xed
+ 0xFF, // 0xee
+ 0xFF, // 0xef
+ 0xFF, // 0xf0
+ 0xFF, // 0xf1
+ 0xFF, // 0xf2
+ 0xFF, // 0xf3
+ 0xFF, // 0xf4
+ 0xFF, // 0xf5
+ 0xFF, // 0xf6
+ 0xFF, // 0xf7
+ 0xFF, // 0xf8
+ 0xFF, // 0xf9
+ 0xFF, // 0xfa
+ 0xFF, // 0xfb
+ 0xFF, // 0xfc
+ 0xFF, // 0xfd
+ 0xFF, // 0xfe
+};
+
+BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ BOOL keyDown = TRUE;
+ CGEventRef kbEvent;
+ CGKeyCode kCode = 0xFF;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ {
+ keyDown = FALSE;
+ }
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ {
+ switch (code)
+ {
+ // case 0x52: //insert
+ case 0x53:
+ kCode = kVK_ForwardDelete;
+ break;
+
+ case 0x4B:
+ kCode = kVK_LeftArrow;
+ break;
+
+ case 0x47:
+ kCode = kVK_Home;
+ break;
+
+ case 0x4F:
+ kCode = kVK_End;
+ break;
+
+ case 0x48:
+ kCode = kVK_UpArrow;
+ break;
+
+ case 0x50:
+ kCode = kVK_DownArrow;
+ break;
+
+ case 0x49:
+ kCode = kVK_PageUp;
+ break;
+
+ case 0x51:
+ kCode = kVK_PageDown;
+ break;
+
+ case 0x4D:
+ kCode = kVK_RightArrow;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ kCode = keymap[code];
+ }
+
+ kbEvent = CGEventCreateKeyboardEvent(source, kCode, keyDown);
+ CGEventPost(kCGHIDEventTap, kbEvent);
+ CFRelease(kbEvent);
+ CFRelease(source);
+ return TRUE;
+}
+
+BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ float width, height;
+ CGWheelCount wheelCount = 2;
+ INT32 scroll_x = 0;
+ INT32 scroll_y = 0;
+
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ INT32 scroll = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ scroll = -(flags & WheelRotationMask) / 392;
+ else
+ scroll = (flags & WheelRotationMask) / 120;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ scroll_y = scroll;
+ else
+ scroll_x = scroll;
+
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine,
+ wheelCount, scroll_y, scroll_x);
+ CGEventPost(kCGHIDEventTap, scrollEvent);
+ CFRelease(scrollEvent);
+ CFRelease(source);
+ }
+ else
+ {
+ mfInfo* mfi;
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventType mouseType = kCGEventNull;
+ CGMouseButton mouseButton = kCGMouseButtonLeft;
+ mfi = mf_info_get_instance();
+ // width and height of primary screen (even in multimon setups
+ width = (float)mfi->servscreen_width;
+ height = (float)mfi->servscreen_height;
+ x += mfi->servscreen_xoffset;
+ y += mfi->servscreen_yoffset;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ if (mfi->mouse_down_left == TRUE)
+ {
+ mouseType = kCGEventLeftMouseDragged;
+ }
+ else if (mfi->mouse_down_right == TRUE)
+ {
+ mouseType = kCGEventRightMouseDragged;
+ }
+ else if (mfi->mouse_down_other == TRUE)
+ {
+ mouseType = kCGEventOtherMouseDragged;
+ }
+ else
+ {
+ mouseType = kCGEventMouseMoved;
+ }
+
+ CGEventRef move = CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y),
+ mouseButton // ignored for just movement
+ );
+ CGEventPost(kCGHIDEventTap, move);
+ CFRelease(move);
+ }
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventLeftMouseDown;
+ mfi->mouse_down_left = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventLeftMouseUp;
+ mfi->mouse_down_right = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ mouseButton = kCGMouseButtonRight;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventRightMouseDown;
+ mfi->mouse_down_right = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventRightMouseUp;
+ mfi->mouse_down_right = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ mouseButton = kCGMouseButtonCenter;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventOtherMouseDown;
+ mfi->mouse_down_other = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventOtherMouseUp;
+ mfi->mouse_down_other = FALSE;
+ }
+ }
+
+ CGEventRef mouseEvent =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, mouseEvent);
+ CFRelease(mouseEvent);
+ CFRelease(source);
+ }
+
+ return TRUE;
+}
+
+BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
+
+BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
+
+BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ return FALSE;
+}
diff --git a/server/Mac/mf_input.h b/server/Mac/mf_input.h
new file mode 100644
index 0000000..4c039f8
--- /dev/null
+++ b/server/Mac/mf_input.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Input)
+ *
+ * Copyright 2013 Corey Clayton <can.of.tuna@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_MAC_INPUT_H
+#define FREERDP_SERVER_MAC_INPUT_H
+
+#include "mf_interface.h"
+
+BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+// dummy versions
+BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+#endif /* FREERDP_SERVER_MAC_INPUT_H */
diff --git a/server/Mac/mf_interface.c b/server/Mac/mf_interface.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/server/Mac/mf_interface.c
diff --git a/server/Mac/mf_interface.h b/server/Mac/mf_interface.h
new file mode 100644
index 0000000..6ba86f3
--- /dev/null
+++ b/server/Mac/mf_interface.h
@@ -0,0 +1,106 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_INTERFACE_H
+#define FREERDP_SERVER_MAC_INTERFACE_H
+
+#include <pthread.h>
+
+#include <freerdp/config.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/listener.h>
+#include <freerdp/freerdp.h>
+
+#include <winpr/crt.h>
+
+#ifdef WITH_SERVER_CHANNELS
+#include <freerdp/channels/wtsvc.h>
+#endif
+
+#ifdef CHANNEL_RDPSND_SERVER
+#include <freerdp/server/rdpsnd.h>
+#include "mf_rdpsnd.h"
+#endif
+
+#ifdef CHANNEL_AUDIN_SERVER
+#include <freerdp/server/audin.h>
+#include "mf_audin.h"
+#endif
+
+#include "mf_types.h"
+
+struct mf_peer_context
+{
+ rdpContext _p;
+
+ mfInfo* info;
+ wStream* s;
+ BOOL activated;
+ UINT32 frame_id;
+ BOOL audin_open;
+ RFX_CONTEXT* rfx_context;
+ NSC_CONTEXT* nsc_context;
+
+#ifdef WITH_SERVER_CHANNELS
+ HANDLE vcm;
+#endif
+
+#ifdef CHANNEL_AUDIN_SERVER
+ audin_server_context* audin;
+#endif
+
+#ifdef CHANNEL_RDPSND_SERVER
+ RdpsndServerContext* rdpsnd;
+#endif
+};
+
+struct mf_info
+{
+ // STREAM* s;
+
+ // screen and monitor info
+ UINT32 screenID;
+ UINT32 virtscreen_width;
+ UINT32 virtscreen_height;
+ UINT32 servscreen_width;
+ UINT32 servscreen_height;
+ UINT32 servscreen_xoffset;
+ UINT32 servscreen_yoffset;
+
+ int bitsPerPixel;
+ int peerCount;
+ int activePeerCount;
+ int framesPerSecond;
+ freerdp_peer** peers;
+ unsigned int framesWaiting;
+ UINT32 scale;
+
+ RFX_RECT invalid;
+ pthread_mutex_t mutex;
+
+ BOOL mouse_down_left;
+ BOOL mouse_down_right;
+ BOOL mouse_down_other;
+ BOOL input_disabled;
+ BOOL force_all_disconnect;
+};
+
+#endif /* FREERDP_SERVER_MAC_INTERFACE_H */
diff --git a/server/Mac/mf_mountain_lion.c b/server/Mac/mf_mountain_lion.c
new file mode 100644
index 0000000..9db86b8
--- /dev/null
+++ b/server/Mac/mf_mountain_lion.c
@@ -0,0 +1,269 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <dispatch/dispatch.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreVideo/CoreVideo.h>
+#include <IOKit/IOKitLib.h>
+#include <IOSurface/IOSurface.h>
+
+#include "mf_mountain_lion.h"
+
+dispatch_semaphore_t region_sem;
+dispatch_semaphore_t data_sem;
+dispatch_queue_t screen_update_q;
+CGDisplayStreamRef stream;
+
+CGDisplayStreamUpdateRef lastUpdate = NULL;
+
+BYTE* localBuf = NULL;
+
+BOOL ready = FALSE;
+
+void (^streamHandler)(CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef,
+ CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status,
+ uint64_t displayTime, IOSurfaceRef frameSurface,
+ CGDisplayStreamUpdateRef updateRef) {
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ // may need to move this down
+ if (ready == TRUE)
+ {
+
+ RFX_RECT rect;
+ unsigned long offset_beg;
+ unsigned long stride;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = 0;
+ rect.height = 0;
+ mf_mlion_peek_dirty_region(&rect);
+
+ // lock surface
+ IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ // get pointer
+ void* baseAddress = IOSurfaceGetBaseAddress(frameSurface);
+ // copy region
+
+ stride = IOSurfaceGetBytesPerRow(frameSurface);
+ // memcpy(localBuf, baseAddress + offset_beg, surflen);
+ for (int i = 0; i < rect.height; i++)
+ {
+ offset_beg = (stride * (rect.y + i) + (rect.x * 4));
+ memcpy(localBuf + offset_beg, baseAddress + offset_beg, rect.width * 4);
+ }
+
+ // unlock surface
+ IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+
+ ready = FALSE;
+ dispatch_semaphore_signal(data_sem);
+ }
+
+ if (status != kCGDisplayStreamFrameStatusFrameComplete)
+ {
+ switch (status)
+ {
+ case kCGDisplayStreamFrameStatusFrameIdle:
+ break;
+
+ case kCGDisplayStreamFrameStatusStopped:
+ break;
+
+ case kCGDisplayStreamFrameStatusFrameBlank:
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (lastUpdate == NULL)
+ {
+ CFRetain(updateRef);
+ lastUpdate = updateRef;
+ }
+ else
+ {
+ CGDisplayStreamUpdateRef tmpRef;
+ tmpRef = lastUpdate;
+ lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef);
+ CFRelease(tmpRef);
+ }
+
+ dispatch_semaphore_signal(region_sem);
+};
+
+int mf_mlion_display_info(UINT32* disp_width, UINT32* disp_height, UINT32* scale)
+{
+ CGDirectDisplayID display_id;
+
+ display_id = CGMainDisplayID();
+
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+
+ size_t pixelWidth = CGDisplayModeGetPixelWidth(mode);
+ // size_t pixelHeight = CGDisplayModeGetPixelHeight(mode);
+
+ size_t wide = CGDisplayPixelsWide(display_id);
+ size_t high = CGDisplayPixelsHigh(display_id);
+
+ CGDisplayModeRelease(mode);
+
+ *disp_width = wide; // pixelWidth;
+ *disp_height = high; // pixelHeight;
+ *scale = pixelWidth / wide;
+
+ return 0;
+}
+
+int mf_mlion_screen_updates_init()
+{
+ CGDirectDisplayID display_id;
+
+ display_id = CGMainDisplayID();
+
+ screen_update_q = dispatch_queue_create("mfreerdp.server.screenUpdate", NULL);
+
+ region_sem = dispatch_semaphore_create(1);
+ data_sem = dispatch_semaphore_create(1);
+
+ UINT32 pixelWidth;
+ UINT32 pixelHeight;
+ UINT32 scale;
+
+ mf_mlion_display_info(&pixelWidth, &pixelHeight, &scale);
+
+ localBuf = malloc(pixelWidth * pixelHeight * 4);
+ if (!localBuf)
+ return -1;
+
+ CFDictionaryRef opts;
+
+ void* keys[2];
+ void* values[2];
+
+ keys[0] = (void*)kCGDisplayStreamShowCursor;
+ values[0] = (void*)kCFBooleanFalse;
+
+ opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1,
+ NULL, NULL);
+
+ stream = CGDisplayStreamCreateWithDispatchQueue(display_id, pixelWidth, pixelHeight, 'BGRA',
+ opts, screen_update_q, streamHandler);
+
+ CFRelease(opts);
+
+ return 0;
+}
+
+int mf_mlion_start_getting_screen_updates()
+{
+ CGError err;
+
+ err = CGDisplayStreamStart(stream);
+
+ if (err != kCGErrorSuccess)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+int mf_mlion_stop_getting_screen_updates()
+{
+ CGError err;
+
+ err = CGDisplayStreamStop(stream);
+
+ if (err != kCGErrorSuccess)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+int mf_mlion_get_dirty_region(RFX_RECT* invalid)
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ if (lastUpdate != NULL)
+ {
+ mf_mlion_peek_dirty_region(invalid);
+ }
+
+ dispatch_semaphore_signal(region_sem);
+
+ return 0;
+}
+
+int mf_mlion_peek_dirty_region(RFX_RECT* invalid)
+{
+ size_t num_rects;
+ CGRect dirtyRegion;
+
+ const CGRect* rects =
+ CGDisplayStreamUpdateGetRects(lastUpdate, kCGDisplayStreamUpdateDirtyRects, &num_rects);
+
+ if (num_rects == 0)
+ {
+ return 0;
+ }
+
+ dirtyRegion = *rects;
+ for (size_t i = 0; i < num_rects; i++)
+ {
+ dirtyRegion = CGRectUnion(dirtyRegion, *(rects + i));
+ }
+
+ invalid->x = dirtyRegion.origin.x;
+ invalid->y = dirtyRegion.origin.y;
+ invalid->height = dirtyRegion.size.height;
+ invalid->width = dirtyRegion.size.width;
+
+ return 0;
+}
+
+int mf_mlion_clear_dirty_region()
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+
+ CFRelease(lastUpdate);
+ lastUpdate = NULL;
+
+ dispatch_semaphore_signal(region_sem);
+
+ return 0;
+}
+
+int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData)
+{
+ dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER);
+ ready = TRUE;
+ dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(region_sem);
+
+ // this second wait allows us to block until data is copied... more on this later
+ dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER);
+ *pxData = localBuf;
+ dispatch_semaphore_signal(data_sem);
+
+ return 0;
+}
diff --git a/server/Mac/mf_mountain_lion.h b/server/Mac/mf_mountain_lion.h
new file mode 100644
index 0000000..c1fbe9b
--- /dev/null
+++ b/server/Mac/mf_mountain_lion.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * OS X Server Event Handling
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_MLION_H
+#define FREERDP_SERVER_MAC_MLION_H
+
+#include <freerdp/codec/rfx.h>
+
+int mf_mlion_display_info(UINT32* disp_width, UINT32* dispHeight, UINT32* scale);
+
+int mf_mlion_screen_updates_init(void);
+
+int mf_mlion_start_getting_screen_updates(void);
+int mf_mlion_stop_getting_screen_updates(void);
+
+int mf_mlion_get_dirty_region(RFX_RECT* invalid);
+int mf_mlion_peek_dirty_region(RFX_RECT* invalid);
+int mf_mlion_clear_dirty_region(void);
+
+int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData);
+
+#endif /* FREERDP_SERVER_MAC_MLION_H */
diff --git a/server/Mac/mf_peer.c b/server/Mac/mf_peer.c
new file mode 100644
index 0000000..0669127
--- /dev/null
+++ b/server/Mac/mf_peer.c
@@ -0,0 +1,485 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <winpr/stream.h>
+#include <freerdp/peer.h>
+#include <freerdp/codec/color.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include "mf_peer.h"
+#include "mf_info.h"
+#include "mf_input.h"
+#include "mf_event.h"
+#include "mf_rdpsnd.h"
+#include "mf_audin.h"
+
+#include <mach/clock.h>
+#include <mach/mach.h>
+#include <dispatch/dispatch.h>
+
+#include "OpenGL/OpenGL.h"
+#include "OpenGL/gl.h"
+
+#include "CoreVideo/CoreVideo.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+// refactor these
+static int info_last_sec = 0;
+static int info_last_nsec = 0;
+
+static dispatch_source_t info_timer;
+static dispatch_queue_t info_queue;
+
+static mfEventQueue* info_event_queue;
+
+static CGLContextObj glContext;
+static CGContextRef bmp;
+static CGImageRef img;
+
+static void mf_peer_context_free(freerdp_peer* client, rdpContext* context);
+
+static BOOL mf_peer_get_fds(freerdp_peer* client, void** rfds, int* rcount)
+{
+ if (info_event_queue->pipe_fd[0] == -1)
+ return TRUE;
+
+ rfds[*rcount] = (void*)(long)info_event_queue->pipe_fd[0];
+ (*rcount)++;
+ return TRUE;
+}
+
+static void mf_peer_rfx_update(freerdp_peer* client)
+{
+ // check
+ mfInfo* mfi = mf_info_get_instance();
+ mf_info_find_invalid_region(mfi);
+
+ if (mf_info_have_invalid_region(mfi) == false)
+ {
+ return;
+ }
+
+ long width;
+ long height;
+ int pitch;
+ BYTE* dataBits = NULL;
+ mf_info_getScreenData(mfi, &width, &height, &dataBits, &pitch);
+ mf_info_clear_invalid_region(mfi);
+ // encode
+ wStream* s;
+ RFX_RECT rect;
+ rdpUpdate* update;
+ mfPeerContext* mfp;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+
+ WINPR_ASSERT(client);
+
+ mfp = (mfPeerContext*)client->context;
+ WINPR_ASSERT(mfp);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ s = mfp->s;
+ WINPR_ASSERT(s);
+
+ Stream_Clear(s);
+ Stream_SetPosition(s, 0);
+ UINT32 x = mfi->invalid.x / mfi->scale;
+ UINT32 y = mfi->invalid.y / mfi->scale;
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = width;
+ rect.height = height;
+
+ rfx_context_reset(mfp->rfx_context, mfi->servscreen_width, mfi->servscreen_height);
+
+ if (!(rfx_compose_message(mfp->rfx_context, s, &rect, 1, (BYTE*)dataBits, rect.width,
+ rect.height, pitch)))
+ {
+ return;
+ }
+
+ cmd.destLeft = x;
+ cmd.destTop = y;
+ cmd.destRight = x + rect.width;
+ cmd.destBottom = y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.codecID = 3;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ // send
+ update->SurfaceBits(update->context, &cmd);
+ // clean up... maybe?
+}
+
+static BOOL mf_peer_check_fds(freerdp_peer* client)
+{
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ mfEvent* event;
+
+ if (context->activated == FALSE)
+ return TRUE;
+
+ event = mf_event_peek(info_event_queue);
+
+ if (event != NULL)
+ {
+ if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_REGION)
+ {
+ }
+ else if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK)
+ {
+ event = mf_event_pop(info_event_queue);
+ mf_peer_rfx_update(client);
+ mf_event_free(event);
+ }
+ }
+
+ return TRUE;
+}
+
+/* Called when we have a new peer connecting */
+static BOOL mf_peer_context_new(freerdp_peer* client, rdpContext* context)
+{
+ rdpSettings* settings;
+ mfPeerContext* peer = (mfPeerContext*)context;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!(peer->info = mf_info_get_instance()))
+ return FALSE;
+
+ if (!(peer->rfx_context = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(settings, FreeRDP_ThreadingFlags))))
+ goto fail;
+
+ rfx_context_reset(peer->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ rfx_context_set_mode(peer->rfx_context, RLGR3);
+ rfx_context_set_pixel_format(peer->rfx_context, PIXEL_FORMAT_BGRA32);
+
+ if (!(peer->s = Stream_New(NULL, 0xFFFF)))
+ goto fail;
+
+ peer->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!peer->vcm || (peer->vcm == INVALID_HANDLE_VALUE))
+ goto fail;
+
+ mf_info_peer_register(peer->info, peer);
+ return TRUE;
+fail:
+ mf_peer_context_free(client, context);
+ return FALSE;
+}
+
+/* Called after a peer disconnects */
+static void mf_peer_context_free(freerdp_peer* client, rdpContext* context)
+{
+ mfPeerContext* peer = (mfPeerContext*)context;
+ if (context)
+ {
+ mf_info_peer_unregister(peer->info, peer);
+ dispatch_suspend(info_timer);
+ Stream_Free(peer->s, TRUE);
+ rfx_context_free(peer->rfx_context);
+ // nsc_context_free(peer->nsc_context);
+#ifdef CHANNEL_AUDIN_SERVER
+
+ mf_peer_audin_uninit(peer);
+
+#endif
+#ifdef CHANNEL_RDPSND_SERVER
+ mf_peer_rdpsnd_stop();
+
+ if (peer->rdpsnd)
+ rdpsnd_server_context_free(peer->rdpsnd);
+
+#endif
+ WTSCloseServer(peer->vcm);
+ }
+}
+
+/* Called when a new client connects */
+static BOOL mf_peer_init(freerdp_peer* client)
+{
+ client->ContextSize = sizeof(mfPeerContext);
+ client->ContextNew = mf_peer_context_new;
+ client->ContextFree = mf_peer_context_free;
+
+ if (!freerdp_peer_context_new(client))
+ return FALSE;
+
+ info_event_queue = mf_event_queue_new();
+ info_queue = dispatch_queue_create("FreeRDP.update.timer", DISPATCH_QUEUE_SERIAL);
+ info_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, info_queue);
+
+ if (info_timer)
+ {
+ // DEBUG_WARN( "created timer\n");
+ dispatch_source_set_timer(info_timer, DISPATCH_TIME_NOW, 42ull * NSEC_PER_MSEC,
+ 100ull * NSEC_PER_MSEC);
+ dispatch_source_set_event_handler(info_timer, ^{
+ // DEBUG_WARN( "dispatch\n");
+ mfEvent* event = mf_event_new(FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK);
+ mf_event_push(info_event_queue, (mfEvent*)event);
+ });
+ dispatch_resume(info_timer);
+ }
+
+ return TRUE;
+}
+
+static BOOL mf_peer_post_connect(freerdp_peer* client)
+{
+ mfInfo* mfi = mf_info_get_instance();
+
+ WINPR_ASSERT(client);
+
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ mfi->scale = 1;
+ // mfi->servscreen_width = 2880 / mfi->scale;
+ // mfi->servscreen_height = 1800 / mfi->scale;
+ UINT32 bitsPerPixel = 32;
+
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != mfi->servscreen_width) ||
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != mfi->servscreen_height))
+ {
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, mfi->servscreen_width))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, mfi->servscreen_height))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, bitsPerPixel))
+ return FALSE;
+
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->DesktopResize);
+ client->context->update->DesktopResize(client->context);
+
+ mfi->mouse_down_left = FALSE;
+ mfi->mouse_down_right = FALSE;
+ mfi->mouse_down_other = FALSE;
+#ifdef CHANNEL_RDPSND_SERVER
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd"))
+ {
+ mf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+#endif
+ /* Dynamic Virtual Channels */
+#ifdef CHANNEL_AUDIN_SERVER
+ mf_peer_audin_init(context); /* Audio Input */
+#endif
+ return TRUE;
+}
+
+static BOOL mf_peer_activate(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ mfPeerContext* context = (mfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ rfx_context_reset(context->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ context->activated = TRUE;
+ return TRUE;
+}
+
+static BOOL mf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ return TRUE;
+}
+
+static BOOL mf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ bool state_down = FALSE;
+
+ if (flags == KBD_FLAGS_DOWN)
+ {
+ state_down = TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL mf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ return FALSE;
+}
+
+static BOOL mf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ return FALSE;
+}
+
+static void* mf_peer_main_loop(void* arg)
+{
+ mfPeerContext* context;
+ rdpSettings* settings;
+ rdpInput* input;
+ rdpUpdate* update;
+ freerdp_peer* client = (freerdp_peer*)arg;
+
+ if (!mf_peer_init(client))
+ goto fail;
+
+ const mf_server_info* info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Initialize the real server settings here */
+ rdpPrivateKey* key = freerdp_key_new_from_file(info->key);
+ if (!key)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto fail;
+ rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert);
+ if (!cert)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, FALSE))
+ goto fail;
+
+ client->PostConnect = mf_peer_post_connect;
+ client->Activate = mf_peer_activate;
+
+ input = client->context->input;
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = mf_peer_synchronize_event;
+ input->KeyboardEvent = mf_input_keyboard_event; // mf_peer_keyboard_event;
+ input->UnicodeKeyboardEvent = mf_peer_unicode_keyboard_event;
+ input->MouseEvent = mf_input_mouse_event;
+ input->ExtendedMouseEvent = mf_input_extended_mouse_event;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ // update->RefreshRect = mf_peer_refresh_rect;
+ update->SuppressOutput = mf_peer_suppress_output;
+
+ WINPR_ASSERT(client->Initialize);
+ const BOOL rc = client->Initialize(client);
+ if (!rc)
+ goto fail;
+ context = (mfPeerContext*)client->context;
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles));
+
+ if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ handles[count++] = WTSVirtualChannelManagerGetEventHandle(context->vcm);
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (client->CheckFileDescriptor(client) != TRUE)
+ {
+ break;
+ }
+
+ if ((mf_peer_check_fds(client)) != TRUE)
+ {
+ break;
+ }
+
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ {
+ break;
+ }
+ }
+
+ client->Disconnect(client);
+ freerdp_peer_context_free(client);
+fail:
+ freerdp_peer_free(client);
+ return NULL;
+}
+
+BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ pthread_t th;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(client);
+
+ client->ContextExtra = instance->info;
+ if (pthread_create(&th, 0, mf_peer_main_loop, client) == 0)
+ {
+ pthread_detach(th);
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/Mac/mf_peer.h b/server/Mac/mf_peer.h
new file mode 100644
index 0000000..e4a966c
--- /dev/null
+++ b/server/Mac/mf_peer.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_PEER_H
+#define FREERDP_SERVER_MAC_PEER_H
+
+#include "mf_interface.h"
+
+typedef struct
+{
+ const char* cert;
+ const char* key;
+} mf_server_info;
+
+BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#endif /* FREERDP_SERVER_MAC_PEER_H */
diff --git a/server/Mac/mf_rdpsnd.c b/server/Mac/mf_rdpsnd.c
new file mode 100644
index 0000000..e1946f2
--- /dev/null
+++ b/server/Mac/mf_rdpsnd.c
@@ -0,0 +1,200 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Output)
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/server/rdpsnd.h>
+
+#include "mf_info.h"
+#include "mf_rdpsnd.h"
+#include "mf_interface.h"
+
+#include <winpr/sysinfo.h>
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+AQRecorderState recorderState;
+
+static void mf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ OSStatus status;
+ BOOL formatAgreed = FALSE;
+ AUDIO_FORMAT* agreedFormat = NULL;
+ // we should actually loop through the list of client formats here
+ // and see if we can send the client something that it supports...
+ WLog_DBG(TAG, "Client supports the following %d formats: ", context->num_client_formats);
+
+ int i = 0;
+ for (; i < context->num_client_formats; i++)
+ {
+ /* TODO: improve the way we agree on a format */
+ for (int j = 0; j < context->num_server_formats; j++)
+ {
+ if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) &&
+ (context->client_formats[i].nChannels == context->server_formats[j].nChannels) &&
+ (context->client_formats[i].nSamplesPerSec ==
+ context->server_formats[j].nSamplesPerSec))
+ {
+ WLog_DBG(TAG, "agreed on format!");
+ formatAgreed = TRUE;
+ agreedFormat = (AUDIO_FORMAT*)&context->server_formats[j];
+ break;
+ }
+ }
+
+ if (formatAgreed == TRUE)
+ break;
+ }
+
+ if (formatAgreed == FALSE)
+ {
+ WLog_DBG(TAG, "Could not agree on a audio format with the server");
+ return;
+ }
+
+ context->SelectFormat(context, i);
+ context->SetVolume(context, 0x7FFF, 0x7FFF);
+
+ switch (agreedFormat->wFormatTag)
+ {
+ case WAVE_FORMAT_ALAW:
+ recorderState.dataFormat.mFormatID = kAudioFormatDVIIntelIMA;
+ break;
+
+ case WAVE_FORMAT_PCM:
+ recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM;
+ break;
+
+ default:
+ recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM;
+ break;
+ }
+
+ recorderState.dataFormat.mSampleRate = agreedFormat->nSamplesPerSec;
+ recorderState.dataFormat.mFormatFlags =
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
+ recorderState.dataFormat.mBytesPerPacket = 4;
+ recorderState.dataFormat.mFramesPerPacket = 1;
+ recorderState.dataFormat.mBytesPerFrame = 4;
+ recorderState.dataFormat.mChannelsPerFrame = agreedFormat->nChannels;
+ recorderState.dataFormat.mBitsPerChannel = agreedFormat->wBitsPerSample;
+ recorderState.snd_context = context;
+ status =
+ AudioQueueNewInput(&recorderState.dataFormat, mf_peer_rdpsnd_input_callback, &recorderState,
+ NULL, kCFRunLoopCommonModes, 0, &recorderState.queue);
+
+ if (status != noErr)
+ {
+ WLog_DBG(TAG, "Failed to create a new Audio Queue. Status code: %" PRId32 "", status);
+ }
+
+ UInt32 dataFormatSize = sizeof(recorderState.dataFormat);
+ AudioQueueGetProperty(recorderState.queue, kAudioConverterCurrentInputStreamDescription,
+ &recorderState.dataFormat, &dataFormatSize);
+ mf_rdpsnd_derive_buffer_size(recorderState.queue, &recorderState.dataFormat, 0.05,
+ &recorderState.bufferByteSize);
+
+ for (int i = 0; i < SND_NUMBUFFERS; ++i)
+ {
+ AudioQueueAllocateBuffer(recorderState.queue, recorderState.bufferByteSize,
+ &recorderState.buffers[i]);
+ AudioQueueEnqueueBuffer(recorderState.queue, recorderState.buffers[i], 0, NULL);
+ }
+
+ recorderState.currentPacket = 0;
+ recorderState.isRunning = true;
+ AudioQueueStart(recorderState.queue, NULL);
+}
+
+BOOL mf_peer_rdpsnd_init(mfPeerContext* context)
+{
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = mf_peer_rdpsnd_activated;
+ context->rdpsnd->Initialize(context->rdpsnd, TRUE);
+ return TRUE;
+}
+
+BOOL mf_peer_rdpsnd_stop()
+{
+ recorderState.isRunning = false;
+ AudioQueueStop(recorderState.queue, true);
+ return TRUE;
+}
+
+void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ,
+ AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime,
+ UInt32 inNumberPacketDescriptions,
+ const AudioStreamPacketDescription* inPacketDescs)
+{
+ OSStatus status;
+ AQRecorderState* rState;
+ rState = inUserData;
+
+ if (inNumberPacketDescriptions == 0 && rState->dataFormat.mBytesPerPacket != 0)
+ {
+ inNumberPacketDescriptions =
+ inBuffer->mAudioDataByteSize / rState->dataFormat.mBytesPerPacket;
+ }
+
+ if (rState->isRunning == 0)
+ {
+ return;
+ }
+
+ rState->snd_context->SendSamples(rState->snd_context, inBuffer->mAudioData,
+ inBuffer->mAudioDataByteSize / 4,
+ (UINT16)(GetTickCount() & 0xffff));
+ status = AudioQueueEnqueueBuffer(rState->queue, inBuffer, 0, NULL);
+
+ if (status != noErr)
+ {
+ WLog_DBG(TAG, "AudioQueueEnqueueBuffer() returned status = %" PRId32 "", status);
+ }
+}
+
+void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue,
+ AudioStreamBasicDescription* ASBDescription, Float64 seconds,
+ UInt32* outBufferSize)
+{
+ static const int maxBufferSize = 0x50000;
+ int maxPacketSize = ASBDescription->mBytesPerPacket;
+
+ if (maxPacketSize == 0)
+ {
+ UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
+ AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize,
+ // in Mac OS X v10.5, instead use
+ // kAudioConverterPropertyMaximumOutputPacketSize
+ &maxPacketSize, &maxVBRPacketSize);
+ }
+
+ Float64 numBytesForTime = ASBDescription->mSampleRate * maxPacketSize * seconds;
+ *outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize);
+}
diff --git a/server/Mac/mf_rdpsnd.h b/server/Mac/mf_rdpsnd.h
new file mode 100644
index 0000000..28bcb0d
--- /dev/null
+++ b/server/Mac/mf_rdpsnd.h
@@ -0,0 +1,58 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server (Audio Output)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_MAC_RDPSND_H
+#define FREERDP_SERVER_MAC_RDPSND_H
+
+#include <CoreAudio/CoreAudio.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "mf_types.h"
+#include "mfreerdp.h"
+
+void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue,
+ AudioStreamBasicDescription* ASBDescription, Float64 seconds,
+ UInt32* outBufferSize);
+
+void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ,
+ AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime,
+ UInt32 inNumberPacketDescriptions,
+ const AudioStreamPacketDescription* inPacketDescs);
+
+#define SND_NUMBUFFERS 3
+typedef struct
+{
+ AudioStreamBasicDescription dataFormat;
+ AudioQueueRef queue;
+ AudioQueueBufferRef buffers[SND_NUMBUFFERS];
+ AudioFileID audioFile;
+ UInt32 bufferByteSize;
+ SInt64 currentPacket;
+ bool isRunning;
+ RdpsndServerContext* snd_context;
+} AQRecorderState;
+
+BOOL mf_peer_rdpsnd_init(mfPeerContext* context);
+BOOL mf_peer_rdpsnd_stop(void);
+
+#endif /* FREERDP_SERVER_MAC_RDPSND_H */
diff --git a/server/Mac/mf_types.h b/server/Mac/mf_types.h
new file mode 100644
index 0000000..e33be83
--- /dev/null
+++ b/server/Mac/mf_types.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2023 Armin Novak <anovak@thincst.com>
+ * Copyright 2023 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_SERVER_MAC_TYPES_H
+#define FREERDP_SERVER_MAC_TYPES_H
+
+#include <pthread.h>
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+
+typedef struct mf_info mfInfo;
+typedef struct mf_peer_context mfPeerContext;
+
+#endif /* FREERDP_SERVER_MAC_TYPES_H */
diff --git a/server/Mac/mfreerdp.c b/server/Mac/mfreerdp.c
new file mode 100644
index 0000000..8a3fffd
--- /dev/null
+++ b/server/Mac/mfreerdp.c
@@ -0,0 +1,108 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/time.h>
+
+#include <CoreGraphics/CGEvent.h>
+
+#include <winpr/crt.h>
+#include <winpr/wtsapi.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/server/server-common.h>
+
+#include "mfreerdp.h"
+#include "mf_peer.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("mac")
+
+static void mf_server_main_loop(freerdp_listener* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->GetEventHandles);
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ break;
+ }
+ }
+
+ instance->Close(instance);
+}
+
+int main(int argc, char* argv[])
+{
+ freerdp_server_warn_unmaintained(argc, argv);
+ mf_server_info info = { .key = "server.key", .cert = "server.crt" };
+
+ freerdp_listener* instance;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ if (!(instance = freerdp_listener_new()))
+ return 1;
+
+ instance->info = &info;
+ instance->PeerAccepted = mf_peer_accepted;
+
+ if (instance->Open(instance, NULL, 3389))
+ {
+ mf_server_main_loop(instance);
+ }
+
+ freerdp_listener_free(instance);
+
+ return 0;
+}
diff --git a/server/Mac/mfreerdp.h b/server/Mac/mfreerdp.h
new file mode 100644
index 0000000..38fa25b
--- /dev/null
+++ b/server/Mac/mfreerdp.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Mac OS X Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_MAC_FREERDP_H
+#define FREERDP_SERVER_MAC_FREERDP_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+
+#endif /* FREERDP_SERVER_MAC_FREERDP_H */
diff --git a/server/Sample/CMakeLists.txt b/server/Sample/CMakeLists.txt
new file mode 100644
index 0000000..e23f65c
--- /dev/null
+++ b/server/Sample/CMakeLists.txt
@@ -0,0 +1,86 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Sample Server cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+set(MODULE_NAME "sfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_SAMPLE")
+
+set(SRCS
+ sfreerdp.c
+ sfreerdp.h
+ sf_audin.c
+ sf_audin.h
+ sf_rdpsnd.c
+ sf_rdpsnd.h
+ sf_encomsp.c
+ sf_encomsp.h)
+
+if (CHANNEL_AINPUT_SERVER)
+ list(APPEND SRCS sf_ainput.c sf_ainput.h)
+endif()
+
+ # On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+if (WITH_BINARY_VERSIONING)
+ set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP${FREERDP_VERSION_MAJOR}/images)
+else()
+ set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP/images)
+endif()
+
+set(SAMPLE_ICONS
+ test_icon.bmp
+ test_icon.png
+ test_icon.jpg
+ test_icon.webp
+)
+install(FILES ${SAMPLE_ICONS} DESTINATION ${SAMPLE_RESOURCE_ROOT})
+
+# We need this in runtime path for TestConnect
+file(COPY test_icon.bmp DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(${MODULE_NAME} ${SRCS})
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+target_compile_definitions(${MODULE_NAME}
+ PRIVATE
+ -DSAMPLE_RESOURCE_ROOT="${SAMPLE_RESOURCE_ROOT}"
+)
+list(APPEND LIBS freerdp-server)
+list(APPEND LIBS winpr freerdp)
+
+target_link_libraries(${MODULE_NAME} ${LIBS})
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Sample")
diff --git a/server/Sample/ModuleOptions.cmake b/server/Sample/ModuleOptions.cmake
new file mode 100644
index 0000000..5621f6d
--- /dev/null
+++ b/server/Sample/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "sfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "Sample")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Sample/sf_ainput.c b/server/Sample/sf_ainput.c
new file mode 100644
index 0000000..1c19e07
--- /dev/null
+++ b/server/Sample/sf_ainput.c
@@ -0,0 +1,92 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Advanced Input)
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 <winpr/assert.h>
+
+#include "sfreerdp.h"
+
+#include "sf_ainput.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/server/ainput.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample.ainput")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT sf_peer_ainput_mouse_event(ainput_server_context* context, UINT64 timestamp,
+ UINT64 flags, INT32 x, INT32 y)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(context);
+
+ WLog_WARN(TAG, "not implemented: 0x%08" PRIx64 ", 0x%08" PRIx64 ", %" PRId32 "x%" PRId32,
+ timestamp, flags, x, y);
+ return CHANNEL_RC_OK;
+}
+
+void sf_peer_ainput_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->ainput = ainput_server_context_new(context->vcm);
+ WINPR_ASSERT(context->ainput);
+
+ context->ainput->rdpcontext = &context->_p;
+ context->ainput->data = context;
+
+ context->ainput->MouseEvent = sf_peer_ainput_mouse_event;
+}
+
+BOOL sf_peer_ainput_start(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->Open)
+ return FALSE;
+
+ return context->ainput->Open(context->ainput) == CHANNEL_RC_OK;
+}
+
+BOOL sf_peer_ainput_stop(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->Close)
+ return FALSE;
+
+ return context->ainput->Close(context->ainput) == CHANNEL_RC_OK;
+}
+
+BOOL sf_peer_ainput_running(testPeerContext* context)
+{
+ if (!context || !context->ainput || !context->ainput->IsOpen)
+ return FALSE;
+
+ return context->ainput->IsOpen(context->ainput);
+}
+
+void sf_peer_ainput_uninit(testPeerContext* context)
+{
+ ainput_server_context_free(context->ainput);
+}
diff --git a/server/Sample/sf_ainput.h b/server/Sample/sf_ainput.h
new file mode 100644
index 0000000..3cf78d5
--- /dev/null
+++ b/server/Sample/sf_ainput.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Advanced Input)
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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_SERVER_SAMPLE_SF_AINPUT_H
+#define FREERDP_SERVER_SAMPLE_SF_AINPUT_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/ainput.h>
+
+#include "sfreerdp.h"
+
+void sf_peer_ainput_init(testPeerContext* context);
+void sf_peer_ainput_uninit(testPeerContext* context);
+
+BOOL sf_peer_ainput_running(testPeerContext* context);
+BOOL sf_peer_ainput_start(testPeerContext* context);
+BOOL sf_peer_ainput_stop(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_AINPUT_H */
diff --git a/server/Sample/sf_audin.c b/server/Sample/sf_audin.c
new file mode 100644
index 0000000..5783fe6
--- /dev/null
+++ b/server/Sample/sf_audin.c
@@ -0,0 +1,112 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 <winpr/assert.h>
+
+#include "sfreerdp.h"
+
+#include "sf_audin.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+#if defined(CHANNEL_AUDIN_SERVER)
+
+static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ /* TODO: Implement */
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ WLog_WARN(TAG, "not implemented");
+ WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data));
+ return CHANNEL_RC_OK;
+}
+
+#endif
+
+BOOL sf_peer_audin_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+#if defined(CHANNEL_AUDIN_SERVER)
+ context->audin = audin_server_context_new(context->vcm);
+ WINPR_ASSERT(context->audin);
+
+ context->audin->rdpcontext = &context->_p;
+ context->audin->userdata = context;
+
+ context->audin->Data = sf_peer_audin_data;
+
+ return audin_server_set_formats(context->audin, -1, NULL);
+#else
+ return TRUE;
+#endif
+}
+
+BOOL sf_peer_audin_start(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->Open)
+ return FALSE;
+
+ return context->audin->Open(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+BOOL sf_peer_audin_stop(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->Close)
+ return FALSE;
+
+ return context->audin->Close(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+BOOL sf_peer_audin_running(testPeerContext* context)
+{
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (!context || !context->audin || !context->audin->IsOpen)
+ return FALSE;
+
+ return context->audin->IsOpen(context->audin);
+#else
+ return FALSE;
+#endif
+}
+
+void sf_peer_audin_uninit(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context_free(context->audin);
+ context->audin = NULL;
+#endif
+}
diff --git a/server/Sample/sf_audin.h b/server/Sample/sf_audin.h
new file mode 100644
index 0000000..1769603
--- /dev/null
+++ b/server/Sample/sf_audin.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Input)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_SAMPLE_SF_AUDIN_H
+#define FREERDP_SERVER_SAMPLE_SF_AUDIN_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_audin_init(testPeerContext* context);
+void sf_peer_audin_uninit(testPeerContext* context);
+
+BOOL sf_peer_audin_running(testPeerContext* context);
+BOOL sf_peer_audin_start(testPeerContext* context);
+BOOL sf_peer_audin_stop(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_AUDIN_H */
diff --git a/server/Sample/sf_encomsp.c b/server/Sample/sf_encomsp.c
new file mode 100644
index 0000000..6d03f79
--- /dev/null
+++ b/server/Sample/sf_encomsp.c
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Lync Multiparty)
+ *
+ * Copyright 2014 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "sf_encomsp.h"
+
+BOOL sf_peer_encomsp_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->encomsp = encomsp_server_context_new(context->vcm);
+ if (!context->encomsp)
+ return FALSE;
+
+ context->encomsp->rdpcontext = &context->_p;
+
+ WINPR_ASSERT(context->encomsp->Start);
+ if (context->encomsp->Start(context->encomsp) != CHANNEL_RC_OK)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/server/Sample/sf_encomsp.h b/server/Sample/sf_encomsp.h
new file mode 100644
index 0000000..7976abc
--- /dev/null
+++ b/server/Sample/sf_encomsp.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Lync Multiparty)
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SAMPLE_SF_ENCOMSP_H
+#define FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/encomsp.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_encomsp_init(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H */
diff --git a/server/Sample/sf_rdpsnd.c b/server/Sample/sf_rdpsnd.c
new file mode 100644
index 0000000..6d4c1ec
--- /dev/null
+++ b/server/Sample/sf_rdpsnd.c
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Output)
+ *
+ * Copyright 2012 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "sf_rdpsnd.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+static void sf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WLog_DBG(TAG, "RDPSND Activated");
+}
+
+BOOL sf_peer_rdpsnd_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ WINPR_ASSERT(context->rdpsnd);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = sf_peer_rdpsnd_activated;
+
+ WINPR_ASSERT(context->rdpsnd->Initialize);
+ if (context->rdpsnd->Initialize(context->rdpsnd, TRUE) != CHANNEL_RC_OK)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/server/Sample/sf_rdpsnd.h b/server/Sample/sf_rdpsnd.h
new file mode 100644
index 0000000..f9b0ef4
--- /dev/null
+++ b/server/Sample/sf_rdpsnd.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server (Audio Output)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_SAMPLE_SF_RDPSND_H
+#define FREERDP_SERVER_SAMPLE_SF_RDPSND_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "sfreerdp.h"
+
+BOOL sf_peer_rdpsnd_init(testPeerContext* context);
+
+#endif /* FREERDP_SERVER_SAMPLE_SF_RDPSND_H */
diff --git a/server/Sample/sfreerdp.c b/server/Sample/sfreerdp.c
new file mode 100644
index 0000000..f39a747
--- /dev/null
+++ b/server/Sample/sfreerdp.c
@@ -0,0 +1,1440 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Test Server
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2011 Vic Lee
+ * Copyright 2014 Norbert Federa <norbert.federa@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <signal.h>
+
+#include <winpr/winpr.h>
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/ssl.h>
+#include <winpr/synch.h>
+#include <winpr/file.h>
+#include <winpr/string.h>
+#include <winpr/path.h>
+#include <winpr/image.h>
+#include <winpr/winsock.h>
+
+#include <freerdp/streamdump.h>
+#include <freerdp/transport_io.h>
+
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/server/rdpsnd.h>
+#include <freerdp/settings.h>
+
+#include "sf_ainput.h"
+#include "sf_audin.h"
+#include "sf_rdpsnd.h"
+#include "sf_encomsp.h"
+
+#include "sfreerdp.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("sample")
+
+#define SAMPLE_SERVER_USE_CLIENT_RESOLUTION 1
+#define SAMPLE_SERVER_DEFAULT_WIDTH 1024
+#define SAMPLE_SERVER_DEFAULT_HEIGHT 768
+
+struct server_info
+{
+ BOOL test_dump_rfx_realtime;
+ const char* test_pcap_file;
+ const char* replay_dump;
+ const char* cert;
+ const char* key;
+};
+
+static void test_peer_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ testPeerContext* context = (testPeerContext*)ctx;
+
+ WINPR_UNUSED(client);
+
+ if (context)
+ {
+ winpr_image_free(context->image, TRUE);
+ if (context->debug_channel_thread)
+ {
+ WINPR_ASSERT(context->stopEvent);
+ SetEvent(context->stopEvent);
+ WaitForSingleObject(context->debug_channel_thread, INFINITE);
+ CloseHandle(context->debug_channel_thread);
+ }
+
+ Stream_Free(context->s, TRUE);
+ free(context->bg_data);
+ rfx_context_free(context->rfx_context);
+ nsc_context_free(context->nsc_context);
+
+ if (context->debug_channel)
+ WTSVirtualChannelClose(context->debug_channel);
+
+ sf_peer_audin_uninit(context);
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ sf_peer_ainput_uninit(context);
+#endif
+
+ rdpsnd_server_context_free(context->rdpsnd);
+ encomsp_server_context_free(context->encomsp);
+
+ WTSCloseServer((HANDLE)context->vcm);
+ }
+}
+
+static BOOL test_peer_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ testPeerContext* context = (testPeerContext*)ctx;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(ctx->settings);
+
+ context->image = winpr_image_new();
+ if (!context->image)
+ goto fail;
+ if (!(context->rfx_context = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags))))
+ goto fail;
+
+ if (!rfx_context_reset(context->rfx_context, SAMPLE_SERVER_DEFAULT_WIDTH,
+ SAMPLE_SERVER_DEFAULT_HEIGHT))
+ goto fail;
+
+ rfx_context_set_mode(context->rfx_context, RLGR3);
+
+ if (!(context->nsc_context = nsc_context_new()))
+ goto fail;
+
+ if (!(context->s = Stream_New(NULL, 65536)))
+ goto fail;
+
+ context->icon_x = UINT32_MAX;
+ context->icon_y = UINT32_MAX;
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ return TRUE;
+fail:
+ test_peer_context_free(client, ctx);
+ return FALSE;
+}
+
+static BOOL test_peer_init(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ client->ContextSize = sizeof(testPeerContext);
+ client->ContextNew = test_peer_context_new;
+ client->ContextFree = test_peer_context_free;
+ return freerdp_peer_context_new(client);
+}
+
+static wStream* test_peer_stream_init(testPeerContext* context)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->s);
+
+ Stream_Clear(context->s);
+ Stream_SetPosition(context->s, 0);
+ return context->s;
+}
+
+static void test_peer_begin_frame(freerdp_peer* client)
+{
+ rdpUpdate* update = NULL;
+ SURFACE_FRAME_MARKER fm = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ fm.frameAction = SURFACECMD_FRAMEACTION_BEGIN;
+ fm.frameId = context->frame_id;
+ WINPR_ASSERT(update->SurfaceFrameMarker);
+ update->SurfaceFrameMarker(update->context, &fm);
+}
+
+static void test_peer_end_frame(freerdp_peer* client)
+{
+ rdpUpdate* update = NULL;
+ SURFACE_FRAME_MARKER fm = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ fm.frameAction = SURFACECMD_FRAMEACTION_END;
+ fm.frameId = context->frame_id;
+ WINPR_ASSERT(update->SurfaceFrameMarker);
+ update->SurfaceFrameMarker(update->context, &fm);
+ context->frame_id++;
+}
+
+static BOOL test_peer_draw_background(freerdp_peer* client)
+{
+ size_t size = 0;
+ wStream* s = NULL;
+ RFX_RECT rect;
+ BYTE* rgb_data = NULL;
+ const rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ testPeerContext* context = NULL;
+ BOOL ret = FALSE;
+ const UINT32 colorFormat = PIXEL_FORMAT_RGB24;
+ const size_t bpp = FreeRDPGetBytesPerPixel(colorFormat);
+
+ WINPR_ASSERT(client);
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec);
+ if (!RemoteFxCodec && !freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ return FALSE;
+
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+
+ s = test_peer_stream_init(context);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ rect.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ size = bpp * rect.width * rect.height;
+
+ if (!(rgb_data = malloc(size)))
+ {
+ WLog_ERR(TAG, "Problem allocating memory");
+ return FALSE;
+ }
+
+ memset(rgb_data, 0xA0, size);
+
+ if (RemoteFxCodec)
+ {
+ WLog_DBG(TAG, "Using RemoteFX codec");
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ if (!rfx_compose_message(context->rfx_context, s, &rect, 1, rgb_data, rect.width,
+ rect.height, rect.width * bpp))
+ {
+ goto out;
+ }
+
+ const UINT32 RemoteFxCodecId =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)RemoteFxCodecId;
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ }
+ else
+ {
+ WLog_DBG(TAG, "Using NSCodec");
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, rgb_data, rect.width, rect.height,
+ rect.width * bpp);
+ const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ WINPR_ASSERT(NSCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)NSCodecId;
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ }
+
+ cmd.destLeft = 0;
+ cmd.destTop = 0;
+ cmd.destRight = rect.width;
+ cmd.destBottom = rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ test_peer_begin_frame(client);
+ update->SurfaceBits(update->context, &cmd);
+ test_peer_end_frame(client);
+ ret = TRUE;
+out:
+ free(rgb_data);
+ return ret;
+}
+
+static int open_icon(wImage* img)
+{
+ char* paths[] = { SAMPLE_RESOURCE_ROOT, "." };
+ const char* names[] = { "test_icon.webp", "test_icon.png", "test_icon.jpg", "test_icon.bmp" };
+
+ for (size_t x = 0; x < ARRAYSIZE(paths); x++)
+ {
+ const char* path = paths[x];
+ if (!winpr_PathFileExists(path))
+ continue;
+
+ for (size_t y = 0; y < ARRAYSIZE(names); y++)
+ {
+ const char* name = names[y];
+ char* file = GetCombinedPath(path, name);
+ int rc = winpr_image_read(img, file);
+ free(file);
+ if (rc > 0)
+ return rc;
+ }
+ }
+ WLog_ERR(TAG, "Unable to open test icon");
+ return -1;
+}
+
+static BOOL test_peer_load_icon(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ {
+ WLog_ERR(TAG, "Client doesn't support RemoteFX or NSCodec");
+ return FALSE;
+ }
+
+ int rc = open_icon(context->image);
+ if (rc <= 0)
+ goto out_fail;
+
+ /* background with same size, which will be used to erase the icon from old position */
+ if (!(context->bg_data = calloc(context->image->height, context->image->width * 3)))
+ goto out_fail;
+
+ memset(context->bg_data, 0xA0, context->image->height * context->image->width * 3ull);
+ return TRUE;
+out_fail:
+ context->bg_data = NULL;
+ return FALSE;
+}
+
+static void test_peer_draw_icon(freerdp_peer* client, UINT32 x, UINT32 y)
+{
+ wStream* s = NULL;
+ RFX_RECT rect;
+ rdpUpdate* update = NULL;
+ rdpSettings* settings = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ testPeerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DumpRemoteFx))
+ return;
+
+ if (context->image->width < 1 || !context->activated)
+ return;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = context->image->width;
+ rect.height = context->image->height;
+
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ if (context->icon_x + context->image->width > w)
+ return;
+ if (context->icon_y + context->image->height > h)
+ return;
+ if (x + context->image->width > w)
+ return;
+ if (y + context->image->height > h)
+ return;
+
+ test_peer_begin_frame(client);
+ const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec);
+ if (RemoteFxCodec)
+ {
+ const UINT32 RemoteFxCodecId =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)RemoteFxCodecId;
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ }
+ else
+ {
+ const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ WINPR_ASSERT(NSCodecId <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)NSCodecId;
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ }
+
+ if (context->icon_x != UINT32_MAX)
+ {
+ const UINT32 colorFormat = PIXEL_FORMAT_RGB24;
+ const UINT32 bpp = FreeRDPGetBytesPerPixel(colorFormat);
+ s = test_peer_stream_init(context);
+
+ if (RemoteFxCodec)
+ {
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ rfx_compose_message(context->rfx_context, s, &rect, 1, context->bg_data, rect.width,
+ rect.height, rect.width * bpp);
+ }
+ else
+ {
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, context->bg_data, rect.width, rect.height,
+ rect.width * bpp);
+ }
+
+ cmd.destLeft = context->icon_x;
+ cmd.destTop = context->icon_y;
+ cmd.destRight = context->icon_x + rect.width;
+ cmd.destBottom = context->icon_y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ WINPR_ASSERT(update->SurfaceBits);
+ update->SurfaceBits(update->context, &cmd);
+ }
+
+ s = test_peer_stream_init(context);
+
+ {
+ const UINT32 colorFormat =
+ context->image->bitsPerPixel > 24 ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGR24;
+
+ if (RemoteFxCodec)
+ {
+ rfx_context_set_pixel_format(context->rfx_context, colorFormat);
+ rfx_compose_message(context->rfx_context, s, &rect, 1, context->image->data, rect.width,
+ rect.height, context->image->scanline);
+ }
+ else
+ {
+ nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat);
+ nsc_compose_message(context->nsc_context, s, context->image->data, rect.width,
+ rect.height, context->image->scanline);
+ }
+ }
+
+ cmd.destLeft = x;
+ cmd.destTop = y;
+ cmd.destRight = x + rect.width;
+ cmd.destBottom = y + rect.height;
+ cmd.bmp.bpp = 32;
+ cmd.bmp.width = rect.width;
+ cmd.bmp.height = rect.height;
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ WINPR_ASSERT(update->SurfaceBits);
+ update->SurfaceBits(update->context, &cmd);
+ context->icon_x = x;
+ context->icon_y = y;
+ test_peer_end_frame(client);
+}
+
+static BOOL test_sleep_tsdiff(UINT32* old_sec, UINT32* old_usec, UINT32 new_sec, UINT32 new_usec)
+{
+ INT64 sec = 0;
+ INT64 usec = 0;
+
+ WINPR_ASSERT(old_sec);
+ WINPR_ASSERT(old_usec);
+
+ if ((*old_sec == 0) && (*old_usec == 0))
+ {
+ *old_sec = new_sec;
+ *old_usec = new_usec;
+ return TRUE;
+ }
+
+ sec = new_sec - *old_sec;
+ usec = new_usec - *old_usec;
+
+ if ((sec < 0) || ((sec == 0) && (usec < 0)))
+ {
+ WLog_ERR(TAG, "Invalid time stamp detected.");
+ return FALSE;
+ }
+
+ *old_sec = new_sec;
+ *old_usec = new_usec;
+
+ while (usec < 0)
+ {
+ usec += 1000000;
+ sec--;
+ }
+
+ if (sec > 0)
+ Sleep((DWORD)sec * 1000);
+
+ if (usec > 0)
+ USleep((DWORD)usec);
+
+ return TRUE;
+}
+
+static BOOL tf_peer_dump_rfx(freerdp_peer* client)
+{
+ wStream* s = NULL;
+ UINT32 prev_seconds = 0;
+ UINT32 prev_useconds = 0;
+ rdpUpdate* update = NULL;
+ rdpPcap* pcap_rfx = NULL;
+ pcap_record record = { 0 };
+ struct server_info* info = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ s = Stream_New(NULL, 512);
+
+ if (!s)
+ return FALSE;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ if (!(pcap_rfx = pcap_open(info->test_pcap_file, FALSE)))
+ return FALSE;
+
+ prev_seconds = prev_useconds = 0;
+
+ while (pcap_has_next_record(pcap_rfx))
+ {
+ if (!pcap_get_next_record_header(pcap_rfx, &record))
+ break;
+
+ if (!Stream_EnsureCapacity(s, record.length))
+ break;
+
+ record.data = Stream_Buffer(s);
+ pcap_get_next_record_content(pcap_rfx, &record);
+ Stream_SetPosition(s, Stream_Capacity(s));
+
+ if (info->test_dump_rfx_realtime &&
+ test_sleep_tsdiff(&prev_seconds, &prev_useconds, record.header.ts_sec,
+ record.header.ts_usec) == FALSE)
+ break;
+
+ WINPR_ASSERT(update->SurfaceCommand);
+ update->SurfaceCommand(update->context, s);
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+ }
+
+ Stream_Free(s, TRUE);
+ pcap_close(pcap_rfx);
+ return TRUE;
+}
+
+static DWORD WINAPI tf_debug_channel_thread_func(LPVOID arg)
+{
+ void* fd = NULL;
+ wStream* s = NULL;
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ ULONG written = 0;
+ testPeerContext* context = (testPeerContext*)arg;
+
+ WINPR_ASSERT(context);
+ if (WTSVirtualChannelQuery(context->debug_channel, WTSVirtualFileHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ fd = *((void**)buffer);
+ WTSFreeMemory(buffer);
+
+ if (!(context->event = CreateWaitObjectEvent(NULL, TRUE, FALSE, fd)))
+ return 0;
+ }
+
+ s = Stream_New(NULL, 4096);
+ WTSVirtualChannelWrite(context->debug_channel, (PCHAR) "test1", 5, &written);
+
+ while (1)
+ {
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ handles[nCount++] = context->event;
+ handles[nCount++] = freerdp_abort_event(&context->_p);
+ handles[nCount++] = context->stopEvent;
+ status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ goto fail;
+ }
+
+ Stream_SetPosition(s, 0);
+
+ if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ if (BytesReturned == 0)
+ break;
+
+ Stream_EnsureRemainingCapacity(s, BytesReturned);
+
+ if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ /* should not happen */
+ break;
+ }
+ }
+
+ Stream_SetPosition(s, BytesReturned);
+ WLog_DBG(TAG, "got %" PRIu32 " bytes", BytesReturned);
+ }
+fail:
+ Stream_Free(s, TRUE);
+ return 0;
+}
+
+static BOOL tf_peer_post_connect(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ /**
+ * This callback is called when the entire connection sequence is done, i.e. we've received the
+ * Font List PDU from the client and sent out the Font Map PDU.
+ * The server may start sending graphics output and receiving keyboard/mouse input after this
+ * callback returns.
+ */
+ WLog_DBG(TAG, "Client %s is activated (osMajorType %" PRIu32 " osMinorType %" PRIu32 ")",
+ client->local ? "(local)" : client->hostname,
+ freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType),
+ freerdp_settings_get_uint32(settings, FreeRDP_OsMinorType));
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AutoLogonEnabled))
+ {
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ WLog_DBG(TAG, " and wants to login automatically as %s\\%s", Domain ? Domain : "",
+ Username);
+ /* A real server may perform OS login here if NLA is not executed previously. */
+ }
+
+ WLog_DBG(TAG, "");
+ WLog_DBG(TAG, "Client requested desktop: %" PRIu32 "x%" PRIu32 "x%" PRIu32 "",
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+#if (SAMPLE_SERVER_USE_CLIENT_RESOLUTION == 1)
+
+ if (!rfx_context_reset(context->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ WLog_DBG(TAG, "Using resolution requested by client.");
+#else
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) =
+ context->rfx_context->width;
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) =
+ context->rfx_context->height;
+ WLog_DBG(TAG, "Resizing client to %" PRIu32 "x%" PRIu32 "",
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ client->update->DesktopResize(client->update->context);
+#endif
+
+ /* A real server should tag the peer as activated here and start sending updates in main loop.
+ */
+ if (!test_peer_load_icon(client))
+ {
+ WLog_DBG(TAG, "Unable to load icon");
+ return FALSE;
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpdbg"))
+ {
+ context->debug_channel = WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, "rdpdbg");
+
+ if (context->debug_channel != NULL)
+ {
+ WLog_DBG(TAG, "Open channel rdpdbg.");
+
+ if (!(context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create stop event");
+ return FALSE;
+ }
+
+ if (!(context->debug_channel_thread =
+ CreateThread(NULL, 0, tf_debug_channel_thread_func, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create debug channel thread");
+ CloseHandle(context->stopEvent);
+ context->stopEvent = NULL;
+ return FALSE;
+ }
+ }
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, RDPSND_CHANNEL_NAME))
+ {
+ sf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ sf_peer_encomsp_init(context); /* Lync Multiparty */
+ }
+
+ /* Dynamic Virtual Channels */
+ sf_peer_audin_init(context); /* Audio Input */
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ sf_peer_ainput_init(context);
+#endif
+
+ /* Return FALSE here would stop the execution of the peer main loop. */
+ return TRUE;
+}
+
+static BOOL tf_peer_activate(freerdp_peer* client)
+{
+ testPeerContext* context = NULL;
+ struct server_info* info = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ context->activated = TRUE;
+ // PACKET_COMPR_TYPE_8K;
+ // PACKET_COMPR_TYPE_64K;
+ // PACKET_COMPR_TYPE_RDP6;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+
+ if (info->test_pcap_file != NULL)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DumpRemoteFx, TRUE))
+ return FALSE;
+
+ if (!tf_peer_dump_rfx(client))
+ return FALSE;
+ }
+ else
+ test_peer_draw_background(client);
+
+ return TRUE;
+}
+
+static BOOL tf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ WINPR_UNUSED(input);
+ WINPR_ASSERT(input);
+ WLog_DBG(TAG, "Client sent a synchronize event (flags:0x%" PRIX32 ")", flags);
+ return TRUE;
+}
+
+static BOOL tf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ freerdp_peer* client = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* context = NULL;
+ testPeerContext* tcontext = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(input);
+
+ context = input->context;
+ WINPR_ASSERT(context);
+
+ client = context->peer;
+ WINPR_ASSERT(client);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ update = context->update;
+ WINPR_ASSERT(update);
+
+ tcontext = (testPeerContext*)context;
+ WINPR_ASSERT(tcontext);
+
+ WLog_DBG(TAG, "Client sent a keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX8 ")", flags,
+ code);
+
+ if (((flags & KBD_FLAGS_RELEASE) == 0) && (code == RDP_SCANCODE_KEY_G)) /* 'g' key */
+ {
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != 800)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth,
+ SAMPLE_SERVER_DEFAULT_WIDTH))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight,
+ SAMPLE_SERVER_DEFAULT_HEIGHT))
+ return FALSE;
+ }
+
+ if (!rfx_context_reset(tcontext->rfx_context,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ WINPR_ASSERT(update->DesktopResize);
+ update->DesktopResize(update->context);
+ tcontext->activated = FALSE;
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_C) /* 'c' key */
+ {
+ if (tcontext->debug_channel)
+ {
+ ULONG written = 0;
+ WTSVirtualChannelWrite(tcontext->debug_channel, (PCHAR) "test2", 5, &written);
+ }
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_X) /* 'x' key */
+ {
+ WINPR_ASSERT(client->Close);
+ client->Close(client);
+ }
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_R) /* 'r' key */
+ {
+ tcontext->audin_open = !tcontext->audin_open;
+ }
+#if defined(CHANNEL_AINPUT_SERVER)
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_I) /* 'i' key */
+ {
+ tcontext->ainput_open = !tcontext->ainput_open;
+ }
+#endif
+ else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_S) /* 's' key */
+ {
+ }
+
+ return TRUE;
+}
+
+static BOOL tf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_ASSERT(input);
+
+ WLog_DBG(TAG,
+ "Client sent a unicode keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX16 ")",
+ flags, code);
+ return TRUE;
+}
+
+static BOOL tf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(flags);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ // WLog_DBG(TAG, "Client sent a mouse event (flags:0x%04"PRIX16" pos:%"PRIu16",%"PRIu16")",
+ // flags, x, y);
+ test_peer_draw_icon(input->context->peer, x + 10, y);
+ return TRUE;
+}
+
+static BOOL tf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(flags);
+ WINPR_ASSERT(input);
+ WINPR_ASSERT(input->context);
+
+ // WLog_DBG(TAG, "Client sent an extended mouse event (flags:0x%04"PRIX16"
+ // pos:%"PRIu16",%"PRIu16")", flags, x, y);
+ test_peer_draw_icon(input->context->peer, x + 10, y);
+ return TRUE;
+}
+
+static BOOL tf_peer_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ WINPR_UNUSED(context);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(areas || (count == 0));
+
+ WLog_DBG(TAG, "Client requested to refresh:");
+
+ for (BYTE i = 0; i < count; i++)
+ {
+ WLog_DBG(TAG, " (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ")", areas[i].left,
+ areas[i].top, areas[i].right, areas[i].bottom);
+ }
+
+ return TRUE;
+}
+
+static BOOL tf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ WINPR_UNUSED(context);
+
+ if (allow > 0)
+ {
+ WINPR_ASSERT(area);
+ WLog_DBG(TAG,
+ "Client restore output (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ").",
+ area->left, area->top, area->right, area->bottom);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Client minimized and suppress output.");
+ }
+
+ return TRUE;
+}
+
+static int hook_peer_write_pdu(rdpTransport* transport, wStream* s)
+{
+ UINT64 ts = 0;
+ wStream* ls = NULL;
+ UINT64 last_ts = 0;
+ const struct server_info* info = NULL;
+ freerdp_peer* client = NULL;
+ testPeerContext* peerCtx = NULL;
+ size_t offset = 0;
+ UINT32 flags = 0;
+ rdpContext* context = transport_get_context(transport);
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ client = context->peer;
+ WINPR_ASSERT(client);
+
+ peerCtx = (testPeerContext*)client->context;
+ WINPR_ASSERT(peerCtx);
+ WINPR_ASSERT(peerCtx->io.WritePdu);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ /* Let the client authenticate.
+ * After that is done, we stop the normal operation and send
+ * a previously recorded session PDU by PDU to the client.
+ *
+ * This is fragile and the connecting client needs to use the same
+ * configuration as the one that recorded the session!
+ */
+ WINPR_ASSERT(info);
+ CONNECTION_STATE state = freerdp_get_state(context);
+ if (state < CONNECTION_STATE_NEGO)
+ return peerCtx->io.WritePdu(transport, s);
+
+ ls = Stream_New(NULL, 4096);
+ if (!ls)
+ goto fail;
+
+ while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0)
+ {
+ int rc = 0;
+ /* Skip messages from client. */
+ if (flags & STREAM_MSG_SRV_TX)
+ {
+ if ((last_ts > 0) && (ts > last_ts))
+ {
+ UINT64 diff = ts - last_ts;
+ Sleep(diff);
+ }
+ last_ts = ts;
+ rc = peerCtx->io.WritePdu(transport, ls);
+ if (rc < 0)
+ goto fail;
+ }
+ Stream_SetPosition(ls, 0);
+ }
+
+fail:
+ Stream_Free(ls, TRUE);
+ return -1;
+}
+
+static DWORD WINAPI test_peer_mainloop(LPVOID arg)
+{
+ BOOL rc = 0;
+ DWORD error = CHANNEL_RC_OK;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = 0;
+ DWORD status = 0;
+ testPeerContext* context = NULL;
+ struct server_info* info = NULL;
+ rdpSettings* settings = NULL;
+ rdpInput* input = NULL;
+ rdpUpdate* update = NULL;
+ freerdp_peer* client = (freerdp_peer*)arg;
+
+ WINPR_ASSERT(client);
+
+ info = client->ContextExtra;
+ WINPR_ASSERT(info);
+
+ if (!test_peer_init(client))
+ {
+ freerdp_peer_free(client);
+ return 0;
+ }
+
+ /* Initialize the real server settings here */
+ WINPR_ASSERT(client->context);
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+ if (info->replay_dump)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE) ||
+ !freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, info->replay_dump))
+ goto fail;
+ }
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(info->key);
+ if (!key)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto fail;
+ rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert);
+ if (!cert)
+ goto fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ goto fail;
+ /* ENCRYPTION_LEVEL_HIGH; */
+ /* ENCRYPTION_LEVEL_LOW; */
+ /* ENCRYPTION_LEVEL_FIPS; */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
+ goto fail;
+
+ client->PostConnect = tf_peer_post_connect;
+ client->Activate = tf_peer_activate;
+
+ WINPR_ASSERT(client->context);
+ input = client->context->input;
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = tf_peer_synchronize_event;
+ input->KeyboardEvent = tf_peer_keyboard_event;
+ input->UnicodeKeyboardEvent = tf_peer_unicode_keyboard_event;
+ input->MouseEvent = tf_peer_mouse_event;
+ input->ExtendedMouseEvent = tf_peer_extended_mouse_event;
+
+ update = client->context->update;
+ WINPR_ASSERT(update);
+
+ update->RefreshRect = tf_peer_refresh_rect;
+ update->SuppressOutput = tf_peer_suppress_output;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ 0xFFFFFF /* FIXME */))
+ goto fail;
+
+ WINPR_ASSERT(client->Initialize);
+ rc = client->Initialize(client);
+ if (!rc)
+ goto fail;
+
+ context = (testPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ if (info->replay_dump)
+ {
+ const rdpTransportIo* cb = freerdp_get_io_callbacks(client->context);
+ rdpTransportIo replay;
+
+ WINPR_ASSERT(cb);
+ replay = *cb;
+ context->io = *cb;
+ replay.WritePdu = hook_peer_write_pdu;
+ freerdp_set_io_callbacks(client->context, &replay);
+ }
+
+ WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname);
+
+ while (error == CHANNEL_RC_OK)
+ {
+ count = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ DWORD tmp = client->GetEventHandles(client, &handles[count], 32 - count);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP transport event handles");
+ break;
+ }
+
+ count += tmp;
+ }
+
+ HANDLE channelHandle = WTSVirtualChannelManagerGetEventHandle(context->vcm);
+ handles[count++] = channelHandle;
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed (errno: %d)", errno);
+ break;
+ }
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+
+ if (WaitForSingleObject(channelHandle, 0) != WAIT_OBJECT_0)
+ continue;
+
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ break;
+
+ /* Handle dynamic virtual channel intializations */
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ switch (WTSVirtualChannelManagerGetDrdynvcState(context->vcm))
+ {
+ case DRDYNVC_STATE_NONE:
+ break;
+
+ case DRDYNVC_STATE_INITIALIZED:
+ break;
+
+ case DRDYNVC_STATE_READY:
+
+ /* Here is the correct state to start dynamic virtual channels */
+ if (sf_peer_audin_running(context) != context->audin_open)
+ {
+ if (!sf_peer_audin_running(context))
+ sf_peer_audin_start(context);
+ else
+ sf_peer_audin_stop(context);
+ }
+
+#if defined(CHANNEL_AINPUT_SERVER)
+ if (sf_peer_ainput_running(context) != context->ainput_open)
+ {
+ if (!sf_peer_ainput_running(context))
+ sf_peer_ainput_start(context);
+ else
+ sf_peer_ainput_stop(context);
+ }
+#endif
+
+ break;
+
+ case DRDYNVC_STATE_FAILED:
+ default:
+ break;
+ }
+ }
+ }
+
+ WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname);
+
+ WINPR_ASSERT(client->Disconnect);
+ client->Disconnect(client);
+fail:
+ freerdp_peer_context_free(client);
+ freerdp_peer_free(client);
+ return error;
+}
+
+static BOOL test_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ HANDLE hThread = NULL;
+ struct server_info* info = NULL;
+
+ WINPR_UNUSED(instance);
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(client);
+
+ info = instance->info;
+ client->ContextExtra = info;
+
+ if (!(hThread = CreateThread(NULL, 0, test_peer_mainloop, (void*)client, 0, NULL)))
+ return FALSE;
+
+ CloseHandle(hThread);
+ return TRUE;
+}
+
+static void test_server_mainloop(freerdp_listener* instance)
+{
+ HANDLE handles[32] = { 0 };
+ DWORD count = 0;
+ DWORD status = 0;
+
+ WINPR_ASSERT(instance);
+ while (1)
+ {
+ WINPR_ASSERT(instance->GetEventHandles);
+ count = instance->GetEventHandles(instance, handles, 32);
+
+ if (0 == count)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_ERR(TAG, "select failed");
+ break;
+ }
+
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+ }
+
+ WINPR_ASSERT(instance->Close);
+ instance->Close(instance);
+}
+
+static const struct
+{
+ const char spcap[7];
+ const char sfast[7];
+ const char sport[7];
+ const char slocal_only[13];
+ const char scert[7];
+ const char skey[6];
+} options = { "--pcap=", "--fast", "--port=", "--local-only", "--cert=", "--key=" };
+
+WINPR_ATTR_FORMAT_ARG(2, 0)
+static void print_entry(FILE* fp, WINPR_FORMAT_ARG const char* fmt, const char* what, size_t size)
+{
+ char buffer[32] = { 0 };
+ strncpy(buffer, what, MIN(size, sizeof(buffer) - 1));
+ fprintf(fp, fmt, buffer);
+}
+
+static WINPR_NORETURN(void usage(const char* app, const char* invalid))
+{
+ FILE* fp = stdout;
+
+ fprintf(fp, "Invalid argument '%s'\n", invalid);
+ fprintf(fp, "Usage: %s <arg>[ <arg> ...]\n", app);
+ fprintf(fp, "Arguments:\n");
+ print_entry(fp, "\t%s<pcap file>\n", options.spcap, sizeof(options.spcap));
+ print_entry(fp, "\t%s<cert file>\n", options.scert, sizeof(options.scert));
+ print_entry(fp, "\t%s<key file>\n", options.skey, sizeof(options.skey));
+ print_entry(fp, "\t%s\n", options.sfast, sizeof(options.sfast));
+ print_entry(fp, "\t%s<port>\n", options.sport, sizeof(options.sport));
+ print_entry(fp, "\t%s\n", options.slocal_only, sizeof(options.slocal_only));
+ exit(-1);
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ BOOL started = FALSE;
+ WSADATA wsaData = { 0 };
+ freerdp_listener* instance = NULL;
+ char* file = NULL;
+ char name[MAX_PATH] = { 0 };
+ long port = 3389;
+ BOOL localOnly = FALSE;
+ struct server_info info = { 0 };
+ const char* app = argv[0];
+
+ info.test_dump_rfx_realtime = TRUE;
+
+ errno = 0;
+
+ for (int i = 1; i < argc; i++)
+ {
+ char* arg = argv[i];
+
+ if (strncmp(arg, options.sfast, sizeof(options.sfast)) == 0)
+ info.test_dump_rfx_realtime = FALSE;
+ else if (strncmp(arg, options.sport, sizeof(options.sport)) == 0)
+ {
+ const char* sport = &arg[sizeof(options.sport)];
+ port = strtol(sport, NULL, 10);
+
+ if ((port < 1) || (port > UINT16_MAX) || (errno != 0))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.slocal_only, sizeof(options.slocal_only)) == 0)
+ localOnly = TRUE;
+ else if (strncmp(arg, options.spcap, sizeof(options.spcap)) == 0)
+ {
+ info.test_pcap_file = &arg[sizeof(options.spcap)];
+ if (!winpr_PathFileExists(info.test_pcap_file))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.scert, sizeof(options.scert)) == 0)
+ {
+ info.cert = &arg[sizeof(options.scert)];
+ if (!winpr_PathFileExists(info.cert))
+ usage(app, arg);
+ }
+ else if (strncmp(arg, options.skey, sizeof(options.skey)) == 0)
+ {
+ info.key = &arg[sizeof(options.skey)];
+ if (!winpr_PathFileExists(info.key))
+ usage(app, arg);
+ }
+ else
+ usage(app, arg);
+ }
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ instance = freerdp_listener_new();
+
+ if (!instance)
+ return -1;
+
+ if (!info.cert)
+ info.cert = "server.crt";
+ if (!info.key)
+ info.key = "server.key";
+
+ instance->info = (void*)&info;
+ instance->PeerAccepted = test_peer_accepted;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto fail;
+
+ /* Open the server socket and start listening. */
+ sprintf_s(name, sizeof(name), "tfreerdp-server.%ld", port);
+ file = GetKnownSubPath(KNOWN_PATH_TEMP, name);
+
+ if (!file)
+ goto fail;
+
+ if (localOnly)
+ {
+ WINPR_ASSERT(instance->OpenLocal);
+ started = instance->OpenLocal(instance, file);
+ }
+ else
+ {
+ WINPR_ASSERT(instance->Open);
+ started = instance->Open(instance, NULL, (UINT16)port);
+ }
+
+ if (started)
+ {
+ /* Entering the server main loop. In a real server the listener can be run in its own
+ * thread. */
+ test_server_mainloop(instance);
+ }
+
+ rc = 0;
+fail:
+ free(file);
+ freerdp_listener_free(instance);
+ WSACleanup();
+ return rc;
+}
diff --git a/server/Sample/sfreerdp.h b/server/Sample/sfreerdp.h
new file mode 100644
index 0000000..cba1052
--- /dev/null
+++ b/server/Sample/sfreerdp.h
@@ -0,0 +1,76 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Sample Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_SAMPLE_SFREERDP_SERVER_H
+#define FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/channels/wtsvc.h>
+#if defined(CHANNEL_AINPUT_SERVER)
+#include <freerdp/server/ainput.h>
+#endif
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+#include <freerdp/server/rdpsnd.h>
+#include <freerdp/server/encomsp.h>
+#include <freerdp/transport_io.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/image.h>
+
+struct test_peer_context
+{
+ rdpContext _p;
+
+ RFX_CONTEXT* rfx_context;
+ NSC_CONTEXT* nsc_context;
+ wStream* s;
+ BYTE* bg_data;
+ UINT32 icon_x;
+ UINT32 icon_y;
+ BOOL activated;
+ HANDLE event;
+ HANDLE stopEvent;
+ HANDLE vcm;
+ void* debug_channel;
+ HANDLE debug_channel_thread;
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin;
+#endif
+ BOOL audin_open;
+#if defined(CHANNEL_AINPUT_SERVER)
+ ainput_server_context* ainput;
+ BOOL ainput_open;
+#endif
+ UINT32 frame_id;
+ RdpsndServerContext* rdpsnd;
+ EncomspServerContext* encomsp;
+
+ rdpTransportIo io;
+ wImage* image;
+};
+typedef struct test_peer_context testPeerContext;
+
+#endif /* FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H */
diff --git a/server/Sample/test_icon.bmp b/server/Sample/test_icon.bmp
new file mode 100644
index 0000000..08935d4
--- /dev/null
+++ b/server/Sample/test_icon.bmp
Binary files differ
diff --git a/server/Sample/test_icon.jpg b/server/Sample/test_icon.jpg
new file mode 100644
index 0000000..758b59e
--- /dev/null
+++ b/server/Sample/test_icon.jpg
Binary files differ
diff --git a/server/Sample/test_icon.png b/server/Sample/test_icon.png
new file mode 100644
index 0000000..91a4a5a
--- /dev/null
+++ b/server/Sample/test_icon.png
Binary files differ
diff --git a/server/Sample/test_icon.ppm b/server/Sample/test_icon.ppm
new file mode 100644
index 0000000..bcc4a0e
--- /dev/null
+++ b/server/Sample/test_icon.ppm
@@ -0,0 +1,5572 @@
+P3
+# CREATOR: GIMP PNM Filter Version 1.1
+29 64
+255
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+159
+159
+160
+135
+154
+160
+85
+141
+160
+82
+141
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+112
+148
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+155
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+114
+148
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+91
+143
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+136
+154
+160
+76
+140
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+137
+154
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+152
+158
+160
+79
+141
+160
+75
+140
+161
+74
+139
+160
+74
+139
+160
+83
+141
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+90
+143
+160
+75
+140
+161
+74
+139
+160
+75
+140
+161
+75
+140
+161
+105
+147
+160
+159
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+125
+151
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+74
+139
+160
+111
+154
+167
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+158
+160
+88
+143
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+138
+160
+99
+170
+189
+134
+171
+180
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+126
+151
+160
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+137
+158
+63
+124
+147
+108
+181
+198
+152
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+80
+141
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+138
+159
+21
+72
+99
+78
+143
+164
+126
+180
+192
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+129
+152
+160
+74
+139
+160
+75
+140
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+73
+138
+159
+25
+77
+104
+12
+60
+88
+105
+176
+194
+145
+167
+172
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+98
+145
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+27
+80
+106
+6
+53
+82
+57
+117
+140
+121
+182
+195
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+83
+141
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+76
+141
+162
+30
+83
+109
+6
+53
+82
+28
+81
+107
+108
+180
+197
+154
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+155
+158
+160
+80
+140
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+74
+139
+160
+74
+138
+159
+73
+138
+159
+75
+140
+161
+37
+92
+118
+2
+48
+78
+19
+69
+96
+100
+171
+189
+147
+165
+170
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+149
+157
+160
+79
+140
+160
+74
+139
+160
+74
+139
+160
+76
+142
+163
+82
+149
+169
+96
+165
+184
+106
+178
+196
+111
+185
+202
+114
+188
+205
+114
+188
+205
+97
+168
+186
+107
+179
+196
+150
+164
+168
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+160
+80
+141
+160
+74
+139
+160
+86
+154
+173
+102
+173
+191
+111
+184
+200
+111
+184
+201
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+111
+184
+201
+110
+183
+200
+113
+179
+195
+138
+170
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+160
+77
+139
+159
+93
+162
+182
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+109
+182
+199
+110
+183
+200
+138
+169
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+146
+156
+159
+99
+165
+182
+112
+185
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+150
+165
+168
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+138
+168
+175
+110
+182
+199
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+107
+180
+197
+107
+168
+183
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+156
+161
+162
+117
+179
+194
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+185
+201
+83
+149
+168
+7
+52
+81
+101
+119
+130
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+144
+167
+172
+110
+183
+200
+110
+182
+199
+110
+183
+200
+109
+182
+199
+61
+125
+148
+47
+91
+116
+58
+95
+118
+43
+88
+112
+64
+128
+151
+108
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+185
+202
+98
+168
+186
+125
+165
+175
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+126
+175
+187
+109
+182
+199
+110
+182
+199
+111
+184
+201
+55
+119
+143
+108
+136
+153
+230
+234
+237
+186
+198
+205
+234
+233
+221
+51
+89
+113
+68
+133
+155
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+123
+176
+188
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+114
+179
+195
+110
+183
+200
+110
+183
+200
+103
+175
+193
+66
+114
+136
+247
+248
+249
+240
+243
+244
+57
+95
+118
+22
+66
+93
+40
+80
+106
+64
+113
+135
+108
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+119
+177
+191
+156
+161
+162
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+182
+199
+110
+183
+200
+110
+183
+200
+100
+172
+190
+106
+143
+161
+249
+250
+250
+251
+252
+252
+132
+155
+170
+51
+89
+113
+145
+165
+178
+79
+121
+142
+108
+181
+198
+110
+183
+200
+109
+182
+199
+110
+184
+200
+113
+187
+203
+103
+175
+193
+104
+177
+194
+117
+180
+195
+154
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+112
+180
+196
+110
+183
+200
+110
+183
+200
+107
+180
+197
+74
+124
+145
+250
+251
+251
+254
+254
+254
+250
+251
+251
+238
+241
+243
+220
+226
+230
+67
+122
+144
+109
+182
+199
+110
+183
+200
+110
+183
+200
+101
+171
+189
+38
+91
+116
+154
+176
+179
+94
+128
+141
+99
+165
+182
+153
+164
+166
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+120
+177
+190
+109
+182
+199
+110
+183
+200
+110
+184
+200
+81
+152
+172
+113
+144
+161
+242
+244
+245
+242
+244
+245
+235
+239
+241
+92
+129
+148
+87
+157
+177
+110
+183
+200
+110
+183
+200
+115
+189
+206
+37
+91
+116
+8
+55
+84
+0
+46
+77
+55
+117
+140
+118
+186
+201
+152
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+134
+171
+180
+109
+182
+199
+109
+181
+198
+109
+182
+199
+113
+187
+203
+88
+159
+179
+83
+140
+160
+105
+152
+169
+76
+134
+155
+88
+159
+179
+112
+185
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+109
+182
+199
+81
+147
+167
+61
+122
+144
+113
+187
+204
+113
+179
+195
+153
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+152
+163
+166
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+182
+199
+108
+180
+198
+109
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+109
+182
+199
+110
+183
+200
+106
+178
+195
+74
+138
+159
+110
+183
+200
+117
+178
+192
+155
+162
+163
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+161
+121
+177
+190
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+95
+164
+183
+77
+142
+163
+110
+183
+200
+123
+175
+188
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+139
+169
+176
+110
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+70
+133
+154
+103
+173
+191
+109
+181
+198
+136
+171
+179
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+159
+160
+160
+115
+179
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+108
+181
+198
+95
+164
+183
+110
+183
+200
+110
+182
+199
+156
+161
+162
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+149
+164
+168
+109
+182
+199
+109
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+111
+183
+200
+109
+182
+199
+136
+170
+177
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+137
+169
+177
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+119
+178
+192
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+162
+120
+176
+189
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+181
+198
+148
+165
+169
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+143
+167
+172
+110
+181
+198
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+181
+198
+109
+182
+199
+126
+175
+186
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+116
+180
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+182
+199
+109
+182
+199
+153
+162
+164
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+132
+172
+181
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+124
+175
+187
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+138
+169
+176
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+115
+165
+178
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+134
+171
+180
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+184
+201
+96
+165
+184
+133
+152
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+119
+177
+190
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+107
+179
+197
+90
+141
+158
+156
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+156
+161
+163
+111
+181
+197
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+82
+149
+169
+127
+151
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+143
+167
+172
+111
+182
+199
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+96
+166
+184
+77
+138
+160
+158
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+131
+172
+182
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+103
+174
+191
+77
+142
+163
+99
+145
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+157
+161
+162
+121
+176
+189
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+82
+149
+168
+74
+140
+160
+126
+152
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+162
+164
+111
+180
+196
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+184
+201
+91
+160
+178
+74
+139
+160
+82
+141
+160
+148
+157
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+145
+166
+171
+128
+173
+184
+122
+176
+189
+129
+174
+184
+160
+160
+160
+160
+160
+160
+160
+160
+160
+144
+166
+171
+109
+182
+199
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+103
+174
+192
+74
+139
+160
+74
+140
+160
+94
+144
+160
+156
+159
+160
+160
+160
+160
+160
+160
+160
+152
+163
+165
+120
+177
+190
+109
+182
+199
+109
+182
+199
+109
+181
+198
+153
+163
+165
+160
+160
+160
+160
+160
+160
+160
+160
+160
+128
+173
+184
+110
+183
+200
+110
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+184
+201
+74
+139
+160
+75
+139
+160
+75
+140
+161
+95
+144
+160
+160
+160
+160
+158
+160
+161
+114
+180
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+151
+164
+167
+160
+160
+160
+160
+160
+160
+160
+160
+160
+115
+179
+194
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+185
+202
+77
+143
+163
+74
+139
+160
+75
+140
+160
+74
+139
+160
+104
+145
+159
+134
+169
+177
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+182
+199
+109
+182
+199
+122
+176
+188
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+112
+185
+202
+79
+144
+165
+74
+139
+160
+75
+140
+161
+74
+139
+160
+74
+138
+160
+92
+155
+174
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+109
+181
+198
+110
+183
+200
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+109
+181
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+112
+186
+202
+75
+141
+162
+74
+139
+160
+75
+140
+161
+75
+140
+160
+74
+139
+160
+74
+139
+160
+87
+154
+173
+111
+184
+201
+110
+183
+200
+110
+183
+200
+109
+181
+198
+110
+183
+200
+160
+160
+160
+160
+160
+160
+160
+160
+160
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+93
+162
+181
+110
+183
+200
+109
+182
+199
+109
+181
+198
+109
+182
+199
+160
+160
+160
+160
+160
+160
+160
+160
+160
+113
+181
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+104
+176
+194
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+76
+141
+162
+103
+174
+192
+110
+182
+199
+110
+183
+200
+109
+182
+199
+160
+160
+160
+160
+160
+160
+160
+160
+160
+121
+176
+189
+109
+182
+199
+110
+183
+200
+109
+182
+199
+119
+187
+203
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+97
+167
+186
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+80
+145
+165
+111
+185
+202
+109
+182
+199
+143
+167
+173
+160
+160
+160
+160
+160
+160
+160
+160
+160
+133
+172
+181
+109
+182
+199
+110
+182
+199
+109
+182
+199
+140
+198
+211
+111
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+111
+184
+201
+87
+156
+175
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+138
+160
+117
+157
+169
+159
+161
+161
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+147
+165
+169
+110
+183
+200
+109
+182
+199
+110
+183
+200
+161
+208
+219
+113
+184
+201
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+105
+177
+194
+77
+142
+163
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+94
+144
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+154
+162
+164
+114
+181
+196
+109
+182
+199
+109
+182
+200
+181
+218
+227
+114
+185
+201
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+110
+183
+200
+95
+163
+183
+50
+106
+130
+75
+141
+162
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+81
+140
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+160
+161
+125
+175
+187
+109
+182
+199
+108
+182
+199
+201
+228
+235
+116
+186
+202
+109
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+183
+200
+77
+141
+162
+36
+88
+115
+48
+104
+128
+69
+133
+154
+74
+139
+160
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+83
+141
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+139
+169
+176
+110
+182
+199
+107
+180
+197
+204
+230
+236
+137
+196
+210
+108
+182
+199
+110
+183
+200
+110
+183
+200
+110
+183
+200
+109
+182
+199
+110
+184
+200
+46
+101
+126
+38
+90
+116
+48
+103
+128
+61
+120
+143
+69
+133
+154
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+97
+145
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+160
+161
+113
+181
+197
+107
+181
+198
+191
+223
+230
+176
+215
+225
+105
+180
+198
+110
+183
+200
+110
+183
+200
+110
+183
+200
+112
+185
+202
+81
+146
+166
+35
+87
+114
+57
+116
+139
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+127
+152
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+140
+169
+175
+107
+181
+199
+171
+212
+222
+218
+237
+241
+101
+178
+196
+110
+183
+200
+110
+183
+200
+110
+183
+200
+105
+177
+195
+42
+96
+122
+44
+98
+124
+71
+134
+156
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+75
+140
+161
+88
+143
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+120
+176
+189
+144
+200
+213
+251
+252
+252
+108
+182
+199
+109
+182
+199
+109
+182
+199
+109
+182
+199
+53
+110
+135
+37
+90
+116
+46
+101
+127
+72
+135
+157
+74
+139
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+161
+75
+140
+160
+74
+139
+160
+82
+141
+160
+146
+156
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+158
+159
+159
+146
+160
+164
+108
+179
+196
+248
+252
+253
+151
+203
+215
+107
+182
+199
+108
+181
+198
+69
+132
+153
+5
+52
+81
+5
+52
+81
+11
+59
+87
+45
+102
+126
+73
+138
+159
+75
+140
+160
+75
+140
+161
+75
+140
+161
+75
+140
+161
+74
+139
+160
+81
+140
+160
+142
+155
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+141
+147
+150
+70
+98
+115
+41
+77
+99
+41
+95
+119
+120
+157
+173
+157
+187
+198
+67
+131
+154
+46
+103
+127
+4
+50
+79
+6
+53
+82
+6
+54
+83
+6
+53
+82
+3
+50
+79
+28
+81
+108
+52
+111
+135
+61
+123
+145
+77
+132
+151
+94
+139
+154
+116
+146
+157
+154
+158
+159
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+155
+156
+157
+61
+91
+110
+19
+62
+89
+6
+53
+82
+6
+53
+82
+10
+55
+83
+19
+62
+89
+44
+81
+104
+47
+82
+103
+11
+56
+84
+6
+53
+82
+10
+56
+84
+28
+68
+93
+50
+83
+104
+71
+98
+115
+97
+116
+128
+126
+137
+144
+145
+152
+155
+150
+155
+157
+155
+157
+158
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
+160
diff --git a/server/Sample/test_icon.webp b/server/Sample/test_icon.webp
new file mode 100644
index 0000000..5a3fa95
--- /dev/null
+++ b/server/Sample/test_icon.webp
Binary files differ
diff --git a/server/Windows/CMakeLists.txt b/server/Windows/CMakeLists.txt
new file mode 100644
index 0000000..2744b65
--- /dev/null
+++ b/server/Windows/CMakeLists.txt
@@ -0,0 +1,128 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+set(MODULE_NAME "wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS")
+
+include (WarnUnmaintained)
+warn_unmaintained(${MODULE_NAME})
+
+include_directories(.)
+
+set(${MODULE_PREFIX}_SRCS
+ wf_update.c
+ wf_update.h
+ wf_dxgi.c
+ wf_dxgi.h
+ wf_input.c
+ wf_input.h
+ wf_interface.c
+ wf_interface.h
+ wf_mirage.c
+ wf_mirage.h
+ wf_peer.c
+ wf_peer.h
+ wf_settings.c
+ wf_settings.h
+ wf_info.c
+ wf_info.h)
+
+if(CHANNEL_RDPSND AND NOT WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_wasapi.c
+ wf_wasapi.h
+ )
+endif()
+
+if(CHANNEL_RDPSND AND WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_directsound.c
+ wf_directsound.h
+ )
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+if(WITH_SERVER_INTERFACE)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+else()
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+endif()
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+if(WITH_SERVER_INTERFACE)
+ add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+else()
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/wfreerdp.c cli/wfreerdp.h)
+ add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ endif()
+endif()
+
+
+if(NOT CMAKE_WINDOWS_VERSION STREQUAL "WINXP")
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} d3d11 dxgi)
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} dsound)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-server freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+if(WITH_SERVER_INTERFACE)
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+ endif()
+else()
+ install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+ endif()
+endif()
+
+if(WITH_SERVER_INTERFACE)
+ add_subdirectory(cli)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/ModuleOptions.cmake b/server/Windows/ModuleOptions.cmake
new file mode 100644
index 0000000..69d7596
--- /dev/null
+++ b/server/Windows/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_SERVER_NAME "wfreerdp-server")
+set(FREERDP_SERVER_PLATFORM "Windows")
+set(FREERDP_SERVER_VENDOR "FreeRDP")
diff --git a/server/Windows/cli/CMakeLists.txt b/server/Windows/cli/CMakeLists.txt
new file mode 100644
index 0000000..e125ac3
--- /dev/null
+++ b/server/Windows/cli/CMakeLists.txt
@@ -0,0 +1,60 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server (CLI) cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+set(MODULE_NAME "wfreerdp-server-cli")
+set(OUTPUT_NAME " wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS_CLI")
+
+include_directories(..)
+
+set(${MODULE_PREFIX}_SRCS
+ wfreerdp.c
+ wfreerdp.h)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}${FREERDP_API_VERSION}")
+else()
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}")
+endif()
+
+set(${MODULE_PREFIX}_LIBS wfreerdp-server)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/cli/wfreerdp.c b/server/Windows/cli/wfreerdp.c
new file mode 100644
index 0000000..efc1bf5
--- /dev/null
+++ b/server/Windows/cli/wfreerdp.c
@@ -0,0 +1,171 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_interface.h"
+
+#include "wfreerdp.h"
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+int IDcount = 0;
+
+BOOL CALLBACK moncb(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
+{
+ WLog_DBG(TAG, "%d\t(%ld, %ld), (%ld, %ld)", IDcount, lprcMonitor->left, lprcMonitor->top,
+ lprcMonitor->right, lprcMonitor->bottom);
+ IDcount++;
+ return TRUE;
+}
+
+int main(int argc, char* argv[])
+{
+ freerdp_server_warn_unmaintained(argc, argv);
+
+ BOOL screen_selected = FALSE;
+ int index = 1;
+ wfServer* server;
+ server = wfreerdp_server_new();
+ set_screen_id(0);
+ // handle args
+ errno = 0;
+
+ while (index < argc)
+ {
+ // first the args that will cause the program to terminate
+ if (strcmp("--list-screens", argv[index]) == 0)
+ {
+ int width;
+ int height;
+ int bpp;
+ WLog_INFO(TAG, "Detecting screens...");
+ WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)");
+
+ for (int i = 0;; i++)
+ {
+ _TCHAR name[128] = { 0 };
+ if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0)
+ {
+ if ((width * height * bpp) == 0)
+ continue;
+
+ WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp);
+ WLog_INFO(TAG, "%s", name);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ {
+ int vscreen_w;
+ int vscreen_h;
+ vscreen_w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ vscreen_h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ WLog_INFO(TAG, "");
+ EnumDisplayMonitors(NULL, NULL, moncb, 0);
+ IDcount = 0;
+ WLog_INFO(TAG, "Virtual Screen = %dx%d", vscreen_w, vscreen_h);
+ }
+
+ return 0;
+ }
+
+ if (strcmp("--screen", argv[index]) == 0)
+ {
+ UINT32 val;
+ screen_selected = TRUE;
+ index++;
+
+ if (index == argc)
+ {
+ WLog_INFO(TAG, "missing screen id parameter");
+ return 0;
+ }
+
+ val = strtoul(argv[index], NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+
+ set_screen_id(val);
+ index++;
+ }
+
+ if (index == argc - 1)
+ {
+ UINT32 val = strtoul(argv[index], NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+
+ server->port = val;
+ break;
+ }
+ }
+
+ if (screen_selected == FALSE)
+ {
+ int width;
+ int height;
+ int bpp;
+ WLog_INFO(TAG, "screen id not provided. attempting to detect...");
+ WLog_INFO(TAG, "Detecting screens...");
+ WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)");
+
+ for (int i = 0;; i++)
+ {
+ _TCHAR name[128] = { 0 };
+ if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0)
+ {
+ if ((width * height * bpp) == 0)
+ continue;
+
+ WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp);
+ WLog_INFO(TAG, "%s", name);
+ set_screen_id(i);
+ break;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ WLog_INFO(TAG, "Starting server");
+ wfreerdp_server_start(server);
+ WaitForSingleObject(server->thread, INFINITE);
+ WLog_INFO(TAG, "Stopping server");
+ wfreerdp_server_stop(server);
+ wfreerdp_server_free(server);
+ return 0;
+}
diff --git a/server/Windows/cli/wfreerdp.h b/server/Windows/cli/wfreerdp.h
new file mode 100644
index 0000000..017106d
--- /dev/null
+++ b/server/Windows/cli/wfreerdp.h
@@ -0,0 +1,25 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_FREERDP_H
+#define FREERDP_SERVER_WIN_FREERDP_H
+
+#include <freerdp/freerdp.h>
+
+#endif /* FREERDP_SERVER_WIN_FREERDP_H */
diff --git a/server/Windows/wf_directsound.c b/server/Windows/wf_directsound.c
new file mode 100644
index 0000000..441397a
--- /dev/null
+++ b/server/Windows/wf_directsound.c
@@ -0,0 +1,219 @@
+#include "wf_directsound.h"
+#include "wf_interface.h"
+#include "wf_info.h"
+#include "wf_rdpsnd.h"
+
+#include <winpr/windows.h>
+
+#define INITGUID
+#include <initguid.h>
+#include <objbase.h>
+
+#define CINTERFACE 1
+#include <mmsystem.h>
+#include <dsound.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+IDirectSoundCapture8* cap;
+IDirectSoundCaptureBuffer8* capBuf;
+DSCBUFFERDESC dscbd;
+DWORD lastPos;
+wfPeerContext* latestPeer;
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
+{
+ latestPeer = peer;
+ return 0;
+}
+
+int wf_directsound_activate(RdpsndServerContext* context)
+{
+ HRESULT hr;
+ wfInfo* wfi;
+ HANDLE hThread;
+
+ LPDIRECTSOUNDCAPTUREBUFFER pDSCB;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed to wfi instance");
+ return 1;
+ }
+ WLog_DBG(TAG, "RDPSND (direct sound) Activated");
+ hr = DirectSoundCaptureCreate8(NULL, &cap, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create sound capture device");
+ return 1;
+ }
+
+ WLog_INFO(TAG, "Created sound capture device");
+ dscbd.dwSize = sizeof(DSCBUFFERDESC);
+ dscbd.dwFlags = 0;
+ dscbd.dwBufferBytes = wfi->agreed_format->nAvgBytesPerSec;
+ dscbd.dwReserved = 0;
+ dscbd.lpwfxFormat = wfi->agreed_format;
+ dscbd.dwFXCount = 0;
+ dscbd.lpDSCFXDesc = NULL;
+
+ hr = cap->lpVtbl->CreateCaptureBuffer(cap, &dscbd, &pDSCB, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create capture buffer");
+ }
+
+ WLog_INFO(TAG, "Created capture buffer");
+ hr = pDSCB->lpVtbl->QueryInterface(pDSCB, &IID_IDirectSoundCaptureBuffer8, (LPVOID*)&capBuf);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to QI capture buffer");
+ }
+ WLog_INFO(TAG, "Created IDirectSoundCaptureBuffer8");
+ pDSCB->lpVtbl->Release(pDSCB);
+ lastPos = 0;
+
+ if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_directsound_thread, latestPeer, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create direct sound thread");
+ return 1;
+ }
+ CloseHandle(hThread);
+
+ return 0;
+}
+
+static DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam)
+{
+ HRESULT hr;
+ DWORD beg = 0;
+ DWORD end = 0;
+ DWORD diff, rate;
+ wfPeerContext* context;
+ wfInfo* wfi;
+
+ VOID* pbCaptureData = NULL;
+ DWORD dwCaptureLength = 0;
+ VOID* pbCaptureData2 = NULL;
+ DWORD dwCaptureLength2 = 0;
+ VOID* pbPlayData = NULL;
+ DWORD dwReadPos = 0;
+ LONG lLockSize = 0;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed get instance");
+ return 1;
+ }
+
+ context = (wfPeerContext*)lpParam;
+ rate = 1000 / 24;
+ WLog_INFO(TAG, "Trying to start capture");
+ hr = capBuf->lpVtbl->Start(capBuf, DSCBSTART_LOOPING);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to start capture");
+ }
+ WLog_INFO(TAG, "Capture started");
+
+ while (1)
+ {
+
+ end = GetTickCount();
+ diff = end - beg;
+
+ if (diff < rate)
+ {
+ Sleep(rate - diff);
+ }
+
+ beg = GetTickCount();
+
+ if (wf_rdpsnd_lock() > 0)
+ {
+ // check for main exit condition
+ if (wfi->snd_stop == TRUE)
+ {
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ hr = capBuf->lpVtbl->GetCurrentPosition(capBuf, NULL, &dwReadPos);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get read pos");
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ lLockSize = dwReadPos - lastPos; // dscbd.dwBufferBytes;
+ if (lLockSize < 0)
+ lLockSize += dscbd.dwBufferBytes;
+
+ // WLog_DBG(TAG, "Last, read, lock = [%"PRIu32", %"PRIu32", %"PRId32"]\n", lastPos,
+ // dwReadPos, lLockSize);
+
+ if (lLockSize == 0)
+ {
+ wf_rdpsnd_unlock();
+ continue;
+ }
+
+ hr = capBuf->lpVtbl->Lock(capBuf, lastPos, lLockSize, &pbCaptureData, &dwCaptureLength,
+ &pbCaptureData2, &dwCaptureLength2, 0L);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to lock sound capture buffer");
+ wf_rdpsnd_unlock();
+ break;
+ }
+
+ // fwrite(pbCaptureData, 1, dwCaptureLength, pFile);
+ // fwrite(pbCaptureData2, 1, dwCaptureLength2, pFile);
+
+ // FIXME: frames = bytes/(bytespersample * channels)
+
+ context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData, dwCaptureLength / 4,
+ (UINT16)(beg & 0xffff));
+ context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData2, dwCaptureLength2 / 4,
+ (UINT16)(beg & 0xffff));
+
+ hr = capBuf->lpVtbl->Unlock(capBuf, pbCaptureData, dwCaptureLength, pbCaptureData2,
+ dwCaptureLength2);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to unlock sound capture buffer");
+ wf_rdpsnd_unlock();
+ return 0;
+ }
+
+ // TODO keep track of location in buffer
+ lastPos += dwCaptureLength;
+ lastPos %= dscbd.dwBufferBytes;
+ lastPos += dwCaptureLength2;
+ lastPos %= dscbd.dwBufferBytes;
+
+ wf_rdpsnd_unlock();
+ }
+ }
+
+ WLog_INFO(TAG, "Trying to stop sound capture");
+ hr = capBuf->lpVtbl->Stop(capBuf);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to stop capture");
+ }
+
+ WLog_INFO(TAG, "Capture stopped");
+ capBuf->lpVtbl->Release(capBuf);
+ cap->lpVtbl->Release(cap);
+
+ lastPos = 0;
+
+ return 0;
+}
diff --git a/server/Windows/wf_directsound.h b/server/Windows/wf_directsound.h
new file mode 100644
index 0000000..01c1bdd
--- /dev/null
+++ b/server/Windows/wf_directsound.h
@@ -0,0 +1,13 @@
+#ifndef FREERDP_SERVER_WIN_DSOUND_H
+#define FREERDP_SERVER_WIN_DSOUND_H
+
+#include <freerdp/server/rdpsnd.h>
+#include "wf_interface.h"
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer);
+
+int wf_directsound_activate(RdpsndServerContext* context);
+
+DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam);
+
+#endif /* FREERDP_SERVER_WIN_DSOUND_H */
diff --git a/server/Windows/wf_dxgi.c b/server/Windows/wf_dxgi.c
new file mode 100644
index 0000000..899cb55
--- /dev/null
+++ b/server/Windows/wf_dxgi.c
@@ -0,0 +1,486 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include "wf_interface.h"
+
+#ifdef WITH_DXGI_1_2
+
+#define CINTERFACE
+
+#include <D3D11.h>
+#include <dxgi1_2.h>
+
+#include <tchar.h>
+#include "wf_dxgi.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+/* Driver types supported */
+D3D_DRIVER_TYPE DriverTypes[] = {
+ D3D_DRIVER_TYPE_HARDWARE,
+ D3D_DRIVER_TYPE_WARP,
+ D3D_DRIVER_TYPE_REFERENCE,
+};
+UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
+
+/* Feature levels supported */
+D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 };
+
+UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
+
+D3D_FEATURE_LEVEL FeatureLevel;
+
+ID3D11Device* gDevice = NULL;
+ID3D11DeviceContext* gContext = NULL;
+IDXGIOutputDuplication* gOutputDuplication = NULL;
+ID3D11Texture2D* gAcquiredDesktopImage = NULL;
+
+IDXGISurface* surf;
+ID3D11Texture2D* sStage;
+
+DXGI_OUTDUPL_FRAME_INFO FrameInfo;
+
+int wf_dxgi_init(wfInfo* wfi)
+{
+ gAcquiredDesktopImage = NULL;
+
+ if (wf_dxgi_createDevice(wfi) != 0)
+ {
+ return 1;
+ }
+
+ if (wf_dxgi_getDuplication(wfi) != 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_createDevice(wfInfo* wfi)
+{
+ HRESULT status;
+ UINT DriverTypeIndex;
+
+ for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
+ {
+ status = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels,
+ NumFeatureLevels, D3D11_SDK_VERSION, &gDevice, &FeatureLevel,
+ &gContext);
+ if (SUCCEEDED(status))
+ break;
+
+ WLog_INFO(TAG, "D3D11CreateDevice returned [%ld] for Driver Type %d", status,
+ DriverTypes[DriverTypeIndex]);
+ }
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to create device in InitializeDx");
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_getDuplication(wfInfo* wfi)
+{
+ HRESULT status;
+ UINT dTop, i = 0;
+ DXGI_OUTPUT_DESC desc = { 0 };
+ IDXGIOutput* pOutput;
+ IDXGIDevice* DxgiDevice = NULL;
+ IDXGIAdapter* DxgiAdapter = NULL;
+ IDXGIOutput* DxgiOutput = NULL;
+ IDXGIOutput1* DxgiOutput1 = NULL;
+
+ status = gDevice->lpVtbl->QueryInterface(gDevice, &IID_IDXGIDevice, (void**)&DxgiDevice);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get QI for DXGI Device");
+ return 1;
+ }
+
+ status = DxgiDevice->lpVtbl->GetParent(DxgiDevice, &IID_IDXGIAdapter, (void**)&DxgiAdapter);
+ DxgiDevice->lpVtbl->Release(DxgiDevice);
+ DxgiDevice = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get parent DXGI Adapter");
+ return 1;
+ }
+
+ pOutput = NULL;
+
+ while (DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND)
+ {
+ DXGI_OUTPUT_DESC* pDesc = &desc;
+
+ status = pOutput->lpVtbl->GetDesc(pOutput, pDesc);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get description");
+ return 1;
+ }
+
+ WLog_INFO(TAG, "Output %u: [%s] [%d]", i, pDesc->DeviceName, pDesc->AttachedToDesktop);
+
+ if (pDesc->AttachedToDesktop)
+ dTop = i;
+
+ pOutput->lpVtbl->Release(pOutput);
+ ++i;
+ }
+
+ dTop = wfi->screenID;
+
+ status = DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, dTop, &DxgiOutput);
+ DxgiAdapter->lpVtbl->Release(DxgiAdapter);
+ DxgiAdapter = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get output");
+ return 1;
+ }
+
+ status =
+ DxgiOutput->lpVtbl->QueryInterface(DxgiOutput, &IID_IDXGIOutput1, (void**)&DxgiOutput1);
+ DxgiOutput->lpVtbl->Release(DxgiOutput);
+ DxgiOutput = NULL;
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get IDXGIOutput1");
+ return 1;
+ }
+
+ status =
+ DxgiOutput1->lpVtbl->DuplicateOutput(DxgiOutput1, (IUnknown*)gDevice, &gOutputDuplication);
+ DxgiOutput1->lpVtbl->Release(DxgiOutput1);
+ DxgiOutput1 = NULL;
+
+ if (FAILED(status))
+ {
+ if (status == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
+ {
+ WLog_ERR(
+ TAG,
+ "There is already the maximum number of applications using the Desktop Duplication "
+ "API running, please close one of those applications and then try again.");
+ return 1;
+ }
+
+ WLog_ERR(TAG, "Failed to get duplicate output. Status = %ld", status);
+ return 1;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_cleanup(wfInfo* wfi)
+{
+ if (wfi->framesWaiting > 0)
+ {
+ wf_dxgi_releasePixelData(wfi);
+ }
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ if (gOutputDuplication)
+ {
+ gOutputDuplication->lpVtbl->Release(gOutputDuplication);
+ gOutputDuplication = NULL;
+ }
+
+ if (gContext)
+ {
+ gContext->lpVtbl->Release(gContext);
+ gContext = NULL;
+ }
+
+ if (gDevice)
+ {
+ gDevice->lpVtbl->Release(gDevice);
+ gDevice = NULL;
+ }
+
+ return 0;
+}
+
+int wf_dxgi_nextFrame(wfInfo* wfi, UINT timeout)
+{
+ HRESULT status = 0;
+ UINT i = 0;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+ IDXGIResource* DesktopResource = NULL;
+
+ if (wfi->framesWaiting > 0)
+ {
+ wf_dxgi_releasePixelData(wfi);
+ }
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ status = gOutputDuplication->lpVtbl->AcquireNextFrame(gOutputDuplication, timeout, &FrameInfo,
+ &DesktopResource);
+
+ if (status == DXGI_ERROR_WAIT_TIMEOUT)
+ {
+ return 1;
+ }
+
+ if (FAILED(status))
+ {
+ if (status == DXGI_ERROR_ACCESS_LOST)
+ {
+ WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status);
+ WLog_ERR(TAG, "Trying to reinitialize due to ACCESS LOST...");
+
+ if (gAcquiredDesktopImage)
+ {
+ gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage);
+ gAcquiredDesktopImage = NULL;
+ }
+
+ if (gOutputDuplication)
+ {
+ gOutputDuplication->lpVtbl->Release(gOutputDuplication);
+ gOutputDuplication = NULL;
+ }
+
+ wf_dxgi_getDuplication(wfi);
+
+ return 1;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status);
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame with status=%ld", status);
+ }
+
+ return 1;
+ }
+ }
+
+ status = DesktopResource->lpVtbl->QueryInterface(DesktopResource, &IID_ID3D11Texture2D,
+ (void**)&gAcquiredDesktopImage);
+ DesktopResource->lpVtbl->Release(DesktopResource);
+ DesktopResource = NULL;
+
+ if (FAILED(status))
+ {
+ return 1;
+ }
+
+ wfi->framesWaiting = FrameInfo.AccumulatedFrames;
+
+ if (FrameInfo.AccumulatedFrames == 0)
+ {
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame with status=%ld", status);
+ }
+ }
+
+ return 0;
+}
+
+int wf_dxgi_getPixelData(wfInfo* wfi, BYTE** data, int* pitch, RECT* invalid)
+{
+ HRESULT status;
+ D3D11_BOX Box;
+ DXGI_MAPPED_RECT mappedRect;
+ D3D11_TEXTURE2D_DESC tDesc;
+
+ tDesc.Width = (invalid->right - invalid->left);
+ tDesc.Height = (invalid->bottom - invalid->top);
+ tDesc.MipLevels = 1;
+ tDesc.ArraySize = 1;
+ tDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ tDesc.SampleDesc.Count = 1;
+ tDesc.SampleDesc.Quality = 0;
+ tDesc.Usage = D3D11_USAGE_STAGING;
+ tDesc.BindFlags = 0;
+ tDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ tDesc.MiscFlags = 0;
+
+ Box.top = invalid->top;
+ Box.left = invalid->left;
+ Box.right = invalid->right;
+ Box.bottom = invalid->bottom;
+ Box.front = 0;
+ Box.back = 1;
+
+ status = gDevice->lpVtbl->CreateTexture2D(gDevice, &tDesc, NULL, &sStage);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to create staging surface");
+ exit(1);
+ return 1;
+ }
+
+ gContext->lpVtbl->CopySubresourceRegion(gContext, (ID3D11Resource*)sStage, 0, 0, 0, 0,
+ (ID3D11Resource*)gAcquiredDesktopImage, 0, &Box);
+
+ status = sStage->lpVtbl->QueryInterface(sStage, &IID_IDXGISurface, (void**)&surf);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to QI staging surface");
+ exit(1);
+ return 1;
+ }
+
+ surf->lpVtbl->Map(surf, &mappedRect, DXGI_MAP_READ);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to map staging surface");
+ exit(1);
+ return 1;
+ }
+
+ *data = mappedRect.pBits;
+ *pitch = mappedRect.Pitch;
+
+ return 0;
+}
+
+int wf_dxgi_releasePixelData(wfInfo* wfi)
+{
+ HRESULT status;
+
+ surf->lpVtbl->Unmap(surf);
+ surf->lpVtbl->Release(surf);
+ surf = NULL;
+ sStage->lpVtbl->Release(sStage);
+ sStage = NULL;
+
+ status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to release frame");
+ return 1;
+ }
+
+ wfi->framesWaiting = 0;
+
+ return 0;
+}
+
+int wf_dxgi_getInvalidRegion(RECT* invalid)
+{
+ HRESULT status;
+ UINT dirty;
+ UINT BufSize;
+ RECT* pRect;
+ BYTE* DirtyRects;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+
+ if (FrameInfo.AccumulatedFrames == 0)
+ {
+ return 1;
+ }
+
+ if (FrameInfo.TotalMetadataBufferSize)
+ {
+
+ if (FrameInfo.TotalMetadataBufferSize > DataBufferSize)
+ {
+ if (DataBuffer)
+ {
+ free(DataBuffer);
+ DataBuffer = NULL;
+ }
+
+ DataBuffer = (BYTE*)malloc(FrameInfo.TotalMetadataBufferSize);
+
+ if (!DataBuffer)
+ {
+ DataBufferSize = 0;
+ WLog_ERR(TAG, "Failed to allocate memory for metadata");
+ exit(1);
+ }
+
+ DataBufferSize = FrameInfo.TotalMetadataBufferSize;
+ }
+
+ BufSize = FrameInfo.TotalMetadataBufferSize;
+
+ status = gOutputDuplication->lpVtbl->GetFrameMoveRects(
+ gOutputDuplication, BufSize, (DXGI_OUTDUPL_MOVE_RECT*)DataBuffer, &BufSize);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get frame move rects");
+ return 1;
+ }
+
+ DirtyRects = DataBuffer + BufSize;
+ BufSize = FrameInfo.TotalMetadataBufferSize - BufSize;
+
+ status = gOutputDuplication->lpVtbl->GetFrameDirtyRects(gOutputDuplication, BufSize,
+ (RECT*)DirtyRects, &BufSize);
+
+ if (FAILED(status))
+ {
+ WLog_ERR(TAG, "Failed to get frame dirty rects");
+ return 1;
+ }
+ dirty = BufSize / sizeof(RECT);
+
+ pRect = (RECT*)DirtyRects;
+
+ for (UINT i = 0; i < dirty; ++i)
+ {
+ UnionRect(invalid, invalid, pRect);
+ ++pRect;
+ }
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/server/Windows/wf_dxgi.h b/server/Windows/wf_dxgi.h
new file mode 100644
index 0000000..560aec0
--- /dev/null
+++ b/server/Windows/wf_dxgi.h
@@ -0,0 +1,41 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_WIN_DXGI_H
+#define FREERDP_SERVER_WIN_DXGI_H
+
+#include "wf_interface.h"
+
+int wf_dxgi_init(wfInfo* context);
+
+int wf_dxgi_createDevice(wfInfo* context);
+
+int wf_dxgi_getDuplication(wfInfo* context);
+
+int wf_dxgi_cleanup(wfInfo* context);
+
+int wf_dxgi_nextFrame(wfInfo* context, UINT timeout);
+
+int wf_dxgi_getPixelData(wfInfo* context, BYTE** data, int* pitch, RECT* invalid);
+
+int wf_dxgi_releasePixelData(wfInfo* context);
+
+int wf_dxgi_getInvalidRegion(RECT* invalid);
+
+#endif /* FREERDP_SERVER_WIN_DXGI_H */
diff --git a/server/Windows/wf_info.c b/server/Windows/wf_info.c
new file mode 100644
index 0000000..7ec754b
--- /dev/null
+++ b/server/Windows/wf_info.c
@@ -0,0 +1,402 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdlib.h>
+
+#include <freerdp/build-config.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_info.h"
+#include "wf_update.h"
+#include "wf_mirage.h"
+#include "wf_dxgi.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+static wfInfo* wfInfoInstance = NULL;
+static int _IDcount = 0;
+
+BOOL wf_info_lock(wfInfo* wfi)
+{
+ DWORD dRes;
+ dRes = WaitForSingleObject(wfi->mutex, INFINITE);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_info_lock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds)
+{
+ DWORD dRes;
+ dRes = WaitForSingleObject(wfi->mutex, dwMilliseconds);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_info_try_lock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_info_unlock(wfInfo* wfi)
+{
+ if (!ReleaseMutex(wfi->mutex))
+ {
+ WLog_ERR(TAG, "wf_info_unlock failed with 0x%08lX", GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+wfInfo* wf_info_init()
+{
+ wfInfo* wfi;
+ wfi = (wfInfo*)calloc(1, sizeof(wfInfo));
+
+ if (wfi != NULL)
+ {
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+ wfi->mutex = CreateMutex(NULL, FALSE, NULL);
+
+ if (wfi->mutex == NULL)
+ {
+ WLog_ERR(TAG, "CreateMutex error: %lu", GetLastError());
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->updateSemaphore = CreateSemaphore(NULL, 0, 32, NULL);
+
+ if (!wfi->updateSemaphore)
+ {
+ WLog_ERR(TAG, "CreateSemaphore error: %lu", GetLastError());
+ CloseHandle(wfi->mutex);
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->updateThread = CreateThread(NULL, 0, wf_update_thread, wfi, CREATE_SUSPENDED, NULL);
+
+ if (!wfi->updateThread)
+ {
+ WLog_ERR(TAG, "Failed to create update thread");
+ CloseHandle(wfi->mutex);
+ CloseHandle(wfi->updateSemaphore);
+ free(wfi);
+ return NULL;
+ }
+
+ wfi->peers =
+ (freerdp_peer**)calloc(FREERDP_SERVER_WIN_INFO_MAXPEERS, sizeof(freerdp_peer*));
+
+ if (!wfi->peers)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for peer");
+ CloseHandle(wfi->mutex);
+ CloseHandle(wfi->updateSemaphore);
+ CloseHandle(wfi->updateThread);
+ free(wfi);
+ return NULL;
+ }
+
+ // Set FPS
+ wfi->framesPerSecond = FREERDP_SERVER_WIN_INFO_DEFAULT_FPS;
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("FramesPerSecond"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ wfi->framesPerSecond = dwValue;
+ }
+
+ RegCloseKey(hKey);
+ // Set input toggle
+ wfi->input_disabled = FALSE;
+ status =
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(hKey, _T("DisableInput"), NULL, &dwType, (BYTE*)&dwValue,
+ &dwSize) == ERROR_SUCCESS)
+ {
+ if (dwValue != 0)
+ wfi->input_disabled = TRUE;
+ }
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return wfi;
+}
+
+wfInfo* wf_info_get_instance()
+{
+ if (wfInfoInstance == NULL)
+ wfInfoInstance = wf_info_init();
+
+ return wfInfoInstance;
+}
+
+BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context)
+{
+ int peerId = 0;
+
+ if (!wfi || !context)
+ return FALSE;
+
+ if (!wf_info_lock(wfi))
+ return FALSE;
+
+ if (wfi->peerCount == FREERDP_SERVER_WIN_INFO_MAXPEERS)
+ goto fail_peer_count;
+
+ context->info = wfi;
+
+ if (!(context->updateEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_update_event;
+
+ // get the offset of the top left corner of selected screen
+ EnumDisplayMonitors(NULL, NULL, wf_info_monEnumCB, 0);
+ _IDcount = 0;
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->peerCount == 0)
+ if (wf_dxgi_init(wfi) != 0)
+ goto fail_driver_init;
+
+#else
+
+ if (!wf_mirror_driver_activate(wfi))
+ goto fail_driver_init;
+
+#endif
+
+ // look through the array of peers until an empty slot
+ for (int i = 0; i < FREERDP_SERVER_WIN_INFO_MAXPEERS; ++i)
+ {
+ // empty index will be our peer id
+ if (wfi->peers[i] == NULL)
+ {
+ peerId = i;
+ break;
+ }
+ }
+
+ wfi->peers[peerId] = ((rdpContext*)context)->peer;
+ wfi->peers[peerId]->pId = peerId;
+ wfi->peerCount++;
+ WLog_INFO(TAG, "Registering Peer: id=%d #=%d", peerId, wfi->peerCount);
+ wf_info_unlock(wfi);
+ wfreerdp_server_peer_callback_event(peerId, FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT);
+ return TRUE;
+fail_driver_init:
+ CloseHandle(context->updateEvent);
+ context->updateEvent = NULL;
+fail_update_event:
+fail_peer_count:
+ context->socketClose = TRUE;
+ wf_info_unlock(wfi);
+ return FALSE;
+}
+
+void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi))
+ {
+ int peerId;
+ peerId = ((rdpContext*)context)->peer->pId;
+ wfi->peers[peerId] = NULL;
+ wfi->peerCount--;
+ CloseHandle(context->updateEvent);
+ WLog_INFO(TAG, "Unregistering Peer: id=%d, #=%d", peerId, wfi->peerCount);
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->peerCount == 0)
+ wf_dxgi_cleanup(wfi);
+
+#endif
+ wf_info_unlock(wfi);
+ wfreerdp_server_peer_callback_event(peerId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT);
+ }
+}
+
+BOOL wf_info_have_updates(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+
+ if (wfi->framesWaiting == 0)
+ return FALSE;
+
+#else
+
+ if (wfi->nextUpdate == wfi->lastUpdate)
+ return FALSE;
+
+#endif
+ return TRUE;
+}
+
+void wf_info_update_changes(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_nextFrame(wfi, wfi->framesPerSecond * 1000);
+#else
+ GETCHANGESBUF* buf;
+ buf = (GETCHANGESBUF*)wfi->changeBuffer;
+ wfi->nextUpdate = buf->buffer->counter;
+#endif
+}
+
+void wf_info_find_invalid_region(wfInfo* wfi)
+{
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_getInvalidRegion(&wfi->invalid);
+#else
+ GETCHANGESBUF* buf;
+ buf = (GETCHANGESBUF*)wfi->changeBuffer;
+
+ for (ULONG i = wfi->lastUpdate; i != wfi->nextUpdate; i = (i + 1) % MAXCHANGES_BUF)
+ {
+ LPRECT lpR = &buf->buffer->pointrect[i].rect;
+
+ // need to make sure we only get updates from the selected screen
+ if ((lpR->left >= wfi->servscreen_xoffset) &&
+ (lpR->right <= (wfi->servscreen_xoffset + wfi->servscreen_width)) &&
+ (lpR->top >= wfi->servscreen_yoffset) &&
+ (lpR->bottom <= (wfi->servscreen_yoffset + wfi->servscreen_height)))
+ {
+ UnionRect(&wfi->invalid, &wfi->invalid, lpR);
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+#endif
+
+ if (wfi->invalid.left < 0)
+ wfi->invalid.left = 0;
+
+ if (wfi->invalid.top < 0)
+ wfi->invalid.top = 0;
+
+ if (wfi->invalid.right >= wfi->servscreen_width)
+ wfi->invalid.right = wfi->servscreen_width - 1;
+
+ if (wfi->invalid.bottom >= wfi->servscreen_height)
+ wfi->invalid.bottom = wfi->servscreen_height - 1;
+
+ // WLog_DBG(TAG, "invalid region: (%"PRId32", %"PRId32"), (%"PRId32", %"PRId32")",
+ // wfi->invalid.left, wfi->invalid.top, wfi->invalid.right, wfi->invalid.bottom);
+}
+
+void wf_info_clear_invalid_region(wfInfo* wfi)
+{
+ wfi->lastUpdate = wfi->nextUpdate;
+ SetRectEmpty(&wfi->invalid);
+}
+
+void wf_info_invalidate_full_screen(wfInfo* wfi)
+{
+ SetRect(&wfi->invalid, 0, 0, wfi->servscreen_width, wfi->servscreen_height);
+}
+
+BOOL wf_info_have_invalid_region(wfInfo* wfi)
+{
+ return IsRectEmpty(&wfi->invalid);
+}
+
+void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch)
+{
+ *width = (wfi->invalid.right - wfi->invalid.left);
+ *height = (wfi->invalid.bottom - wfi->invalid.top);
+#ifdef WITH_DXGI_1_2
+ wf_dxgi_getPixelData(wfi, pBits, pitch, &wfi->invalid);
+#else
+ {
+ long offset;
+ GETCHANGESBUF* changes;
+ changes = (GETCHANGESBUF*)wfi->changeBuffer;
+ *width += 1;
+ *height += 1;
+ offset = (4 * wfi->invalid.left) + (wfi->invalid.top * wfi->virtscreen_width * 4);
+ *pBits = ((BYTE*)(changes->Userbuffer)) + offset;
+ *pitch = wfi->virtscreen_width * 4;
+ }
+#endif
+}
+
+BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor,
+ LPARAM dwData)
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ if (_IDcount == wfi->screenID)
+ {
+ wfi->servscreen_xoffset = lprcMonitor->left;
+ wfi->servscreen_yoffset = lprcMonitor->top;
+ }
+
+ _IDcount++;
+ return TRUE;
+}
diff --git a/server/Windows/wf_info.h b/server/Windows/wf_info.h
new file mode 100644
index 0000000..82b1781
--- /dev/null
+++ b/server/Windows/wf_info.h
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_WIN_INFO_H
+#define FREERDP_SERVER_WIN_INFO_H
+
+#include "wf_interface.h"
+
+#define FREERDP_SERVER_WIN_INFO_DEFAULT_FPS 24
+#define FREERDP_SERVER_WIN_INFO_MAXPEERS 32
+
+BOOL wf_info_lock(wfInfo* wfi);
+BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds);
+BOOL wf_info_unlock(wfInfo* wfi);
+
+wfInfo* wf_info_get_instance(void);
+BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context);
+void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context);
+
+BOOL wf_info_have_updates(wfInfo* wfi);
+void wf_info_update_changes(wfInfo* wfi);
+void wf_info_find_invalid_region(wfInfo* wfi);
+void wf_info_clear_invalid_region(wfInfo* wfi);
+void wf_info_invalidate_full_screen(wfInfo* wfi);
+BOOL wf_info_have_invalid_region(wfInfo* wfi);
+void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch);
+BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor,
+ LPARAM dwData);
+
+#endif /* FREERDP_SERVER_WIN_INFO_H */
diff --git a/server/Windows/wf_input.c b/server/Windows/wf_input.c
new file mode 100644
index 0000000..a9fdd45
--- /dev/null
+++ b/server/Windows/wf_input.c
@@ -0,0 +1,223 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/windows.h>
+
+#include "wf_input.h"
+#include "wf_info.h"
+
+BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ INPUT keyboard_event;
+ WINPR_UNUSED(input);
+ keyboard_event.type = INPUT_KEYBOARD;
+ keyboard_event.ki.wVk = 0;
+ keyboard_event.ki.wScan = code;
+ keyboard_event.ki.dwFlags = KEYEVENTF_SCANCODE;
+ keyboard_event.ki.dwExtraInfo = 0;
+ keyboard_event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+
+ SendInput(1, &keyboard_event, sizeof(INPUT));
+ return TRUE;
+}
+
+BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ INPUT keyboard_event;
+ WINPR_UNUSED(input);
+ keyboard_event.type = INPUT_KEYBOARD;
+ keyboard_event.ki.wVk = 0;
+ keyboard_event.ki.wScan = code;
+ keyboard_event.ki.dwFlags = KEYEVENTF_UNICODE;
+ keyboard_event.ki.dwExtraInfo = 0;
+ keyboard_event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ SendInput(1, &keyboard_event, sizeof(INPUT));
+ return TRUE;
+}
+
+BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ INPUT mouse_event = { 0 };
+ float width, height;
+ WINPR_UNUSED(input);
+
+ WINPR_ASSERT(input);
+ mouse_event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ mouse_event.mi.dwFlags = MOUSEEVENTF_WHEEL;
+ mouse_event.mi.mouseData = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ mouse_event.mi.mouseData *= -1;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else
+ {
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ // width and height of primary screen (even in multimon setups
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ x += wfi->servscreen_xoffset;
+ y += wfi->servscreen_yoffset;
+ mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MOVE;
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2))
+ {
+ INPUT mouse_event = { 0 };
+ mouse_event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ float width, height;
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ // width and height of primary screen (even in multimon setups
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ x += wfi->servscreen_xoffset;
+ y += wfi->servscreen_yoffset;
+ mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+
+ mouse_event.mi.dx = mouse_event.mi.dy = mouse_event.mi.dwFlags = 0;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_XDOWN;
+ else
+ mouse_event.mi.dwFlags |= MOUSEEVENTF_XUP;
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ mouse_event.mi.mouseData = XBUTTON1;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ mouse_event.mi.mouseData = XBUTTON2;
+
+ SendInput(1, &mouse_event, sizeof(INPUT));
+ }
+ else
+ {
+ wf_peer_mouse_event(input, flags, x, y);
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(code);
+ return TRUE;
+}
+
+BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(code);
+ return TRUE;
+}
+
+BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(x);
+ WINPR_UNUSED(y);
+ return TRUE;
+}
+
+BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ WINPR_UNUSED(input);
+ WINPR_UNUSED(flags);
+ WINPR_UNUSED(x);
+ WINPR_UNUSED(y);
+ return TRUE;
+}
diff --git a/server/Windows/wf_input.h b/server/Windows/wf_input.h
new file mode 100644
index 0000000..8123652
--- /dev/null
+++ b/server/Windows/wf_input.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_INPUT_H
+#define FREERDP_SERVER_WIN_INPUT_H
+
+#include "wf_interface.h"
+
+BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+// dummy versions
+BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code);
+BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code);
+BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y);
+
+#endif /* FREERDP_SERVER_WIN_INPUT_H */
diff --git a/server/Windows/wf_interface.c b/server/Windows/wf_interface.c
new file mode 100644
index 0000000..37923bf
--- /dev/null
+++ b/server/Windows/wf_interface.c
@@ -0,0 +1,341 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+#include <winpr/winsock.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/constants.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/build-config.h>
+
+#include "wf_peer.h"
+#include "wf_settings.h"
+#include "wf_info.h"
+
+#include "wf_interface.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server"
+
+static cbCallback cbEvent = NULL;
+
+int get_screen_info(int id, _TCHAR* name, size_t length, int* width, int* height, int* bpp)
+{
+ DISPLAY_DEVICE dd = { 0 };
+
+ dd.cb = sizeof(DISPLAY_DEVICE);
+
+ if (EnumDisplayDevices(NULL, id, &dd, 0) != 0)
+ {
+ HDC dc;
+
+ if (name != NULL)
+ _stprintf_s(name, length, _T("%s (%s)"), dd.DeviceName, dd.DeviceString);
+
+ dc = CreateDC(dd.DeviceName, NULL, NULL, NULL);
+ *width = GetDeviceCaps(dc, HORZRES);
+ *height = GetDeviceCaps(dc, VERTRES);
+ *bpp = GetDeviceCaps(dc, BITSPIXEL);
+ // ReleaseDC(NULL, dc);
+ DeleteDC(dc);
+ }
+ else
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+void set_screen_id(int id)
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return;
+ wfi->screenID = id;
+
+ return;
+}
+
+static DWORD WINAPI wf_server_main_loop(LPVOID lpParam)
+{
+ freerdp_listener* instance;
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ {
+ WLog_ERR(TAG, "Failed to get instance");
+ return -1;
+ }
+
+ wfi->force_all_disconnect = FALSE;
+
+ instance = (freerdp_listener*)lpParam;
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->GetEventHandles);
+ WINPR_ASSERT(instance->CheckFileDescriptor);
+
+ while (wfi->force_all_disconnect == FALSE)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (instance->CheckFileDescriptor(instance) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+ }
+
+ WLog_INFO(TAG, "wf_server_main_loop terminating");
+ instance->Close(instance);
+
+ return 0;
+}
+
+BOOL wfreerdp_server_start(wfServer* server)
+{
+ freerdp_listener* instance;
+
+ server->instance = freerdp_listener_new();
+ server->instance->PeerAccepted = wf_peer_accepted;
+ instance = server->instance;
+
+ wf_settings_read_dword(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("DefaultPort"), &server->port);
+
+ if (!instance->Open(instance, NULL, (UINT16)server->port))
+ return FALSE;
+
+ if (!(server->thread = CreateThread(NULL, 0, wf_server_main_loop, (void*)instance, 0, NULL)))
+ return FALSE;
+
+ return TRUE;
+}
+
+BOOL wfreerdp_server_stop(wfServer* server)
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ WLog_INFO(TAG, "Stopping server");
+ wfi->force_all_disconnect = TRUE;
+ server->instance->Close(server->instance);
+ return TRUE;
+}
+
+wfServer* wfreerdp_server_new()
+{
+ WSADATA wsaData;
+ wfServer* server;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ return NULL;
+
+ server = (wfServer*)calloc(1, sizeof(wfServer));
+
+ if (server)
+ {
+ server->port = 3389;
+ }
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ cbEvent = NULL;
+
+ return server;
+}
+
+void wfreerdp_server_free(wfServer* server)
+{
+ free(server);
+
+ WSACleanup();
+}
+
+BOOL wfreerdp_server_is_running(wfServer* server)
+{
+ DWORD tStatus;
+ BOOL bRet;
+
+ bRet = GetExitCodeThread(server->thread, &tStatus);
+ if (bRet == 0)
+ {
+ WLog_ERR(TAG, "Error in call to GetExitCodeThread");
+ return FALSE;
+ }
+
+ if (tStatus == STILL_ACTIVE)
+ return TRUE;
+ return FALSE;
+}
+
+UINT32 wfreerdp_server_num_peers()
+{
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return -1;
+ return wfi->peerCount;
+}
+
+UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return 0;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ UINT32 sLen;
+
+ sLen = strnlen_s(peer->hostname, 50);
+ swprintf(dstStr, 50, L"%hs", peer->hostname);
+ return sLen;
+ }
+ else
+ {
+ WLog_WARN(TAG, "nonexistent peer id=%d", pId);
+ return 0;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_local(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->local;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_connected(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->connected;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_activated(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->activated;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+BOOL wfreerdp_server_peer_is_authenticated(int pId)
+{
+ wfInfo* wfi;
+ freerdp_peer* peer;
+
+ wfi = wf_info_get_instance();
+ if (!wfi)
+ return FALSE;
+ peer = wfi->peers[pId];
+
+ if (peer)
+ {
+ return peer->authenticated;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void wfreerdp_server_register_callback_event(cbCallback cb)
+{
+ cbEvent = cb;
+}
+
+void wfreerdp_server_peer_callback_event(int pId, UINT32 eType)
+{
+ if (cbEvent)
+ cbEvent(pId, eType);
+}
diff --git a/server/Windows/wf_interface.h b/server/Windows/wf_interface.h
new file mode 100644
index 0000000..5fcbad4
--- /dev/null
+++ b/server/Windows/wf_interface.h
@@ -0,0 +1,140 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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_WIN_INTERFACE_H
+#define FREERDP_SERVER_WIN_INTERFACE_H
+
+#include <winpr/windows.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codec/rfx.h>
+
+#include <freerdp/server/rdpsnd.h>
+
+#if _WIN32_WINNT >= 0x0602
+#define WITH_DXGI_1_2 1
+#endif
+
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT 1
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT 2
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE 4
+#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH 8
+
+typedef struct wf_info wfInfo;
+typedef struct wf_peer_context wfPeerContext;
+
+struct wf_info
+{
+ wStream* s;
+
+ // screen and monitor info
+ int screenID;
+ int virtscreen_width;
+ int virtscreen_height;
+ int servscreen_width;
+ int servscreen_height;
+ int servscreen_xoffset;
+ int servscreen_yoffset;
+
+ int frame_idx;
+ int bitsPerPixel;
+ HDC driverDC;
+ int peerCount;
+ int activePeerCount;
+ void* changeBuffer;
+ int framesPerSecond;
+ LPTSTR deviceKey;
+ TCHAR deviceName[32];
+ freerdp_peer** peers;
+ BOOL mirrorDriverActive;
+ UINT framesWaiting;
+
+ HANDLE snd_mutex;
+ BOOL snd_stop;
+ AUDIO_FORMAT* agreed_format;
+
+ RECT invalid;
+ HANDLE mutex;
+ BOOL updatePending;
+ HANDLE updateEvent;
+ HANDLE updateThread;
+ HANDLE updateSemaphore;
+ RFX_CONTEXT* rfx_context;
+ unsigned long lastUpdate;
+ unsigned long nextUpdate;
+ SURFACE_BITS_COMMAND cmd;
+
+ BOOL input_disabled;
+ BOOL force_all_disconnect;
+};
+
+struct wf_peer_context
+{
+ rdpContext _p;
+
+ wfInfo* info;
+ int frame_idx;
+ HANDLE updateEvent;
+ BOOL socketClose;
+ HANDLE socketEvent;
+ HANDLE socketThread;
+ HANDLE socketSemaphore;
+
+ HANDLE vcm;
+ RdpsndServerContext* rdpsnd;
+};
+
+struct wf_server
+{
+ DWORD port;
+ HANDLE thread;
+ freerdp_listener* instance;
+};
+typedef struct wf_server wfServer;
+
+typedef void(__stdcall* cbCallback)(int, UINT32);
+
+FREERDP_API int get_screen_info(int id, _TCHAR* name, size_t length, int* w, int* h, int* b);
+FREERDP_API void set_screen_id(int id);
+
+FREERDP_API BOOL wfreerdp_server_start(wfServer* server);
+FREERDP_API BOOL wfreerdp_server_stop(wfServer* server);
+
+FREERDP_API wfServer* wfreerdp_server_new(void);
+FREERDP_API void wfreerdp_server_free(wfServer* server);
+
+FREERDP_API BOOL wfreerdp_server_is_running(wfServer* server);
+
+FREERDP_API UINT32 wfreerdp_server_num_peers(void);
+FREERDP_API UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr);
+FREERDP_API BOOL wfreerdp_server_peer_is_local(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_connected(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_activated(int pId);
+FREERDP_API BOOL wfreerdp_server_peer_is_authenticated(int pId);
+
+FREERDP_API void wfreerdp_server_register_callback_event(cbCallback cb);
+
+void wfreerdp_server_peer_callback_event(int pId, UINT32 eType);
+
+#endif /* FREERDP_SERVER_WIN_INTERFACE_H */
diff --git a/server/Windows/wf_mirage.c b/server/Windows/wf_mirage.c
new file mode 100644
index 0000000..524ff6e
--- /dev/null
+++ b/server/Windows/wf_mirage.c
@@ -0,0 +1,361 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012-2013 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_mirage.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("Windows.mirror")
+
+#define DEVICE_KEY_PREFIX _T("\\Registry\\Machine\\")
+/*
+This function will iterate over the loaded display devices until it finds
+the mirror device we want to load. If found, it will then copy the registry
+key corresponding to the device to the wfi and returns TRUE. Otherwise
+the function returns FALSE.
+*/
+BOOL wf_mirror_driver_find_display_device(wfInfo* wfi)
+{
+ BOOL result;
+ BOOL devFound;
+ DWORD deviceNumber;
+ DISPLAY_DEVICE deviceInfo;
+ devFound = FALSE;
+ deviceNumber = 0;
+ deviceInfo.cb = sizeof(deviceInfo);
+
+ while (result = EnumDisplayDevices(NULL, deviceNumber, &deviceInfo, 0))
+ {
+ if (_tcscmp(deviceInfo.DeviceString, _T("Mirage Driver")) == 0)
+ {
+ int deviceKeyLength;
+ int deviceKeyPrefixLength;
+ deviceKeyPrefixLength = _tcslen(DEVICE_KEY_PREFIX);
+
+ if (_tcsnicmp(deviceInfo.DeviceKey, DEVICE_KEY_PREFIX, deviceKeyPrefixLength) == 0)
+ {
+ deviceKeyLength = _tcslen(deviceInfo.DeviceKey) - deviceKeyPrefixLength;
+ wfi->deviceKey = (LPTSTR)malloc((deviceKeyLength + 1) * sizeof(TCHAR));
+
+ if (!wfi->deviceKey)
+ return FALSE;
+
+ _tcsncpy_s(wfi->deviceKey, deviceKeyLength + 1,
+ &deviceInfo.DeviceKey[deviceKeyPrefixLength], deviceKeyLength);
+ }
+
+ _tcsncpy_s(wfi->deviceName, 32, deviceInfo.DeviceName, _tcslen(deviceInfo.DeviceName));
+ return TRUE;
+ }
+
+ deviceNumber++;
+ }
+
+ return FALSE;
+}
+
+/**
+ * This function will attempt to access the the windows registry using the device
+ * key stored in the current wfi. It will attempt to read the value of the
+ * "Attach.ToDesktop" subkey and will return TRUE if the value is already set to
+ * val. If unable to read the subkey, this function will return FALSE. If the
+ * subkey is not set to val it will then attempt to set it to val and return TRUE. If
+ * unsuccessful or an unexpected value is encountered, the function returns
+ * FALSE.
+ */
+
+BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode)
+{
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+ status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, wfi->deviceKey, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY,
+ &hKey);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error opening RegKey: status=0x%08lX", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ return FALSE;
+ }
+
+ dwSize = sizeof(DWORD);
+ status = RegQueryValueEx(hKey, _T("Attach.ToDesktop"), NULL, &dwType, (BYTE*)&dwValue, &dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error querying RegKey: status=0x%08lX", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ return FALSE;
+ }
+
+ if (dwValue ^ mode) // only if we want to change modes
+ {
+ dwValue = mode;
+ dwSize = sizeof(DWORD);
+ status = RegSetValueEx(hKey, _T("Attach.ToDesktop"), 0, REG_DWORD, (BYTE*)&dwValue, dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ WLog_DBG(TAG, "Error writing registry key: %ld", status);
+
+ if (status == ERROR_ACCESS_DENIED)
+ WLog_DBG(TAG, "access denied. Do you have admin privleges?");
+
+ WLog_DBG(TAG, "");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void wf_mirror_driver_print_display_change_status(LONG status)
+{
+ TCHAR disp_change[64];
+
+ switch (status)
+ {
+ case DISP_CHANGE_SUCCESSFUL:
+ _tcscpy(disp_change, _T("DISP_CHANGE_SUCCESSFUL"));
+ break;
+
+ case DISP_CHANGE_BADDUALVIEW:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADDUALVIEW"));
+ break;
+
+ case DISP_CHANGE_BADFLAGS:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADFLAGS"));
+ break;
+
+ case DISP_CHANGE_BADMODE:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADMODE"));
+ break;
+
+ case DISP_CHANGE_BADPARAM:
+ _tcscpy(disp_change, _T("DISP_CHANGE_BADPARAM"));
+ break;
+
+ case DISP_CHANGE_FAILED:
+ _tcscpy(disp_change, _T("DISP_CHANGE_FAILED"));
+ break;
+
+ case DISP_CHANGE_NOTUPDATED:
+ _tcscpy(disp_change, _T("DISP_CHANGE_NOTUPDATED"));
+ break;
+
+ case DISP_CHANGE_RESTART:
+ _tcscpy(disp_change, _T("DISP_CHANGE_RESTART"));
+ break;
+
+ default:
+ _tcscpy(disp_change, _T("DISP_CHANGE_UNKNOWN"));
+ break;
+ }
+
+ if (status != DISP_CHANGE_SUCCESSFUL)
+ WLog_ERR(TAG, "ChangeDisplaySettingsEx() failed with %s (%ld)", disp_change, status);
+ else
+ WLog_INFO(TAG, "ChangeDisplaySettingsEx() succeeded with %s (%ld)", disp_change, status);
+}
+
+/**
+ * This function will attempt to apply the currently configured display settings
+ * in the registry to the display driver. It will return TRUE if successful
+ * otherwise it returns FALSE.
+ * If mode is MIRROR_UNLOAD then the the driver will be asked to remove itself.
+ */
+
+BOOL wf_mirror_driver_update(wfInfo* wfi, int mode)
+{
+ BOOL status;
+ DWORD* extHdr;
+ WORD drvExtraSaved;
+ DEVMODE* deviceMode;
+ LONG disp_change_status;
+ DWORD dmf_devmodewext_magic_sig = 0xDF20C0DE;
+
+ if ((mode != MIRROR_LOAD) && (mode != MIRROR_UNLOAD))
+ {
+ WLog_DBG(TAG, "Invalid mirror mode!");
+ return FALSE;
+ }
+
+ deviceMode = (DEVMODE*)malloc(sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX);
+
+ if (!deviceMode)
+ return FALSE;
+
+ deviceMode->dmDriverExtra = 2 * sizeof(DWORD);
+ extHdr = (DWORD*)((BYTE*)&deviceMode + sizeof(DEVMODE));
+ extHdr[0] = dmf_devmodewext_magic_sig;
+ extHdr[1] = 0;
+ drvExtraSaved = deviceMode->dmDriverExtra;
+ memset(deviceMode, 0, sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX);
+ deviceMode->dmSize = sizeof(DEVMODE);
+ deviceMode->dmDriverExtra = drvExtraSaved;
+
+ if (mode == MIRROR_LOAD)
+ {
+ wfi->virtscreen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ wfi->virtscreen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ deviceMode->dmPelsWidth = wfi->virtscreen_width;
+ deviceMode->dmPelsHeight = wfi->virtscreen_height;
+ deviceMode->dmBitsPerPel = wfi->bitsPerPixel;
+ deviceMode->dmPosition.x = wfi->servscreen_xoffset;
+ deviceMode->dmPosition.y = wfi->servscreen_yoffset;
+ }
+
+ deviceMode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
+ _tcsncpy_s(deviceMode->dmDeviceName, 32, wfi->deviceName, _tcslen(wfi->deviceName));
+ disp_change_status =
+ ChangeDisplaySettingsEx(wfi->deviceName, deviceMode, NULL, CDS_UPDATEREGISTRY, NULL);
+ status = (disp_change_status == DISP_CHANGE_SUCCESSFUL) ? TRUE : FALSE;
+
+ if (!status)
+ wf_mirror_driver_print_display_change_status(disp_change_status);
+
+ return status;
+}
+
+BOOL wf_mirror_driver_map_memory(wfInfo* wfi)
+{
+ int status;
+ wfi->driverDC = CreateDC(wfi->deviceName, NULL, NULL, NULL);
+
+ if (wfi->driverDC == NULL)
+ {
+ WLog_ERR(TAG, "Could not create device driver context!");
+ {
+ LPVOID lpMsgBuf;
+ DWORD dw = GetLastError();
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0,
+ NULL);
+ // Display the error message and exit the process
+ WLog_ERR(TAG, "CreateDC failed on device [%s] with error %lu: %s", wfi->deviceName, dw,
+ lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ }
+ return FALSE;
+ }
+
+ wfi->changeBuffer = calloc(1, sizeof(GETCHANGESBUF));
+
+ if (!wfi->changeBuffer)
+ return FALSE;
+
+ status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_map, 0, 0, sizeof(GETCHANGESBUF),
+ (LPSTR)wfi->changeBuffer);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "Failed to map shared memory from the driver! code %d", status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Unmap the shared memory and release the DC */
+
+BOOL wf_mirror_driver_cleanup(wfInfo* wfi)
+{
+ int status;
+ status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_unmap, sizeof(GETCHANGESBUF),
+ (LPSTR)wfi->changeBuffer, 0, 0);
+
+ if (status <= 0)
+ {
+ WLog_ERR(TAG, "Failed to unmap shared memory from the driver! code %d", status);
+ }
+
+ if (wfi->driverDC != NULL)
+ {
+ status = DeleteDC(wfi->driverDC);
+
+ if (status == 0)
+ {
+ WLog_ERR(TAG, "Failed to release DC!");
+ }
+ }
+
+ free(wfi->changeBuffer);
+ return TRUE;
+}
+
+BOOL wf_mirror_driver_activate(wfInfo* wfi)
+{
+ if (!wfi->mirrorDriverActive)
+ {
+ WLog_DBG(TAG, "Activating Mirror Driver");
+
+ if (wf_mirror_driver_find_display_device(wfi) == FALSE)
+ {
+ WLog_DBG(TAG, "Could not find dfmirage mirror driver! Is it installed?");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_display_device_attach(wfi, 1) == FALSE)
+ {
+ WLog_DBG(TAG, "Could not attach display device!");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_update(wfi, MIRROR_LOAD) == FALSE)
+ {
+ WLog_DBG(TAG, "could not update system with new display settings!");
+ return FALSE;
+ }
+
+ if (wf_mirror_driver_map_memory(wfi) == FALSE)
+ {
+ WLog_DBG(TAG, "Unable to map memory for mirror driver!");
+ return FALSE;
+ }
+
+ wfi->mirrorDriverActive = TRUE;
+ }
+
+ return TRUE;
+}
+
+void wf_mirror_driver_deactivate(wfInfo* wfi)
+{
+ if (wfi->mirrorDriverActive)
+ {
+ WLog_DBG(TAG, "Deactivating Mirror Driver");
+ wf_mirror_driver_cleanup(wfi);
+ wf_mirror_driver_display_device_attach(wfi, 0);
+ wf_mirror_driver_update(wfi, MIRROR_UNLOAD);
+ wfi->mirrorDriverActive = FALSE;
+ }
+}
diff --git a/server/Windows/wf_mirage.h b/server/Windows/wf_mirage.h
new file mode 100644
index 0000000..a03f0b9
--- /dev/null
+++ b/server/Windows/wf_mirage.h
@@ -0,0 +1,219 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012-2013 Corey Clayton <can.of.tuna@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_WIN_MIRAGE_H
+#define FREERDP_SERVER_WIN_MIRAGE_H
+
+#include "wf_interface.h"
+
+enum
+{
+ MIRROR_LOAD = 0,
+ MIRROR_UNLOAD = 1
+};
+
+enum
+{
+ DMF_ESCAPE_BASE_1_VB = 1030,
+ DMF_ESCAPE_BASE_2_VB = 1026,
+ DMF_ESCAPE_BASE_3_VB = 24
+};
+
+#ifdef _WIN64
+
+#define CLIENT_64BIT 0x8000
+
+enum
+{
+ DMF_ESCAPE_BASE_1 = CLIENT_64BIT | DMF_ESCAPE_BASE_1_VB,
+ DMF_ESCAPE_BASE_2 = CLIENT_64BIT | DMF_ESCAPE_BASE_2_VB,
+ DMF_ESCAPE_BASE_3 = CLIENT_64BIT | DMF_ESCAPE_BASE_3_VB,
+};
+
+#else
+
+enum
+{
+ DMF_ESCAPE_BASE_1 = DMF_ESCAPE_BASE_1_VB,
+ DMF_ESCAPE_BASE_2 = DMF_ESCAPE_BASE_2_VB,
+ DMF_ESCAPE_BASE_3 = DMF_ESCAPE_BASE_3_VB,
+};
+
+#endif
+
+typedef enum
+{
+ dmf_esc_qry_ver_info = DMF_ESCAPE_BASE_2 + 0,
+ dmf_esc_usm_pipe_map = DMF_ESCAPE_BASE_1 + 0,
+ dmf_esc_usm_pipe_unmap = DMF_ESCAPE_BASE_1 + 1,
+ dmf_esc_test = DMF_ESCAPE_BASE_1 + 20,
+ dmf_esc_usm_pipe_mapping_test = DMF_ESCAPE_BASE_1 + 21,
+ dmf_esc_pointer_shape_get = DMF_ESCAPE_BASE_3,
+
+} dmf_escape;
+
+#define CLIP_LIMIT 50
+#define MAXCHANGES_BUF 20000
+
+typedef enum
+{
+ dmf_dfo_IGNORE = 0,
+ dmf_dfo_FROM_SCREEN = 1,
+ dmf_dfo_FROM_DIB = 2,
+ dmf_dfo_TO_SCREEN = 3,
+ dmf_dfo_SCREEN_SCREEN = 11,
+ dmf_dfo_BLIT = 12,
+ dmf_dfo_SOLIDFILL = 13,
+ dmf_dfo_BLEND = 14,
+ dmf_dfo_TRANS = 15,
+ dmf_dfo_PLG = 17,
+ dmf_dfo_TEXTOUT = 18,
+ dmf_dfo_Ptr_Shape = 19,
+ dmf_dfo_Ptr_Engage = 48,
+ dmf_dfo_Ptr_Avert = 49,
+ dmf_dfn_assert_on = 64,
+ dmf_dfn_assert_off = 65,
+} dmf_UpdEvent;
+
+#define NOCACHE 1
+#define OLDCACHE 2
+#define NEWCACHE 3
+
+typedef struct
+{
+ ULONG type;
+ RECT rect;
+#ifndef DFMIRAGE_LEAN
+ RECT origrect;
+ POINT point;
+ ULONG color;
+ ULONG refcolor;
+#endif
+} CHANGES_RECORD;
+
+typedef CHANGES_RECORD* PCHANGES_RECORD;
+
+typedef struct
+{
+ ULONG counter;
+ CHANGES_RECORD pointrect[MAXCHANGES_BUF];
+} CHANGES_BUF;
+
+#define EXT_DEVMODE_SIZE_MAX 3072
+#define DMF_PIPE_SEC_SIZE_DEFAULT ALIGN64K(sizeof(CHANGES_BUF))
+
+typedef struct
+{
+ CHANGES_BUF* buffer;
+ PVOID Userbuffer;
+} GETCHANGESBUF;
+
+#define dmf_sprb_ERRORMASK 0x07FF
+#define dmf_sprb_STRICTSESSION_AFF 0x1FFF
+
+typedef enum
+{
+ dmf_sprb_internal_error = 0x0001,
+ dmf_sprb_miniport_gen_error = 0x0004,
+ dmf_sprb_memory_alloc_failed = 0x0008,
+ dmf_sprb_pipe_buff_overflow = 0x0010,
+ dmf_sprb_pipe_buff_insufficient = 0x0020,
+ dmf_sprb_pipe_not_ready = 0x0040,
+ dmf_sprb_gdi_err = 0x0100,
+ dmf_sprb_owner_died = 0x0400,
+ dmf_sprb_tgtwnd_gone = 0x0800,
+ dmf_sprb_pdev_detached = 0x2000,
+} dmf_session_prob_status;
+
+#define DMF_ESC_RET_FAILF 0x80000000
+#define DMF_ESC_RET_SSTMASK 0x0000FFFF
+#define DMF_ESC_RET_IMMMASK 0x7FFF0000
+
+typedef enum
+{
+ dmf_escret_generic_ok = 0x00010000,
+ dmf_escret_bad_state = 0x00100000,
+ dmf_escret_access_denied = 0x00200000,
+ dmf_escret_bad_buffer_size = 0x00400000,
+ dmf_escret_internal_err = 0x00800000,
+ dmf_escret_out_of_memory = 0x02000000,
+ dmf_escret_already_connected = 0x04000000,
+ dmf_escret_oh_boy_too_late = 0x08000000,
+ dmf_escret_bad_window = 0x10000000,
+ dmf_escret_drv_ver_higher = 0x20000000,
+ dmf_escret_drv_ver_lower = 0x40000000,
+} dmf_esc_retcode;
+
+typedef struct
+{
+ ULONG cbSize;
+ ULONG app_actual_version;
+ ULONG display_minreq_version;
+ ULONG connect_options;
+} Esc_dmf_Qvi_IN;
+
+enum
+{
+ esc_qvi_prod_name_max = 16,
+};
+
+#define ESC_QVI_PROD_MIRAGE "MIRAGE"
+#define ESC_QVI_PROD_QUASAR "QUASAR"
+
+typedef struct
+{
+ ULONG cbSize;
+ ULONG display_actual_version;
+ ULONG miniport_actual_version;
+ ULONG app_minreq_version;
+ ULONG display_buildno;
+ ULONG miniport_buildno;
+ char prod_name[esc_qvi_prod_name_max];
+} Esc_dmf_Qvi_OUT;
+
+typedef struct
+{
+ ULONG cbSize;
+ char* pDstBmBuf;
+ ULONG nDstBmBufSize;
+} Esc_dmf_pointer_shape_get_IN;
+
+typedef struct
+{
+ ULONG cbSize;
+ POINTL BmSize;
+ char* pMaskBm;
+ ULONG nMaskBmSize;
+ char* pColorBm;
+ ULONG nColorBmSize;
+ char* pColorBmPal;
+ ULONG nColorBmPalEntries;
+} Esc_dmf_pointer_shape_get_OUT;
+
+BOOL wf_mirror_driver_find_display_device(wfInfo* wfi);
+BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode);
+BOOL wf_mirror_driver_update(wfInfo* wfi, int mode);
+BOOL wf_mirror_driver_map_memory(wfInfo* wfi);
+BOOL wf_mirror_driver_cleanup(wfInfo* wfi);
+
+BOOL wf_mirror_driver_activate(wfInfo* wfi);
+void wf_mirror_driver_deactivate(wfInfo* wfi);
+
+#endif /* FREERDP_SERVER_WIN_MIRAGE_H */
diff --git a/server/Windows/wf_peer.c b/server/Windows/wf_peer.c
new file mode 100644
index 0000000..4342a3b
--- /dev/null
+++ b/server/Windows/wf_peer.c
@@ -0,0 +1,414 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/stream.h>
+#include <winpr/windows.h>
+
+#include <freerdp/listener.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/build-config.h>
+#include <freerdp/crypto/certificate.h>
+
+#include "wf_info.h"
+#include "wf_input.h"
+#include "wf_mirage.h"
+#include "wf_update.h"
+#include "wf_settings.h"
+#include "wf_rdpsnd.h"
+
+#include "wf_peer.h"
+#include <freerdp/peer.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING
+
+static DWORD WINAPI wf_peer_main_loop(LPVOID lpParam);
+
+static BOOL wf_peer_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ wfPeerContext* context = (wfPeerContext*)ctx;
+ WINPR_ASSERT(context);
+
+ if (!(context->info = wf_info_get_instance()))
+ return FALSE;
+
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!wf_info_peer_register(context->info, context))
+ {
+ WTSCloseServer(context->vcm);
+ context->vcm = NULL;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void wf_peer_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ wfPeerContext* context = (wfPeerContext*)ctx;
+ WINPR_ASSERT(context);
+
+ wf_info_peer_unregister(context->info, context);
+
+ if (context->rdpsnd)
+ {
+ wf_rdpsnd_lock();
+ context->info->snd_stop = TRUE;
+ rdpsnd_server_context_free(context->rdpsnd);
+ wf_rdpsnd_unlock();
+ }
+
+ WTSCloseServer(context->vcm);
+}
+
+static BOOL wf_peer_init(freerdp_peer* client)
+{
+ client->ContextSize = sizeof(wfPeerContext);
+ client->ContextNew = wf_peer_context_new;
+ client->ContextFree = wf_peer_context_free;
+ return freerdp_peer_context_new(client);
+}
+
+static BOOL wf_peer_post_connect(freerdp_peer* client)
+{
+ wfInfo* wfi;
+ rdpSettings* settings;
+ wfPeerContext* context;
+
+ WINPR_ASSERT(client);
+
+ context = (wfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ wfi = context->info;
+ WINPR_ASSERT(wfi);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if ((get_screen_info(wfi->screenID, NULL, 0, &wfi->servscreen_width, &wfi->servscreen_height,
+ &wfi->bitsPerPixel) == 0) ||
+ (wfi->servscreen_width == 0) || (wfi->servscreen_height == 0) || (wfi->bitsPerPixel == 0))
+ {
+ WLog_ERR(TAG, "postconnect: error getting screen info for screen %d", wfi->screenID);
+ WLog_ERR(TAG, "\t%dx%dx%d", wfi->servscreen_height, wfi->servscreen_width,
+ wfi->bitsPerPixel);
+ return FALSE;
+ }
+
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != wfi->servscreen_width) ||
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != wfi->servscreen_height))
+ {
+ /*
+ WLog_DBG(TAG, "Client requested resolution %"PRIu32"x%"PRIu32", but will resize to %dx%d",
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), wfi->servscreen_width,
+ wfi->servscreen_height);
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, wfi->servscreen_width) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, wfi->servscreen_height) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, wfi->bitsPerPixel))
+ return FALSE;
+
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->DesktopResize);
+ client->context->update->DesktopResize(client->context);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd"))
+ {
+ wf_peer_rdpsnd_init(context); /* Audio Output */
+ }
+
+ return TRUE;
+}
+
+static BOOL wf_peer_activate(freerdp_peer* client)
+{
+ wfInfo* wfi;
+ wfPeerContext* context = (wfPeerContext*)client->context;
+ wfi = context->info;
+ client->activated = TRUE;
+ wf_update_peer_activate(wfi, context);
+ wfreerdp_server_peer_callback_event(((rdpContext*)context)->peer->pId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE);
+ return TRUE;
+}
+
+static BOOL wf_peer_logon(freerdp_peer* client, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ wfreerdp_server_peer_callback_event(((rdpContext*)client->context)->peer->pId,
+ FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH);
+ return TRUE;
+}
+
+static BOOL wf_peer_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ return TRUE;
+}
+
+BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client)
+{
+ HANDLE hThread;
+
+ if (!(hThread = CreateThread(NULL, 0, wf_peer_main_loop, client, 0, NULL)))
+ return FALSE;
+
+ CloseHandle(hThread);
+ return TRUE;
+}
+
+static DWORD WINAPI wf_peer_socket_listener(LPVOID lpParam)
+{
+ wfPeerContext* context;
+ freerdp_peer* client = (freerdp_peer*)lpParam;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->GetEventHandles);
+ WINPR_ASSERT(client->CheckFileDescriptor);
+
+ context = (wfPeerContext*)client->context;
+ WINPR_ASSERT(context);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles));
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ SetEvent(context->socketEvent);
+ WaitForSingleObject(context->socketSemaphore, INFINITE);
+
+ if (context->socketClose)
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL wf_peer_read_settings(freerdp_peer* client)
+{
+ rdpSettings* settings;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ char* CertificateFile = NULL;
+ if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("CertificateFile"),
+ &(CertificateFile)))
+ CertificateFile = _strdup("server.crt");
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(CertificateFile);
+ free(CertificateFile);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ char* PrivateKeyFile = NULL;
+ if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("PrivateKeyFile"),
+ &(PrivateKeyFile)))
+ PrivateKeyFile = _strdup("server.key");
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(PrivateKeyFile);
+ free(PrivateKeyFile);
+
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ return TRUE;
+}
+
+DWORD WINAPI wf_peer_main_loop(LPVOID lpParam)
+{
+ wfInfo* wfi;
+ DWORD nCount;
+ DWORD status;
+ HANDLE handles[32];
+ rdpSettings* settings;
+ wfPeerContext* context;
+ freerdp_peer* client = (freerdp_peer*)lpParam;
+
+ if (!wf_peer_init(client))
+ goto fail_peer_init;
+
+ WINPR_ASSERT(client->context);
+
+ settings = client->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE))
+ goto fail_peer_init;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, FALSE))
+ goto fail_peer_init;
+
+ if (!wf_peer_read_settings(client))
+ goto fail_peer_init;
+
+ client->PostConnect = wf_peer_post_connect;
+ client->Activate = wf_peer_activate;
+ client->Logon = wf_peer_logon;
+
+ WINPR_ASSERT(client->context->input);
+ client->context->input->SynchronizeEvent = wf_peer_synchronize_event;
+ client->context->input->KeyboardEvent = wf_peer_keyboard_event;
+ client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event;
+ client->context->input->MouseEvent = wf_peer_mouse_event;
+ client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event;
+
+ WINPR_ASSERT(client->Initialize);
+ if (!client->Initialize(client))
+ goto fail_client_initialize;
+
+ context = (wfPeerContext*)client->context;
+
+ if (context->socketClose)
+ goto fail_socked_closed;
+
+ wfi = context->info;
+
+ if (wfi->input_disabled)
+ {
+ WLog_INFO(TAG, "client input is disabled");
+ client->context->input->KeyboardEvent = wf_peer_keyboard_event_dummy;
+ client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event_dummy;
+ client->context->input->MouseEvent = wf_peer_mouse_event_dummy;
+ client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event_dummy;
+ }
+
+ if (!(context->socketEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_socket_event;
+
+ if (!(context->socketSemaphore = CreateSemaphore(NULL, 0, 1, NULL)))
+ goto fail_socket_semaphore;
+
+ if (!(context->socketThread = CreateThread(NULL, 0, wf_peer_socket_listener, client, 0, NULL)))
+ goto fail_socket_thread;
+
+ WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname);
+ nCount = 0;
+ handles[nCount++] = context->updateEvent;
+ handles[nCount++] = context->socketEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+
+ if ((status == WAIT_FAILED) || (status == WAIT_TIMEOUT))
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (WaitForSingleObject(context->updateEvent, 0) == 0)
+ {
+ if (client->activated)
+ wf_update_peer_send(wfi, context);
+
+ ResetEvent(context->updateEvent);
+ ReleaseSemaphore(wfi->updateSemaphore, 1, NULL);
+ }
+
+ if (WaitForSingleObject(context->socketEvent, 0) == 0)
+ {
+ if (client->CheckFileDescriptor(client) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to check peer file descriptor");
+ context->socketClose = TRUE;
+ }
+
+ ResetEvent(context->socketEvent);
+ ReleaseSemaphore(context->socketSemaphore, 1, NULL);
+
+ if (context->socketClose)
+ break;
+ }
+
+ // force disconnect
+ if (wfi->force_all_disconnect == TRUE)
+ {
+ WLog_INFO(TAG, "Forcing Disconnect -> ");
+ break;
+ }
+
+ /* FIXME: we should wait on this, instead of calling it every time */
+ if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE)
+ break;
+ }
+
+ WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname);
+
+ if (WaitForSingleObject(context->updateEvent, 0) == 0)
+ {
+ ResetEvent(context->updateEvent);
+ ReleaseSemaphore(wfi->updateSemaphore, 1, NULL);
+ }
+
+ wf_update_peer_deactivate(wfi, context);
+ client->Disconnect(client);
+fail_socket_thread:
+ CloseHandle(context->socketSemaphore);
+ context->socketSemaphore = NULL;
+fail_socket_semaphore:
+ CloseHandle(context->socketEvent);
+ context->socketEvent = NULL;
+fail_socket_event:
+fail_socked_closed:
+fail_client_initialize:
+ freerdp_peer_context_free(client);
+fail_peer_init:
+ freerdp_peer_free(client);
+ return 0;
+}
diff --git a/server/Windows/wf_peer.h b/server/Windows/wf_peer.h
new file mode 100644
index 0000000..19d823c
--- /dev/null
+++ b/server/Windows/wf_peer.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_PEER_H
+#define FREERDP_SERVER_WIN_PEER_H
+
+#include "wf_interface.h"
+
+#include <freerdp/listener.h>
+
+BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#endif /* FREERDP_SERVER_WIN_PEER_H */
diff --git a/server/Windows/wf_rdpsnd.c b/server/Windows/wf_rdpsnd.c
new file mode 100644
index 0000000..b313c35
--- /dev/null
+++ b/server/Windows/wf_rdpsnd.c
@@ -0,0 +1,152 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server (Audio Output)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/windows.h>
+#include <freerdp/server/server-common.h>
+
+#include "wf_rdpsnd.h"
+#include "wf_info.h"
+
+#ifdef WITH_RDPSND_DSOUND
+
+#include "wf_directsound.h"
+
+#else
+
+#include "wf_wasapi.h"
+
+#endif
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+static void wf_peer_rdpsnd_activated(RdpsndServerContext* context)
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+ wfi->agreed_format = NULL;
+ WLog_DBG(TAG, "Client supports the following %d formats:", context->num_client_formats);
+
+ for (size_t i = 0; i < context->num_client_formats; i++)
+ {
+ // TODO: improve the way we agree on a format
+ for (size_t j = 0; j < context->num_server_formats; j++)
+ {
+ if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) &&
+ (context->client_formats[i].nChannels == context->server_formats[j].nChannels) &&
+ (context->client_formats[i].nSamplesPerSec ==
+ context->server_formats[j].nSamplesPerSec))
+ {
+ WLog_DBG(TAG, "agreed on format!");
+ wfi->agreed_format = (AUDIO_FORMAT*)&context->server_formats[j];
+ break;
+ }
+ }
+
+ if (wfi->agreed_format != NULL)
+ break;
+ }
+
+ if (wfi->agreed_format == NULL)
+ {
+ WLog_ERR(TAG, "Could not agree on a audio format with the server");
+ return;
+ }
+
+ context->SelectFormat(context, i);
+ context->SetVolume(context, 0x7FFF, 0x7FFF);
+#ifdef WITH_RDPSND_DSOUND
+ wf_directsound_activate(context);
+#else
+ wf_wasapi_activate(context);
+#endif
+}
+
+int wf_rdpsnd_lock()
+{
+ DWORD dRes;
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+ dRes = WaitForSingleObject(wfi->snd_mutex, INFINITE);
+
+ switch (dRes)
+ {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ return TRUE;
+ break;
+
+ case WAIT_TIMEOUT:
+ return FALSE;
+ break;
+
+ case WAIT_FAILED:
+ WLog_ERR(TAG, "wf_rdpsnd_lock failed with 0x%08lX", GetLastError());
+ return -1;
+ break;
+ }
+
+ return -1;
+}
+
+int wf_rdpsnd_unlock()
+{
+ wfInfo* wfi;
+ wfi = wf_info_get_instance();
+
+ if (ReleaseMutex(wfi->snd_mutex) == 0)
+ {
+ WLog_DBG(TAG, "wf_rdpsnd_unlock failed with 0x%08lX", GetLastError());
+ return -1;
+ }
+
+ return TRUE;
+}
+
+BOOL wf_peer_rdpsnd_init(wfPeerContext* context)
+{
+ wfInfo* wfi = wf_info_get_instance();
+
+ if (!wfi)
+ return FALSE;
+
+ if (!(wfi->snd_mutex = CreateMutex(NULL, FALSE, NULL)))
+ return FALSE;
+
+ context->rdpsnd = rdpsnd_server_context_new(context->vcm);
+ context->rdpsnd->rdpcontext = &context->_p;
+ context->rdpsnd->data = context;
+ context->rdpsnd->num_server_formats =
+ server_rdpsnd_get_formats(&context->rdpsnd->server_formats);
+
+ if (context->rdpsnd->num_server_formats > 0)
+ context->rdpsnd->src_format = &context->rdpsnd->server_formats[0];
+
+ context->rdpsnd->Activated = wf_peer_rdpsnd_activated;
+ context->rdpsnd->Initialize(context->rdpsnd, TRUE);
+ wf_rdpsnd_set_latest_peer(context);
+ wfi->snd_stop = FALSE;
+ return TRUE;
+}
diff --git a/server/Windows/wf_rdpsnd.h b/server/Windows/wf_rdpsnd.h
new file mode 100644
index 0000000..88e631d
--- /dev/null
+++ b/server/Windows/wf_rdpsnd.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server (Audio Output)
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_RDPSND_H
+#define FREERDP_SERVER_WIN_RDPSND_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+#include <freerdp/server/rdpsnd.h>
+
+#include "wf_interface.h"
+
+int wf_rdpsnd_lock(void);
+int wf_rdpsnd_unlock(void);
+BOOL wf_peer_rdpsnd_init(wfPeerContext* context);
+
+#endif /* FREERDP_SERVER_WIN_RDPSND_H */
diff --git a/server/Windows/wf_settings.c b/server/Windows/wf_settings.c
new file mode 100644
index 0000000..63d2327
--- /dev/null
+++ b/server/Windows/wf_settings.c
@@ -0,0 +1,102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/tchar.h>
+#include <winpr/windows.h>
+
+#include "wf_settings.h"
+
+BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value)
+{
+ HKEY hKey;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ DWORD dwValue;
+
+ status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status == ERROR_SUCCESS)
+ {
+ dwSize = sizeof(DWORD);
+
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)&dwValue, &dwSize);
+
+ if (status == ERROR_SUCCESS)
+ *value = dwValue;
+
+ RegCloseKey(hKey);
+
+ return (status == ERROR_SUCCESS) ? TRUE : FALSE;
+ }
+
+ return FALSE;
+}
+
+BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value)
+{
+ HKEY hKey;
+ int length;
+ LONG status;
+ DWORD dwType;
+ DWORD dwSize;
+ char* strA;
+ TCHAR* strX = NULL;
+
+ status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
+
+ if (status != ERROR_SUCCESS)
+ return FALSE;
+
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, NULL, &dwSize);
+
+ if (status == ERROR_SUCCESS)
+ {
+ strX = (LPTSTR)malloc(dwSize + sizeof(TCHAR));
+ if (!strX)
+ return FALSE;
+ status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)strX, &dwSize);
+
+ if (status != ERROR_SUCCESS)
+ {
+ free(strX);
+ RegCloseKey(hKey);
+ return FALSE;
+ }
+ }
+
+ if (strX)
+ {
+#ifdef UNICODE
+ length = WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), NULL, 0, NULL, NULL);
+ strA = (char*)malloc(length + 1);
+ WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), strA, length, NULL, NULL);
+ strA[length] = '\0';
+ free(strX);
+#else
+ strA = (char*)strX;
+#endif
+ *value = strA;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/Windows/wf_settings.h b/server/Windows/wf_settings.h
new file mode 100644
index 0000000..40e25aa
--- /dev/null
+++ b/server/Windows/wf_settings.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_SETTINGS_H
+#define FREERDP_SERVER_WIN_SETTINGS_H
+
+#include "wf_interface.h"
+
+BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value);
+BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value);
+
+#endif /* FREERDP_SERVER_WIN_SETTINGS_H */
diff --git a/server/Windows/wf_update.c b/server/Windows/wf_update.c
new file mode 100644
index 0000000..06d60c9
--- /dev/null
+++ b/server/Windows/wf_update.c
@@ -0,0 +1,251 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Client
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 Corey Clayton <can.of.tuna@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+
+#include <winpr/windows.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/listener.h>
+
+#include "wf_peer.h"
+#include "wf_info.h"
+#include "wf_mirage.h"
+
+#include "wf_update.h"
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+DWORD WINAPI wf_update_thread(LPVOID lpParam)
+{
+ DWORD fps;
+ wfInfo* wfi;
+ DWORD beg, end;
+ DWORD diff, rate;
+ wfi = (wfInfo*)lpParam;
+ fps = wfi->framesPerSecond;
+ rate = 1000 / fps;
+
+ while (1)
+ {
+ beg = GetTickCount();
+
+ if (wf_info_lock(wfi) > 0)
+ {
+ if (wfi->activePeerCount > 0)
+ {
+ wf_info_update_changes(wfi);
+
+ if (wf_info_have_updates(wfi))
+ {
+ wf_update_encode(wfi);
+ // WLog_DBG(TAG, "Start of parallel sending");
+ int index = 0;
+
+ for (int peerindex = 0; peerindex < wfi->peerCount; peerindex++)
+ {
+ for (; index < FREERDP_SERVER_WIN_INFO_MAXPEERS; index++)
+ {
+ if (wfi->peers[index] && wfi->peers[index]->activated)
+ {
+ // WLog_DBG(TAG, "Setting event for %d of %d", index + 1,
+ // wfi->activePeerCount);
+ SetEvent(((wfPeerContext*)wfi->peers[index]->context)->updateEvent);
+ }
+ }
+ }
+
+ for (int index = 0; index < wfi->activePeerCount; index++)
+ {
+ // WLog_DBG(TAG, "Waiting for %d of %d", index + 1, wfi->activePeerCount);
+ // WaitForSingleObject(wfi->updateSemaphore, INFINITE);
+ WaitForSingleObject(wfi->updateSemaphore, 1000);
+ }
+
+ // WLog_DBG(TAG, "End of parallel sending");
+ wf_info_clear_invalid_region(wfi);
+ }
+ }
+
+ wf_info_unlock(wfi);
+ }
+
+ end = GetTickCount();
+ diff = end - beg;
+
+ if (diff < rate)
+ {
+ Sleep(rate - diff);
+ }
+ }
+
+ // WLog_DBG(TAG, "Exiting Update Thread");
+ return 0;
+}
+
+void wf_update_encode(wfInfo* wfi)
+{
+ RFX_RECT rect;
+ long height, width;
+ BYTE* pDataBits = NULL;
+ int stride;
+ SURFACE_BITS_COMMAND* cmd;
+ wf_info_find_invalid_region(wfi);
+ cmd = &wfi->cmd;
+ Stream_SetPosition(wfi->s, 0);
+ wf_info_getScreenData(wfi, &width, &height, &pDataBits, &stride);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = (UINT16)width;
+ rect.height = (UINT16)height;
+ // WLog_DBG(TAG, "x:%"PRId32" y:%"PRId32" w:%ld h:%ld", wfi->invalid.left, wfi->invalid.top,
+ // width, height);
+ Stream_Clear(wfi->s);
+
+ if (!(rfx_compose_message(wfi->rfx_context, wfi->s, &rect, 1, pDataBits, width, height,
+ stride)))
+ {
+ return;
+ }
+
+ wfi->frame_idx = rfx_context_get_frame_idx(wfi->rfx_context);
+ cmd->destLeft = wfi->invalid.left;
+ cmd->destTop = wfi->invalid.top;
+ cmd->destRight = wfi->invalid.left + width;
+ cmd->destBottom = wfi->invalid.top + height;
+ cmd->bmp.bpp = 32;
+ cmd->bmp.codecID = 3;
+ cmd->bmp.width = width;
+ cmd->bmp.height = height;
+ cmd->bmp.bitmapDataLength = Stream_GetPosition(wfi->s);
+ cmd->bmp.bitmapData = Stream_Buffer(wfi->s);
+}
+
+void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context)
+{
+ freerdp_peer* client;
+
+ WINPR_ASSERT(wfi);
+ WINPR_ASSERT(context);
+
+ client = ((rdpContext*)context)->peer;
+ WINPR_ASSERT(client);
+
+ /* This happens when the RemoteFX encoder state is reset */
+
+ if (wfi->frame_idx == 1)
+ context->frame_idx = 0;
+
+ /*
+ * When a new client connects, it is possible that old frames from
+ * from a previous encoding state remain. Those frames should be discarded
+ * as they will cause an error condition in mstsc.
+ */
+
+ if ((context->frame_idx + 1) != wfi->frame_idx)
+ {
+ /* This frame is meant to be discarded */
+ if (context->frame_idx == 0)
+ return;
+
+ /* This is an unexpected error condition */
+ WLog_DBG(TAG, "Unexpected Frame Index: Actual: %d Expected: %d", wfi->frame_idx,
+ context->frame_idx + 1);
+ }
+
+ WINPR_ASSERT(client->context);
+ WINPR_ASSERT(client->context->settings);
+ WINPR_ASSERT(client->context->update);
+ WINPR_ASSERT(client->context->update->SurfaceBits);
+
+ wfi->cmd.bmp.codecID =
+ freerdp_settings_get_uint32(client->context->settings, FreeRDP_RemoteFxCodecId);
+ client->context->update->SurfaceBits(client->context, &wfi->cmd);
+ context->frame_idx++;
+}
+
+void wf_update_encoder_reset(wfInfo* wfi)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ WLog_DBG(TAG, "Resetting encoder");
+
+ if (wfi->rfx_context)
+ {
+ rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
+ }
+ else
+ {
+ /* TODO: pass ThreadingFlags somehow */
+ wfi->rfx_context = rfx_context_new(TRUE);
+ rfx_context_set_mode(wfi->rfx_context, RLGR3);
+ rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height);
+ rfx_context_set_pixel_format(wfi->rfx_context, PIXEL_FORMAT_BGRA32);
+ wfi->s = Stream_New(NULL, 0xFFFF);
+ }
+
+ wf_info_invalidate_full_screen(wfi);
+ wf_info_unlock(wfi);
+ }
+}
+
+void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ if (wfi->activePeerCount < 1)
+ {
+#ifndef WITH_DXGI_1_2
+ wf_mirror_driver_activate(wfi);
+#endif
+ ResumeThread(wfi->updateThread);
+ }
+
+ wf_update_encoder_reset(wfi);
+ wfi->activePeerCount++;
+ WLog_DBG(TAG, "Activating Peer Updates: %d", wfi->activePeerCount);
+ wf_info_unlock(wfi);
+ }
+}
+
+void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context)
+{
+ if (wf_info_lock(wfi) > 0)
+ {
+ freerdp_peer* client = ((rdpContext*)context)->peer;
+
+ if (client->activated)
+ {
+ if (wfi->activePeerCount <= 1)
+ {
+ wf_mirror_driver_deactivate(wfi);
+ }
+
+ client->activated = FALSE;
+ wfi->activePeerCount--;
+ WLog_DBG(TAG, "Deactivating Peer Updates: %d", wfi->activePeerCount);
+ }
+
+ wf_info_unlock(wfi);
+ }
+}
diff --git a/server/Windows/wf_update.h b/server/Windows/wf_update.h
new file mode 100644
index 0000000..47553af
--- /dev/null
+++ b/server/Windows/wf_update.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Windows Server
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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_WIN_UPDATE_H
+#define FREERDP_SERVER_WIN_UPDATE_H
+
+#include "wf_interface.h"
+
+void wf_update_encode(wfInfo* wfi);
+void wf_update_send(wfInfo* wfi);
+
+DWORD WINAPI wf_update_thread(LPVOID lpParam);
+
+void wf_update_begin(wfInfo* wfi);
+void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context);
+void wf_update_end(wfInfo* wfi);
+
+void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context);
+void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context);
+
+#endif /* FREERDP_SERVER_WIN_UPDATE_H */
diff --git a/server/Windows/wf_wasapi.c b/server/Windows/wf_wasapi.c
new file mode 100644
index 0000000..3925f99
--- /dev/null
+++ b/server/Windows/wf_wasapi.c
@@ -0,0 +1,333 @@
+
+#include "wf_wasapi.h"
+#include "wf_info.h"
+
+#include <initguid.h>
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <audioclient.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("windows")
+
+//#define REFTIMES_PER_SEC 10000000
+//#define REFTIMES_PER_MILLISEC 10000
+
+#define REFTIMES_PER_SEC 100000
+#define REFTIMES_PER_MILLISEC 100
+
+//#define REFTIMES_PER_SEC 50000
+//#define REFTIMES_PER_MILLISEC 50
+
+#ifndef __MINGW32__
+DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92,
+ 0x91, 0x69, 0x2E);
+DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36,
+ 0x17, 0xE6);
+DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03,
+ 0xb2);
+DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c,
+ 0xd3, 0x17);
+#endif
+
+LPWSTR devStr = NULL;
+wfPeerContext* latestPeer = NULL;
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
+{
+ latestPeer = peer;
+ return 0;
+}
+
+int wf_wasapi_activate(RdpsndServerContext* context)
+{
+ wchar_t* pattern = L"Stereo Mix";
+ HANDLE hThread;
+
+ wf_wasapi_get_device_string(pattern, &devStr);
+
+ if (devStr == NULL)
+ {
+ WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd.");
+ return 1;
+ }
+
+ WLog_DBG(TAG, "RDPSND (WASAPI) Activated");
+ if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed");
+ return 1;
+ }
+ CloseHandle(hThread);
+
+ return 0;
+}
+
+int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr)
+{
+ HRESULT hr;
+ IMMDeviceEnumerator* pEnumerator = NULL;
+ IMMDeviceCollection* pCollection = NULL;
+ IMMDevice* pEndpoint = NULL;
+ IPropertyStore* pProps = NULL;
+ LPWSTR pwszID = NULL;
+ unsigned int count;
+
+ CoInitialize(NULL);
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
+ (void**)&pEnumerator);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate device enumerator");
+ exit(1);
+ }
+
+ hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE,
+ &pCollection);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to create endpoint collection");
+ exit(1);
+ }
+
+ pCollection->lpVtbl->GetCount(pCollection, &count);
+ WLog_INFO(TAG, "Num endpoints: %u", count);
+
+ if (count == 0)
+ {
+ WLog_ERR(TAG, "No endpoints!");
+ exit(1);
+ }
+
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ PROPVARIANT nameVar;
+ PropVariantInit(&nameVar);
+
+ hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get endpoint %u", i);
+ exit(1);
+ }
+
+ hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get endpoint ID");
+ exit(1);
+ }
+
+ hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to open property store");
+ exit(1);
+ }
+
+ hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get device friendly name");
+ exit(1);
+ }
+
+ // do this a more reliable way
+ if (wcscmp(pattern, nameVar.pwszVal) < 0)
+ {
+ unsigned int devStrLen;
+ WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID);
+ // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal);
+ devStrLen = wcslen(pwszID);
+ *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2);
+ if (!deviceStr)
+ return -1;
+ wcscpy_s(*deviceStr, devStrLen + 1, pwszID);
+ }
+ CoTaskMemFree(pwszID);
+ pwszID = NULL;
+ PropVariantClear(&nameVar);
+
+ pProps->lpVtbl->Release(pProps);
+ pProps = NULL;
+
+ pEndpoint->lpVtbl->Release(pEndpoint);
+ pEndpoint = NULL;
+ }
+
+ pCollection->lpVtbl->Release(pCollection);
+ pCollection = NULL;
+
+ pEnumerator->lpVtbl->Release(pEnumerator);
+ pEnumerator = NULL;
+ CoUninitialize();
+
+ return 0;
+}
+
+DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam)
+{
+ IMMDeviceEnumerator* pEnumerator = NULL;
+ IMMDevice* pDevice = NULL;
+ IAudioClient* pAudioClient = NULL;
+ IAudioCaptureClient* pCaptureClient = NULL;
+ WAVEFORMATEX* pwfx = NULL;
+ HRESULT hr;
+ REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
+ REFERENCE_TIME hnsActualDuration;
+ UINT32 bufferFrameCount;
+ UINT32 numFramesAvailable;
+ UINT32 packetLength = 0;
+ UINT32 dCount = 0;
+ BYTE* pData;
+
+ wfPeerContext* context;
+ wfInfo* wfi;
+
+ wfi = wf_info_get_instance();
+ context = (wfPeerContext*)lpParam;
+
+ CoInitialize(NULL);
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
+ (void**)&pEnumerator);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate device enumerator");
+ exit(1);
+ }
+
+ hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to cocreate get device");
+ exit(1);
+ }
+
+ hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL,
+ (void**)&pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to activate audio client");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get mix format");
+ exit(1);
+ }
+
+ pwfx->wFormatTag = wfi->agreed_format->wFormatTag;
+ pwfx->nChannels = wfi->agreed_format->nChannels;
+ pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec;
+ pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec;
+ pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign;
+ pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample;
+ pwfx->cbSize = wfi->agreed_format->cbSize;
+
+ hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0,
+ hnsRequestedDuration, 0, pwfx, NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to initialize the audio client");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get buffer size");
+ exit(1);
+ }
+
+ hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient,
+ (void**)&pCaptureClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get the capture client");
+ exit(1);
+ }
+
+ hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
+
+ hr = pAudioClient->lpVtbl->Start(pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to start capture");
+ exit(1);
+ }
+
+ dCount = 0;
+
+ while (wfi->snd_stop == FALSE)
+ {
+ DWORD flags;
+
+ Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
+
+ hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get packet length");
+ exit(1);
+ }
+
+ while (packetLength != 0)
+ {
+ hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable,
+ &flags, NULL, NULL);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get buffer");
+ exit(1);
+ }
+
+ // Here we are writing the audio data
+ // not sure if this flag is ever set by the system; msdn is not clear about it
+ if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
+ context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength,
+ (UINT16)(GetTickCount() & 0xffff));
+
+ hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to release buffer");
+ exit(1);
+ }
+
+ hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to get packet length");
+ exit(1);
+ }
+ }
+ }
+
+ pAudioClient->lpVtbl->Stop(pAudioClient);
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "Failed to stop audio client");
+ exit(1);
+ }
+
+ CoTaskMemFree(pwfx);
+
+ if (pEnumerator != NULL)
+ pEnumerator->lpVtbl->Release(pEnumerator);
+
+ if (pDevice != NULL)
+ pDevice->lpVtbl->Release(pDevice);
+
+ if (pAudioClient != NULL)
+ pAudioClient->lpVtbl->Release(pAudioClient);
+
+ if (pCaptureClient != NULL)
+ pCaptureClient->lpVtbl->Release(pCaptureClient);
+
+ CoUninitialize();
+
+ return 0;
+}
diff --git a/server/Windows/wf_wasapi.h b/server/Windows/wf_wasapi.h
new file mode 100644
index 0000000..da9c7dc
--- /dev/null
+++ b/server/Windows/wf_wasapi.h
@@ -0,0 +1,15 @@
+#ifndef FREERDP_SERVER_WIN_WASAPI_H
+#define FREERDP_SERVER_WIN_WASAPI_H
+
+#include <freerdp/server/rdpsnd.h>
+#include "wf_interface.h"
+
+int wf_rdpsnd_set_latest_peer(wfPeerContext* peer);
+
+int wf_wasapi_activate(RdpsndServerContext* context);
+
+int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr);
+
+DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam);
+
+#endif /* FREERDP_SERVER_WIN_WASAPI_H */
diff --git a/server/common/CMakeLists.txt b/server/common/CMakeLists.txt
new file mode 100644
index 0000000..5552688
--- /dev/null
+++ b/server/common/CMakeLists.txt
@@ -0,0 +1,77 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Server Common
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+
+set(MODULE_NAME "freerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER")
+
+# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link
+# interface. Run "cmake --help-policy CMP0022" for policy details. Use the
+# cmake_policy command to set the policy and suppress this warning.
+if(POLICY CMP0022)
+ cmake_policy(SET CMP0022 NEW)
+endif()
+
+set(${MODULE_PREFIX}_SRCS
+ server.c)
+
+foreach(FREERDP_CHANNELS_SERVER_SRC ${FREERDP_CHANNELS_SERVER_SRCS})
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} "${FREERDP_CHANNELS_SERVER_SRC}")
+endforeach()
+
+if(MSVC)
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS})
+endif()
+
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_SERVER_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC winpr freerdp)
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ServerTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common")
diff --git a/server/common/server.c b/server/common/server.c
new file mode 100644
index 0000000..62f126f
--- /dev/null
+++ b/server/common/server.c
@@ -0,0 +1,236 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Server Common
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/wtypes.h>
+#include <freerdp/codec/audio.h>
+#include <freerdp/codec/dsp.h>
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/log.h>
+
+#define TAG FREERDP_TAG("server.common")
+
+size_t server_audin_get_formats(AUDIO_FORMAT** dst_formats)
+{
+ /* Default supported audio formats */
+ BYTE adpcm_data_7[] = { 0xf4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_data_3[] = { 0xf4, 0x03, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_data_1[] = { 0xf4, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00,
+ 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff };
+ BYTE adpcm_dvi_data_7[] = { 0xf9, 0x07 };
+ BYTE adpcm_dvi_data_3[] = { 0xf9, 0x03 };
+ BYTE adpcm_dvi_data_1[] = { 0xf9, 0x01 };
+ BYTE gsm610_data[] = { 0x40, 0x01 };
+ const AUDIO_FORMAT default_supported_audio_formats[] = {
+ /* Formats sent by windows 10 server */
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 24000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 20000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 16000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 12000, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 44100, 44359, 2048, 4, 32, adpcm_data_7 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 44100, 44251, 2048, 4, 2, adpcm_dvi_data_7 },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 22050, 22311, 1024, 4, 32, adpcm_data_3 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 22050, 22201, 1024, 4, 2, adpcm_dvi_data_3 },
+ { WAVE_FORMAT_ADPCM, 1, 44100, 22179, 1024, 4, 32, adpcm_data_7 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 44100, 22125, 1024, 4, 2, adpcm_dvi_data_7 },
+ { WAVE_FORMAT_ADPCM, 2, 11025, 11289, 512, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 11025, 11177, 512, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_ADPCM, 1, 22050, 11155, 512, 4, 32, adpcm_data_3 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 22050, 11100, 512, 4, 2, adpcm_dvi_data_3 },
+ { WAVE_FORMAT_GSM610, 1, 44100, 8957, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_ADPCM, 2, 8000, 8192, 512, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 2, 8000, 8110, 512, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_ADPCM, 1, 11025, 5644, 256, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 11025, 5588, 256, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_GSM610, 1, 22050, 4478, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_ADPCM, 1, 8000, 4096, 256, 4, 32, adpcm_data_1 },
+ { WAVE_FORMAT_DVI_ADPCM, 1, 8000, 4055, 256, 4, 2, adpcm_dvi_data_1 },
+ { WAVE_FORMAT_GSM610, 1, 11025, 2239, 65, 0, 2, gsm610_data },
+ { WAVE_FORMAT_GSM610, 1, 8000, 1625, 65, 0, 2, gsm610_data },
+ /* Formats added for others */
+
+ { WAVE_FORMAT_MSG723, 2, 44100, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 2, 22050, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 1, 44100, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 1, 22050, 0, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 22050, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 1, 44100, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 1, 22050, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 44100, 88200, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 22050, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 1, 44100, 44100, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MULAW, 1, 22050, 22050, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 44100, 88200, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 1, 44100, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_ALAW, 1, 22050, 22050, 2, 8, 0, NULL }
+ };
+ const size_t nrDefaultFormatsMax = ARRAYSIZE(default_supported_audio_formats);
+ size_t nr_formats = 0;
+ AUDIO_FORMAT* formats = audio_formats_new(nrDefaultFormatsMax);
+
+ if (!dst_formats)
+ goto fail;
+
+ *dst_formats = NULL;
+
+ if (!formats)
+ goto fail;
+
+ for (size_t x = 0; x < nrDefaultFormatsMax; x++)
+ {
+ const AUDIO_FORMAT* format = &default_supported_audio_formats[x];
+
+ if (freerdp_dsp_supports_format(format, FALSE))
+ {
+ AUDIO_FORMAT* dst = &formats[nr_formats++];
+
+ if (!audio_format_copy(format, dst))
+ goto fail;
+ }
+ }
+
+ *dst_formats = formats;
+ return nr_formats;
+fail:
+ audio_formats_free(formats, nrDefaultFormatsMax);
+ return 0;
+}
+
+size_t server_rdpsnd_get_formats(AUDIO_FORMAT** dst_formats)
+{
+ /* Default supported audio formats */
+ static const AUDIO_FORMAT default_supported_audio_formats[] = {
+ { WAVE_FORMAT_AAC_MS, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MPEGLAYER3, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_MSG723, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_GSM610, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ADPCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL },
+ { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ { WAVE_FORMAT_MULAW, 2, 22050, 44100, 2, 8, 0, NULL },
+ };
+ AUDIO_FORMAT* supported_audio_formats =
+ audio_formats_new(ARRAYSIZE(default_supported_audio_formats));
+
+ if (!supported_audio_formats)
+ goto fail;
+
+ size_t y = 0;
+ for (size_t x = 0; x < ARRAYSIZE(default_supported_audio_formats); x++)
+ {
+ const AUDIO_FORMAT* format = &default_supported_audio_formats[x];
+
+ if (freerdp_dsp_supports_format(format, TRUE))
+ supported_audio_formats[y++] = *format;
+ }
+
+ /* Set default audio formats. */
+ *dst_formats = supported_audio_formats;
+ return y;
+fail:
+ audio_formats_free(supported_audio_formats, ARRAYSIZE(default_supported_audio_formats));
+
+ if (dst_formats)
+ *dst_formats = NULL;
+
+ return 0;
+}
+
+void freerdp_server_warn_unmaintained(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[unmaintained] %s server is currently unmaintained!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues!");
+ WLog_Print_unchecked(
+ log, log_level,
+ "Be prepared to fix issues yourself though as nobody is actively working on this.");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone) - if you intend using this component write us a message");
+}
+
+void freerdp_server_warn_experimental(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[experimental] %s server is currently experimental!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues or create a new one!");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
+
+void freerdp_server_warn_deprecated(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[deprecated] %s server has been deprecated", app);
+ WLog_Print_unchecked(log, log_level, "As replacement there is a SDL based client available.");
+ WLog_Print_unchecked(
+ log, log_level,
+ "If you are interested in keeping %s alive get in touch with the developers", app);
+ WLog_Print_unchecked(
+ log, log_level,
+ "The project is hosted at https://github.com/freerdp/freerdp and "
+ " developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
diff --git a/server/freerdp-server.pc.in b/server/freerdp-server.pc.in
new file mode 100644
index 0000000..d7d6629
--- /dev/null
+++ b/server/freerdp-server.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-server@FREERDP_API_VERSION@
+
+Name: FreeRDP server
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt
new file mode 100644
index 0000000..4693b17
--- /dev/null
+++ b/server/proxy/CMakeLists.txt
@@ -0,0 +1,131 @@
+# 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>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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(CMakeDependentOption)
+set(MODULE_NAME "freerdp-server-proxy")
+set(MODULE_PREFIX "FREERDP_SERVER_PROXY")
+
+set(${MODULE_PREFIX}_SRCS
+ pf_context.c
+ pf_channel.c
+ pf_channel.h
+ pf_client.c
+ pf_client.h
+ pf_input.c
+ pf_input.h
+ pf_update.c
+ pf_update.h
+ pf_server.c
+ pf_server.h
+ pf_config.c
+ pf_modules.c
+ pf_utils.h
+ pf_utils.c
+ $<TARGET_OBJECTS:pf_channels>
+ )
+
+set(PROXY_APP_SRCS freerdp_proxy.c)
+
+option(WITH_PROXY_EMULATE_SMARTCARD "Compile proxy smartcard emulation" OFF)
+add_subdirectory("channels")
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+set(PRIVATE_LIBS
+ freerdp-client
+ freerdp-server
+)
+
+set(PUBLIC_LIBS
+ winpr
+ freerdp
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${PRIVATE_LIBS} PUBLIC ${PUBLIC_LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ProxyTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Proxy")
+
+# pkg-config
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-proxy.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-proxy)
+
+SetFreeRDPCMakeInstallDir(FREERDP_PROXY_CMAKE_INSTALL_DIR "FreeRDP-Proxy${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ProxyConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+install(EXPORT FreeRDP-ProxyTargets DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/proxy")
+
+option(WITH_PROXY_APP "Compile proxy application" ON)
+
+if (WITH_PROXY_APP)
+ add_subdirectory("cli")
+endif()
+
+option(WITH_PROXY_MODULES "Compile proxy modules" ON)
+if (WITH_PROXY_MODULES)
+ add_subdirectory("modules")
+endif()
+
diff --git a/server/proxy/FreeRDP-ProxyConfig.cmake.in b/server/proxy/FreeRDP-ProxyConfig.cmake.in
new file mode 100644
index 0000000..406da3a
--- /dev/null
+++ b/server/proxy/FreeRDP-ProxyConfig.cmake.in
@@ -0,0 +1,10 @@
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Proxy_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Proxy_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Proxy_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Proxy_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ProxyTargets.cmake")
diff --git a/server/proxy/channels/CMakeLists.txt b/server/proxy/channels/CMakeLists.txt
new file mode 100644
index 0000000..1915d83
--- /dev/null
+++ b/server/proxy/channels/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+set(MODULE_NAME pf_channels)
+set(SOURCES
+ pf_channel_rdpdr.c
+ pf_channel_rdpdr.h
+ pf_channel_drdynvc.c
+ pf_channel_drdynvc.h
+)
+
+if (WITH_PROXY_EMULATE_SMARTCARD)
+ list(APPEND SOURCES
+ pf_channel_smartcard.c
+ pf_channel_smartcard.h
+ )
+endif()
+
+add_library(${MODULE_NAME} OBJECT ${SOURCES})
diff --git a/server/proxy/channels/pf_channel_drdynvc.c b/server/proxy/channels/pf_channel_drdynvc.c
new file mode 100644
index 0000000..9d8cab9
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.c
@@ -0,0 +1,711 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * Copyright 2022 David Fort <contact@hardening-consulting.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.
+ */
+#include <winpr/assert.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/utils/drdynvc.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_channel_drdynvc.h"
+#include "../pf_channel.h"
+#include "../proxy_modules.h"
+#include "../pf_utils.h"
+
+#define DTAG PROXY_TAG("drdynvc")
+
+/** @brief channel opened status */
+typedef enum
+{
+ CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */
+ CHANNEL_OPENSTATE_OPENED, /*!< opened */
+ CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */
+} PfDynChannelOpenStatus;
+
+typedef struct p_server_dynamic_channel_context pServerDynamicChannelContext;
+typedef struct DynChannelTrackerState DynChannelTrackerState;
+
+typedef PfChannelResult (*dynamic_channel_on_data_fn)(pServerContext* ps,
+ pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker,
+ BOOL firstPacket, BOOL lastPacket);
+
+/** @brief tracker state for a drdynvc stream */
+struct DynChannelTrackerState
+{
+ UINT32 currentDataLength;
+ UINT32 CurrentDataReceived;
+ UINT32 CurrentDataFragments;
+ wStream* currentPacket;
+ dynamic_channel_on_data_fn dataCallback;
+};
+
+typedef void (*channel_data_dtor_fn)(void** user_data);
+
+struct p_server_dynamic_channel_context
+{
+ char* channelName;
+ UINT32 channelId;
+ PfDynChannelOpenStatus openStatus;
+ pf_utils_channel_mode channelMode;
+ BOOL packetReassembly;
+ DynChannelTrackerState backTracker;
+ DynChannelTrackerState frontTracker;
+
+ void* channelData;
+ channel_data_dtor_fn channelDataDtor;
+};
+
+/** @brief context for the dynamic channel */
+typedef struct
+{
+ wHashTable* channels;
+ ChannelStateTracker* backTracker;
+ ChannelStateTracker* frontTracker;
+ wLog* log;
+} DynChannelContext;
+
+/** @brief result of dynamic channel packet treatment */
+typedef enum
+{
+ DYNCVC_READ_OK, /*!< read was OK */
+ DYNCVC_READ_ERROR, /*!< an error happened during read */
+ DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
+} DynvcReadResult;
+
+static const char* openstatus2str(PfDynChannelOpenStatus status)
+{
+ switch (status)
+ {
+ case CHANNEL_OPENSTATE_WAITING_OPEN_STATUS:
+ return "CHANNEL_OPENSTATE_WAITING_OPEN_STATUS";
+ case CHANNEL_OPENSTATE_CLOSED:
+ return "CHANNEL_OPENSTATE_CLOSED";
+ case CHANNEL_OPENSTATE_OPENED:
+ return "CHANNEL_OPENSTATE_OPENED";
+ default:
+ return "CHANNEL_OPENSTATE_UNKNOWN";
+ }
+}
+
+static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(tracker);
+ WINPR_ASSERT(ps->pdata);
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ proxyDynChannelInterceptData dyn = { .name = channel->channelName,
+ .channelId = channel->channelId,
+ .data = currentPacket,
+ .isBackData = isBackData,
+ .first = firstPacket,
+ .last = lastPacket,
+ .rewritten = FALSE,
+ .packetSize = channelTracker_getCurrentPacketSize(tracker),
+ .result = PF_CHANNEL_RESULT_ERROR };
+ Stream_SealLength(dyn.data);
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ channelTracker_setCurrentPacketSize(tracker, dyn.packetSize);
+ if (dyn.rewritten)
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ return dyn.result;
+}
+
+static pServerDynamicChannelContext* DynamicChannelContext_new(wLog* log, pServerContext* ps,
+ const char* name, UINT32 id)
+{
+ WINPR_ASSERT(log);
+
+ pServerDynamicChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating dynamic channel context '%s'", name);
+ return NULL;
+ }
+
+ ret->channelId = id;
+ ret->channelName = _strdup(name);
+ if (!ret->channelName)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating name in dynamic channel context '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ ret->frontTracker.dataCallback = data_cb;
+ ret->backTracker.dataCallback = data_cb;
+
+ proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE };
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) &&
+ dyn.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ ret->openStatus = CHANNEL_OPENSTATE_OPENED;
+ ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT);
+
+ return ret;
+}
+
+static void DynamicChannelContext_free(void* ptr)
+{
+ pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr;
+ if (!c)
+ return;
+
+ if (c->backTracker.currentPacket)
+ Stream_Free(c->backTracker.currentPacket, TRUE);
+
+ if (c->frontTracker.currentPacket)
+ Stream_Free(c->frontTracker.currentPacket, TRUE);
+
+ if (c->channelDataDtor)
+ c->channelDataDtor(&c->channelData);
+
+ free(c->channelName);
+ free(c);
+}
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* objA, const void* objB)
+{
+ const UINT32* v1 = objA;
+ const UINT32* v2 = objB;
+ return (*v1 == *v2);
+}
+
+static DynvcReadResult dynvc_read_varInt(wLog* log, wStream* s, size_t len, UINT64* varInt,
+ BOOL last)
+{
+ WINPR_ASSERT(varInt);
+ switch (len)
+ {
+ case 0x00:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 1))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT8(s, *varInt);
+ break;
+ case 0x01:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT16(s, *varInt);
+ break;
+ case 0x02:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT32(s, *varInt);
+ break;
+ case 0x03:
+ default:
+ WLog_Print(log, WLOG_ERROR, "Unknown int len %" PRIuz, len);
+ return DYNCVC_READ_ERROR;
+ }
+ return DYNCVC_READ_OK;
+}
+
+static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ BYTE cmd = 0;
+ BYTE byte0 = 0;
+ wStream* s = NULL;
+ wStream sbuffer;
+ BOOL haveChannelId = 0;
+ BOOL haveLength = 0;
+ UINT64 dynChannelId = 0;
+ UINT64 Length = 0;
+ pServerDynamicChannelContext* dynChannel = NULL;
+
+ WINPR_ASSERT(tracker);
+
+ DynChannelContext* dynChannelContext =
+ (DynChannelContext*)channelTracker_getCustomData(tracker);
+ WINPR_ASSERT(dynChannelContext);
+
+ BOOL isBackData = (tracker == dynChannelContext->backTracker);
+ DynChannelTrackerState* trackerState = NULL;
+
+ UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
+ proxyData* pdata = channelTracker_getPData(tracker);
+ WINPR_ASSERT(pdata);
+
+ const char* direction = isBackData ? "B->F" : "F->B";
+
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 1))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT8(s, byte0);
+ cmd = byte0 >> 4;
+
+ switch (cmd)
+ {
+ case CREATE_REQUEST_PDU:
+ case CLOSE_REQUEST_PDU:
+ case DATA_PDU:
+ case DATA_COMPRESSED_PDU:
+ haveChannelId = TRUE;
+ haveLength = FALSE;
+ break;
+ case DATA_FIRST_PDU:
+ case DATA_FIRST_COMPRESSED_PDU:
+ haveLength = TRUE;
+ haveChannelId = TRUE;
+ break;
+ default:
+ haveChannelId = FALSE;
+ haveLength = FALSE;
+ break;
+ }
+
+ if (haveChannelId)
+ {
+ BYTE cbId = byte0 & 0x03;
+
+ switch (dynvc_read_varInt(dynChannelContext->log, s, cbId, &dynChannelId, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid channelId field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ /* we always try to retrieve the dynamic channel in case it would have been opened
+ * and closed
+ */
+ dynChannel = (pServerDynamicChannelContext*)HashTable_GetItemValue(
+ dynChannelContext->channels, &dynChannelId);
+ if (cmd != CREATE_REQUEST_PDU || !isBackData)
+ {
+ if (!dynChannel)
+ {
+ /* we've not found the target channel, so we drop this chunk, plus all the rest of
+ * the packet */
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ return PF_CHANNEL_RESULT_DROP;
+ }
+ }
+ }
+
+ if (haveLength)
+ {
+ BYTE lenLen = (byte0 >> 2) & 0x03;
+ switch (dynvc_read_varInt(dynChannelContext->log, s, lenLen, &Length, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid length field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ switch (cmd)
+ {
+ case CAPABILITY_REQUEST_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "DynvcTracker: %s CAPABILITY_%s",
+ direction, isBackData ? "REQUEST" : "RESPONSE");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case CREATE_REQUEST_PDU:
+ {
+ UINT32 creationStatus = 0;
+
+ /* we only want the full packet */
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ if (isBackData)
+ {
+ proxyChannelDataEventInfo dev = { 0 };
+ const char* name = Stream_ConstPointer(s);
+ const size_t nameLen = Stream_GetRemainingLength(s);
+
+ const size_t len = strnlen(name, nameLen);
+ if ((len == 0) || (len == nameLen))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ dev.channel_id = dynChannelId;
+ dev.channel_name = name;
+ dev.data = Stream_Buffer(s);
+ dev.data_len = Stream_GetPosition(currentPacket);
+ dev.flags = flags;
+ dev.total_size = Stream_GetPosition(currentPacket);
+
+ if (dynChannel)
+ {
+ WLog_Print(
+ dynChannelContext->log, WLOG_WARN,
+ "Reusing channel id %" PRIu32 ", previously %s [state %s, mode %s], now %s",
+ dynChannel->channelId, dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ pf_utils_channel_mode_string(dynChannel->channelMode), dev.channel_name);
+
+ HashTable_Remove(dynChannelContext->channels, &dynChannel->channelId);
+ }
+
+ if (!pf_modules_run_filter(pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata,
+ &dev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ dynChannel = DynamicChannelContext_new(dynChannelContext->log, pdata->ps, name,
+ dynChannelId);
+ if (!dynChannel)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create dynamic channel context data");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "Adding channel '%s'[%d]",
+ dynChannel->channelName, dynChannel->channelId);
+ if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId,
+ dynChannel))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable register dynamic channel context data");
+ DynamicChannelContext_free(dynChannel);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
+ }
+
+ /* CREATE_REQUEST_PDU response */
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 4))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT32(s, creationStatus);
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%" PRIu64 ",%s): %s CREATE_RESPONSE openStatus=%" PRIu32,
+ dynChannelId, dynChannel->channelName, direction, creationStatus);
+
+ if (creationStatus == 0)
+ dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
+
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
+ }
+
+ case CLOSE_REQUEST_PDU:
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s): %s Close request on channel", dynChannel->channelName,
+ direction);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_WARN,
+ "DynvcTracker(%s): is in state %s, expected %s", dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ openstatus2str(CHANNEL_OPENSTATE_OPENED));
+ }
+ dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ case SOFT_SYNC_REQUEST_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_REQUEST_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ /*TODO: return pf_treat_softsync_req(pdata, s);*/
+ return PF_CHANNEL_RESULT_PASS;
+
+ case SOFT_SYNC_RESPONSE_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_RESPONSE_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case DATA_FIRST_PDU:
+ case DATA_PDU:
+ /* treat these below */
+ trackerState = isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
+ break;
+
+ case DATA_FIRST_COMPRESSED_PDU:
+ case DATA_COMPRESSED_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "TODO: compressed data packets, pass them as is for now");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ default:
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker(%s [%s]): channel is not opened", dynChannel->channelName,
+ drdynvc_get_packet_type(cmd));
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s DATA_FIRST currentPacketLength=%" PRIu64 "",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, Length);
+ trackerState->currentDataLength = Length;
+ trackerState->CurrentDataReceived = 0;
+ trackerState->CurrentDataFragments = 0;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+ }
+
+ if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU)
+ {
+ size_t extraSize = Stream_GetRemainingLength(s);
+
+ trackerState->CurrentDataFragments++;
+ trackerState->CurrentDataReceived += extraSize;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (!trackerState->currentPacket)
+ {
+ trackerState->currentPacket = Stream_New(NULL, 1024);
+ if (!trackerState->currentPacket)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unable to grow current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
+ }
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction,
+ trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
+ trackerState->currentDataLength);
+ }
+
+ if (cmd == DATA_PDU)
+ {
+ if (trackerState->currentDataLength)
+ {
+ if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker (%s [%s]): reassembled packet (%" PRIu32
+ ") is bigger than announced length (%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd),
+ trackerState->CurrentDataReceived, trackerState->currentDataLength);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+ else
+ {
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+ }
+ }
+
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ switch (dynChannel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ result = channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ if (trackerState->dataCallback)
+ {
+ result = trackerState->dataCallback(pdata->ps, dynChannel, isBackData, tracker,
+ firstPacket, lastPacket);
+ }
+ else
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "no intercept callback for channel %s(fromBack=%d), dropping packet",
+ dynChannel->channelName, isBackData);
+ result = PF_CHANNEL_RESULT_DROP;
+ }
+ break;
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unknown channel mode %d",
+ dynChannel->channelMode);
+ result = PF_CHANNEL_RESULT_ERROR;
+ break;
+ }
+
+ if (!trackerState->currentDataLength ||
+ (trackerState->CurrentDataReceived == trackerState->currentDataLength))
+ {
+ trackerState->currentDataLength = 0;
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+
+ if (dynChannel->packetReassembly && trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+
+ return result;
+}
+
+static void DynChannelContext_free(void* context)
+{
+ DynChannelContext* c = context;
+ if (!c)
+ return;
+ channelTracker_free(c->backTracker);
+ channelTracker_free(c->frontTracker);
+ HashTable_Free(c->channels);
+ free(c);
+}
+
+static const char* dynamic_context(void* arg)
+{
+ proxyData* pdata = arg;
+ if (!pdata)
+ return "pdata=null";
+ return pdata->session_id;
+}
+
+static DynChannelContext* DynChannelContext_new(proxyData* pdata,
+ pServerStaticChannelContext* channel)
+{
+ DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
+ if (!dyn)
+ return FALSE;
+
+ dyn->log = WLog_Get(DTAG);
+ WINPR_ASSERT(dyn->log);
+ WLog_SetContext(dyn->log, dynamic_context, pdata);
+
+ dyn->backTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->backTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->backTracker, pdata))
+ goto fail;
+
+ dyn->frontTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->frontTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->frontTracker, pdata))
+ goto fail;
+
+ dyn->channels = HashTable_New(FALSE);
+ if (!dyn->channels)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(dyn->channels, ChannelId_Hash))
+ goto fail;
+
+ wObject* kobj = HashTable_KeyObject(dyn->channels);
+ WINPR_ASSERT(kobj);
+ kobj->fnObjectEquals = ChannelId_Compare;
+
+ wObject* vobj = HashTable_ValueObject(dyn->channels);
+ WINPR_ASSERT(vobj);
+ vobj->fnObjectFree = DynamicChannelContext_free;
+
+ return dyn;
+
+fail:
+ DynChannelContext_free(dyn);
+ return NULL;
+}
+
+static PfChannelResult pf_dynvc_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize);
+}
+
+static PfChannelResult pf_dynvc_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize);
+}
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel)
+{
+ DynChannelContext* ret = DynChannelContext_new(pdata, channel);
+ if (!ret)
+ return FALSE;
+
+ channel->onBackData = pf_dynvc_back_data;
+ channel->onFrontData = pf_dynvc_front_data;
+ channel->contextDtor = DynChannelContext_free;
+ channel->context = ret;
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_drdynvc.h b/server/proxy/channels/pf_channel_drdynvc.h
new file mode 100644
index 0000000..b084143
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.h
@@ -0,0 +1,26 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * Copyright 2022 David Fort <contact@hardening-consulting.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 SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+#define SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */
diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c
new file mode 100644
index 0000000..cb79266
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.c
@@ -0,0 +1,2017 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/print.h>
+
+#include "pf_channel_rdpdr.h"
+#include "pf_channel_smartcard.h"
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#define RTAG PROXY_TAG("channel.rdpdr")
+
+#define SCARD_DEVICE_ID UINT32_MAX
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ wStream* s;
+ wStream* buffer;
+ UINT16 versionMajor;
+ UINT16 versionMinor;
+ UINT32 clientID;
+ UINT32 computerNameLen;
+ BOOL computerNameUnicode;
+ union
+ {
+ WCHAR* wc;
+ char* c;
+ void* v;
+ } computerName;
+ UINT32 SpecialDeviceCount;
+ UINT32 capabilityVersions[6];
+} pf_channel_common_context;
+
+typedef enum
+{
+ STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01,
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02,
+ STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04,
+ STATE_CLIENT_CHANNEL_RUNNING = 0x10
+} pf_channel_client_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_client_state state;
+ UINT32 flags;
+ UINT16 maxMajorVersion;
+ UINT16 maxMinorVersion;
+ wQueue* queue;
+ wLog* log;
+} pf_channel_client_context;
+
+typedef enum
+{
+ STATE_SERVER_INITIAL,
+ STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY,
+ STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST,
+ STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE,
+ STATE_SERVER_CHANNEL_RUNNING
+} pf_channel_server_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_server_state state;
+ DWORD SessionId;
+ HANDLE handle;
+ wArrayList* blockedDevices;
+ wLog* log;
+} pf_channel_server_context;
+
+#define proxy_client "[proxy<-->client]"
+#define proxy_server "[proxy<-->server]"
+
+#define proxy_client_rx proxy_client " receive"
+#define proxy_client_tx proxy_client " send"
+#define proxy_server_rx proxy_server " receive"
+#define proxy_server_tx proxy_server " send"
+
+#define SERVER_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_rx fmt, ##__VA_ARGS__)
+#define CLIENT_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_rx fmt, ##__VA_ARGS__)
+#define SERVER_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_tx fmt, ##__VA_ARGS__)
+#define CLIENT_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_tx fmt, ##__VA_ARGS__)
+#define RX_LOG(srv, lvl, fmt, ...) \
+ do \
+ { \
+ if (srv) \
+ { \
+ SERVER_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ CLIENT_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define SERVER_RXTX_LOG(send, log, lvl, fmt, ...) \
+ do \
+ { \
+ if (send) \
+ { \
+ SERVER_TX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ SERVER_RX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define Stream_CheckAndLogRequiredLengthSrv(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_client_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthClient(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_server_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthRx(srv, log, s, len) \
+ Stream_CheckAndLogRequiredLengthRx_(srv, log, s, len, 1, __func__, __FILE__, __LINE__)
+static BOOL Stream_CheckAndLogRequiredLengthRx_(BOOL srv, wLog* log, wStream* s, size_t nmemb,
+ size_t size, const char* fkt, const char* file,
+ size_t line)
+{
+ const char* fmt =
+ srv ? proxy_server_rx " %s(%s:%" PRIuz ")" : proxy_client_rx " %s(%s:%" PRIuz ")";
+
+ return Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, fmt, fkt, file,
+ line);
+}
+
+static const char* rdpdr_server_state_to_string(pf_channel_server_state state)
+{
+ switch (state)
+ {
+ case STATE_SERVER_INITIAL:
+ return "STATE_SERVER_INITIAL";
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY";
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST";
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE";
+ case STATE_SERVER_CHANNEL_RUNNING:
+ return "STATE_SERVER_CHANNEL_RUNNING";
+ default:
+ return "STATE_SERVER_UNKNOWN";
+ }
+}
+
+static const char* rdpdr_client_state_to_string(pf_channel_client_state state)
+{
+ switch (state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM";
+ case STATE_CLIENT_CHANNEL_RUNNING:
+ return "STATE_CLIENT_CHANNEL_RUNNING";
+ default:
+ return "STATE_CLIENT_UNKNOWN";
+ }
+}
+
+static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->s);
+ if (!Stream_SetPosition(rdpdr->s, 0))
+ return NULL;
+ if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4))
+ return NULL;
+ Stream_Write_UINT16(rdpdr->s, component);
+ Stream_Write_UINT16(rdpdr->s, PacketID);
+ return rdpdr->s;
+}
+
+static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static UINT rdpdr_client_send(wLog* log, pClientContext* pc, wStream* s)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc->context.instance);
+
+ if (!pc->connected)
+ {
+ CLIENT_TX_LOG(log, WLOG_WARN, "Ignoring channel %s message, not connected!",
+ RDPDR_SVC_CHANNEL_NAME);
+ return CHANNEL_RC_OK;
+ }
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ /* Ignore unmappable channels. Might happen when the channel was already down and
+ * some delayed message is tried to be sent. */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SealLength(s);
+ rdpdr_dump_send_packet(log, WLOG_TRACE, s, proxy_server_tx);
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s),
+ Stream_Length(s)))
+ return ERROR_EVT_CHANNEL_NOT_FOUND;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s)
+{
+ BOOL status = 0;
+ size_t len = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->handle);
+ WINPR_ASSERT(s);
+
+ Stream_SealLength(s);
+ len = Stream_Length(s);
+ WINPR_ASSERT(len <= ULONG_MAX);
+
+ rdpdr_dump_send_packet(context->log, WLOG_TRACE, s, proxy_client_tx);
+ status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL);
+ return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static BOOL rdpdr_process_server_header(BOOL server, wLog* log, wStream* s, UINT16 component,
+ UINT16 PacketId, size_t expect)
+{
+ UINT16 rpacketid = 0;
+ UINT16 rcomponent = 0;
+
+ WINPR_ASSERT(s);
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, 4))
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, rcomponent);
+ Stream_Read_UINT16(s, rpacketid);
+
+ if (rcomponent != component)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got component %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+
+ if (rpacketid != PacketId)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got PacketID %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_packetid_string(rpacketid));
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, expect))
+ {
+ RX_LOG(server, log, WLOG_WARN,
+ "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", "
+ "got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect,
+ Stream_GetRemainingLength(s));
+ return ERROR_INVALID_DATA;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdpdr_check_version(BOOL server, wLog* log, UINT16 versionMajor, UINT16 versionMinor,
+ UINT16 component, UINT16 PacketId)
+{
+ if (versionMajor != RDPDR_VERSION_MAJOR)
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ RDPDR_VERSION_MAJOR, versionMajor);
+ return FALSE;
+ }
+ switch (versionMinor)
+ {
+ case RDPDR_VERSION_MINOR_RDP50:
+ case RDPDR_VERSION_MINOR_RDP51:
+ case RDPDR_VERSION_MINOR_RDP52:
+ case RDPDR_VERSION_MINOR_RDP6X:
+ case RDPDR_VERSION_MINOR_RDP10X:
+ break;
+ default:
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] unsupported MinorVersion %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ versionMinor);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Read_UINT16(s, rdpdr->common.versionMinor);
+
+ if (!rdpdr_check_version(FALSE, rdpdr->log, rdpdr->common.versionMajor,
+ rdpdr->common.versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ /* Limit maximum channel protocol version to the one set by proxy server */
+ if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion)
+ {
+ rdpdr->common.versionMajor = rdpdr->maxMajorVersion;
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+ }
+ else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion)
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+
+ Stream_Read_UINT32(s, rdpdr->common.clientID);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */
+ Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */
+ Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM;
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+
+ if (!rdpdr_check_version(TRUE, rdpdr->log, versionMajor, versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ if ((rdpdr->common.versionMajor != versionMajor) ||
+ (rdpdr->common.versionMinor != versionMinor))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN,
+ "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+ Stream_Read_UINT32(s, clientID);
+ if (rdpdr->common.clientID != clientID)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.clientID, clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s,
+ pClientContext* pc)
+{
+ UINT32 unicodeFlag = 0;
+ UINT32 codePage = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, unicodeFlag);
+ rdpdr->common.computerNameUnicode = (unicodeFlag & 1);
+
+ Stream_Read_UINT32(s, codePage);
+ WINPR_UNUSED(codePage); /* Field is ignored */
+ Stream_Read_UINT32(s, rdpdr->common.computerNameLen);
+ if (!Stream_CheckAndLogRequiredLengthSrv(rdpdr->log, s, rdpdr->common.computerNameLen))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN, "[%s | %s]: missing data, got %" PRIu32 ", expected %" PRIu32,
+ rdpdr_component_string(RDPDR_CTYP_CORE), rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME),
+ Stream_GetRemainingLength(s), rdpdr->common.computerNameLen);
+ return ERROR_INVALID_DATA;
+ }
+ void* tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+
+ Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+
+ pc->computerNameLen = rdpdr->common.computerNameLen;
+ pc->computerNameUnicode = rdpdr->common.computerNameUnicode;
+ tmp = realloc(pc->computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ pc->computerName.v = tmp;
+ memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pc);
+
+ {
+ void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+ rdpdr->common.computerNameLen = pc->computerNameLen;
+ rdpdr->common.computerNameUnicode = pc->computerNameUnicode;
+ memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen);
+ }
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12U + rdpdr->common.computerNameLen);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode
+ ? 1
+ : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */
+ Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */
+ Stream_Write_UINT32(s, rdpdr->common.computerNameLen);
+ Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+#define rdpdr_ignore_capset(srv, log, s, header) \
+ rdpdr_ignore_capset_((srv), (log), (s), header, __func__)
+static UINT rdpdr_ignore_capset_(BOOL srv, wLog* log, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header, const char* fkt)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_SERVER_CAPABILITY, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek(s, 2); /* pad (2 bytes) */
+
+ for (UINT16 i = 0; i < numCapabilities; i++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_client_process_general_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_process_printer_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_process_port_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_process_drive_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_process_smartcard_capset(rdpdr, s, &header);
+ break;
+
+ default:
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "unknown capability 0x%04" PRIx16 ", length %" PRIu16
+ ", version %" PRIu32,
+ header.CapabilityType, header.CapabilityLength, header.Version);
+ Stream_Seek(s, header.CapabilityLength);
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL rdpdr_write_general_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, 44,
+ rdpdr->capabilityVersions[CAP_GENERAL_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */
+ Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */
+ Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */
+ Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */
+ Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */
+ Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU |
+ RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
+ Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */
+ Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special
+ devices to be redirected before logon */
+ return TRUE;
+}
+
+static BOOL rdpdr_write_printer_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PRINTER_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_port_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PORT_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_drive_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_DRIVE_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_smartcard_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_SMARTCARD_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY;
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek_UINT16(s); /* padding */
+
+ for (UINT16 x = 0; x < numCapabilities; x++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ default:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] invalid capability type 0x%04" PRIx16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ header.CapabilityType);
+ status = ERROR_INVALID_DATA;
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ return status;
+}
+
+static UINT rdpdr_send_client_capability_response(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s)
+{
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+ if (!rdpdr_check_version(FALSE, rdpdr->log, versionMajor, versionMinor, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, clientID);
+
+ if ((versionMajor != rdpdr->common.versionMajor) ||
+ (versionMinor != rdpdr->common.versionMinor))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16
+ ", downgraded to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor,
+ versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+
+ if (clientID != rdpdr->common.clientID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID,
+ clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL
+rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM |
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ const UINT16 rcomponent = RDPDR_CTYP_CORE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if ((rdpdr->flags & mask) == mask)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "already past this state, abort!");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthClient(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ if (rcomponent != component)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got component %s, expected %s",
+ rdpdr_component_string(component), rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, packetid);
+ Stream_Rewind(s, 4);
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_CAPABILITY:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK;
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ default:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK;
+ }
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+ Stream_Write(s, "SCARD\0\0\0", 8);
+ Stream_Write_UINT32(s, 6);
+ Stream_Write(s, "SCARD\0", 6);
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_DEVICE_REPLY;
+ UINT32 deviceID = 0;
+ UINT32 resultCode = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, deviceID);
+ Stream_Read_UINT32(s, resultCode);
+
+ if (deviceID != SCARD_DEVICE_ID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ SCARD_DEVICE_ID, deviceID);
+ }
+ else if (resultCode != 0)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+ }
+ else
+ CLIENT_RX_LOG(rdpdr->log, WLOG_DEBUG,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32
+ " -> emulated smartcard redirected!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static BOOL pf_channel_rdpdr_rewrite_device_list_to(wStream* s, UINT32 fromVersion,
+ UINT32 toVersion)
+{
+ BOOL rc = FALSE;
+ if (fromVersion == toVersion)
+ return TRUE;
+
+ const size_t cap = Stream_GetRemainingLength(s);
+ wStream* clone = Stream_New(NULL, cap);
+ if (!clone)
+ goto fail;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Copy(s, clone, cap);
+ Stream_SealLength(clone);
+
+ Stream_SetPosition(clone, 0);
+ Stream_SetPosition(s, pos);
+
+ /* Skip device count */
+ if (!Stream_SafeSeek(s, 4))
+ goto fail;
+
+ UINT32 count = 0;
+ if (Stream_GetRemainingLength(clone) < 4)
+ goto fail;
+ Stream_Read_UINT32(clone, count);
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ RdpdrDevice device = { 0 };
+ const size_t charCount = ARRAYSIZE(device.PreferredDosName);
+ if (Stream_GetRemainingLength(clone) < 20)
+ goto fail;
+
+ Stream_Read_UINT32(clone, device.DeviceType); /* DeviceType (4 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read(clone, device.PreferredDosName, charCount); /* PreferredDosName (8 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */
+ device.DeviceData = Stream_Pointer(clone);
+ if (!Stream_SafeSeek(clone, device.DeviceDataLength))
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ goto fail;
+ Stream_Write_UINT32(s, device.DeviceType);
+ Stream_Write_UINT32(s, device.DeviceId);
+ Stream_Write(s, device.PreferredDosName, charCount);
+
+ if (device.DeviceType == RDPDR_DTYP_FILESYSTEM)
+ {
+ if (toVersion == DRIVE_CAPABILITY_VERSION_01)
+ Stream_Write_UINT32(s, 0); /* No unicode name */
+ else
+ {
+ const size_t datalen = charCount * sizeof(WCHAR);
+ if (!Stream_EnsureRemainingCapacity(s, datalen + sizeof(UINT32)))
+ goto fail;
+ Stream_Write_UINT32(s, datalen);
+
+ const SSIZE_T rcw = Stream_Write_UTF16_String_From_UTF8(
+ s, charCount, device.PreferredDosName, charCount - 1, TRUE);
+ if (rcw < 0)
+ goto fail;
+ }
+ }
+ else
+ {
+ Stream_Write_UINT32(s, device.DeviceDataLength);
+ if (!Stream_EnsureRemainingCapacity(s, device.DeviceDataLength))
+ goto fail;
+ Stream_Write(s, device.DeviceData, device.DeviceDataLength);
+ }
+ }
+
+ Stream_SealLength(s);
+ rc = TRUE;
+
+fail:
+ Stream_Free(clone, TRUE);
+ return rc;
+}
+
+static BOOL pf_channel_rdpdr_rewrite_device_list(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s, BOOL toServer)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(ps);
+
+ const size_t pos = Stream_GetPosition(s);
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+ if ((component != RDPDR_CTYP_CORE) || (packetid != PAKID_CORE_DEVICELIST_ANNOUNCE))
+ {
+ Stream_SetPosition(s, pos);
+ return TRUE;
+ }
+
+ const pf_channel_server_context* srv =
+ HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!srv)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "No channel %s in intercep map", RDPDR_SVC_CHANNEL_NAME);
+ return FALSE;
+ }
+
+ UINT32 from = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ UINT32 to = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ if (toServer)
+ {
+ from = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ to = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ }
+ if (!pf_channel_rdpdr_rewrite_device_list_to(s, from, to))
+ return FALSE;
+
+ Stream_SetPosition(s, pos);
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_send_to_server(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ if (ps)
+ {
+ UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME);
+
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (server_channel_id == 0)
+ return TRUE;
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, TRUE))
+ return FALSE;
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_tx);
+ WINPR_ASSERT(ps->context.peer);
+ WINPR_ASSERT(ps->context.peer->SendChannelData);
+ return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id,
+ Stream_Buffer(s), len);
+ }
+ return TRUE;
+}
+
+static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s,
+ UINT16 component, UINT16 packetid)
+{
+ WINPR_ASSERT(rdpdr);
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "[%s | %s]", rdpdr_component_string(component),
+ rdpdr_packetid_string(packetid));
+ if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+}
+
+static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s,
+ UINT16* pPacketid)
+{
+ BOOL rc = FALSE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 deviceID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pPacketid);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, deviceID);
+
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "got: [%s | %s]: [0x%08" PRIx32 "]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_USER_LOGGEDON:
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ *pPacketid = packetid;
+ break;
+ default:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID);
+ goto fail;
+ }
+
+ rc = TRUE;
+
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+
+ if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING)
+ return FALSE;
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return TRUE;
+
+ Queue_Lock(rdpdr->queue);
+ while (Queue_Count(rdpdr->queue) > 0)
+ {
+ wStream* s = Queue_Dequeue(rdpdr->queue);
+ if (!s)
+ continue;
+
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_tx " (queue) ");
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId,
+ Stream_Buffer(s), len))
+ {
+ CLIENT_TX_LOG(rdpdr->log, WLOG_ERROR, "xxxxxx TODO: Failed to send data!");
+ }
+ Stream_Free(s, TRUE);
+ }
+ Queue_Unlock(rdpdr->queue);
+ return TRUE;
+}
+
+static BOOL rdpdr_handle_server_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ pf_channel_client_context* rdpdr = NULL;
+ pServerContext* ps = NULL;
+ wStream* s = NULL;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ UINT16 packetid = 0;
+#endif
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+ WINPR_ASSERT(channel_name);
+ WINPR_ASSERT(xdata);
+
+ ps = pc->pdata->ps;
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ {
+ CLIENT_RX_LOG(WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap",
+ channel_name, channelId);
+ return FALSE;
+ }
+ s = rdpdr->common.buffer;
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]",
+ channel_name, channelId, xsize);
+ return FALSE;
+ }
+ Stream_Write(s, xdata, xsize);
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ if (Stream_Length(s) != totalSize)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (server -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pc))
+ {
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) !=
+ CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ }
+ else
+#endif
+ {
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ pf_channel_send_client_queue(pc, rdpdr);
+ }
+
+ break;
+ case STATE_CLIENT_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_io_requests(rdpdr, s, &packetid))
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+ else
+ {
+ switch (packetid)
+ {
+ case PAKID_CORE_USER_LOGGEDON:
+ return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s,
+ RDPDR_CTYP_CORE, packetid);
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ {
+ wStream* out = rdpdr_client_get_send_buffer(
+ rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0);
+ WINPR_ASSERT(out);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_DEVICE_IOREQUEST, 20))
+ return FALSE;
+
+ if (!pf_channel_smartcard_client_handle(rdpdr->log, pc, s, out,
+ rdpdr_client_send))
+ return FALSE;
+ }
+ break;
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ pf_channel_rdpdr_client_reset(pc);
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case PAKID_CORE_SERVER_CAPABILITY:
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ rdpdr->flags = 0;
+ return pf_channel_rdpdr_client_handle(pc, channelId, channel_name, xdata,
+ xsize, flags, totalSize);
+ case PAKID_CORE_DEVICE_REPLY:
+ break;
+ default:
+ CLIENT_RX_LOG(
+ rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! [%s] aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state),
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ }
+ break;
+#else
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+#endif
+ default:
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common)
+{
+ if (!common)
+ return;
+ free(common->computerName.v);
+ Stream_Free(common->s, TRUE);
+ Stream_Free(common->buffer, TRUE);
+}
+
+static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ Queue_Free(entry->queue);
+ free(entry);
+}
+
+static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common,
+ void (*fkt)(InterceptContextMapEntry*))
+{
+ if (!common)
+ return FALSE;
+ common->base.free = fkt;
+ common->s = Stream_New(NULL, 1024);
+ if (!common->s)
+ return FALSE;
+ common->buffer = Stream_New(NULL, 1024);
+ if (!common->buffer)
+ return FALSE;
+ common->computerNameUnicode = 1;
+ common->computerName.v = NULL;
+ common->versionMajor = RDPDR_VERSION_MAJOR;
+ common->versionMinor = RDPDR_VERSION_MINOR_RDP10X;
+ common->clientID = SCARD_DEVICE_ID;
+
+ const UINT32 versions[] = { 0,
+ GENERAL_CAPABILITY_VERSION_02,
+ PRINT_CAPABILITY_VERSION_01,
+ PORT_CAPABILITY_VERSION_01,
+ DRIVE_CAPABILITY_VERSION_02,
+ SMARTCARD_CAPABILITY_VERSION_01 };
+
+ memcpy(common->capabilityVersions, versions, sizeof(common->capabilityVersions));
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_pass_message(pServerContext* ps, pClientContext* pc,
+ UINT16 channelId, const char* channel_name,
+ wStream* s)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ return TRUE; /* Ignore data for channels not available on proxy -> server connection */
+ WINPR_ASSERT(rdpdr->queue);
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, FALSE))
+ return FALSE;
+ if (!Queue_Enqueue(rdpdr->queue, s))
+ return FALSE;
+ pf_channel_send_client_queue(pc, rdpdr);
+ return TRUE;
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s)
+{
+ size_t pos = 0;
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdpdr->log, s, count, sizeof(UINT32)))
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 deviceID = 0;
+ BYTE* dst = Stream_Pointer(s);
+ Stream_Read_UINT32(s, deviceID);
+ if (deviceID == SCARD_DEVICE_ID)
+ {
+ ArrayList_Remove(rdpdr->blockedDevices, (void*)(size_t)deviceID);
+
+ /* This is the only device, filter it! */
+ if (count == 1)
+ return TRUE;
+
+ /* Remove this device from the list */
+ memmove(dst, Stream_ConstPointer(s), (count - x - 1) * sizeof(UINT32));
+
+ count--;
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 DeviceID = 0;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ Stream_Read_UINT32(s, DeviceID);
+ return ArrayList_Contains(rdpdr->blockedDevices, (void*)(size_t)DeviceID);
+}
+
+static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 DeviceType = 0;
+ UINT32 DeviceId = 0;
+ char PreferredDosName[8];
+ UINT32 DeviceDataLength = 0;
+ BYTE* dst = Stream_Pointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 20))
+ return TRUE;
+ Stream_Read_UINT32(s, DeviceType);
+ Stream_Read_UINT32(s, DeviceId);
+ Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName));
+ Stream_Read_UINT32(s, DeviceDataLength);
+ if (!Stream_SafeSeek(s, DeviceDataLength))
+ return TRUE;
+ if (DeviceType == RDPDR_DTYP_SMARTCARD)
+ {
+ ArrayList_Append(rdpdr->blockedDevices, (void*)(size_t)DeviceId);
+ if (count == 1)
+ return TRUE;
+
+ WLog_Print(rdpdr->log, WLOG_INFO, "Filtering smartcard device 0x%08" PRIx32 "",
+ DeviceId);
+
+ memmove(dst, Stream_ConstPointer(s), Stream_GetRemainingLength(s));
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count - 1);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr,
+ wStream* s)
+{
+ BOOL rc = TRUE;
+ size_t pos = 0;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ if (filter_smartcard_device_list_announce(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ if (filter_smartcard_device_list_remove(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (filter_smartcard_device_io_request(rdpdr, s))
+ goto fail;
+ break;
+
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ case PAKID_CORE_USER_LOGGEDON:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ goto fail;
+ default:
+ break;
+ }
+
+ rc = FALSE;
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+static void* stream_copy(const void* obj)
+{
+ const wStream* src = obj;
+ wStream* dst = Stream_New(NULL, Stream_Capacity(src));
+ if (!dst)
+ return NULL;
+ memcpy(Stream_Buffer(dst), Stream_ConstBuffer(src), Stream_Capacity(dst));
+ Stream_SetLength(dst, Stream_Length(src));
+ Stream_SetPosition(dst, Stream_GetPosition(src));
+ return dst;
+}
+
+static void stream_free(void* obj)
+{
+ wStream* s = obj;
+ Stream_Free(s, TRUE);
+}
+
+static const char* pf_channel_rdpdr_client_context(void* arg)
+{
+ pClientContext* pc = arg;
+ if (!pc)
+ return "pc=null";
+ if (!pc->pdata)
+ return "pc->pdata=null";
+ return pc->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc)
+{
+ wObject* obj = NULL;
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_client_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_client_context, pc);
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free))
+ goto fail;
+
+ rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR;
+ rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ rdpdr->queue = Queue_New(TRUE, 0, 0);
+ if (!rdpdr->queue)
+ goto fail;
+ obj = Queue_Object(rdpdr->queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = stream_copy;
+ obj->fnObjectFree = stream_free;
+ if (!HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_client_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_server_context* entry = (pf_channel_server_context*)base;
+ if (!entry)
+ return;
+
+ WTSVirtualChannelClose(entry->handle);
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ ArrayList_Free(entry->blockedDevices);
+ free(entry);
+}
+
+static const char* pf_channel_rdpdr_server_context(void* arg)
+{
+ pServerContext* ps = arg;
+ if (!ps)
+ return "ps=null";
+ if (!ps->pdata)
+ return "ps->pdata=null";
+ return ps->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_server_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_server_context, ps);
+
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free))
+ goto fail;
+ rdpdr->state = STATE_SERVER_INITIAL;
+
+ rdpdr->blockedDevices = ArrayList_New(FALSE);
+ if (!rdpdr->blockedDevices)
+ goto fail;
+
+ rdpdr->SessionId = WTS_CURRENT_SESSION;
+ if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId,
+ &BytesReturned))
+ {
+ rdpdr->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ }
+
+ rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0);
+ if (rdpdr->handle == 0)
+ goto fail;
+ if (!HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_server_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_server_free(pServerContext* ps)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+ HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static pf_channel_server_context* get_channel(pServerContext* ps, BOOL send)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ {
+ SERVER_RXTX_LOG(send, WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s missing context in interceptContextMap",
+ RDPDR_SVC_CHANNEL_NAME);
+ return NULL;
+ }
+
+ return rdpdr;
+}
+
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ wStream* s = NULL;
+ pClientContext* pc = NULL;
+ pf_channel_server_context* rdpdr = get_channel(ps, FALSE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(ps->pdata);
+ pc = ps->pdata->pc;
+
+ s = rdpdr->common.buffer;
+
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ return FALSE;
+ Stream_Write(s, xdata, xsize);
+
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if (Stream_Length(s) != totalSize)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (client -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST;
+ break;
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE;
+ break;
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_CHANNEL_RUNNING;
+ break;
+ case STATE_SERVER_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_device_list_announce_request(rdpdr, s))
+ {
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+ }
+ else
+ return pf_channel_smartcard_server_handle(ps, s);
+#else
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+#endif
+ break;
+ default:
+ case STATE_SERVER_INITIAL:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN, "Invalid state %s",
+ rdpdr_server_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = get_channel(ps, TRUE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL);
+ if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ return TRUE;
+
+ Queue_Clear(rdpdr->queue);
+ rdpdr->flags = 0;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ return TRUE;
+}
+
+static PfChannelResult pf_rdpdr_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->back_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+static PfChannelResult pf_rdpdr_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->front_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_rdpdr_back_data;
+ channel->onFrontData = pf_rdpdr_front_data;
+
+ if (!pf_channel_rdpdr_server_new(ps))
+ return FALSE;
+ if (!pf_channel_rdpdr_server_announce(ps))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_rdpdr.h b/server/proxy/channels/pf_channel_rdpdr.h
new file mode 100644
index 0000000..dbc2e75
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_RDPDR_H
+#define FREERDP_SERVER_PROXY_RDPDR_H
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel);
+
+void pf_channel_rdpdr_client_free(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+void pf_channel_rdpdr_server_free(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps);
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+#endif /* FREERDP_SERVER_PROXY_RDPDR_H */
diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c
new file mode 100644
index 0000000..1d2bdfe
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.c
@@ -0,0 +1,397 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/string.h>
+
+#include <winpr/smartcard.h>
+#include <winpr/pool.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/smartcard_call.h>
+
+#include "pf_channel_smartcard.h"
+#include "pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("channel.scard")
+
+#define SCARD_SVC_CHANNEL_NAME "SCARD"
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ scard_call_context* callctx;
+ PTP_POOL ThreadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+ wArrayList* workObjects;
+} pf_channel_client_context;
+
+typedef struct
+{
+ SMARTCARD_OPERATION op;
+ wStream* out;
+ pClientContext* pc;
+ wLog* log;
+ pf_scard_send_fkt_t send_fkt;
+} pf_channel_client_queue_element;
+
+static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+ if (!scard)
+ WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME);
+ return scard;
+}
+
+static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
+ UINT32 ioStatus)
+{
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 dID = 0;
+ UINT32 cID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(op);
+ WINPR_ASSERT(out);
+
+ pos = Stream_GetPosition(out);
+ Stream_SetPosition(out, 0);
+ if (!Stream_CheckAndLogRequiredLength(TAG, out, 16))
+ return FALSE;
+
+ Stream_Read_UINT16(out, component);
+ Stream_Read_UINT16(out, packetid);
+
+ Stream_Read_UINT32(out, dID);
+ Stream_Read_UINT32(out, cID);
+
+ WINPR_ASSERT(component == RDPDR_CTYP_CORE);
+ WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION);
+ WINPR_ASSERT(dID == op->deviceID);
+ WINPR_ASSERT(cID == op->completionID);
+
+ Stream_Write_UINT32(out, ioStatus);
+ Stream_SetPosition(out, pos);
+ return TRUE;
+}
+
+struct thread_arg
+{
+ pf_channel_client_context* scard;
+ pf_channel_client_queue_element* e;
+};
+
+static void queue_free(void* obj);
+static void* queue_copy(const void* obj);
+
+static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
+{
+ struct thread_arg* arg = Context;
+ pf_channel_client_context* scard = arg->scard;
+ {
+ UINT32 ioStatus = 0;
+ LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus,
+ &arg->e->op);
+ if (rc == CHANNEL_RC_OK)
+ {
+ if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus))
+ arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out);
+ }
+ }
+ queue_free(arg->e);
+ free(arg);
+ ArrayList_Remove(scard->workObjects, Work);
+}
+
+static BOOL start_irp_thread(pf_channel_client_context* scard,
+ const pf_channel_client_queue_element* e)
+{
+ PTP_WORK work = NULL;
+ struct thread_arg* arg = calloc(1, sizeof(struct thread_arg));
+ if (!arg)
+ return FALSE;
+ arg->scard = scard;
+ arg->e = queue_copy(e);
+ if (!arg->e)
+ goto fail;
+
+ work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv);
+ if (!work)
+ goto fail;
+ ArrayList_Append(scard->workObjects, work);
+ SubmitThreadpoolWork(work);
+
+ return TRUE;
+
+fail:
+ if (arg)
+ queue_free(arg->e);
+ free(arg);
+ return FALSE;
+}
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t send_fkt)
+{
+ BOOL rc = FALSE;
+ LONG status = 0;
+ UINT32 FileId = 0;
+ UINT32 CompletionId = 0;
+ UINT32 ioStatus = 0;
+ pf_channel_client_queue_element e = { 0 };
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(send_fkt);
+ WINPR_ASSERT(s);
+
+ if (!scard)
+ return FALSE;
+
+ e.log = log;
+ e.pc = pc;
+ e.out = out;
+ e.send_fkt = send_fkt;
+
+ /* Skip IRP header */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+ else
+ {
+ UINT32 DeviceId = 0;
+ UINT32 MajorFunction = 0;
+ UINT32 MinorFunction = 0;
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */
+ Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */
+ Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */
+ Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */
+
+ if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
+ {
+ WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME,
+ rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction));
+ return FALSE;
+ }
+ e.op.completionID = CompletionId;
+ e.op.deviceID = DeviceId;
+
+ if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0))
+ return FALSE;
+ }
+
+ status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op);
+ if (status != 0)
+ goto fail;
+
+ switch (e.op.ioControlCode)
+ {
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ case SCARD_IOCTL_LISTREADERSA:
+ case SCARD_IOCTL_LISTREADERSW:
+ case SCARD_IOCTL_LOCATECARDSA:
+ case SCARD_IOCTL_LOCATECARDSW:
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ case SCARD_IOCTL_CONNECTA:
+ case SCARD_IOCTL_CONNECTW:
+ case SCARD_IOCTL_RECONNECT:
+ case SCARD_IOCTL_DISCONNECT:
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ case SCARD_IOCTL_ENDTRANSACTION:
+ case SCARD_IOCTL_STATE:
+ case SCARD_IOCTL_STATUSA:
+ case SCARD_IOCTL_STATUSW:
+ case SCARD_IOCTL_TRANSMIT:
+ case SCARD_IOCTL_CONTROL:
+ case SCARD_IOCTL_GETATTRIB:
+ case SCARD_IOCTL_SETATTRIB:
+ if (!start_irp_thread(scard, &e))
+ goto fail;
+ return TRUE;
+
+ default:
+ status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op);
+ if (status != 0)
+ goto fail;
+ if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus))
+ goto fail;
+ break;
+ }
+
+ rc = send_fkt(log, pc, out) == CHANNEL_RC_OK;
+
+fail:
+ smartcard_operation_free(&e.op, FALSE);
+ return rc;
+}
+
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s)
+{
+ WLog_ERR(TAG, "TODO: unimplemented");
+ return TRUE;
+}
+
+static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset)
+{
+ WINPR_ASSERT(scard);
+ smartcard_call_context_signal_stop(scard->callctx, FALSE);
+
+ while (ArrayList_Count(scard->workObjects) > 0)
+ {
+ PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0);
+ if (!work)
+ continue;
+ WaitForThreadpoolWorkCallbacks(work, TRUE);
+ }
+
+ smartcard_call_context_signal_stop(scard->callctx, reset);
+}
+
+static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ /* Set the stop event.
+ * All threads waiting in blocking operations will abort at the next
+ * available polling slot */
+ channel_stop_and_wait(entry, FALSE);
+ ArrayList_Free(entry->workObjects);
+ CloseThreadpool(entry->ThreadPool);
+ DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv);
+
+ smartcard_call_context_free(entry->callctx);
+ free(entry);
+}
+
+static void queue_free(void* obj)
+{
+ pf_channel_client_queue_element* element = obj;
+ if (!element)
+ return;
+ smartcard_operation_free(&element->op, FALSE);
+ Stream_Free(element->out, TRUE);
+ free(element);
+}
+
+static void* queue_copy(const void* obj)
+{
+ const pf_channel_client_queue_element* other = obj;
+ pf_channel_client_queue_element* copy = NULL;
+ if (!other)
+ return NULL;
+ copy = calloc(1, sizeof(pf_channel_client_queue_element));
+ if (!copy)
+ return NULL;
+
+ *copy = *other;
+ copy->out = Stream_New(NULL, Stream_Capacity(other->out));
+ if (!copy->out)
+ goto fail;
+ Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out));
+ return copy;
+fail:
+ queue_free(copy);
+ return NULL;
+}
+
+static void work_object_free(void* arg)
+{
+ PTP_WORK work = arg;
+ CloseThreadpoolWork(work);
+}
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = calloc(1, sizeof(pf_channel_client_context));
+ if (!scard)
+ return FALSE;
+ scard->base.free = pf_channel_scard_client_context_free;
+ scard->callctx = smartcard_call_context_new(pc->context.settings);
+ if (!scard->callctx)
+ goto fail;
+
+ scard->workObjects = ArrayList_New(TRUE);
+ if (!scard->workObjects)
+ goto fail;
+ obj = ArrayList_Object(scard->workObjects);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = work_object_free;
+
+ scard->ThreadPool = CreateThreadpool(NULL);
+ if (!scard->ThreadPool)
+ goto fail;
+ InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool);
+
+ return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard);
+fail:
+ pf_channel_scard_client_context_free(&scard->base);
+ return FALSE;
+}
+
+void pf_channel_smartcard_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+}
+
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return FALSE;
+ return smartcard_call_is_configured(scard->callctx);
+}
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return TRUE;
+
+ channel_stop_and_wait(scard, TRUE);
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_smartcard.h b/server/proxy/channels/pf_channel_smartcard.h
new file mode 100644
index 0000000..975636d
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_SCARD_H
+#define FREERDP_SERVER_PROXY_SCARD_H
+
+#include <winpr/wlog.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+typedef UINT (*pf_scard_send_fkt_t)(wLog* log, pClientContext*, wStream*);
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc);
+void pf_channel_smartcard_client_free(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc);
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t fkt);
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s);
+
+#endif /* FREERDP_SERVER_PROXY_SCARD_H */
diff --git a/server/proxy/cli/CMakeLists.txt b/server/proxy/cli/CMakeLists.txt
new file mode 100644
index 0000000..1416b4a
--- /dev/null
+++ b/server/proxy/cli/CMakeLists.txt
@@ -0,0 +1,60 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server
+#
+# Copyright 2021 Armin Novak <armin.novak@thincast.com>
+# Copyright 2021 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.
+
+set(PROXY_APP_SRCS freerdp_proxy.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+set(APP_NAME "freerdp-proxy")
+add_executable(${APP_NAME}
+ ${PROXY_APP_SRCS}
+)
+
+set(MANPAGE_NAME ${APP_NAME}.1)
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${APP_NAME}
+ PROPERTIES
+ OUTPUT_NAME "${APP_NAME}${FREERDP_API_VERSION}"
+ )
+ set(MANPAGE_NAME ${APP_NAME}${FREERDP_API_VERSION}.1)
+endif()
+
+target_link_libraries(${APP_NAME} ${MODULE_NAME})
+install(TARGETS ${APP_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${APP_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${APP_NAME} PROPERTY FOLDER "Server/proxy")
+
+configure_file(${APP_NAME}.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME})
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME} 1)
diff --git a/server/proxy/cli/freerdp-proxy.1.in b/server/proxy/cli/freerdp-proxy.1.in
new file mode 100644
index 0000000..c41f97c
--- /dev/null
+++ b/server/proxy/cli/freerdp-proxy.1.in
@@ -0,0 +1,85 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2023-12-14 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A server binary allowing MITM proxying of RDP connections
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB-h\fP]
+[\fB--help\fP]
+[\fB--buildconfig\fP]
+[\fB--dump-config\fP \fB<config file>\fP]
+[\fB-v\fP]
+[\fB--version\fP]
+[\fB<config file>\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+can be used to proxy a RDP connection between a target server and connecting clients.
+Possible usage scenarios are:
+.IP Proxying
+Connect outdated/insecure RDP servers from behind a (more secure) proxy
+.IP Analysis
+Allow detailed protocol analysis of (many) unknown protocol features (channels)
+.IP Inspection
+MITM proxy for session inspection and recording
+
+.SH OPTIONS
+.IP -h,--help
+Display a help text explaining usage.
+.IP --buildconfig
+Print the build configuration of the proxy and exit.
+.IP -v,--version
+Print the version of the proxy and exit.
+.IP --dump-config \fB<config-ini-file>\fP
+Dump a template configuration to \fB<config-ini-file>\fP
+.IP \fB<config-ini-file>\fP
+Start the proxy with settings read from \fB<config-ini-file>\fP
+
+.SH WARNING
+The proxy does not support authentication out of the box but acts simply as intermediary.
+Only \fBRDP\fP and \fBTLS\fP security modes are supported, \fBNLA\fP will fail for connections to the proxy.
+To implement authentication a \fBproxy-module\fP can be implemented that can authenticate against some backend
+and map connecting users and credentials to target server users and credentials.
+
+.SH EXAMPLES
+@MANPAGE_NAME@ /some/config/file
+
+@MANPAGE_NAME@ --dump-config /some/config/file
+
+.SH PREPARATIONS
+
+1. generate certificates for proxy
+
+\fBwinpr-makecert -rdp -path . proxy\fP
+
+2. generate proxy configuration
+
+\fB@MANPAGE_NAME@ --dump-config proxy.ini\fP
+
+3. edit configurartion and:
+
+ * provide (preferrably absolute) paths for \fBCertificateFile\fP and \fBPrivateKeyFile\fP generated previously
+ * remove the \fBCertificateContents\fP and \fBPrivateKeyContents\fP
+ * Adjust the \fB[Server]\fP settings \fBHost\fP and \fBPort\fP to bind a specific port on a network interface
+ * Adjust the \fB[Target]\fP \fBHost\fP and \fBPort\fP settings to the \fBRDP\fP target server
+ * Adjust (or remove if unuse) the \fBPlugins\fP settings
+
+3. start proxy server
+
+ \fB@MANPAGE_NAME@ proxy.ini\fP
+
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/server/proxy/cli/freerdp_proxy.c b/server/proxy/cli/freerdp_proxy.c
new file mode 100644
index 0000000..bc53ae2
--- /dev/null
+++ b/server/proxy/cli/freerdp_proxy.c
@@ -0,0 +1,161 @@
+/**
+ * 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.
+ */
+
+#include <winpr/collections.h>
+
+#include <freerdp/version.h>
+#include <freerdp/freerdp.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#define TAG PROXY_TAG("server")
+
+static proxyServer* server = NULL;
+
+#if defined(_WIN32)
+static const char* strsignal(int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ return "SIGINT";
+ case SIGTERM:
+ return "SIGTERM";
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif
+
+static void cleanup_handler(int signum)
+{
+ printf("\n");
+ WLog_INFO(TAG, "caught signal %s [%d], starting cleanup...", strsignal(signum), signum);
+
+ WLog_INFO(TAG, "stopping all connections.");
+ pf_server_stop(server);
+}
+
+static void pf_server_register_signal_handlers(void)
+{
+ signal(SIGINT, cleanup_handler);
+ signal(SIGTERM, cleanup_handler);
+#ifndef _WIN32
+ signal(SIGQUIT, cleanup_handler);
+ signal(SIGKILL, cleanup_handler);
+#endif
+}
+
+static WINPR_NORETURN(void usage(const char* app))
+{
+ printf("Usage:\n");
+ printf("%s -h Display this help text.\n", app);
+ printf("%s --help Display this help text.\n", app);
+ printf("%s --buildconfig Print the build configuration.\n", app);
+ printf("%s <config ini file> Start the proxy with <config.ini>\n", app);
+ printf("%s --dump-config <config ini file> Create a template <config.ini>\n", app);
+ printf("%s -v Print out binary version.\n", app);
+ printf("%s --version Print out binary version.\n", app);
+ exit(0);
+}
+
+static void version(const char* app)
+{
+ printf("%s version %s", app, freerdp_get_version_string());
+ exit(0);
+}
+
+static WINPR_NORETURN(void buildconfig(const char* app))
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ printf("%s", freerdp_get_build_config());
+ exit(0);
+}
+
+int main(int argc, char* argv[])
+{
+ proxyConfig* config = NULL;
+ char* config_path = "config.ini";
+ int status = -1;
+
+ pf_server_register_signal_handlers();
+
+ WLog_INFO(TAG, "freerdp-proxy version info:");
+ WLog_INFO(TAG, "\tFreeRDP version: %s", FREERDP_VERSION_FULL);
+ WLog_INFO(TAG, "\tGit commit: %s", FREERDP_GIT_REVISION);
+ WLog_DBG(TAG, "\tBuild config: %s", freerdp_get_build_config());
+
+ if (argc < 2)
+ usage(argv[0]);
+
+ {
+ const char* arg = argv[1];
+
+ if (_stricmp(arg, "-h") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--help") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--buildconfig") == 0)
+ buildconfig(argv[0]);
+ else if (_stricmp(arg, "--dump-config") == 0)
+ {
+ if (argc <= 2)
+ usage(argv[0]);
+ pf_server_config_dump(argv[2]);
+ status = 0;
+ goto fail;
+ }
+ else if (_stricmp(arg, "-v") == 0)
+ version(argv[0]);
+ else if (_stricmp(arg, "--version") == 0)
+ version(argv[0]);
+ config_path = argv[1];
+ }
+
+ config = pf_server_config_load_file(config_path);
+ if (!config)
+ goto fail;
+
+ pf_server_config_print(config);
+
+ server = pf_server_new(config);
+ pf_server_config_free(config);
+
+ if (!server)
+ goto fail;
+
+ if (!pf_server_start(server))
+ goto fail;
+
+ if (!pf_server_run(server))
+ goto fail;
+
+ status = 0;
+
+fail:
+ pf_server_free(server);
+
+ return status;
+}
diff --git a/server/proxy/config.ini b/server/proxy/config.ini
new file mode 100644
index 0000000..a8ac44b
--- /dev/null
+++ b/server/proxy/config.ini
@@ -0,0 +1,53 @@
+[Server]
+Host = 0.0.0.0
+Port = 3389
+
+[Target]
+; If this value is set to TRUE, the target server info will be parsed using the
+; load balance info setting at runtime. The format is
+; "Cookie: msts=<target server>", and can be set in an rdp file for windows/mac,
+; and the /load-balance-info: CLI option for xfreerdp. Otherwise, the server
+; will always connect to the same target, using the configured values of `Host`
+; and `Port`.
+FixedTarget = TRUE
+Host = CustomHost
+Port = 3389
+
+[Input]
+Mouse = TRUE
+Keyboard = TRUE
+
+[Security]
+ServerTlsSecurity = TRUE
+ServerRdpSecurity = FALSE
+ClientTlsSecurity = TRUE
+ClientRdpSecurity = FALSE
+ClientNlaSecurity = TRUE
+ClientAllowFallbackToTls = TRUE
+
+[Channels]
+GFX = TRUE
+DisplayControl = TRUE
+Clipboard = TRUE
+AudioOutput = TRUE
+RemoteApp = TRUE
+; a list of comma seperated static channels that will be proxied. This feature is useful,
+; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy.
+; Passthrough = ""
+
+[Clipboard]
+TextOnly = FALSE
+MaxTextLength = 10 # 0 for no limit.
+
+[GFXSettings]
+DecodeGFX = TRUE
+
+[Plugins]
+; An optional, comma separated list of paths to modules that the proxy should load at startup.
+;
+; Modules = "proxy-demo-plugin.so"
+
+; An optional, comma separated list of required plugins (names),
+; that the proxy won't start without having them loaded.
+;
+; Required = "demo"
diff --git a/server/proxy/freerdp-proxy.pc.in b/server/proxy/freerdp-proxy.pc.in
new file mode 100644
index 0000000..4655075
--- /dev/null
+++ b/server/proxy/freerdp-proxy.pc.in
@@ -0,0 +1,16 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-server-proxy@FREERDP_API_VERSION@
+
+Name: FreeRDP proxy
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@ freerdp-server@FREERDP_VERSION_MAJOR@ freerdp-client@FREERDP_VERSION_MAJOR@
+
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/proxy/modules/CMakeLists.txt b/server/proxy/modules/CMakeLists.txt
new file mode 100644
index 0000000..500a3e7
--- /dev/null
+++ b/server/proxy/modules/CMakeLists.txt
@@ -0,0 +1,33 @@
+# 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.
+
+# The third-party directory is meant for third-party components to be built
+# as part of the main FreeRDP build system, making separate maintenance easier.
+# Subdirectories of the third-party directory are ignored by git, but are
+# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used.
+
+# include proxy header files for proxy modules
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy")
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy/modules")
+
+# taken from FreeRDP/third-party/CMakeLists.txt
+file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt")
+
+foreach(dir ${all_valid_subdirs})
+ if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt")
+ string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir})
+ message(STATUS "Adding proxy module ${dir_trimmed}")
+ add_subdirectory(${dir_trimmed})
+ endif()
+endforeach(dir)
diff --git a/server/proxy/modules/README.md b/server/proxy/modules/README.md
new file mode 100644
index 0000000..f1d2206
--- /dev/null
+++ b/server/proxy/modules/README.md
@@ -0,0 +1,66 @@
+# Proxy module API
+
+`freerdp-proxy` has an API for hooking/filtering certain events/messages.
+A module can register callbacks to events, allowing to record the data and control whether to pass/ignore, or right out drop the connection.
+
+During startup, the proxy reads its modules from the configuration:
+
+```ini
+[Plugins]
+Modules = demo,cap
+```
+
+These modules are loaded in a best effort manner. Additionally there is a configuration field for modules that must be loaded,
+so the proxy refuses to start if they are not found:
+
+```ini
+[Plugins]
+Required = demo,cap
+```
+
+Modules must be installed as shared libraris in the `<base install>/lib/freerdp3/proxy` folder and match the pattern
+`proxy-<name>-plugin.<ext>` (e.g. `proxy-demo-plugin.so`) to be found.
+For security reasons loading by full path is not supported and only the installation path is used for lookup.
+
+## Currently supported hook events
+
+### Client
+
+* ClientInitConnect: Called before the client tries to open a connection
+* ClientUninitConnect: Called after the client has disconnected
+* ClientPreConnect: Called in client PreConnect callback
+* ClientPostConnect: Called in client PostConnect callback
+* ClientPostDisconnect: Called in client PostDisconnect callback
+* ClientX509Certificate: Called in client X509 certificate verification callback
+* ClientLoginFailure: Called in client login failure callback
+* ClientEndPaint: Called in client EndPaint callback
+
+### Server
+
+* ServerPostConnect: Called after a client has connected
+* ServerPeerActivate: Called after a client has activated
+* ServerChannelsInit: Called after channels are initialized
+* ServerChannelsFree: Called after channels are cleaned up
+* ServerSessionEnd: Called after the client connection disconnected
+
+## Currently supported filter events
+
+* KeyboardEvent: Keyboard event, e.g. all key press and release events
+* MouseEvent: Mouse event, e.g. mouse movement and button press/release events
+* ClientChannelData: Client static channel data
+* ServerChannelData: Server static channel data
+* DynamicChannelCreate: Dynamic channel create
+* ServerFetchTargetAddr: Fetch target address (e.g. RDP TargetInfo)
+* ServerPeerLogon: A peer is logging on
+
+## Developing a new module
+* Create a new file that includes `freerdp/server/proxy/proxy_modules_api.h`.
+* Implement the `proxy_module_entry_point` function and register the callbacks you are interested in.
+* Each callback receives two parameters:
+ * `connectionInfo* info` holds connection info of the raised event.
+ * `void* param` holds the actual event data. It should be casted by the filter to the suitable struct from `filters_api.h`.
+* Each callback must return a `BOOL`:
+ * `FALSE`: The event will not be proxied.
+ * `TRUE`: The event will be proxied.
+
+A demo can be found in `filter_demo.c`.
diff --git a/server/proxy/modules/bitmap-filter/CMakeLists.txt b/server/proxy/modules/bitmap-filter/CMakeLists.txt
new file mode 100644
index 0000000..d2cc03b
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ bitmap-filter.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr freerdp)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/bitmap-filter/bitmap-filter.cpp b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
new file mode 100644
index 0000000..d1b2e78
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
@@ -0,0 +1,453 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <iostream>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("persist-bitmap-filter")
+
+static constexpr char plugin_name[] = "bitmap-filter";
+static constexpr char plugin_desc[] =
+ "this plugin deactivates and filters persistent bitmap cache.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+static const std::vector<std::string> plugin_dyn_intercept = { RDPGFX_DVC_CHANNEL_NAME };
+
+class DynChannelState
+{
+
+ public:
+ bool skip() const
+ {
+ return _toSkip != 0;
+ }
+
+ bool skip(size_t s)
+ {
+ if (s > _toSkip)
+ _toSkip = 0;
+ else
+ _toSkip -= s;
+ return skip();
+ }
+
+ size_t remaining() const
+ {
+ return _toSkip;
+ }
+
+ size_t total() const
+ {
+ return _totalSkipSize;
+ }
+
+ void setSkipSize(size_t len)
+ {
+ _toSkip = _totalSkipSize = len;
+ }
+
+ bool drop() const
+ {
+ return _drop;
+ }
+
+ void setDrop(bool d)
+ {
+ _drop = d;
+ }
+
+ uint32_t channelId() const
+ {
+ return _channelId;
+ }
+
+ void setChannelId(uint32_t id)
+ {
+ _channelId = id;
+ }
+
+ private:
+ size_t _toSkip = 0;
+ size_t _totalSkipSize = 0;
+ bool _drop = false;
+ uint32_t _channelId = 0;
+};
+
+static BOOL filter_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->pc);
+ WINPR_ASSERT(custom);
+
+ auto settings = pdata->pc->context.settings;
+
+ /* We do not want persistent bitmap cache to be used with proxy */
+ return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE);
+}
+
+static BOOL filter_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_dyn_intercept.begin(), plugin_dyn_intercept.end(),
+ data->name) != plugin_dyn_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static size_t drdynvc_cblen_to_bytes(UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ return 1;
+
+ case 1:
+ return 2;
+
+ default:
+ return 4;
+ }
+}
+
+static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 cbLen)
+{
+ UINT32 val = 0;
+
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Read_UINT8(s, val);
+ break;
+
+ case 1:
+ Stream_Read_UINT16(s, val);
+ break;
+
+ default:
+ Stream_Read_UINT32(s, val);
+ break;
+ }
+
+ return val;
+}
+
+static BOOL drdynvc_try_read_header(wStream* s, size_t& channelId, size_t& length)
+{
+ UINT8 value = 0;
+ Stream_SetPosition(s, 0);
+ if (Stream_GetRemainingLength(s) < 1)
+ return FALSE;
+ Stream_Read_UINT8(s, value);
+
+ const UINT8 cmd = (value & 0xf0) >> 4;
+ const UINT8 Sp = (value & 0x0c) >> 2;
+ const UINT8 cbChId = (value & 0x03);
+
+ switch (cmd)
+ {
+ case DATA_PDU:
+ case DATA_FIRST_PDU:
+ break;
+ default:
+ return FALSE;
+ }
+
+ const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId);
+ if (Stream_GetRemainingLength(s) < channelIdLen)
+ return FALSE;
+
+ channelId = drdynvc_read_variable_uint(s, cbChId);
+ length = Stream_Length(s);
+ if (cmd == DATA_FIRST_PDU)
+ {
+ const size_t dataLen = drdynvc_cblen_to_bytes(Sp);
+ if (Stream_GetRemainingLength(s) < dataLen)
+ return FALSE;
+
+ length = drdynvc_read_variable_uint(s, Sp);
+ }
+
+ return TRUE;
+}
+
+static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static UINT8 drdynvc_value_to_cblen(UINT32 value)
+{
+ if (value <= 0xFF)
+ return 0;
+ if (value <= 0xFFFF)
+ return 1;
+ return 2;
+}
+
+static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Write_UINT8(s, static_cast<UINT8>(value));
+ break;
+
+ case 1:
+ Stream_Write_UINT16(s, static_cast<UINT16>(value));
+ break;
+
+ default:
+ Stream_Write_UINT32(s, value);
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL drdynvc_write_header(wStream* s, UINT32 channelId)
+{
+ const UINT8 cbChId = drdynvc_value_to_cblen(channelId);
+ const UINT8 value = (DATA_PDU << 4) | cbChId;
+ const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1;
+
+ if (!Stream_EnsureRemainingCapacity(s, len))
+ return FALSE;
+
+ Stream_Write_UINT8(s, value);
+ return drdynvc_write_variable_uint(s, value, cbChId);
+}
+
+static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data,
+ size_t startPosition, UINT32 channelId)
+{
+ WINPR_ASSERT(data);
+
+ Stream_SetPosition(data->data, startPosition);
+ if (!drdynvc_write_header(data->data, channelId))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
+ return FALSE;
+ Stream_Write_UINT16(data->data, 0);
+ Stream_SealLength(data->data);
+
+ WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
+ data->rewritten = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (!data->isBackData &&
+ (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
+ {
+ auto state = filter_get_plugin_data(plugin, pdata);
+ if (!state)
+ {
+ WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
+ plugin_name);
+ return FALSE;
+ }
+ const size_t inputDataLength = Stream_Length(data->data);
+ UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
+
+ const auto pos = Stream_GetPosition(data->data);
+ if (!state->skip())
+ {
+ if (data->first)
+ {
+ size_t channelId = 0;
+ size_t length = 0;
+ if (drdynvc_try_read_header(data->data, channelId, length))
+ {
+ if (Stream_GetRemainingLength(data->data) >= 2)
+ {
+ Stream_Read_UINT16(data->data, cmdId);
+ state->setSkipSize(length);
+ state->setDrop(false);
+ }
+ }
+
+ switch (cmdId)
+ {
+ case RDPGFX_CMDID_CACHEIMPORTOFFER:
+ state->setDrop(true);
+ state->setChannelId(channelId);
+ break;
+ default:
+ break;
+ }
+ Stream_SetPosition(data->data, pos);
+ }
+ }
+
+ if (state->skip())
+ {
+ state->skip(inputDataLength);
+ if (state->drop())
+ {
+ WLog_WARN(TAG,
+ "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
+ ", remaining: %" PRIuz "]",
+ pdata->session_id, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
+ inputDataLength, state->remaining());
+ data->result = PF_CHANNEL_RESULT_DROP;
+
+#if 0 // TODO: Sending this does screw up some windows RDP server versions :/
+ if (state->remaining() == 0)
+ {
+ if (!filter_forward_empty_offer(pdata->session_id, data, pos,
+ state->channelId()))
+ return FALSE;
+ }
+#endif
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+
+ auto newstate = new DynChannelState();
+ if (!filter_set_plugin_data(plugin, pdata, newstate))
+ {
+ delete newstate;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+ filter_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.ServerSessionStarted = filter_server_session_started;
+ plugin.ServerSessionEnd = filter_server_session_end;
+
+ plugin.ClientPreConnect = filter_client_pre_connect;
+
+ plugin.StaticChannelToIntercept = filter_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = filter_dyn_channel_intercept;
+
+ plugin.custom = plugins_manager;
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt
new file mode 100644
index 0000000..bdd85a3
--- /dev/null
+++ b/server/proxy/modules/demo/CMakeLists.txt
@@ -0,0 +1,53 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-demo-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ demo.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp
new file mode 100644
index 0000000..75526ef
--- /dev/null
+++ b/server/proxy/modules/demo/demo.cpp
@@ -0,0 +1,422 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server Demo C++ Module
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <iostream>
+
+#include <freerdp/api.h>
+#include <freerdp/scancode.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#define TAG MODULE_TAG("demo")
+
+struct demo_custom_data
+{
+ proxyPluginsManager* mgr;
+ int somesetting;
+};
+
+static constexpr char plugin_name[] = "demo";
+static constexpr char plugin_desc[] = "this is a test plugin";
+
+static BOOL demo_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ std::cout << "C++ demo plugin: unloading..." << std::endl;
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ delete static_cast<struct demo_custom_data*>(plugin->custom);
+
+ return TRUE;
+}
+
+static BOOL demo_client_init_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_uninit_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_disconnect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_x509_certificate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_login_failure(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_end_paint(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_redirect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_activate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_init(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_free(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_filter_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyKeyboardEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B)
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_filter_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyUnicodeEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->code == 'b')
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseExEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_INFO(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "]", channel->channel_name, channel->channel_id);
+ return TRUE;
+}
+
+static BOOL demo_server_fetch_target_addr(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_logon(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto info = static_cast<const proxyServerPeerLogon*>(param);
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(info);
+ WINPR_ASSERT(info->identity);
+
+ WLog_INFO(TAG, "%d", info->automatic);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct demo_custom_data* custom = nullptr;
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+ plugin.PluginUnload = demo_plugin_unload;
+ plugin.ClientInitConnect = demo_client_init_connect;
+ plugin.ClientUninitConnect = demo_client_uninit_connect;
+ plugin.ClientPreConnect = demo_client_pre_connect;
+ plugin.ClientPostConnect = demo_client_post_connect;
+ plugin.ClientPostDisconnect = demo_client_post_disconnect;
+ plugin.ClientX509Certificate = demo_client_x509_certificate;
+ plugin.ClientLoginFailure = demo_client_login_failure;
+ plugin.ClientEndPaint = demo_client_end_paint;
+ plugin.ClientRedirect = demo_client_redirect;
+ plugin.ServerPostConnect = demo_server_post_connect;
+ plugin.ServerPeerActivate = demo_server_peer_activate;
+ plugin.ServerChannelsInit = demo_server_channels_init;
+ plugin.ServerChannelsFree = demo_server_channels_free;
+ plugin.ServerSessionEnd = demo_server_session_end;
+ plugin.KeyboardEvent = demo_filter_keyboard_event;
+ plugin.UnicodeEvent = demo_filter_unicode_event;
+ plugin.MouseEvent = demo_mouse_event;
+ plugin.MouseExEvent = demo_mouse_ex_event;
+ plugin.ClientChannelData = demo_client_channel_data;
+ plugin.ServerChannelData = demo_server_channel_data;
+ plugin.DynamicChannelCreate = demo_dynamic_channel_create;
+ plugin.ServerFetchTargetAddr = demo_server_fetch_target_addr;
+ plugin.ServerPeerLogon = demo_server_peer_logon;
+
+ plugin.StaticChannelToIntercept = demo_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = demo_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = demo_dyn_channel_intercept;
+
+ plugin.userdata = userdata;
+
+ custom = new (struct demo_custom_data);
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->somesetting = 42;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/dyn-channel-dump/CMakeLists.txt b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
new file mode 100644
index 0000000..dc0fc53
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
@@ -0,0 +1,58 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2023 Armin Novak <anovak@thincast.com>
+# Copyright 2023 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-dyn-channel-dump-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ dyn-channel-dump.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ winpr
+ freerdp
+ freerdp-client
+ freerdp-server
+ freerdp-server-proxy
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
new file mode 100644
index 0000000..d80902f
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
@@ -0,0 +1,436 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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 <fstream>
+#include <iostream>
+#include <regex>
+#include <vector>
+#include <sstream>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <atomic>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("dyn-channel-dump")
+
+static constexpr char plugin_name[] = "dyn-channel-dump";
+static constexpr char plugin_desc[] =
+ "This plugin dumps configurable dynamic channel data to a file.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+
+static constexpr char key_path[] = "path";
+static constexpr char key_channels[] = "channels";
+
+class PluginData
+{
+ public:
+ PluginData(proxyPluginsManager* mgr) : _mgr(mgr), _sessionid(0)
+ {
+ }
+
+ proxyPluginsManager* mgr() const
+ {
+ return _mgr;
+ }
+
+ uint64_t session()
+ {
+ return _sessionid++;
+ }
+
+ private:
+ proxyPluginsManager* _mgr;
+ uint64_t _sessionid;
+};
+
+class ChannelData
+{
+ public:
+ ChannelData(const std::string& base, const std::vector<std::string>& list, uint64_t sessionid)
+ : _base(base), _channels_to_dump(list), _session_id(sessionid)
+ {
+ char str[64] = {};
+ _snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id);
+ _base /= str;
+ }
+
+ bool add(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ if (_map.find(name) == _map.end())
+ {
+ WLog_INFO(TAG, "adding '%s' to dump list", name.c_str());
+ _map.insert({ name, 0 });
+ }
+ return true;
+ }
+
+ std::ofstream stream(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ auto& atom = _map[name];
+ auto count = atom++;
+ auto path = filepath(name, back, count);
+ WLog_DBG(TAG, "[%s] writing file '%s'", name.c_str(), path.c_str());
+ return std::ofstream(path);
+ }
+
+ bool dump_enabled(const std::string& name) const
+ {
+ if (name.empty())
+ {
+ WLog_WARN(TAG, "empty dynamic channel name, skipping");
+ return false;
+ }
+
+ auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) !=
+ _channels_to_dump.end();
+ WLog_DBG(TAG, "channel '%s' dumping %s", name.c_str(), enabled ? "enabled" : "disabled");
+ return enabled;
+ }
+
+ bool ensure_path_exists()
+ {
+ if (!fs::exists(_base))
+ {
+ if (!fs::create_directories(_base))
+ {
+ WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str());
+ return false;
+ }
+ }
+ else if (!fs::is_directory(_base))
+ {
+ WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str());
+ return false;
+ }
+ return true;
+ }
+
+ bool create()
+ {
+ if (!ensure_path_exists())
+ return false;
+
+ if (_channels_to_dump.empty())
+ {
+ WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return false;
+ }
+ return true;
+ }
+
+ uint64_t session() const
+ {
+ return _session_id;
+ }
+
+ private:
+ fs::path filepath(const std::string& channel, bool back, uint64_t count) const
+ {
+ auto name = idstr(channel, back);
+ char cstr[32] = {};
+ _snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count);
+ auto path = _base / cstr;
+ path += name;
+ path += ".dump";
+ return path;
+ }
+
+ std::string idstr(const std::string& name, bool back) const
+ {
+ std::stringstream ss;
+ ss << name << ".";
+ if (back)
+ ss << "back";
+ else
+ ss << "front";
+ return ss.str();
+ }
+
+ private:
+ fs::path _base;
+ std::vector<std::string> _channels_to_dump;
+
+ std::mutex _mux;
+ std::map<std::string, uint64_t> _map;
+ uint64_t _session_id;
+};
+
+static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ auto plugindata = static_cast<PluginData*>(plugin->custom);
+ WINPR_ASSERT(plugindata);
+ return plugindata;
+}
+
+static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ delete cdata;
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name)
+{
+ auto config = dump_get_plugin_data(plugin, pdata);
+ if (!config)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return false;
+ }
+ return config->dump_enabled(name);
+}
+
+static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->intercept = dump_channel_enabled(plugin, pdata, data->name);
+ if (data->intercept)
+ {
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ return FALSE;
+
+ if (!cdata->add(data->name, false))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ if (!cdata->add(data->name, true))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ WLog_INFO(TAG, "Dumping channel '%s'", data->name);
+ }
+ return TRUE;
+}
+
+static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ {
+ WLog_INFO(TAG, "intercepting channel '%s'", data->name);
+ data->intercept = TRUE;
+ }
+
+ return TRUE;
+}
+
+static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (dump_channel_enabled(plugin, pdata, data->name))
+ {
+ WLog_DBG(TAG, "intercepting channel '%s'", data->name);
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return FALSE;
+ }
+
+ if (!cdata->ensure_path_exists())
+ return FALSE;
+
+ auto stream = cdata->stream(data->name, data->isBackData);
+ auto buffer = reinterpret_cast<const char*>(Stream_ConstBuffer(data->data));
+ if (!stream.is_open() || !stream.good())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.write(buffer, Stream_Length(data->data));
+ if (stream.fail())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.flush();
+ }
+
+ return TRUE;
+}
+
+static std::vector<std::string> split(const std::string& input, const std::string& regex)
+{
+ // passing -1 as the submatch index parameter performs splitting
+ std::regex re(regex);
+ std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
+ std::sregex_token_iterator last;
+ return { first, last };
+}
+
+static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto custom = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(custom);
+
+ auto config = pdata->config;
+ WINPR_ASSERT(config);
+
+ auto cpath = pf_config_get(config, plugin_name, key_path);
+ if (!cpath)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_path);
+ return FALSE;
+ }
+ auto cchannels = pf_config_get(config, plugin_name, key_channels);
+ if (!cchannels)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return FALSE;
+ }
+
+ std::string path(cpath);
+ std::string channels(cchannels);
+ std::vector<std::string> list = split(channels, "[;,]");
+ auto cfg = new ChannelData(path, list, custom->session());
+ if (!cfg || !cfg->create())
+ {
+ delete cfg;
+ return FALSE;
+ }
+
+ dump_set_plugin_data(plugin, pdata, cfg);
+
+ WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
+ return TRUE;
+}
+
+static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto cfg = dump_get_plugin_data(plugin, pdata);
+ if (cfg)
+ WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session());
+ dump_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+static BOOL dump_unload(proxyPlugin* plugin)
+{
+ if (!plugin)
+ return TRUE;
+ delete static_cast<PluginData*>(plugin->custom);
+ return TRUE;
+}
+
+extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
+ void* userdata);
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.PluginUnload = dump_unload;
+ plugin.ServerSessionStarted = dump_session_started;
+ plugin.ServerSessionEnd = dump_session_end;
+
+ plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = dump_dyn_channel_intercept;
+
+ plugin.custom = new PluginData(plugins_manager);
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/pf_channel.c b/server/proxy/pf_channel.c
new file mode 100644
index 0000000..a8f66c4
--- /dev/null
+++ b/server/proxy/pf_channel.c
@@ -0,0 +1,355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2022 David Fort <contact@hardening-consulting.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.
+ */
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "proxy_modules.h"
+#include "pf_channel.h"
+
+#define TAG PROXY_TAG("channel")
+
+/** @brief a tracker for channel packets */
+struct _ChannelStateTracker
+{
+ pServerStaticChannelContext* channel;
+ ChannelTrackerMode mode;
+ wStream* currentPacket;
+ size_t currentPacketReceived;
+ size_t currentPacketSize;
+ size_t currentPacketFragments;
+
+ ChannelTrackerPeekFn peekFn;
+ void* trackerData;
+ proxyData* pdata;
+};
+
+static BOOL channelTracker_resetCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+
+ BOOL create = TRUE;
+ if (tracker->currentPacket)
+ {
+ const size_t cap = Stream_Capacity(tracker->currentPacket);
+ if (cap < 1 * 1000 * 1000)
+ create = FALSE;
+ else
+ Stream_Free(tracker->currentPacket, TRUE);
+ }
+
+ if (create)
+ tracker->currentPacket = Stream_New(NULL, 10 * 1024);
+ if (!tracker->currentPacket)
+ return FALSE;
+ Stream_SetPosition(tracker->currentPacket, 0);
+ return TRUE;
+}
+
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data)
+{
+ ChannelStateTracker* ret = calloc(1, sizeof(ChannelStateTracker));
+ if (!ret)
+ return ret;
+
+ WINPR_ASSERT(fn);
+
+ ret->channel = channel;
+ ret->peekFn = fn;
+
+ if (!channelTracker_setCustomData(ret, data))
+ goto fail;
+
+ if (!channelTracker_resetCurrentPacket(ret))
+ goto fail;
+
+ return ret;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ channelTracker_free(ret);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize)
+{
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST);
+ BOOL lastPacket = (flags & CHANNEL_FLAG_LAST);
+
+ WINPR_ASSERT(tracker);
+
+ WLog_VRB(TAG, "channelTracker_update(%s): sz=%" PRIuz " first=%d last=%d",
+ tracker->channel->channel_name, xsize, firstPacket, lastPacket);
+ if (flags & CHANNEL_FLAG_FIRST)
+ {
+ if (!channelTracker_resetCurrentPacket(tracker))
+ return FALSE;
+ channelTracker_setCurrentPacketSize(tracker, totalSize);
+ tracker->currentPacketReceived = 0;
+ tracker->currentPacketFragments = 0;
+ }
+
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ if (tracker->currentPacketReceived + xsize > currentPacketSize)
+ WLog_INFO(TAG, "cumulated size is bigger (%" PRIuz ") than total size (%" PRIuz ")",
+ tracker->currentPacketReceived + xsize, currentPacketSize);
+ }
+
+ tracker->currentPacketReceived += xsize;
+ tracker->currentPacketFragments++;
+
+ switch (channelTracker_getMode(tracker))
+ {
+ case CHANNEL_TRACKER_PEEK:
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ if (!Stream_EnsureRemainingCapacity(currentPacket, xsize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Write(currentPacket, xdata, xsize);
+
+ WINPR_ASSERT(tracker->peekFn);
+ result = tracker->peekFn(tracker, firstPacket, lastPacket);
+ }
+ break;
+ case CHANNEL_TRACKER_PASS:
+ result = PF_CHANNEL_RESULT_PASS;
+ break;
+ case CHANNEL_TRACKER_DROP:
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ }
+
+ if (lastPacket)
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PEEK);
+
+ if (tracker->currentPacketReceived != currentPacketSize)
+ WLog_INFO(TAG, "cumulated size(%" PRIuz ") does not match total size (%" PRIuz ")",
+ tracker->currentPacketReceived, currentPacketSize);
+ }
+
+ return result;
+}
+
+void channelTracker_free(ChannelStateTracker* t)
+{
+ if (!t)
+ return;
+
+ Stream_Free(t->currentPacket, TRUE);
+ free(t);
+}
+
+/**
+ * Flushes the current accumulated tracker content, if it's the first packet, then
+ * when can just return that the packet shall be passed, otherwise to have to refragment
+ * the accumulated current packet.
+ */
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toBack)
+{
+ proxyData* pdata = NULL;
+ pServerContext* ps = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT32 flags = CHANNEL_FLAG_FIRST;
+ BOOL r = 0;
+ const char* direction = toBack ? "F->B" : "B->F";
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(t);
+ wStream* currentPacket = channelTracker_getCurrentPacket(t);
+
+ WINPR_ASSERT(t);
+
+ WLog_VRB(TAG, "channelTracker_flushCurrent(%s): %s sz=%" PRIuz " first=%d last=%d",
+ t->channel->channel_name, direction, Stream_GetPosition(currentPacket), first, last);
+
+ if (first)
+ return PF_CHANNEL_RESULT_PASS;
+
+ pdata = t->pdata;
+ channel = t->channel;
+ if (last)
+ flags |= CHANNEL_FLAG_LAST;
+
+ if (toBack)
+ {
+ proxyChannelDataEventInfo ev;
+
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = Stream_Buffer(currentPacket);
+ ev.data_len = Stream_GetPosition(currentPacket);
+ ev.flags = flags;
+ ev.total_size = currentPacketSize;
+
+ if (!pdata->pc->sendChannelData)
+ return PF_CHANNEL_RESULT_ERROR;
+
+ return pdata->pc->sendChannelData(pdata->pc, &ev) ? PF_CHANNEL_RESULT_DROP
+ : PF_CHANNEL_RESULT_ERROR;
+ }
+
+ ps = pdata->ps;
+ r = ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ currentPacketSize, flags, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+
+ return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
+}
+
+static PfChannelResult pf_channel_generic_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->back_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+static PfChannelResult pf_channel_generic_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_channel_generic_back_data;
+ channel->onFrontData = pf_channel_generic_front_data;
+ return TRUE;
+}
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode)
+{
+ WINPR_ASSERT(tracker);
+ tracker->mode = mode;
+ return TRUE;
+}
+
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->mode;
+}
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata)
+{
+ WINPR_ASSERT(tracker);
+ tracker->pdata = pdata;
+ return TRUE;
+}
+
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->pdata;
+}
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacket;
+}
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data)
+{
+ WINPR_ASSERT(tracker);
+ tracker->trackerData = data;
+ return TRUE;
+}
+
+void* channelTracker_getCustomData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->trackerData;
+}
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacketSize;
+}
+
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size)
+{
+ WINPR_ASSERT(tracker);
+ tracker->currentPacketSize = size;
+ return TRUE;
+}
diff --git a/server/proxy/pf_channel.h b/server/proxy/pf_channel.h
new file mode 100644
index 0000000..9e0da7e
--- /dev/null
+++ b/server/proxy/pf_channel.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * Copyright 2022 David Fort <contact@hardening-consulting.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 SERVER_PROXY_PF_CHANNEL_H_
+#define SERVER_PROXY_PF_CHANNEL_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+/** @brief operating mode of a channel tracker */
+typedef enum
+{
+ CHANNEL_TRACKER_PEEK, /*!< inquiring content, accumulating packet fragments */
+ CHANNEL_TRACKER_PASS, /*!< pass all the fragments of the current packet */
+ CHANNEL_TRACKER_DROP /*!< drop all the fragments of the current packet */
+} ChannelTrackerMode;
+
+typedef struct _ChannelStateTracker ChannelStateTracker;
+typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first,
+ BOOL lastPacket);
+
+void channelTracker_free(ChannelStateTracker* t);
+
+WINPR_ATTR_MALLOC(channelTracker_free, 1)
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data);
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode);
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata);
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data);
+void* channelTracker_getCustomData(ChannelStateTracker* tracker);
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker);
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker);
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size);
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize);
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toFront);
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_PF_CHANNEL_H_ */
diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c
new file mode 100644
index 0000000..2f34f97
--- /dev/null
+++ b/server/proxy/pf_client.c
@@ -0,0 +1,1102 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/channels.h>
+
+#include "pf_client.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_rdpdr.h"
+#include "channels/pf_channel_smartcard.h"
+
+#define TAG PROXY_TAG("client")
+
+static void channel_data_free(void* obj);
+static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ if (!pf_context_copy_settings(ps->settings, pc->settings))
+ return FALSE;
+
+ /*
+ * DesktopResize causes internal function rdp_server_reactivate to be called,
+ * which causes the reactivation.
+ */
+ WINPR_ASSERT(ps->update);
+ if (!ps->update->DesktopResize(ps))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void pf_client_on_error_info(void* ctx, const ErrorInfoEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ if (e->code == ERRINFO_NONE)
+ return;
+
+ PROXY_LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code,
+ freerdp_get_error_info_string(e->code));
+
+ /* forward error back to client */
+ freerdp_set_error_info(ps->context.rdp, e->code);
+ freerdp_send_error_info(ps->context.rdp);
+}
+
+static void pf_client_on_activated(void* ctx, const ActivatedEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ peer = ps->context.peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ PROXY_LOG_INFO(TAG, pc, "client activated, registering server input callbacks");
+
+ /* Register server input/update callbacks only after proxy client is fully activated */
+ pf_server_register_input_callbacks(peer->context->input);
+ pf_server_register_update_callbacks(peer->context->update);
+}
+
+static BOOL pf_client_load_rdpsnd(pClientContext* pc)
+{
+ rdpContext* context = (rdpContext*)pc;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ /*
+ * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd
+ * backend. Otherwise, use sys:fake.
+ */
+ if (!freerdp_static_channel_collection_find(context->settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[2] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(context->settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc)
+{
+ pServerContext* ps = NULL;
+ rdpSettings* settings = NULL;
+ DWORD lb_info_len = 0;
+ const char* lb_info = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len);
+ if (!lb_info)
+ return TRUE;
+
+ return freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, lb_info,
+ lb_info_len);
+}
+
+static BOOL str_is_empty(const char* str)
+{
+ if (!str)
+ return TRUE;
+ if (strlen(str) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL pf_client_use_proxy_smartcard_auth(const rdpSettings* settings)
+{
+ BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon);
+ const char* key = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
+ const char* cert = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
+
+ if (!enable)
+ return FALSE;
+
+ if (str_is_empty(key))
+ return FALSE;
+
+ if (str_is_empty(cert))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings,
+ const char* name, void* data)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ PVIRTUALCHANNELENTRYEX entryEx = NULL;
+ entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry(
+ name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
+
+ if (!entryEx)
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (entryEx)
+ {
+ if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0)
+ {
+ WLog_INFO(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_INFO(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL pf_client_pre_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /*
+ * as the client's settings are copied from the server's, GlyphSupportLevel might not be
+ * GLYPH_SUPPORT_NONE. the proxy currently do not support GDI & GLYPH_SUPPORT_CACHE, so
+ * GlyphCacheSupport must be explicitly set to GLYPH_SUPPORT_NONE.
+ *
+ * Also, OrderSupport need to be zeroed, because it is currently not supported.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ return FALSE;
+
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ /* Multimon */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return FALSE;
+
+ /* Sound */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, config->AudioInput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, config->AudioOutput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection,
+ config->DeviceRedirection) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl,
+ config->DisplayControl) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, config->Multitouch))
+ return FALSE;
+
+ if (config->RemoteApp)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RAIL_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (config->DeviceRedirection)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RDPDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+ }
+
+ /* Display control */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, config->DisplayControl))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate,
+ config->DisplayControl))
+ return FALSE;
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ }
+
+ if (config->Clipboard)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, CLIPRDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, config->Clipboard))
+ return FALSE;
+ }
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, TRUE))
+ return FALSE;
+
+ PubSub_SubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ PubSub_SubscribeActivated(instance->context->pubSub, pf_client_on_activated);
+ if (!pf_client_use_peer_load_balance_info(pc))
+ return FALSE;
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc);
+}
+
+/** @brief arguments for updateBackIdFn */
+typedef struct
+{
+ pServerContext* ps;
+ const char* name;
+ UINT32 backId;
+} UpdateBackIdArgs;
+
+static BOOL updateBackIdFn(const void* key, void* value, void* arg)
+{
+ pServerStaticChannelContext* current = (pServerStaticChannelContext*)value;
+ UpdateBackIdArgs* updateArgs = (UpdateBackIdArgs*)arg;
+
+ if (strcmp(updateArgs->name, current->channel_name) != 0)
+ return TRUE;
+
+ current->back_channel_id = updateArgs->backId;
+ if (!HashTable_Insert(updateArgs->ps->channelsByBackId, &current->back_channel_id, current))
+ {
+ WLog_ERR(TAG, "error inserting channel in channelsByBackId table");
+ }
+ return FALSE;
+}
+
+static BOOL pf_client_update_back_id(pServerContext* ps, const char* name, UINT32 backId)
+{
+ UpdateBackIdArgs res = { ps, name, backId };
+
+ return HashTable_Foreach(ps->channelsByFrontId, updateBackIdFn, &res) == FALSE;
+}
+
+static BOOL pf_client_load_channels(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+ /**
+ * Load all required plugins / channels / libraries specified by current
+ * settings.
+ */
+ PROXY_LOG_INFO(TAG, pc, "Loading addins");
+
+ if (!pf_client_load_rdpsnd(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
+ return FALSE;
+ }
+
+ if (!pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_client_load_addins(instance->context->channels, settings))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load addins");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_rdpdr_client_new(pc))
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_new(pc))
+ return FALSE;
+#endif
+ /* Copy the current channel settings from the peer connection to the client. */
+ if (!freerdp_channels_from_mcs(settings, &ps->context))
+ return FALSE;
+
+ /* Filter out channels we do not want */
+ {
+ CHANNEL_DEF* channels = (CHANNEL_DEF*)freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_ChannelDefArray, 0);
+ size_t size = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount);
+ UINT32 id = MCS_GLOBAL_CHANNEL_ID + 1;
+
+ WINPR_ASSERT(channels || (size == 0));
+
+ size_t x = 0;
+ for (; x < size;)
+ {
+ CHANNEL_DEF* cur = &channels[x];
+ proxyChannelDataEventInfo dev = { 0 };
+
+ dev.channel_name = cur->name;
+ dev.flags = cur->options;
+
+ /* Filter out channels blocked by config */
+ if (!pf_modules_run_filter(pc->pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, pc->pdata,
+ &dev))
+ {
+ const size_t s = size - MIN(size, x + 1);
+ memmove(cur, &cur[1], sizeof(CHANNEL_DEF) * s);
+ size--;
+ }
+ else
+ {
+ if (!pf_client_update_back_id(ps, cur->name, id++))
+ {
+ WLog_ERR(TAG, "unable to update backid for channel %s", cur->name);
+ return FALSE;
+ }
+ x++;
+ }
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, x))
+ return FALSE;
+ }
+ }
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOAD_CHANNELS, pc->pdata, pc);
+}
+
+static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(xdata || (xsize == 0));
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ channel = HashTable_GetItemValue(ps->channelsByBackId, &channelId64);
+ if (!channel)
+ return TRUE;
+
+ WINPR_ASSERT(channel->onBackData);
+ switch (channel->onBackData(pdata, channel, xdata, xsize, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (channel->front_channel_id == 0)
+ return TRUE;
+
+ return ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ totalSize, flags, xdata, xsize);
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ return freerdp_heartbeat_send_heartbeat_pdu(ps->context.peer, period, count1, count2);
+}
+
+static BOOL pf_client_send_channel_data(pClientContext* pc, const proxyChannelDataEventInfo* ev)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(ev);
+
+ return Queue_Enqueue(pc->cached_server_channel_data, ev);
+}
+
+static BOOL sendQueuedChannelData(pClientContext* pc)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(pc);
+
+ if (pc->connected)
+ {
+ proxyChannelDataEventInfo* ev = NULL;
+
+ Queue_Lock(pc->cached_server_channel_data);
+ while (rc && (ev = Queue_Dequeue(pc->cached_server_channel_data)))
+ {
+ UINT16 channelId = 0;
+ WINPR_ASSERT(pc->context.instance);
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, ev->channel_name);
+ /* Ignore unmappable channels */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ rc = TRUE;
+ else
+ {
+ WINPR_ASSERT(pc->context.instance->SendChannelPacket);
+ rc = pc->context.instance->SendChannelPacket(pc->context.instance, channelId,
+ ev->total_size, ev->flags, ev->data,
+ ev->data_len);
+ }
+ channel_data_free(ev);
+ }
+
+ Queue_Unlock(pc->cached_server_channel_data);
+ }
+
+ return rc;
+}
+
+/**
+ * Called after a RDP connection was successfully established.
+ * Settings might have changed during negotiation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and painting callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL pf_client_post_connect(freerdp* instance)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* ps = NULL;
+ pClientContext* pc = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(instance);
+ context = instance->context;
+ WINPR_ASSERT(context);
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+ update = context->update;
+ WINPR_ASSERT(update);
+ pc = (pClientContext*)context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = (rdpContext*)pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_CONNECT, pc->pdata, pc))
+ return FALSE;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ WINPR_ASSERT(freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi));
+
+ pf_client_register_update_callbacks(update);
+
+ /* virtual channels receive data hook */
+ pc->client_receive_channel_data_original = instance->ReceiveChannelData;
+ instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
+
+ instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat;
+
+ pc->connected = TRUE;
+
+ /* Send cached channel data */
+ sendQueuedChannelData(pc);
+
+ /*
+ * after the connection fully established and settings were negotiated with target server,
+ * send a reactivation sequence to the client with the negotiated settings. This way,
+ * settings are synchorinized between proxy's peer and and remote target.
+ */
+ return proxy_server_reactivate(ps, context);
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void pf_client_post_disconnect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_free(pc);
+#endif
+
+ pf_channel_rdpdr_client_free(pc);
+
+ pc->connected = FALSE;
+ pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_DISCONNECT, pc->pdata, pc);
+
+ PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ gdi_free(instance);
+
+ /* Only close the connection if NLA fallback process is done */
+ if (!pc->allow_next_conn_failure)
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_client_redirect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ if (!instance->context)
+ return FALSE;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_reset(pc);
+#endif
+ pf_channel_rdpdr_client_reset(pc);
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_REDIRECT, pc->pdata, pc);
+}
+
+/*
+ * pf_client_should_retry_without_nla:
+ *
+ * returns TRUE if in case of connection failure, the client should try again without NLA.
+ * Otherwise, returns FALSE.
+ */
+static BOOL pf_client_should_retry_without_nla(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!config->ClientAllowFallbackToTls ||
+ !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ return config->ClientTlsSecurity || config->ClientRdpSecurity;
+}
+
+static void pf_client_set_security_settings(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ClientRdpSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ClientTlsSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ClientNlaSecurity);
+
+ if (pf_client_use_proxy_smartcard_auth(settings))
+ {
+ /* Smartcard authentication requires smartcard redirection to be enabled */
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+
+ /* Reset username/domain, we will get that info later from the sc cert */
+ freerdp_settings_set_string(settings, FreeRDP_Username, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_Domain, NULL);
+ }
+}
+
+static BOOL pf_client_connect_without_nla(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(pc);
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ if (!freerdp_context_reset(instance))
+ return FALSE;
+
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ /* If already disabled abort early. */
+ if (!freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ /* disable NLA */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+
+ /* do not allow next connection failure */
+ pc->allow_next_conn_failure = FALSE;
+ return freerdp_connect(instance);
+}
+
+static BOOL pf_client_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ rdpSettings* settings = NULL;
+ BOOL rc = FALSE;
+ BOOL retry = FALSE;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s",
+ freerdp_settings_get_string(settings, FreeRDP_Username),
+ freerdp_settings_get_string(settings, FreeRDP_Domain));
+
+ pf_client_set_security_settings(pc);
+ if (pf_client_should_retry_without_nla(pc))
+ retry = pc->allow_next_conn_failure = TRUE;
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using security settings: rdp=%d, tls=%d, nla=%d",
+ freerdp_settings_get_bool(settings, FreeRDP_RdpSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_TlsSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity));
+
+ if (!freerdp_connect(instance))
+ {
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata, pc))
+ goto out;
+
+ if (!retry)
+ goto out;
+
+ PROXY_LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA");
+ if (!pf_client_connect_without_nla(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!");
+ goto out;
+ }
+ }
+
+ rc = TRUE;
+out:
+ pc->allow_next_conn_failure = FALSE;
+ return rc;
+}
+
+/**
+ * RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends.
+ */
+static DWORD WINAPI pf_client_thread_proc(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ proxyData* pdata = NULL;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ WINPR_ASSERT(pc);
+
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ /*
+ * during redirection, freerdp's abort event might be overriden (reset) by the library, after
+ * the server set it in order to shutdown the connection. it means that the server might signal
+ * the client to abort, but the library code will override the signal and the client will
+ * continue its work instead of exiting. That's why the client must wait on `pdata->abort_event`
+ * too, which will never be modified by the library.
+ */
+ handles[nCount++] = pdata->abort_event;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_INIT_CONNECT, pdata, pc))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+
+ if (!pf_client_connect(instance))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+ handles[nCount++] = Queue_Event(pc->cached_server_channel_data);
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ UINT32 tmp = freerdp_get_event_handles(instance->context, &handles[nCount],
+ ARRAYSIZE(handles) - nCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount + tmp, handles, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status);
+ break;
+ }
+
+ /* abort_event triggered */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ break;
+
+ if (proxy_data_shall_disconnect(pdata))
+ break;
+
+ if (!freerdp_check_event_handles(instance->context))
+ {
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ sendQueuedChannelData(pc);
+ }
+
+ freerdp_disconnect(instance);
+
+end:
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_UNINIT_CONNECT, pdata, pc);
+
+ return 0;
+}
+
+static int pf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static void pf_client_context_free(freerdp* instance, rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ WINPR_UNUSED(instance);
+
+ if (!pc)
+ return;
+
+ pc->sendChannelData = NULL;
+ Queue_Free(pc->cached_server_channel_data);
+ Stream_Free(pc->remote_pem, TRUE);
+ free(pc->remote_hostname);
+ free(pc->computerName.v);
+ HashTable_Free(pc->interceptContextMap);
+}
+
+static int pf_client_verify_X509_certificate(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags)
+{
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(length > 0);
+ WINPR_ASSERT(hostname);
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+
+ if (!Stream_EnsureCapacity(pc->remote_pem, length))
+ return 0;
+ Stream_SetPosition(pc->remote_pem, 0);
+
+ free(pc->remote_hostname);
+ pc->remote_hostname = NULL;
+
+ if (length > 0)
+ Stream_Write(pc->remote_pem, data, length);
+
+ if (hostname)
+ pc->remote_hostname = _strdup(hostname);
+ pc->remote_port = port;
+ pc->remote_flags = flags;
+
+ Stream_SealLength(pc->remote_pem);
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_VERIFY_X509, pc->pdata, pc))
+ return 0;
+ return 1;
+}
+
+void channel_data_free(void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ proxyChannelDataEventInfo* dst = obj;
+ if (dst)
+ {
+ cnv.cpv = dst->data;
+ free(cnv.pv);
+
+ cnv.cpv = dst->channel_name;
+ free(cnv.pv);
+ free(dst);
+ }
+}
+
+static void* channel_data_copy(const void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ const proxyChannelDataEventInfo* src = obj;
+ proxyChannelDataEventInfo* dst = NULL;
+
+ WINPR_ASSERT(src);
+
+ dst = calloc(1, sizeof(proxyChannelDataEventInfo));
+ if (!dst)
+ goto fail;
+
+ *dst = *src;
+ if (src->channel_name)
+ {
+ dst->channel_name = _strdup(src->channel_name);
+ if (!dst->channel_name)
+ goto fail;
+ }
+ dst->data = malloc(src->data_len);
+ if (!dst->data)
+ goto fail;
+
+ cnv.cpv = dst->data;
+ memcpy(cnv.pv, src->data, src->data_len);
+ return dst;
+
+fail:
+ channel_data_free(dst);
+ return NULL;
+}
+
+static BOOL pf_client_client_new(freerdp* instance, rdpContext* context)
+{
+ wObject* obj = NULL;
+ pClientContext* pc = (pClientContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->LoadChannels = pf_client_load_channels;
+ instance->PreConnect = pf_client_pre_connect;
+ instance->PostConnect = pf_client_post_connect;
+ instance->PostDisconnect = pf_client_post_disconnect;
+ instance->Redirect = pf_client_redirect;
+ instance->LogonErrorInfo = pf_logon_error_info;
+ instance->VerifyX509Certificate = pf_client_verify_X509_certificate;
+
+ pc->remote_pem = Stream_New(NULL, 4096);
+ if (!pc->remote_pem)
+ return FALSE;
+
+ pc->sendChannelData = pf_client_send_channel_data;
+ pc->cached_server_channel_data = Queue_New(TRUE, -1, -1);
+ if (!pc->cached_server_channel_data)
+ return FALSE;
+ obj = Queue_Object(pc->cached_server_channel_data);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = channel_data_copy;
+ obj->fnObjectFree = channel_data_free;
+
+ pc->interceptContextMap = HashTable_New(FALSE);
+ if (!pc->interceptContextMap)
+ return FALSE;
+
+ if (!HashTable_SetupForStringData(pc->interceptContextMap, FALSE))
+ return FALSE;
+
+ obj = HashTable_ValueObject(pc->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ return TRUE;
+}
+
+static int pf_client_client_stop(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+ PROXY_LOG_DBG(TAG, pc, "aborting client connection");
+ proxy_data_abort_connect(pdata);
+ freerdp_abort_connect_context(context);
+
+ return 0;
+}
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->ContextSize = sizeof(pClientContext);
+ /* Client init and finish */
+ pEntryPoints->ClientNew = pf_client_client_new;
+ pEntryPoints->ClientFree = pf_client_context_free;
+ pEntryPoints->ClientStop = pf_client_client_stop;
+ return 0;
+}
+
+/**
+ * Starts running a client connection towards target server.
+ */
+DWORD WINAPI pf_client_start(LPVOID arg)
+{
+ DWORD rc = 1;
+ pClientContext* pc = (pClientContext*)arg;
+
+ WINPR_ASSERT(pc);
+ if (freerdp_client_start(&pc->context) == 0)
+ rc = pf_client_thread_proc(pc);
+ freerdp_client_stop(&pc->context);
+ return rc;
+}
diff --git a/server/proxy/pf_client.h b/server/proxy/pf_client.h
new file mode 100644
index 0000000..cda87a8
--- /dev/null
+++ b/server/proxy/pf_client.h
@@ -0,0 +1,31 @@
+/**
+ * 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_PFCLIENT_H
+#define FREERDP_SERVER_PROXY_PFCLIENT_H
+
+#include <freerdp/freerdp.h>
+#include <winpr/wtypes.h>
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+DWORD WINAPI pf_client_start(LPVOID arg);
+
+#endif /* FREERDP_SERVER_PROXY_PFCLIENT_H */
diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c
new file mode 100644
index 0000000..bcce1b1
--- /dev/null
+++ b/server/proxy/pf_config.c
@@ -0,0 +1,1348 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021,2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2021,2023 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 <stdio.h>
+#include <string.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/collections.h>
+#include <winpr/cmdline.h>
+
+#include "pf_server.h"
+#include <freerdp/server/proxy/proxy_config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/channels/rail.h>
+#include <freerdp/channels/rdpei.h>
+#include <freerdp/channels/tsmf.h>
+#include <freerdp/channels/video.h>
+#include <freerdp/channels/rdpecam.h>
+
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("config")
+
+#define CONFIG_PRINT_SECTION(section) WLog_INFO(TAG, "\t%s:", section)
+#define CONFIG_PRINT_SECTION_KEY(section, key) WLog_INFO(TAG, "\t%s/%s:", section, key)
+#define CONFIG_PRINT_STR(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, config->key)
+#define CONFIG_PRINT_STR_CONTENT(config, key) \
+ WLog_INFO(TAG, "\t\t%s: %s", #key, config->key ? "set" : NULL)
+#define CONFIG_PRINT_BOOL(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, boolstr(config->key))
+#define CONFIG_PRINT_UINT16(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu16 "", #key, config->key)
+#define CONFIG_PRINT_UINT32(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu32 "", #key, config->key)
+
+static const char* bool_str_true = "true";
+static const char* bool_str_false = "false";
+static const char* boolstr(BOOL rc)
+{
+ return rc ? bool_str_true : bool_str_false;
+}
+
+static const char* section_server = "Server";
+static const char* key_host = "Host";
+static const char* key_port = "Port";
+
+static const char* section_target = "Target";
+static const char* key_target_fixed = "FixedTarget";
+static const char* key_target_user = "User";
+static const char* key_target_pwd = "Password";
+static const char* key_target_domain = "Domain";
+static const char* key_target_tls_seclevel = "TlsSecLevel";
+
+static const char* section_clipboard = "Clipboard";
+static const char* key_clip_text_only = "TextOnly";
+static const char* key_clip_text_max_len = "MaxTextLength";
+
+static const char* section_gfx_settings = "GFXSettings";
+static const char* key_gfx_decode = "DecodeGFX";
+
+static const char* section_plugins = "Plugins";
+static const char* key_plugins_modules = "Modules";
+static const char* key_plugins_required = "Required";
+
+static const char* section_channels = "Channels";
+static const char* key_channels_gfx = "GFX";
+static const char* key_channels_disp = "DisplayControl";
+static const char* key_channels_clip = "Clipboard";
+static const char* key_channels_mic = "AudioInput";
+static const char* key_channels_sound = "AudioOutput";
+static const char* key_channels_rdpdr = "DeviceRedirection";
+static const char* key_channels_video = "VideoRedirection";
+static const char* key_channels_camera = "CameraRedirection";
+static const char* key_channels_rails = "RemoteApp";
+static const char* key_channels_blacklist = "PassthroughIsBlacklist";
+static const char* key_channels_pass = "Passthrough";
+static const char* key_channels_intercept = "Intercept";
+
+static const char* section_input = "Input";
+static const char* key_input_kbd = "Keyboard";
+static const char* key_input_mouse = "Mouse";
+static const char* key_input_multitouch = "Multitouch";
+
+static const char* section_security = "Security";
+static const char* key_security_server_nla = "ServerNlaSecurity";
+static const char* key_security_server_tls = "ServerTlsSecurity";
+static const char* key_security_server_rdp = "ServerRdpSecurity";
+static const char* key_security_client_nla = "ClientNlaSecurity";
+static const char* key_security_client_tls = "ClientTlsSecurity";
+static const char* key_security_client_rdp = "ClientRdpSecurity";
+static const char* key_security_client_fallback = "ClientAllowFallbackToTls";
+
+static const char* section_certificates = "Certificates";
+static const char* key_private_key_file = "PrivateKeyFile";
+static const char* key_private_key_content = "PrivateKeyContent";
+static const char* key_cert_file = "CertificateFile";
+static const char* key_cert_content = "CertificateContent";
+
+static char** pf_config_parse_comma_separated_list(const char* list, size_t* count)
+{
+ if (!list || !count)
+ return NULL;
+
+ if (strlen(list) == 0)
+ {
+ *count = 0;
+ return NULL;
+ }
+
+ return CommandLineParseCommaSeparatedValues(list, count);
+}
+
+static BOOL pf_config_get_uint16(wIniFile* ini, const char* section, const char* key,
+ UINT16* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval && required)
+ {
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return FALSE;
+ }
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val <= 0) || (val > UINT16_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT16)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_uint32(wIniFile* ini, const char* section, const char* key,
+ UINT32* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return !required;
+ }
+
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val < 0) || (val > INT32_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT32)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_bool(wIniFile* ini, const char* section, const char* key, BOOL fallback)
+{
+ int num_value = 0;
+ const char* str_value = NULL;
+
+ str_value = IniFile_GetKeyValueString(ini, section, key);
+ if (!str_value)
+ {
+ WLog_WARN(TAG, "key '%s.%s' not found, value defaults to %s.", section, key,
+ fallback ? bool_str_true : bool_str_false);
+ return fallback;
+ }
+
+ if (_stricmp(str_value, bool_str_true) == 0)
+ return TRUE;
+ if (_stricmp(str_value, bool_str_false) == 0)
+ return FALSE;
+
+ num_value = IniFile_GetKeyValueInt(ini, section, key);
+
+ if (num_value != 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static const char* pf_config_get_str(wIniFile* ini, const char* section, const char* key,
+ BOOL required)
+{
+ const char* value = NULL;
+
+ value = IniFile_GetKeyValueString(ini, section, key);
+
+ if (!value)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' not found.", section, key);
+ return NULL;
+ }
+
+ return value;
+}
+
+static BOOL pf_config_load_server(wIniFile* ini, proxyConfig* config)
+{
+ const char* host = NULL;
+
+ WINPR_ASSERT(config);
+ host = pf_config_get_str(ini, section_server, key_host, FALSE);
+
+ if (!host)
+ return TRUE;
+
+ config->Host = _strdup(host);
+
+ if (!config->Host)
+ return FALSE;
+
+ if (!pf_config_get_uint16(ini, section_server, key_port, &config->Port, TRUE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_target(wIniFile* ini, proxyConfig* config)
+{
+ const char* target_value = NULL;
+
+ WINPR_ASSERT(config);
+ config->FixedTarget = pf_config_get_bool(ini, section_target, key_target_fixed, FALSE);
+
+ if (!pf_config_get_uint16(ini, section_target, key_port, &config->TargetPort,
+ config->FixedTarget))
+ return FALSE;
+
+ if (!pf_config_get_uint32(ini, section_target, key_target_tls_seclevel,
+ &config->TargetTlsSecLevel, FALSE))
+ return FALSE;
+
+ if (config->FixedTarget)
+ {
+ target_value = pf_config_get_str(ini, section_target, key_host, TRUE);
+ if (!target_value)
+ return FALSE;
+
+ config->TargetHost = _strdup(target_value);
+ if (!config->TargetHost)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_user, FALSE);
+ if (target_value)
+ {
+ config->TargetUser = _strdup(target_value);
+ if (!config->TargetUser)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_pwd, FALSE);
+ if (target_value)
+ {
+ config->TargetPassword = _strdup(target_value);
+ if (!config->TargetPassword)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_domain, FALSE);
+ if (target_value)
+ {
+ config->TargetDomain = _strdup(target_value);
+ if (!config->TargetDomain)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->GFX = pf_config_get_bool(ini, section_channels, key_channels_gfx, TRUE);
+ config->DisplayControl = pf_config_get_bool(ini, section_channels, key_channels_disp, TRUE);
+ config->Clipboard = pf_config_get_bool(ini, section_channels, key_channels_clip, FALSE);
+ config->AudioOutput = pf_config_get_bool(ini, section_channels, key_channels_mic, TRUE);
+ config->AudioInput = pf_config_get_bool(ini, section_channels, key_channels_sound, TRUE);
+ config->DeviceRedirection = pf_config_get_bool(ini, section_channels, key_channels_rdpdr, TRUE);
+ config->VideoRedirection = pf_config_get_bool(ini, section_channels, key_channels_video, TRUE);
+ config->CameraRedirection =
+ pf_config_get_bool(ini, section_channels, key_channels_camera, TRUE);
+ config->RemoteApp = pf_config_get_bool(ini, section_channels, key_channels_rails, FALSE);
+ config->PassthroughIsBlacklist =
+ pf_config_get_bool(ini, section_channels, key_channels_blacklist, FALSE);
+ config->Passthrough = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_pass, FALSE),
+ &config->PassthroughCount);
+ config->Intercept = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_intercept, FALSE),
+ &config->InterceptCount);
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_input(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->Keyboard = pf_config_get_bool(ini, section_input, key_input_kbd, TRUE);
+ config->Mouse = pf_config_get_bool(ini, section_input, key_input_mouse, TRUE);
+ config->Multitouch = pf_config_get_bool(ini, section_input, key_input_multitouch, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_security(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->ServerTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_tls, TRUE);
+ config->ServerNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_nla, FALSE);
+ config->ServerRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_rdp, TRUE);
+
+ config->ClientTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_tls, TRUE);
+ config->ClientNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_nla, TRUE);
+ config->ClientRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_rdp, TRUE);
+ config->ClientAllowFallbackToTls =
+ pf_config_get_bool(ini, section_security, key_security_client_fallback, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->TextOnly = pf_config_get_bool(ini, section_clipboard, key_clip_text_only, FALSE);
+
+ if (!pf_config_get_uint32(ini, section_clipboard, key_clip_text_max_len, &config->MaxTextLength,
+ FALSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config)
+{
+ const char* modules_to_load = NULL;
+ const char* required_modules = NULL;
+
+ modules_to_load = pf_config_get_str(ini, section_plugins, key_plugins_modules, FALSE);
+ required_modules = pf_config_get_str(ini, section_plugins, key_plugins_required, FALSE);
+
+ WINPR_ASSERT(config);
+ config->Modules = pf_config_parse_comma_separated_list(modules_to_load, &config->ModulesCount);
+
+ config->RequiredPlugins =
+ pf_config_parse_comma_separated_list(required_modules, &config->RequiredPluginsCount);
+ return TRUE;
+}
+
+static BOOL pf_config_load_gfx_settings(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->DecodeGFX = pf_config_get_bool(ini, section_gfx_settings, key_gfx_decode, FALSE);
+ return TRUE;
+}
+
+static char* pf_config_decode_base64(const char* data, const char* name, size_t* pLength)
+{
+ const char* headers[] = { "-----BEGIN PUBLIC KEY-----", "-----BEGIN RSA PUBLIC KEY-----",
+ "-----BEGIN CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----",
+ "-----BEGIN RSA PRIVATE KEY-----" };
+
+ size_t decoded_length = 0;
+ char* decoded = NULL;
+ if (!data)
+ {
+ WLog_ERR(TAG, "Invalid base64 data [%p] for %s", data, name);
+ return NULL;
+ }
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(pLength);
+
+ const size_t length = strlen(data);
+
+ if (strncmp(data, "-----", 5) == 0)
+ {
+ BOOL expected = FALSE;
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+
+ if (strncmp(data, header, strlen(header)) == 0)
+ expected = TRUE;
+ }
+
+ if (!expected)
+ {
+ /* Extract header for log message
+ * expected format is '----- SOMETEXT -----'
+ */
+ char hdr[128] = { 0 };
+ const char* end = strchr(&data[5], '-');
+ if (end)
+ {
+ while (*end == '-')
+ end++;
+
+ const size_t s = MIN(ARRAYSIZE(hdr) - 1, end - data);
+ memcpy(hdr, data, s);
+ }
+
+ WLog_WARN(TAG, "PEM has unexpected header '%s'. Known supported headers are:", hdr);
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+ WLog_WARN(TAG, "%s", header);
+ }
+ }
+
+ *pLength = length + 1;
+ return _strdup(data);
+ }
+
+ crypto_base64_decode(data, length, (BYTE**)&decoded, &decoded_length);
+ if (!decoded || decoded_length == 0)
+ {
+ WLog_ERR(TAG, "Failed to decode base64 data of length %" PRIuz " for %s", length, name);
+ free(decoded);
+ return NULL;
+ }
+
+ *pLength = strnlen(decoded, decoded_length) + 1;
+ return decoded;
+}
+
+static BOOL pf_config_load_certificates(wIniFile* ini, proxyConfig* config)
+{
+ const char* tmp1 = NULL;
+ const char* tmp2 = NULL;
+
+ WINPR_ASSERT(ini);
+ WINPR_ASSERT(config);
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_cert_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates, key_cert_file,
+ tmp1);
+ return FALSE;
+ }
+ config->CertificateFile = _strdup(tmp1);
+ config->CertificatePEM =
+ crypto_read_pem(config->CertificateFile, &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ config->CertificatePEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_cert_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates, key_cert_content);
+ return FALSE;
+ }
+ config->CertificateContent = _strdup(tmp2);
+ config->CertificatePEM = pf_config_decode_base64(
+ config->CertificateContent, "CertificateContent", &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ }
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "required settings",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_private_key_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates,
+ key_private_key_file, tmp1);
+ return FALSE;
+ }
+ config->PrivateKeyFile = _strdup(tmp1);
+ config->PrivateKeyPEM =
+ crypto_read_pem(config->PrivateKeyFile, &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ config->PrivateKeyPEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_private_key_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ config->PrivateKeyContent = _strdup(tmp2);
+ config->PrivateKeyPEM = pf_config_decode_base64(
+ config->PrivateKeyContent, "PrivateKeyContent", &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ }
+
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "are required settings",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+proxyConfig* server_config_load_ini(wIniFile* ini)
+{
+ proxyConfig* config = NULL;
+
+ WINPR_ASSERT(ini);
+
+ config = calloc(1, sizeof(proxyConfig));
+ if (config)
+ {
+ /* Set default values != 0 */
+ config->TargetTlsSecLevel = 1;
+
+ /* Load from ini */
+ if (!pf_config_load_server(ini, config))
+ goto out;
+
+ if (!pf_config_load_target(ini, config))
+ goto out;
+
+ if (!pf_config_load_channels(ini, config))
+ goto out;
+
+ if (!pf_config_load_input(ini, config))
+ goto out;
+
+ if (!pf_config_load_security(ini, config))
+ goto out;
+
+ if (!pf_config_load_modules(ini, config))
+ goto out;
+
+ if (!pf_config_load_clipboard(ini, config))
+ goto out;
+
+ if (!pf_config_load_gfx_settings(ini, config))
+ goto out;
+
+ if (!pf_config_load_certificates(ini, config))
+ goto out;
+ config->ini = IniFile_Clone(ini);
+ if (!config->ini)
+ goto out;
+ }
+ return config;
+out:
+ pf_server_config_free(config);
+ return NULL;
+}
+
+BOOL pf_server_config_dump(const char* file)
+{
+ BOOL rc = FALSE;
+ wIniFile* ini = IniFile_New();
+ if (!ini)
+ return FALSE;
+
+ /* Proxy server configuration */
+ if (IniFile_SetKeyValueString(ini, section_server, key_host, "0.0.0.0") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_server, key_port, 3389) < 0)
+ goto fail;
+
+ /* Target configuration */
+ if (IniFile_SetKeyValueString(ini, section_target, key_host, "somehost.example.com") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_port, 3389) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_target, key_target_fixed, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_target_tls_seclevel, 1) < 0)
+ goto fail;
+
+ /* Channel configuration */
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_gfx, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_disp, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_clip, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_mic, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_sound, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rdpdr, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_video, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_camera, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rails, bool_str_false) < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_blacklist, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_pass, "") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_intercept, "") < 0)
+ goto fail;
+
+ /* Input configuration */
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_kbd, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_mouse, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_multitouch, bool_str_true) < 0)
+ goto fail;
+
+ /* Security settings */
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_nla, bool_str_false) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_rdp, bool_str_true) <
+ 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_nla, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_rdp, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_fallback,
+ bool_str_true) < 0)
+ goto fail;
+
+ /* Module configuration */
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_modules,
+ "module1,module2,...") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_required,
+ "module1,module2,...") < 0)
+ goto fail;
+
+ /* Clipboard configuration */
+ if (IniFile_SetKeyValueString(ini, section_clipboard, key_clip_text_only, bool_str_false) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_clipboard, key_clip_text_max_len, 0) < 0)
+ goto fail;
+
+ /* GFX configuration */
+ if (IniFile_SetKeyValueString(ini, section_gfx_settings, key_gfx_decode, bool_str_false) < 0)
+ goto fail;
+
+ /* Certificate configuration */
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_file,
+ "<absolute path to some certificate file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_content,
+ "<Contents of some certificate file in PEM format>") < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_file,
+ "<absolute path to some private key file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_content,
+ "<Contents of some private key file in PEM format>") < 0)
+ goto fail;
+
+ /* store configuration */
+ if (IniFile_WriteFile(ini, file) < 0)
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ IniFile_Free(ini);
+ return rc;
+}
+
+proxyConfig* pf_server_config_load_buffer(const char* buffer)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = NULL;
+
+ ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadBuffer(ini, buffer) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini: '%s'", buffer);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+proxyConfig* pf_server_config_load_file(const char* path)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadFile(ini, path) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini file: '%s'", path);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+static void pf_server_config_print_list(char** list, size_t count)
+{
+ WINPR_ASSERT(list);
+ for (size_t i = 0; i < count; i++)
+ WLog_INFO(TAG, "\t\t- %s", list[i]);
+}
+
+void pf_server_config_print(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ WLog_INFO(TAG, "Proxy configuration:");
+
+ CONFIG_PRINT_SECTION(section_server);
+ CONFIG_PRINT_STR(config, Host);
+ CONFIG_PRINT_UINT16(config, Port);
+
+ if (config->FixedTarget)
+ {
+ CONFIG_PRINT_SECTION(section_target);
+ CONFIG_PRINT_STR(config, TargetHost);
+ CONFIG_PRINT_UINT16(config, TargetPort);
+ CONFIG_PRINT_UINT32(config, TargetTlsSecLevel);
+
+ if (config->TargetUser)
+ CONFIG_PRINT_STR(config, TargetUser);
+ if (config->TargetDomain)
+ CONFIG_PRINT_STR(config, TargetDomain);
+ }
+
+ CONFIG_PRINT_SECTION(section_input);
+ CONFIG_PRINT_BOOL(config, Keyboard);
+ CONFIG_PRINT_BOOL(config, Mouse);
+ CONFIG_PRINT_BOOL(config, Multitouch);
+
+ CONFIG_PRINT_SECTION(section_security);
+ CONFIG_PRINT_BOOL(config, ServerNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ServerTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ServerRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ClientTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ClientRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientAllowFallbackToTls);
+
+ CONFIG_PRINT_SECTION(section_channels);
+ CONFIG_PRINT_BOOL(config, GFX);
+ CONFIG_PRINT_BOOL(config, DisplayControl);
+ CONFIG_PRINT_BOOL(config, Clipboard);
+ CONFIG_PRINT_BOOL(config, AudioOutput);
+ CONFIG_PRINT_BOOL(config, AudioInput);
+ CONFIG_PRINT_BOOL(config, DeviceRedirection);
+ CONFIG_PRINT_BOOL(config, VideoRedirection);
+ CONFIG_PRINT_BOOL(config, CameraRedirection);
+ CONFIG_PRINT_BOOL(config, RemoteApp);
+ CONFIG_PRINT_BOOL(config, PassthroughIsBlacklist);
+
+ if (config->PassthroughCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy:");
+ pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
+ }
+
+ if (config->InterceptCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy-Intercept:");
+ pf_server_config_print_list(config->Intercept, config->InterceptCount);
+ }
+
+ CONFIG_PRINT_SECTION(section_clipboard);
+ CONFIG_PRINT_BOOL(config, TextOnly);
+ if (config->MaxTextLength > 0)
+ CONFIG_PRINT_UINT32(config, MaxTextLength);
+
+ CONFIG_PRINT_SECTION(section_gfx_settings);
+ CONFIG_PRINT_BOOL(config, DecodeGFX);
+
+ /* modules */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_modules);
+ for (size_t x = 0; x < config->ModulesCount; x++)
+ CONFIG_PRINT_STR(config, Modules[x]);
+
+ /* Required plugins */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_required);
+ for (size_t x = 0; x < config->RequiredPluginsCount; x++)
+ CONFIG_PRINT_STR(config, RequiredPlugins[x]);
+
+ CONFIG_PRINT_SECTION(section_certificates);
+ CONFIG_PRINT_STR(config, CertificateFile);
+ CONFIG_PRINT_STR_CONTENT(config, CertificateContent);
+ CONFIG_PRINT_STR(config, PrivateKeyFile);
+ CONFIG_PRINT_STR_CONTENT(config, PrivateKeyContent);
+}
+
+void pf_server_config_free(proxyConfig* config)
+{
+ if (config == NULL)
+ return;
+
+ free(config->Passthrough);
+ free(config->Intercept);
+ free(config->RequiredPlugins);
+ free(config->Modules);
+ free(config->TargetHost);
+ free(config->Host);
+ free(config->CertificateFile);
+ free(config->CertificateContent);
+ if (config->CertificatePEM)
+ memset(config->CertificatePEM, 0, config->CertificatePEMLength);
+ free(config->CertificatePEM);
+ free(config->PrivateKeyFile);
+ free(config->PrivateKeyContent);
+ if (config->PrivateKeyPEM)
+ memset(config->PrivateKeyPEM, 0, config->PrivateKeyPEMLength);
+ free(config->PrivateKeyPEM);
+ IniFile_Free(config->ini);
+ free(config);
+}
+
+size_t pf_config_required_plugins_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->RequiredPluginsCount;
+}
+
+const char* pf_config_required_plugin(const proxyConfig* config, size_t index)
+{
+ WINPR_ASSERT(config);
+ if (index >= config->RequiredPluginsCount)
+ return NULL;
+
+ return config->RequiredPlugins[index];
+}
+
+size_t pf_config_modules_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->ModulesCount;
+}
+
+const char** pf_config_modules(const proxyConfig* config)
+{
+ union
+ {
+ char** ppc;
+ const char** cppc;
+ } cnv;
+
+ WINPR_ASSERT(config);
+
+ cnv.ppc = config->Modules;
+ return cnv.cppc;
+}
+
+static BOOL pf_config_copy_string(char** dst, const char* src)
+{
+ *dst = NULL;
+ if (src)
+ *dst = _strdup(src);
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_n(char** dst, const char* src, size_t size)
+{
+ *dst = NULL;
+
+ if (src && (size > 0))
+ {
+ WINPR_ASSERT(strnlen(src, size) == size - 1);
+ *dst = calloc(size, sizeof(char));
+ if (!*dst)
+ return FALSE;
+ memcpy(*dst, src, size);
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_list(char*** dst, size_t* size, char** src, size_t srcSize)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(size);
+ WINPR_ASSERT(src || (srcSize == 0));
+
+ *dst = NULL;
+ *size = 0;
+ if (srcSize == 0)
+ return TRUE;
+ {
+ char* csv = CommandLineToCommaSeparatedValues(srcSize, src);
+ *dst = CommandLineParseCommaSeparatedValues(csv, size);
+ free(csv);
+ }
+
+ return TRUE;
+}
+
+BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config)
+{
+ proxyConfig* tmp = calloc(1, sizeof(proxyConfig));
+
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(config);
+
+ if (!tmp)
+ return FALSE;
+
+ *tmp = *config;
+
+ if (!pf_config_copy_string(&tmp->Host, config->Host))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->TargetHost, config->TargetHost))
+ goto fail;
+
+ if (!pf_config_copy_string_list(&tmp->Passthrough, &tmp->PassthroughCount, config->Passthrough,
+ config->PassthroughCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Intercept, &tmp->InterceptCount, config->Intercept,
+ config->InterceptCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Modules, &tmp->ModulesCount, config->Modules,
+ config->ModulesCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->RequiredPlugins, &tmp->RequiredPluginsCount,
+ config->RequiredPlugins, config->RequiredPluginsCount))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateFile, config->CertificateFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateContent, config->CertificateContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->CertificatePEM, config->CertificatePEM,
+ config->CertificatePEMLength))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyFile, config->PrivateKeyFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyContent, config->PrivateKeyContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->PrivateKeyPEM, config->PrivateKeyPEM,
+ config->PrivateKeyPEMLength))
+ goto fail;
+
+ tmp->ini = IniFile_Clone(config->ini);
+ if (!tmp->ini)
+ goto fail;
+
+ *dst = tmp;
+ return TRUE;
+
+fail:
+ pf_server_config_free(tmp);
+ return FALSE;
+}
+
+struct config_plugin_data
+{
+ proxyPluginsManager* mgr;
+ const proxyConfig* config;
+};
+
+static const char config_plugin_name[] = "config";
+static const char config_plugin_desc[] =
+ "A plugin filtering according to proxy configuration file rules";
+
+static BOOL config_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ {
+ free(plugin->custom);
+ plugin->custom = NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL config_plugin_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyKeyboardEventInfo* event_data = (const proxyKeyboardEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyUnicodeEventInfo* event_data = (const proxyUnicodeEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseEventInfo* event_data = (const proxyMouseEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseExEventInfo* event_data = (const proxyMouseExEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+
+ if (accept)
+ {
+ if (strncmp(RDPGFX_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPGFX_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->GFX;
+ else if (strncmp(RDPSND_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPSND_LOSSY_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_LOSSY_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(AUDIN_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(AUDIN_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioInput;
+ else if (strncmp(RDPEI_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPEI_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Multitouch;
+ else if (strncmp(TSMF_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(TSMF_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_CONTROL_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_CONTROL_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_DATA_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_DATA_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(RDPECAM_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPECAM_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->CameraRedirection;
+ }
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "]: %s", channel->channel_name, channel->channel_id,
+ boolstr(accept));
+ return accept;
+}
+
+static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+ if (accept)
+ {
+ if (strncmp(CLIPRDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(CLIPRDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Clipboard;
+ else if (strncmp(RDPSND_CHANNEL_NAME, channel->channel_name, sizeof(RDPSND_CHANNEL_NAME)) ==
+ 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DeviceRedirection;
+ else if (strncmp(DISP_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(DISP_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DisplayControl;
+ else if (strncmp(RAIL_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RAIL_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->RemoteApp;
+ }
+
+ WLog_DBG(TAG, "%s [static]: %s", channel->channel_name, boolstr(accept));
+ return accept;
+}
+
+BOOL pf_config_plugin(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct config_plugin_data* custom = NULL;
+ proxyPlugin plugin = { 0 };
+
+ plugin.name = config_plugin_name;
+ plugin.description = config_plugin_desc;
+ plugin.PluginUnload = config_plugin_unload;
+
+ plugin.KeyboardEvent = config_plugin_keyboard_event;
+ plugin.UnicodeEvent = config_plugin_unicode_event;
+ plugin.MouseEvent = config_plugin_mouse_event;
+ plugin.MouseExEvent = config_plugin_mouse_ex_event;
+ plugin.ClientChannelData = config_plugin_client_channel_data;
+ plugin.ServerChannelData = config_plugin_server_channel_data;
+ plugin.ChannelCreate = config_plugin_channel_create;
+ plugin.DynamicChannelCreate = config_plugin_dynamic_channel_create;
+ plugin.userdata = userdata;
+
+ custom = calloc(1, sizeof(struct config_plugin_data));
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->config = userdata;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
+
+const char* pf_config_get(const proxyConfig* config, const char* section, const char* key)
+{
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(config->ini);
+ WINPR_ASSERT(section);
+ WINPR_ASSERT(key);
+
+ return IniFile_GetKeyValueString(config->ini, section, key);
+}
diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c
new file mode 100644
index 0000000..04433d9
--- /dev/null
+++ b/server/proxy/pf_context.c
@@ -0,0 +1,394 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/crypto.h>
+#include <winpr/print.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_server.h>
+
+#include "pf_client.h"
+#include "pf_utils.h"
+#include "proxy_modules.h"
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* pv1, const void* pv2)
+{
+ const UINT32* v1 = pv1;
+ const UINT32* v2 = pv2;
+ WINPR_ASSERT(v1);
+ WINPR_ASSERT(v2);
+ return (*v1 == *v2);
+}
+
+pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const char* name,
+ UINT32 id)
+{
+ pServerStaticChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating channel context for '%s'", name);
+ return NULL;
+ }
+
+ ret->front_channel_id = id;
+ ret->channel_name = _strdup(name);
+ if (!ret->channel_name)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating name in channel context for '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ proxyChannelToInterceptData channel = { .name = name, .channelId = id, .intercept = FALSE };
+
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_STATIC_INTERCEPT_LIST, ps->pdata,
+ &channel) &&
+ channel.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ return ret;
+}
+
+void StaticChannelContext_free(pServerStaticChannelContext* ctx)
+{
+ if (!ctx)
+ return;
+
+ IFCALL(ctx->contextDtor, ctx->context);
+
+ free(ctx->channel_name);
+ free(ctx);
+}
+
+static void HashStaticChannelContext_free(void* ptr)
+{
+ pServerStaticChannelContext* ctx = (pServerStaticChannelContext*)ptr;
+ StaticChannelContext_free(ctx);
+}
+
+/* Proxy context initialization callback */
+static void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx);
+static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ wObject* obj = NULL;
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+
+ context->dynvcReady = NULL;
+
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ goto error;
+
+ if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ context->interceptContextMap = HashTable_New(FALSE);
+ if (!context->interceptContextMap)
+ goto error;
+ if (!HashTable_SetupForStringData(context->interceptContextMap, FALSE))
+ goto error;
+ obj = HashTable_ValueObject(context->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ /* channels by ids */
+ context->channelsByFrontId = HashTable_New(FALSE);
+ if (!context->channelsByFrontId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByFrontId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByFrontId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ obj = HashTable_ValueObject(context->channelsByFrontId);
+ obj->fnObjectFree = HashStaticChannelContext_free;
+
+ context->channelsByBackId = HashTable_New(FALSE);
+ if (!context->channelsByBackId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByBackId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByBackId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ return TRUE;
+
+error:
+ client_to_proxy_context_free(client, ctx);
+
+ return FALSE;
+}
+
+/* Proxy context free callback */
+void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_UNUSED(client);
+
+ if (!context)
+ return;
+
+ if (context->dynvcReady)
+ {
+ CloseHandle(context->dynvcReady);
+ context->dynvcReady = NULL;
+ }
+
+ HashTable_Free(context->interceptContextMap);
+ HashTable_Free(context->channelsByFrontId);
+ HashTable_Free(context->channelsByBackId);
+
+ if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE))
+ WTSCloseServer((HANDLE)context->vcm);
+ context->vcm = NULL;
+}
+
+BOOL pf_context_init_server_context(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ client->ContextSize = sizeof(pServerContext);
+ client->ContextNew = client_to_proxy_context_new;
+ client->ContextFree = client_to_proxy_context_free;
+
+ return freerdp_peer_context_new(client);
+}
+
+static BOOL pf_context_revert_str_settings(rdpSettings* dst, const rdpSettings* before, size_t nr,
+ const FreeRDP_Settings_Keys_String* ids)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(before);
+ WINPR_ASSERT(ids || (nr == 0));
+
+ for (size_t x = 0; x < nr; x++)
+ {
+ FreeRDP_Settings_Keys_String id = ids[x];
+ const char* what = freerdp_settings_get_string(before, id);
+ if (!freerdp_settings_set_string(dst, id, what))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void intercept_context_entry_free(void* obj)
+{
+ InterceptContextMapEntry* entry = obj;
+ if (!entry)
+ return;
+ if (!entry->free)
+ return;
+ entry->free(entry);
+}
+
+BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src)
+{
+ BOOL rc = FALSE;
+ rdpSettings* before_copy = NULL;
+ const FreeRDP_Settings_Keys_String to_revert[] = { FreeRDP_ConfigPath,
+ FreeRDP_CertificateName };
+
+ if (!dst || !src)
+ return FALSE;
+
+ before_copy = freerdp_settings_clone(dst);
+ if (!before_copy)
+ return FALSE;
+
+ if (!freerdp_settings_copy(dst, src))
+ goto out_fail;
+
+ /* keep original ServerMode value */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_ServerMode))
+ goto out_fail;
+
+ /* revert some values that must not be changed */
+ if (!pf_context_revert_str_settings(dst, before_copy, ARRAYSIZE(to_revert), to_revert))
+ goto out_fail;
+
+ if (!freerdp_settings_get_bool(dst, FreeRDP_ServerMode))
+ {
+ /* adjust instance pointer */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_instance))
+ goto out_fail;
+
+ /*
+ * RdpServerRsaKey must be set to NULL if `dst` is client's context
+ * it must be freed before setting it to NULL to avoid a memory leak!
+ */
+
+ if (!freerdp_settings_set_pointer_len(dst, FreeRDP_RdpServerRsaKey, NULL, 1))
+ goto out_fail;
+ }
+
+ /* We handle certificate management for this client ourselfes. */
+ rc = freerdp_settings_set_bool(dst, FreeRDP_ExternalCertificateManagement, TRUE);
+
+out_fail:
+ freerdp_settings_free(before_copy);
+ return rc;
+}
+
+pClientContext* pf_context_create_client_context(const rdpSettings* clientSettings)
+{
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ pClientContext* pc = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(clientSettings);
+
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ return NULL;
+
+ pc = (pClientContext*)context;
+
+ if (!pf_context_copy_settings(context->settings, clientSettings))
+ goto error;
+
+ return pc;
+error:
+ freerdp_client_context_free(context);
+ return NULL;
+}
+
+proxyData* proxy_data_new(void)
+{
+ BYTE temp[16];
+ char* hex = NULL;
+ proxyData* pdata = NULL;
+
+ pdata = calloc(1, sizeof(proxyData));
+ if (!pdata)
+ return NULL;
+
+ if (!(pdata->abort_event = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ if (!(pdata->gfx_server_ready = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ winpr_RAND(&temp, 16);
+ hex = winpr_BinToHexString(temp, 16, FALSE);
+ if (!hex)
+ goto error;
+
+ CopyMemory(pdata->session_id, hex, PROXY_SESSION_ID_LENGTH);
+ pdata->session_id[PROXY_SESSION_ID_LENGTH] = '\0';
+ free(hex);
+
+ if (!(pdata->modules_info = HashTable_New(FALSE)))
+ goto error;
+
+ /* modules_info maps between plugin name to custom data */
+ if (!HashTable_SetupForStringData(pdata->modules_info, FALSE))
+ goto error;
+
+ return pdata;
+error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ proxy_data_free(pdata);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/* updates circular pointers between proxyData and pClientContext instances */
+void proxy_data_set_client_context(proxyData* pdata, pClientContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->pc = context;
+ context->pdata = pdata;
+}
+
+/* updates circular pointers between proxyData and pServerContext instances */
+void proxy_data_set_server_context(proxyData* pdata, pServerContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->ps = context;
+ context->pdata = pdata;
+}
+
+void proxy_data_free(proxyData* pdata)
+{
+ if (!pdata)
+ return;
+
+ if (pdata->abort_event)
+ CloseHandle(pdata->abort_event);
+
+ if (pdata->client_thread)
+ CloseHandle(pdata->client_thread);
+
+ if (pdata->gfx_server_ready)
+ CloseHandle(pdata->gfx_server_ready);
+
+ if (pdata->modules_info)
+ HashTable_Free(pdata->modules_info);
+
+ if (pdata->pc)
+ freerdp_client_context_free(&pdata->pc->context);
+
+ free(pdata);
+}
+
+void proxy_data_abort_connect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ SetEvent(pdata->abort_event);
+ if (pdata->pc)
+ freerdp_abort_connect_context(&pdata->pc->context);
+}
+
+BOOL proxy_data_shall_disconnect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ return WaitForSingleObject(pdata->abort_event, 0) == WAIT_OBJECT_0;
+}
diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c
new file mode 100644
index 0000000..2ed1c14
--- /dev/null
+++ b/server/proxy/pf_input.c
@@ -0,0 +1,206 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "proxy_modules.h"
+
+static BOOL pf_server_check_and_sync_input_state(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+
+ if (!freerdp_is_active_state(&pc->context))
+ return FALSE;
+ if (pc->input_state_sync_pending)
+ {
+ BOOL rc = freerdp_input_send_synchronize_event(pc->context.input, pc->input_state);
+ if (rc)
+ pc->input_state_sync_pending = FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_server_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ pc->input_state = flags;
+ pc->input_state_sync_pending = TRUE;
+
+ pf_server_check_and_sync_input_state(pc);
+ return TRUE;
+}
+
+static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ const proxyConfig* config = NULL;
+ proxyKeyboardEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.rdp_scan_code = code;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_KEYBOARD, pc->pdata, &event))
+ return freerdp_input_send_keyboard_event(pc->context.input, flags, code);
+
+ return TRUE;
+}
+
+static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ const proxyConfig* config = NULL;
+ proxyUnicodeEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.code = code;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_UNICODE, pc->pdata, &event))
+ return freerdp_input_send_unicode_keyboard_event(pc->context.input, flags, code);
+ return TRUE;
+}
+
+static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ proxyMouseEventInfo event = { 0 };
+ const proxyConfig* config = NULL;
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_mouse_event(pc->context.input, flags, x, y);
+
+ return TRUE;
+}
+
+static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ const proxyConfig* config = NULL;
+ proxyMouseExEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_extended_mouse_event(pc->context.input, flags, x, y);
+ return TRUE;
+}
+
+void pf_server_register_input_callbacks(rdpInput* input)
+{
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = pf_server_synchronize_event;
+ input->KeyboardEvent = pf_server_keyboard_event;
+ input->UnicodeKeyboardEvent = pf_server_unicode_keyboard_event;
+ input->MouseEvent = pf_server_mouse_event;
+ input->ExtendedMouseEvent = pf_server_extended_mouse_event;
+}
diff --git a/server/proxy/pf_input.h b/server/proxy/pf_input.h
new file mode 100644
index 0000000..ea9c542
--- /dev/null
+++ b/server/proxy/pf_input.h
@@ -0,0 +1,29 @@
+/**
+ * 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_PFINPUT_H
+#define FREERDP_SERVER_PROXY_PFINPUT_H
+
+#include <freerdp/freerdp.h>
+
+void pf_server_register_input_callbacks(rdpInput* input);
+
+#endif /* FREERDP_SERVER_PROXY_PFINPUT_H */
diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c
new file mode 100644
index 0000000..e3447cb
--- /dev/null
+++ b/server/proxy/pf_modules.c
@@ -0,0 +1,633 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server modules API
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/assert.h>
+
+#include <winpr/file.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <freerdp/api.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("modules")
+
+#define MODULE_ENTRY_POINT "proxy_module_entry_point"
+
+struct proxy_module
+{
+ proxyPluginsManager mgr;
+ wArrayList* plugins;
+ wArrayList* handles;
+};
+
+static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result)
+{
+ switch (result)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ return "FILTER_TYPE_KEYBOARD";
+ case FILTER_TYPE_UNICODE:
+ return "FILTER_TYPE_UNICODE";
+ case FILTER_TYPE_MOUSE:
+ return "FILTER_TYPE_MOUSE";
+ case FILTER_TYPE_MOUSE_EX:
+ return "FILTER_TYPE_MOUSE_EX";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE";
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR";
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ return "FILTER_TYPE_SERVER_PEER_LOGON";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE";
+ case FILTER_LAST:
+ return "FILTER_LAST";
+ default:
+ return "FILTER_UNKNOWN";
+ }
+}
+
+static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result)
+{
+ switch (result)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_INIT_CONNECT";
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_UNINIT_CONNECT";
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ return "HOOK_TYPE_CLIENT_PRE_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ return "HOOK_TYPE_CLIENT_POST_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ return "HOOK_TYPE_CLIENT_POST_DISCONNECT";
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ return "HOOK_TYPE_CLIENT_REDIRECT";
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ return "HOOK_TYPE_CLIENT_VERIFY_X509";
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ return "HOOK_TYPE_CLIENT_LOGIN_FAILURE";
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ return "HOOK_TYPE_CLIENT_END_PAINT";
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ return "HOOK_TYPE_SERVER_POST_CONNECT";
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ return "HOOK_TYPE_SERVER_ACTIVATE";
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ return "HOOK_TYPE_SERVER_CHANNELS_INIT";
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ return "HOOK_TYPE_SERVER_CHANNELS_FREE";
+ case HOOK_TYPE_SERVER_SESSION_END:
+ return "HOOK_TYPE_SERVER_SESSION_END";
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ return "HOOK_TYPE_CLIENT_LOAD_CHANNELS";
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ return "HOOK_TYPE_SERVER_SESSION_INITIALIZE";
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ return "HOOK_TYPE_SERVER_SESSION_STARTED";
+ case HOOK_LAST:
+ return "HOOK_LAST";
+ default:
+ return "HOOK_TYPE_UNKNOWN";
+ }
+}
+
+static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL ok = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* custom = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type));
+
+ switch (type)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_END:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom);
+ break;
+
+ case HOOK_LAST:
+ default:
+ WLog_ERR(TAG, "invalid hook called");
+ }
+
+ if (!ok)
+ {
+ WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name,
+ pf_modules_get_hook_type_string(type));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * runs all hooks of type `type`.
+ *
+ * @type: hook type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+ return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata,
+ custom);
+}
+
+static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* param = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running filter: %s", plugin->name);
+
+ switch (type)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_UNICODE:
+ result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE:
+ result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE_EX:
+ result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_INTERCEPT_CHANNEL:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_DYN_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_STATIC_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_LAST:
+ default:
+ WLog_ERR(TAG, "invalid filter called");
+ }
+
+ if (!result)
+ {
+ /* current filter return FALSE, no need to run other filters. */
+ WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name,
+ pf_modules_get_filter_type_string(type));
+ }
+ return result;
+}
+
+/*
+ * runs all filters of type `type`.
+ *
+ * @type: filter type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ return ArrayList_ForEach(module->plugins, pf_modules_ArrayList_ForEachFkt, type, pdata, param);
+}
+
+/*
+ * stores per-session data needed by a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * @info: pointer to per-session data.
+ */
+static BOOL pf_modules_set_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata, void* data)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+
+ WINPR_ASSERT(plugin_name);
+
+ ccharconv.ccp = plugin_name;
+ if (data == NULL) /* no need to store anything */
+ return FALSE;
+
+ if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
+ {
+ WLog_ERR(TAG, "[%s]: HashTable_Insert failed!");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * returns per-session data needed a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * if there's no data related to `plugin_name` in `context` (current session), a NULL will be
+ * returned.
+ */
+static void* pf_modules_get_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+ WINPR_ASSERT(plugin_name);
+ WINPR_ASSERT(pdata);
+ ccharconv.ccp = plugin_name;
+
+ return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp);
+}
+
+static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WLog_DBG(TAG, "is called!");
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*);
+
+ WINPR_UNUSED(index);
+
+ if (strcmp(plugin->name, plugin_to_register->name) == 0)
+ {
+ WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr,
+ const proxyPlugin* plugin_to_register)
+{
+ proxyPlugin internal = { 0 };
+ proxyModule* module = (proxyModule*)mgr;
+ WINPR_ASSERT(module);
+
+ if (!plugin_to_register)
+ return FALSE;
+
+ internal = *plugin_to_register;
+ internal.mgr = mgr;
+
+ /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */
+ if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal))
+ return FALSE;
+
+ if (!ArrayList_Append(module->plugins, &internal))
+ {
+ WLog_ERR(TAG, "failed adding plugin to list: %s", plugin_to_register->name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ const char* plugin_name = va_arg(ap, const char*);
+ BOOL* res = va_arg(ap, BOOL*);
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+ WINPR_ASSERT(res);
+
+ if (strcmp(plugin->name, plugin_name) == 0)
+ *res = TRUE;
+ return TRUE;
+}
+
+BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(module);
+ if (ArrayList_Count(module->plugins) < 1)
+ return FALSE;
+ if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc))
+ return FALSE;
+ return rc;
+}
+
+static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+
+ WLog_INFO(TAG, "\tName: %s", plugin->name);
+ WLog_INFO(TAG, "\tDescription: %s", plugin->description);
+ return TRUE;
+}
+
+void pf_modules_list_loaded_plugins(proxyModule* module)
+{
+ size_t count = 0;
+
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ count = ArrayList_Count(module->plugins);
+
+ if (count > 0)
+ WLog_INFO(TAG, "Loaded plugins:");
+
+ ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt);
+}
+
+static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata)
+{
+ HMODULE handle = NULL;
+ proxyModuleEntryPoint pEntryPoint = NULL;
+ WINPR_ASSERT(module);
+
+ handle = LoadLibraryX(module_path);
+
+ if (handle == NULL)
+ {
+ WLog_ERR(TAG, "failed loading external library: %s", module_path);
+ return FALSE;
+ }
+
+ pEntryPoint = (proxyModuleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT);
+ if (!pEntryPoint)
+ {
+ WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path);
+ goto error;
+ }
+ if (!ArrayList_Append(module->handles, handle))
+ {
+ WLog_ERR(TAG, "ArrayList_Append failed!");
+ goto error;
+ }
+ return pf_modules_add(module, pEntryPoint, userdata);
+
+error:
+ FreeLibrary(handle);
+ return FALSE;
+}
+
+static void free_handle(void* obj)
+{
+ HANDLE handle = (HANDLE)obj;
+ if (handle)
+ FreeLibrary(handle);
+}
+
+static void free_plugin(void* obj)
+{
+ proxyPlugin* plugin = (proxyPlugin*)obj;
+ WINPR_ASSERT(plugin);
+
+ if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin))
+ WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name);
+
+ free(plugin);
+}
+
+static void* new_plugin(const void* obj)
+{
+ const proxyPlugin* src = obj;
+ proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
+ if (!proxy)
+ return NULL;
+ *proxy = *src;
+ return proxy;
+}
+
+proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
+{
+ wObject* obj = NULL;
+ char* path = NULL;
+ proxyModule* module = calloc(1, sizeof(proxyModule));
+ if (!module)
+ return NULL;
+
+ module->mgr.RegisterPlugin = pf_modules_register_plugin;
+ module->mgr.SetPluginData = pf_modules_set_plugin_data;
+ module->mgr.GetPluginData = pf_modules_get_plugin_data;
+ module->mgr.AbortConnect = pf_modules_abort_connect;
+ module->plugins = ArrayList_New(FALSE);
+
+ if (module->plugins == NULL)
+ {
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ obj = ArrayList_Object(module->plugins);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = free_plugin;
+ obj->fnObjectNew = new_plugin;
+
+ module->handles = ArrayList_New(FALSE);
+ if (module->handles == NULL)
+ {
+
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ ArrayList_Object(module->handles)->fnObjectFree = free_handle;
+
+ if (count > 0)
+ {
+ WINPR_ASSERT(root_dir);
+ if (!winpr_PathFileExists(root_dir))
+ path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir);
+ else
+ path = _strdup(root_dir);
+
+ if (!winpr_PathFileExists(path))
+ {
+ if (!winpr_PathMakePath(path, NULL))
+ {
+ WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
+ goto error;
+ }
+ }
+
+ if (winpr_PathFileExists(path))
+ WLog_DBG(TAG, "modules root directory: %s", path);
+
+ for (size_t i = 0; i < count; i++)
+ {
+ char name[8192] = { 0 };
+ char* fullpath = NULL;
+ _snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i],
+ FREERDP_SHARED_LIBRARY_SUFFIX);
+ fullpath = GetCombinedPath(path, name);
+ pf_modules_load_module(fullpath, module, NULL);
+ free(fullpath);
+ }
+ }
+
+ free(path);
+ return module;
+
+error:
+ free(path);
+ pf_modules_free(module);
+ return NULL;
+}
+
+void pf_modules_free(proxyModule* module)
+{
+ if (!module)
+ return;
+
+ ArrayList_Free(module->plugins);
+ ArrayList_Free(module->handles);
+ free(module);
+}
+
+BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(ep);
+
+ return ep(&module->mgr, userdata);
+}
diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c
new file mode 100644
index 0000000..545ab93
--- /dev/null
+++ b/server/proxy/pf_server.c
@@ -0,0 +1,1073 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/winsock.h>
+#include <winpr/thread.h>
+#include <errno.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_server.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "pf_client.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_drdynvc.h"
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+typedef struct
+{
+ HANDLE thread;
+ freerdp_peer* client;
+} peer_thread_args;
+
+static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String targetID,
+ FreeRDP_Settings_Keys_UInt32 portID)
+{
+#define TARGET_MAX (100)
+#define ROUTING_TOKEN_PREFIX "Cookie: msts="
+ char* colon = NULL;
+ size_t len = 0;
+ DWORD routing_token_length = 0;
+ const size_t prefix_len = strnlen(ROUTING_TOKEN_PREFIX, sizeof(ROUTING_TOKEN_PREFIX));
+ const char* routing_token = freerdp_nego_get_routing_token(context, &routing_token_length);
+ pServerContext* ps = (pServerContext*)context;
+
+ if (!routing_token)
+ return FALSE;
+
+ if ((routing_token_length <= prefix_len) || (routing_token_length >= TARGET_MAX))
+ {
+ PROXY_LOG_ERR(TAG, ps, "invalid routing token length: %" PRIu32 "", routing_token_length);
+ return FALSE;
+ }
+
+ len = routing_token_length - prefix_len;
+
+ if (!freerdp_settings_set_string_len(settings, targetID, routing_token + prefix_len, len))
+ return FALSE;
+
+ const char* target = freerdp_settings_get_string(settings, targetID);
+ colon = strchr(target, ':');
+
+ if (colon)
+ {
+ /* port is specified */
+ unsigned long p = strtoul(colon + 1, NULL, 10);
+
+ if (p > USHRT_MAX)
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, portID, p))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_get_target_info(rdpContext* context, rdpSettings* settings,
+ const proxyConfig* config)
+{
+ pServerContext* ps = (pServerContext*)context;
+ proxyFetchTargetEventInfo ev = { 0 };
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ ev.fetch_method = config->FixedTarget ? PROXY_FETCH_TARGET_METHOD_CONFIG
+ : PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO;
+
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, ps->pdata,
+ &ev))
+ return FALSE;
+
+ switch (ev.fetch_method)
+ {
+ case PROXY_FETCH_TARGET_METHOD_DEFAULT:
+ case PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO:
+ return pf_server_parse_target_from_routing_token(
+ context, settings, FreeRDP_ServerHostname, FreeRDP_ServerPort);
+
+ case PROXY_FETCH_TARGET_METHOD_CONFIG:
+ {
+ WINPR_ASSERT(config);
+
+ if (config->TargetPort > 0)
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, config->TargetPort);
+ else
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 3389);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel,
+ config->TargetTlsSecLevel))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, config->TargetHost))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ if (config->TargetUser)
+ freerdp_settings_set_string(settings, FreeRDP_Username, config->TargetUser);
+
+ if (config->TargetDomain)
+ freerdp_settings_set_string(settings, FreeRDP_Domain, config->TargetDomain);
+
+ if (config->TargetPassword)
+ freerdp_settings_set_string(settings, FreeRDP_Password, config->TargetPassword);
+
+ return TRUE;
+ }
+ case PROXY_FETCH_TARGET_USE_CUSTOM_ADDR:
+ {
+ if (!ev.target_address)
+ {
+ PROXY_LOG_ERR(TAG, ps,
+ "router: using CUSTOM_ADDR fetch method, but target_address == NULL");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, ev.target_address))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ free(ev.target_address);
+ return freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, ev.target_port);
+ }
+ default:
+ PROXY_LOG_ERR(TAG, ps, "unknown target fetch method: %d", ev.fetch_method);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_setup_channels(freerdp_peer* peer)
+{
+ char** accepted_channels = NULL;
+ size_t accepted_channels_count = 0;
+ pServerContext* ps = (pServerContext*)peer->context;
+
+ accepted_channels = WTSGetAcceptedChannelNames(peer, &accepted_channels_count);
+ if (!accepted_channels)
+ return TRUE;
+
+ for (size_t i = 0; i < accepted_channels_count; i++)
+ {
+ pServerStaticChannelContext* channelContext = NULL;
+ const char* cname = accepted_channels[i];
+ UINT16 channelId = WTSChannelGetId(peer, cname);
+
+ PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s (%" PRIu16 ")", cname, channelId);
+ channelContext = StaticChannelContext_new(ps, cname, channelId);
+ if (!channelContext)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error seting up channelContext for '%s'", cname);
+ return FALSE;
+ }
+
+ if (strcmp(cname, DRDYNVC_SVC_CHANNEL_NAME) == 0)
+ {
+ if (!pf_channel_setup_drdynvc(ps->pdata, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up dynamic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else if (strcmp(cname, RDPDR_SVC_CHANNEL_NAME) == 0 &&
+ (channelContext->channelMode == PF_UTILS_CHANNEL_INTERCEPT))
+ {
+ if (!pf_channel_setup_rdpdr(ps, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up redirection channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_setup_generic(channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up generic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+
+ if (!HashTable_Insert(ps->channelsByFrontId, &channelContext->front_channel_id,
+ channelContext))
+ {
+ StaticChannelContext_free(channelContext);
+ PROXY_LOG_ERR(TAG, ps, "error inserting channelContext in byId table for '%s'", cname);
+ return FALSE;
+ }
+ }
+
+ free(accepted_channels);
+ return TRUE;
+}
+
+/* Event callbacks */
+
+/**
+ * This callback is called when the entire connection sequence is done (as
+ * described in MS-RDPBCGR section 1.3)
+ *
+ * The server may start sending graphics output and receiving keyboard/mouse
+ * input after this callback returns.
+ */
+static BOOL pf_server_post_connect(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ rdpSettings* client_settings = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* frontSettings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ frontSettings = peer->context->settings;
+ WINPR_ASSERT(frontSettings);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ const char* ClientHostname = freerdp_settings_get_string(frontSettings, FreeRDP_ClientHostname);
+ PROXY_LOG_INFO(TAG, ps, "Accepted client: %s", ClientHostname);
+ if (!pf_server_setup_channels(peer))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error setting up channels");
+ return FALSE;
+ }
+
+ pc = pf_context_create_client_context(frontSettings);
+ if (pc == NULL)
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client context!");
+ return FALSE;
+ }
+
+ client_settings = pc->context.settings;
+
+ /* keep both sides of the connection in pdata */
+ proxy_data_set_client_context(pdata, pc);
+
+ if (!pf_server_get_target_info(peer->context, client_settings, pdata->config))
+ {
+ PROXY_LOG_INFO(TAG, ps, "pf_server_get_target_info failed!");
+ return FALSE;
+ }
+
+ PROXY_LOG_INFO(TAG, ps, "remote target is %s:%" PRIu32 "",
+ freerdp_settings_get_string(client_settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(client_settings, FreeRDP_ServerPort));
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer))
+ return FALSE;
+
+ /* Start a proxy's client in it's own thread */
+ if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client thread");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_activate(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ settings = peer->context->settings;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_ACTIVATE, pdata, peer))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_server_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ proxyServerPeerLogon info = { 0 };
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(identity);
+
+ info.identity = identity;
+ info.automatic = automatic;
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PEER_LOGON, pdata, &info))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+ /* proxy as is, there's no need to do anything here */
+ return TRUE;
+}
+
+static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
+ const BYTE* data, size_t size, UINT32 flags,
+ size_t totalSize)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+ const proxyConfig* config = NULL;
+ const pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ pc = pdata->pc;
+ config = pdata->config;
+ WINPR_ASSERT(config);
+ /*
+ * client side is not initialized yet, call original callback.
+ * this is probably a drdynvc message between peer and proxy server,
+ * which doesn't need to be proxied.
+ */
+ if (!pc)
+ goto original_cb;
+
+ channel = HashTable_GetItemValue(ps->channelsByFrontId, &channelId64);
+ if (!channel)
+ {
+ PROXY_LOG_ERR(TAG, ps, "channel id=%" PRIu64 " not registered here, dropping", channelId64);
+ return TRUE;
+ }
+
+ WINPR_ASSERT(channel->onFrontData);
+ switch (channel->onFrontData(pdata, channel, data, size, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ {
+ proxyChannelDataEventInfo ev = { 0 };
+
+ ev.channel_id = channelId;
+ ev.channel_name = channel->channel_name;
+ ev.data = data;
+ ev.data_len = size;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+ return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
+ }
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ return FALSE;
+ }
+
+original_cb:
+ WINPR_ASSERT(pdata->server_receive_channel_data_original);
+ return pdata->server_receive_channel_data_original(peer, channelId, data, size, flags,
+ totalSize);
+}
+
+static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+
+ pServerContext* ps = (pServerContext*)peer->context;
+ if (!ps)
+ return FALSE;
+
+ rdpSettings* settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ proxyData* pdata = proxy_data_new();
+ if (!pdata)
+ return FALSE;
+ proxyServer* server = (proxyServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+ proxy_data_set_server_context(pdata, ps);
+
+ pdata->module = server->module;
+ const proxyConfig* config = pdata->config = server->config;
+
+ rdpPrivateKey* key = freerdp_key_new_from_pem(config->PrivateKeyPEM);
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(config->CertificatePEM);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ /* currently not supporting GDI orders */
+ {
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+ }
+
+ WINPR_ASSERT(peer->context->update);
+ peer->context->update->autoCalculateBitmapData = FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, config->GFX))
+ return FALSE;
+
+ if (pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+ }
+
+ if (config->RemoteApp)
+ {
+ const UINT32 mask =
+ RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
+ RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED | RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED |
+ RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteApplicationSupportLevel, mask))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAppLanguageBarSupported, TRUE))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ServerRdpSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ServerTlsSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ServerNlaSecurity))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ 0xFFFFFF)) /* FIXME */
+ return FALSE;
+
+ peer->PostConnect = pf_server_post_connect;
+ peer->Activate = pf_server_activate;
+ peer->Logon = pf_server_logon;
+ peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
+
+ /* virtual channels receive data hook */
+ pdata->server_receive_channel_data_original = peer->ReceiveChannelData;
+ peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
+
+ if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO, TRUE))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Handles an incoming client connection, to be run in it's own thread.
+ *
+ * arg is a pointer to a freerdp_peer representing the client.
+ */
+static DWORD WINAPI pf_server_handle_peer(LPVOID arg)
+{
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ peer_thread_args* args = arg;
+
+ WINPR_ASSERT(args);
+
+ freerdp_peer* client = args->client;
+ WINPR_ASSERT(client);
+
+ proxyServer* server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ size_t count = ArrayList_Count(server->peer_list);
+
+ if (!pf_context_init_server_context(client))
+ goto out_free_peer;
+
+ if (!pf_server_initialize_peer_connection(client))
+ goto out_free_peer;
+
+ ps = (pServerContext*)client->context;
+ WINPR_ASSERT(ps);
+ PROXY_LOG_DBG(TAG, ps, "Added peer, %" PRIuz " connected", count);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_INITIALIZE, pdata, client))
+ goto out_free_peer;
+
+ WINPR_ASSERT(client->Initialize);
+ client->Initialize(client);
+
+ PROXY_LOG_INFO(TAG, ps, "new connection: proxy address: %s, client address: %s",
+ pdata->config->Host, client->hostname);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_STARTED, pdata, client))
+ goto out_free_peer;
+
+ while (1)
+ {
+ HANDLE ChannelEvent = INVALID_HANDLE_VALUE;
+ DWORD eventCount = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ const DWORD tmp = client->GetEventHandles(client, &eventHandles[eventCount],
+ ARRAYSIZE(eventHandles) - eventCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to get FreeRDP transport event handles");
+ break;
+ }
+
+ eventCount += tmp;
+ }
+ /* Main client event handling loop */
+ ChannelEvent = WTSVirtualChannelManagerGetEventHandle(ps->vcm);
+
+ WINPR_ASSERT(ChannelEvent && (ChannelEvent != INVALID_HANDLE_VALUE));
+ WINPR_ASSERT(pdata->abort_event && (pdata->abort_event != INVALID_HANDLE_VALUE));
+ eventHandles[eventCount++] = ChannelEvent;
+ eventHandles[eventCount++] = pdata->abort_event;
+ eventHandles[eventCount++] = server->stopEvent;
+
+ const DWORD status = WaitForMultipleObjects(
+ eventCount, eventHandles, FALSE, 1000); /* Do periodic polling to avoid client hang */
+
+ if (status == WAIT_FAILED)
+ {
+ PROXY_LOG_ERR(TAG, ps, "WaitForMultipleObjects failed (status: %" PRIu32 ")", status);
+ break;
+ }
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+
+ if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
+ {
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "WTSVirtualChannelManagerCheckFileDescriptor failure");
+ goto fail;
+ }
+ }
+
+ /* only disconnect after checking client's and vcm's file descriptors */
+ if (proxy_data_shall_disconnect(pdata))
+ {
+ PROXY_LOG_INFO(TAG, ps, "abort event is set, closing connection with peer %s",
+ client->hostname);
+ break;
+ }
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ {
+ PROXY_LOG_INFO(TAG, ps, "Server shutting down, terminating peer");
+ break;
+ }
+
+ switch (WTSVirtualChannelManagerGetDrdynvcState(ps->vcm))
+ {
+ /* Dynamic channel status may have been changed after processing */
+ case DRDYNVC_STATE_NONE:
+
+ /* Initialize drdynvc channel */
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to initialize drdynvc channel");
+ goto fail;
+ }
+
+ break;
+
+ case DRDYNVC_STATE_READY:
+ if (WaitForSingleObject(ps->dynvcReady, 0) == WAIT_TIMEOUT)
+ {
+ SetEvent(ps->dynvcReady);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+fail:
+
+ PROXY_LOG_INFO(TAG, ps, "starting shutdown of connection");
+ PROXY_LOG_INFO(TAG, ps, "stopping proxy's client");
+
+ /* Abort the client. */
+ proxy_data_abort_connect(pdata);
+
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_END, pdata, client);
+
+ PROXY_LOG_INFO(TAG, ps, "freeing server's channels");
+
+ WINPR_ASSERT(client->Close);
+ client->Close(client);
+
+ WINPR_ASSERT(client->Disconnect);
+ client->Disconnect(client);
+
+out_free_peer:
+ PROXY_LOG_INFO(TAG, ps, "freeing proxy data");
+
+ if (pdata && pdata->client_thread)
+ {
+ proxy_data_abort_connect(pdata);
+ WaitForSingleObject(pdata->client_thread, INFINITE);
+ }
+
+ {
+ ArrayList_Lock(server->peer_list);
+ ArrayList_Remove(server->peer_list, args->thread);
+ count = ArrayList_Count(server->peer_list);
+ ArrayList_Unlock(server->peer_list);
+ }
+ PROXY_LOG_DBG(TAG, ps, "Removed peer, %" PRIuz " connected", count);
+ freerdp_peer_context_free(client);
+ freerdp_peer_free(client);
+ proxy_data_free(pdata);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+ free(args);
+ ExitThread(0);
+ return 0;
+}
+
+static BOOL pf_server_start_peer(freerdp_peer* client)
+{
+ HANDLE hThread = NULL;
+ proxyServer* server = NULL;
+ peer_thread_args* args = calloc(1, sizeof(peer_thread_args));
+ if (!args)
+ return FALSE;
+
+ WINPR_ASSERT(client);
+ args->client = client;
+
+ server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ hThread = CreateThread(NULL, 0, pf_server_handle_peer, args, CREATE_SUSPENDED, NULL);
+ if (!hThread)
+ return FALSE;
+
+ args->thread = hThread;
+ if (!ArrayList_Append(server->peer_list, hThread))
+ {
+ CloseHandle(hThread);
+ return FALSE;
+ }
+
+ return ResumeThread(hThread) != (DWORD)-1;
+}
+
+static BOOL pf_server_peer_accepted(freerdp_listener* listener, freerdp_peer* client)
+{
+ WINPR_ASSERT(listener);
+ WINPR_ASSERT(client);
+
+ client->ContextExtra = listener->info;
+
+ return pf_server_start_peer(client);
+}
+
+BOOL pf_server_start(proxyServer* server)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->config);
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->Open);
+ if (!server->listener->Open(server->listener, server->config->Host, server->config->Port))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_from_socket(proxyServer* server, int socket)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->OpenFromSocket);
+ if (!server->listener->OpenFromSocket(server->listener, socket))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_with_peer_socket(proxyServer* server, int peer_fd)
+{
+ struct sockaddr_storage peer_addr;
+ socklen_t len = sizeof(peer_addr);
+ freerdp_peer* client = NULL;
+
+ WINPR_ASSERT(server);
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ goto fail;
+
+ client = freerdp_peer_new(peer_fd);
+ if (!client)
+ goto fail;
+
+ if (getpeername(peer_fd, (struct sockaddr*)&peer_addr, &len) != 0)
+ goto fail;
+
+ if (!freerdp_peer_set_local_and_hostname(client, &peer_addr))
+ goto fail;
+
+ client->ContextExtra = server;
+
+ if (!pf_server_start_peer(client))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ WLog_ERR(TAG, "PeerAccepted callback failed");
+ freerdp_peer_free(client);
+ return FALSE;
+}
+
+static BOOL are_all_required_modules_loaded(proxyModule* module, const proxyConfig* config)
+{
+ for (size_t i = 0; i < pf_config_required_plugins_count(config); i++)
+ {
+ const char* plugin_name = pf_config_required_plugin(config, i);
+
+ if (!pf_modules_is_plugin_loaded(module, plugin_name))
+ {
+ WLog_ERR(TAG, "Required plugin '%s' is not loaded. stopping.", plugin_name);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void peer_free(void* obj)
+{
+ HANDLE hdl = (HANDLE)obj;
+ CloseHandle(hdl);
+}
+
+proxyServer* pf_server_new(const proxyConfig* config)
+{
+ wObject* obj = NULL;
+ proxyServer* server = NULL;
+
+ WINPR_ASSERT(config);
+
+ server = calloc(1, sizeof(proxyServer));
+ if (!server)
+ return NULL;
+
+ if (!pf_config_clone(&server->config, config))
+ goto out;
+
+ server->module = pf_modules_new(FREERDP_PROXY_PLUGINDIR, pf_config_modules(server->config),
+ pf_config_modules_count(server->config));
+ if (!server->module)
+ {
+ WLog_ERR(TAG, "failed to initialize proxy modules!");
+ goto out;
+ }
+
+ pf_modules_list_loaded_plugins(server->module);
+ if (!are_all_required_modules_loaded(server->module, server->config))
+ goto out;
+
+ server->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!server->stopEvent)
+ goto out;
+
+ server->listener = freerdp_listener_new();
+ if (!server->listener)
+ goto out;
+
+ server->peer_list = ArrayList_New(FALSE);
+ if (!server->peer_list)
+ goto out;
+
+ obj = ArrayList_Object(server->peer_list);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = peer_free;
+
+ server->listener->info = server;
+ server->listener->PeerAccepted = pf_server_peer_accepted;
+
+ if (!pf_modules_add(server->module, pf_config_plugin, (void*)server->config))
+ goto out;
+
+ return server;
+
+out:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ pf_server_free(server);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL pf_server_run(proxyServer* server)
+{
+ BOOL rc = TRUE;
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD eventCount = 0;
+ DWORD status = 0;
+ freerdp_listener* listener = NULL;
+
+ WINPR_ASSERT(server);
+
+ listener = server->listener;
+ WINPR_ASSERT(listener);
+
+ while (1)
+ {
+ WINPR_ASSERT(listener->GetEventHandles);
+ eventCount = listener->GetEventHandles(listener, eventHandles, ARRAYSIZE(eventHandles));
+
+ if ((0 == eventCount) || (eventCount >= ARRAYSIZE(eventHandles)))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ WINPR_ASSERT(server->stopEvent);
+ eventHandles[eventCount++] = server->stopEvent;
+ status = WaitForMultipleObjects(eventCount, eventHandles, FALSE, 1000);
+
+ if (WAIT_FAILED == status)
+ break;
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ break;
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_ERR(TAG, "select failed");
+ rc = FALSE;
+ break;
+ }
+
+ WINPR_ASSERT(listener->CheckFileDescriptor);
+ if (listener->CheckFileDescriptor(listener) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to accept new peer");
+ // TODO: Set out of resource error
+ continue;
+ }
+ }
+
+ WINPR_ASSERT(listener->Close);
+ listener->Close(listener);
+ return rc;
+}
+
+void pf_server_stop(proxyServer* server)
+{
+
+ if (!server)
+ return;
+
+ /* signal main thread to stop and wait for the thread to exit */
+ SetEvent(server->stopEvent);
+}
+
+void pf_server_free(proxyServer* server)
+{
+ if (!server)
+ return;
+
+ pf_server_stop(server);
+
+ if (server->peer_list)
+ {
+ while (ArrayList_Count(server->peer_list) > 0)
+ {
+ /* pf_server_stop triggers the threads to shut down.
+ * loop here until all of them stopped.
+ *
+ * This must be done before ArrayList_Free otherwise the thread removal
+ * in pf_server_handle_peer will deadlock due to both threads trying to
+ * lock the list.
+ */
+ Sleep(100);
+ }
+ }
+ ArrayList_Free(server->peer_list);
+ freerdp_listener_free(server->listener);
+
+ if (server->stopEvent)
+ CloseHandle(server->stopEvent);
+
+ pf_server_config_free(server->config);
+ pf_modules_free(server->module);
+ free(server);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+}
+
+BOOL pf_server_add_module(proxyServer* server, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(ep);
+
+ return pf_modules_add(server->module, ep, userdata);
+}
diff --git a/server/proxy/pf_server.h b/server/proxy/pf_server.h
new file mode 100644
index 0000000..2ac84f2
--- /dev/null
+++ b/server/proxy/pf_server.h
@@ -0,0 +1,43 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 INT_FREERDP_SERVER_PROXY_SERVER_H
+#define INT_FREERDP_SERVER_PROXY_SERVER_H
+
+#include <winpr/collections.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+
+struct proxy_server
+{
+ proxyModule* module;
+ proxyConfig* config;
+
+ freerdp_listener* listener;
+ HANDLE stopEvent; /* an event used to signal the main thread to stop */
+ wArrayList* peer_list;
+};
+
+#endif /* INT_FREERDP_SERVER_PROXY_SERVER_H */
diff --git a/server/proxy/pf_update.c b/server/proxy/pf_update.c
new file mode 100644
index 0000000..e572810
--- /dev/null
+++ b/server/proxy/pf_update.c
@@ -0,0 +1,629 @@
+/**
+ * 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>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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/display.h>
+#include <freerdp/session.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_update.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("update")
+
+static BOOL pf_server_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->RefreshRect);
+ return pc->update->RefreshRect(pc, count, areas);
+}
+
+static BOOL pf_server_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->SuppressOutput);
+ return pc->update->SuppressOutput(pc, allow, area);
+}
+
+/* Proxy from PC to PS */
+
+/**
+ * This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas.
+ */
+static BOOL pf_client_begin_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BeginPaint);
+ WLog_DBG(TAG, "called");
+ return ps->update->BeginPaint(ps);
+}
+
+/**
+ * This function is called when the library completed composing a new
+ * frame. Read out the changed areas and blit them to your output device.
+ * The image buffer will have the format specified by gdi_init
+ */
+static BOOL pf_client_end_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->EndPaint);
+
+ WLog_DBG(TAG, "called");
+
+ /* proxy end paint */
+ if (!ps->update->EndPaint(ps))
+ return FALSE;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_END_PAINT, pdata, context))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_client_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmap)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BitmapUpdate);
+ WLog_DBG(TAG, "called");
+ return ps->update->BitmapUpdate(ps, bitmap);
+}
+
+static BOOL pf_client_desktop_resize(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->DesktopResize);
+ WINPR_ASSERT(context->settings);
+ WINPR_ASSERT(ps->settings);
+ WLog_DBG(TAG, "called");
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopHeight))
+ return FALSE;
+ return ps->update->DesktopResize(ps);
+}
+
+static BOOL pf_client_remote_monitors(rdpContext* context, UINT32 count,
+ const MONITOR_DEF* monitors)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WLog_DBG(TAG, "called");
+ return freerdp_display_send_monitor_layout(ps, count, monitors);
+}
+
+static BOOL pf_client_send_pointer_system(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer_system)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerSystem);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerSystem(ps, pointer_system);
+}
+
+static BOOL pf_client_send_pointer_position(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointerPosition)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerPosition);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerPosition(ps, pointerPosition);
+}
+
+static BOOL pf_client_send_pointer_color(rdpContext* context,
+ const POINTER_COLOR_UPDATE* pointer_color)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerColor);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerColor(ps, pointer_color);
+}
+
+static BOOL pf_client_send_pointer_large(rdpContext* context,
+ const POINTER_LARGE_UPDATE* pointer_large)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerLarge);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerLarge(ps, pointer_large);
+}
+
+static BOOL pf_client_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerNew);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerNew(ps, pointer_new);
+}
+
+static BOOL pf_client_send_pointer_cached(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer_cached)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerCached);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerCached(ps, pointer_cached);
+}
+
+static BOOL pf_client_save_session_info(rdpContext* context, UINT32 type, void* data)
+{
+ logon_info* logonInfo = NULL;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SaveSessionInfo);
+
+ WLog_DBG(TAG, "called");
+
+ switch (type)
+ {
+ case INFO_TYPE_LOGON:
+ case INFO_TYPE_LOGON_LONG:
+ {
+ logonInfo = (logon_info*)data;
+ PROXY_LOG_INFO(TAG, pc, "client logon info: Username: %s, Domain: %s",
+ logonInfo->username, logonInfo->domain);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ps->update->SaveSessionInfo(ps, type, data);
+}
+
+static BOOL pf_client_server_status_info(rdpContext* context, UINT32 status)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->ServerStatusInfo);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->ServerStatusInfo(ps, status);
+}
+
+static BOOL pf_client_set_keyboard_indicators(rdpContext* context, UINT16 led_flags)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardIndicators);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardIndicators(ps, led_flags);
+}
+
+static BOOL pf_client_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardImeStatus);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardImeStatus(ps, imeId, imeState, imeConvMode);
+}
+
+static BOOL pf_client_window_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCreate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowUpdate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowIcon(ps, orderInfo, windowIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCachedIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCachedIcon(ps, orderInfo, windowCachedIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconCreate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconUpdate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->MonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->MonitoredDesktop(ps, orderInfo, monitoredDesktop);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NonMonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NonMonitoredDesktop(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+void pf_server_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->RefreshRect = pf_server_refresh_rect;
+ update->SuppressOutput = pf_server_suppress_output;
+}
+
+void pf_client_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->BeginPaint = pf_client_begin_paint;
+ update->EndPaint = pf_client_end_paint;
+ update->BitmapUpdate = pf_client_bitmap_update;
+ update->DesktopResize = pf_client_desktop_resize;
+ update->RemoteMonitors = pf_client_remote_monitors;
+ update->SaveSessionInfo = pf_client_save_session_info;
+ update->ServerStatusInfo = pf_client_server_status_info;
+ update->SetKeyboardIndicators = pf_client_set_keyboard_indicators;
+ update->SetKeyboardImeStatus = pf_client_set_keyboard_ime_status;
+
+ /* Rail window updates */
+ update->window->WindowCreate = pf_client_window_create;
+ update->window->WindowUpdate = pf_client_window_update;
+ update->window->WindowIcon = pf_client_window_icon;
+ update->window->WindowCachedIcon = pf_client_window_cached_icon;
+ update->window->WindowDelete = pf_client_window_delete;
+ update->window->NotifyIconCreate = pf_client_notify_icon_create;
+ update->window->NotifyIconUpdate = pf_client_notify_icon_update;
+ update->window->NotifyIconDelete = pf_client_notify_icon_delete;
+ update->window->MonitoredDesktop = pf_client_monitored_desktop;
+ update->window->NonMonitoredDesktop = pf_client_non_monitored_desktop;
+
+ /* Pointer updates */
+ update->pointer->PointerSystem = pf_client_send_pointer_system;
+ update->pointer->PointerPosition = pf_client_send_pointer_position;
+ update->pointer->PointerColor = pf_client_send_pointer_color;
+ update->pointer->PointerLarge = pf_client_send_pointer_large;
+ update->pointer->PointerNew = pf_client_send_pointer_new;
+ update->pointer->PointerCached = pf_client_send_pointer_cached;
+}
diff --git a/server/proxy/pf_update.h b/server/proxy/pf_update.h
new file mode 100644
index 0000000..81a9c32
--- /dev/null
+++ b/server/proxy/pf_update.h
@@ -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_PFUPDATE_H
+#define FREERDP_SERVER_PROXY_PFUPDATE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+void pf_server_register_update_callbacks(rdpUpdate* update);
+void pf_client_register_update_callbacks(rdpUpdate* update);
+
+#endif /* FREERDP_SERVER_PROXY_PFUPDATE_H */
diff --git a/server/proxy/pf_utils.c b/server/proxy/pf_utils.c
new file mode 100644
index 0000000..f8c17af
--- /dev/null
+++ b/server/proxy/pf_utils.c
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * * Copyright 2021 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 <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("utils")
+
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name)
+{
+ pf_utils_channel_mode rc = PF_UTILS_CHANNEL_NOT_HANDLED;
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(name);
+
+ for (size_t i = 0; i < config->InterceptCount; i++)
+ {
+ const char* channel_name = config->Intercept[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ rc = PF_UTILS_CHANNEL_INTERCEPT;
+ goto end;
+ }
+ }
+
+ for (size_t i = 0; i < config->PassthroughCount; i++)
+ {
+ const char* channel_name = config->Passthrough[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_BLOCK;
+ else
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+ }
+ else if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+
+end:
+ WLog_DBG(TAG, "%s -> %s", name, pf_utils_channel_mode_string(rc));
+ return rc;
+}
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+
+ /* TODO: For the time being only passthrough mode is supported. */
+ return TRUE;
+}
+
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode)
+{
+ switch (mode)
+ {
+ case PF_UTILS_CHANNEL_BLOCK:
+ return "blocked";
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ return "passthrough";
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ return "intercepted";
+ case PF_UTILS_CHANNEL_NOT_HANDLED:
+ default:
+ return "ignored";
+ }
+}
diff --git a/server/proxy/pf_utils.h b/server/proxy/pf_utils.h
new file mode 100644
index 0000000..0e899e9
--- /dev/null
+++ b/server/proxy/pf_utils.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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_SERVER_PROXY_PFUTILS_H
+#define FREERDP_SERVER_PROXY_PFUTILS_H
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+/**
+ * @brief pf_utils_channel_is_passthrough Checks of a channel identified by 'name'
+ * should be handled as passthrough.
+ *
+ * @param config The proxy configuration to check against. Must NOT be NULL.
+ * @param name The name of the channel. Must NOT be NULL.
+ * @return -1 if the channel is not handled, 0 if the channel should be ignored,
+ * 1 if the channel should be passed, 2 the channel will be intercepted
+ * e.g. proxy client and server are termination points and data passed
+ * between.
+ */
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name);
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode);
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config);
+
+#endif /* FREERDP_SERVER_PROXY_PFUTILS_H */
diff --git a/server/proxy/proxy_modules.h b/server/proxy/proxy_modules.h
new file mode 100644
index 0000000..d5883a5
--- /dev/null
+++ b/server/proxy/proxy_modules.h
@@ -0,0 +1,100 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * 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_MODULES_H
+#define FREERDP_SERVER_PROXY_MODULES_H
+
+#include <winpr/wtypes.h>
+#include <winpr/collections.h>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+typedef enum
+{
+ FILTER_TYPE_KEYBOARD, /* proxyKeyboardEventInfo */
+ FILTER_TYPE_UNICODE, /* proxyUnicodeEventInfo */
+ FILTER_TYPE_MOUSE, /* proxyMouseEventInfo */
+ FILTER_TYPE_MOUSE_EX, /* proxyMouseExEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, /* proxyFetchTargetEventInfo */
+ FILTER_TYPE_SERVER_PEER_LOGON, /* proxyServerPeerLogon */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+
+ FILTER_TYPE_STATIC_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_DYN_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_INTERCEPT_CHANNEL, /* proxyDynChannelInterceptData */
+ FILTER_LAST
+} PF_FILTER_TYPE;
+
+typedef enum
+{
+ HOOK_TYPE_CLIENT_INIT_CONNECT,
+ HOOK_TYPE_CLIENT_UNINIT_CONNECT,
+ HOOK_TYPE_CLIENT_PRE_CONNECT,
+ HOOK_TYPE_CLIENT_POST_CONNECT,
+ HOOK_TYPE_CLIENT_POST_DISCONNECT,
+ HOOK_TYPE_CLIENT_REDIRECT,
+ HOOK_TYPE_CLIENT_VERIFY_X509,
+ HOOK_TYPE_CLIENT_LOGIN_FAILURE,
+ HOOK_TYPE_CLIENT_END_PAINT,
+ HOOK_TYPE_CLIENT_LOAD_CHANNELS,
+
+ HOOK_TYPE_SERVER_POST_CONNECT,
+ HOOK_TYPE_SERVER_ACTIVATE,
+ HOOK_TYPE_SERVER_CHANNELS_INIT,
+ HOOK_TYPE_SERVER_CHANNELS_FREE,
+ HOOK_TYPE_SERVER_SESSION_END,
+ HOOK_TYPE_SERVER_SESSION_INITIALIZE,
+ HOOK_TYPE_SERVER_SESSION_STARTED,
+
+ HOOK_LAST
+} PF_HOOK_TYPE;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count);
+
+ /**
+ * @brief pf_modules_add Registers a new plugin
+ * @param ep A module entry point function, must NOT be NULL
+ * @return TRUE for success, FALSE otherwise
+ */
+ BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata);
+
+ BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name);
+ void pf_modules_list_loaded_plugins(proxyModule* module);
+
+ BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata,
+ void* param);
+ BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata,
+ void* custom);
+
+ void pf_modules_free(proxyModule* module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_MODULES_H */
diff --git a/server/shadow/CMakeLists.txt b/server/shadow/CMakeLists.txt
new file mode 100644
index 0000000..72adcca
--- /dev/null
+++ b/server/shadow/CMakeLists.txt
@@ -0,0 +1,245 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Shadow Server cmake build script
+#
+# Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+
+# freerdp-shadow library
+
+set(MODULE_NAME "freerdp-shadow")
+
+set(SRCS
+ shadow_client.c
+ shadow_client.h
+ shadow_lobby.c
+ shadow_lobby.h
+ shadow_input.c
+ shadow_input.h
+ shadow_screen.c
+ shadow_screen.h
+ shadow_surface.c
+ shadow_surface.h
+ shadow_encoder.c
+ shadow_encoder.h
+ shadow_capture.c
+ shadow_capture.h
+ shadow_channels.c
+ shadow_channels.h
+ shadow_encomsp.c
+ shadow_encomsp.h
+ shadow_remdesk.c
+ shadow_remdesk.h
+ shadow_rdpsnd.c
+ shadow_rdpsnd.h
+ shadow_audin.c
+ shadow_audin.h
+ shadow_rdpgfx.c
+ shadow_rdpgfx.h
+ shadow_subsystem.c
+ shadow_subsystem.h
+ shadow_mcevent.c
+ shadow_mcevent.h
+ shadow_server.c
+ shadow.h)
+
+if (NOT FREERDP_UNIFIED_BUILD)
+ find_package(rdtk 0 REQUIRED)
+ include_directories(${RDTK_INCLUDE_DIR})
+else()
+ if (NOT WITH_RDTK)
+ message(FATAL_ERROR "-DWITH_RDTK=ON is required for unified FreeRDP build with shadow server")
+ endif()
+ include_directories(${PROJECT_SOURCE_DIR}/rdtk/include)
+ include_directories(${PROJECT_BINARY_DIR}/rdtk/include)
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_PATCH 0)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ freerdp
+ freerdp-server
+ winpr
+ winpr-tools
+ rdtk
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_VERSION_MAJOR})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+# subsystem library
+
+set(MODULE_NAME "freerdp-shadow-subsystem")
+
+set(SRCS
+ shadow_subsystem_builtin.c)
+
+if(WIN32)
+ add_subdirectory(Win)
+elseif(NOT APPLE)
+ add_subdirectory(X11)
+elseif(APPLE AND NOT IOS)
+ add_subdirectory(Mac)
+endif()
+
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ freerdp-shadow-subsystem-impl
+ freerdp-shadow
+ freerdp
+ winpr
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+if (NOT BUILD_SHARED_LIBS)
+ install(TARGETS freerdp-shadow-subsystem-impl
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ EXPORT FreeRDP-ShadowTargets
+ )
+endif()
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+# command-line executable
+
+set(MODULE_NAME "freerdp-shadow-cli")
+
+set(SRCS
+ shadow.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_executable(${MODULE_NAME} ${SRCS})
+
+set(MANPAGE_NAME "${MODULE_NAME}")
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ set(MANPAGE_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+endif()
+
+list(APPEND LIBS freerdp-shadow-subsystem freerdp-shadow freerdp winpr)
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow")
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-shadow.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc @ONLY)
+configure_file(freerdp-shadow-cli.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-shadow)
+
+SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Shadow${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ShadowConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake
+ DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ShadowTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR})
diff --git a/server/shadow/FreeRDP-ShadowConfig.cmake.in b/server/shadow/FreeRDP-ShadowConfig.cmake.in
new file mode 100644
index 0000000..9b6f24c
--- /dev/null
+++ b/server/shadow/FreeRDP-ShadowConfig.cmake.in
@@ -0,0 +1,14 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+find_dependency(FreeRDP-Server @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Shadow_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Shadow_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Shadow_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Shadow_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ShadowTargets.cmake")
diff --git a/server/shadow/Mac/CMakeLists.txt b/server/shadow/Mac/CMakeLists.txt
new file mode 100644
index 0000000..20c9a7b
--- /dev/null
+++ b/server/shadow/Mac/CMakeLists.txt
@@ -0,0 +1,31 @@
+
+include (WarnUnmaintained)
+warn_unmaintained("mac shadow server subsystem")
+
+find_library(IOKIT IOKit REQUIRED)
+find_library(IOSURFACE IOSurface REQUIRED)
+find_library(CARBON Carbon REQUIRED)
+find_package(PAM)
+
+set(LIBS
+ ${IOKIT}
+ ${IOSURFACE}
+ ${CARBON}
+)
+
+if(PAM_FOUND)
+ add_definitions(-DWITH_PAM)
+ include_directories(${PAM_INCLUDE_DIR})
+ list(APPEND LIBS ${PAM_LIBRARY})
+else()
+ message("building without PAM authentication support")
+endif()
+
+add_definitions(-DWITH_SHADOW_MAC)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ mac_shadow.h
+ mac_shadow.c
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ ${LIBS}
+)
diff --git a/server/shadow/Mac/mac_shadow.c b/server/shadow/Mac/mac_shadow.c
new file mode 100644
index 0000000..ba6d2b0
--- /dev/null
+++ b/server/shadow/Mac/mac_shadow.c
@@ -0,0 +1,680 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/input.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/server/server-common.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/log.h>
+
+#include "mac_shadow.h"
+
+#define TAG SERVER_TAG("shadow.mac")
+
+static macShadowSubsystem* g_Subsystem = NULL;
+
+static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+ DWORD vkcode;
+ DWORD keycode;
+ BOOL extended;
+ CGEventRef kbdEvent;
+ CGEventSourceRef source;
+ extended = (flags & KBD_FLAGS_EXTENDED) ? TRUE : FALSE;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ if (extended)
+ code |= KBDEXT;
+
+ vkcode = GetVirtualKeyCodeFromVirtualScanCode(code, 4);
+
+ if (extended)
+ vkcode |= KBDEXT;
+
+ keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE);
+
+ source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+
+ if (flags & KBD_FLAGS_DOWN)
+ {
+ kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, TRUE);
+ CGEventPost(kCGHIDEventTap, kbdEvent);
+ CFRelease(kbdEvent);
+ }
+ else if (flags & KBD_FLAGS_RELEASE)
+ {
+ kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, FALSE);
+ CGEventPost(kCGHIDEventTap, kbdEvent);
+ CFRelease(kbdEvent);
+ }
+
+ CFRelease(source);
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+ macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
+ UINT32 scrollX = 0;
+ UINT32 scrollY = 0;
+ CGWheelCount wheelCount = 2;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ scrollY = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ {
+ scrollY = -(flags & WheelRotationMask) / 392;
+ }
+ else
+ {
+ scrollY = (flags & WheelRotationMask) / 120;
+ }
+
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventRef scroll = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine,
+ wheelCount, scrollY, scrollX);
+ CGEventPost(kCGHIDEventTap, scroll);
+ CFRelease(scroll);
+ CFRelease(source);
+ }
+ else
+ {
+ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
+ CGEventType mouseType = kCGEventNull;
+ CGMouseButton mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ if (mac->mouseDownLeft)
+ mouseType = kCGEventLeftMouseDragged;
+ else if (mac->mouseDownRight)
+ mouseType = kCGEventRightMouseDragged;
+ else if (mac->mouseDownOther)
+ mouseType = kCGEventOtherMouseDragged;
+ else
+ mouseType = kCGEventMouseMoved;
+
+ CGEventRef move =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, move);
+ CFRelease(move);
+ }
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ mouseButton = kCGMouseButtonLeft;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventLeftMouseDown;
+ mac->mouseDownLeft = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventLeftMouseUp;
+ mac->mouseDownLeft = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ mouseButton = kCGMouseButtonRight;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventRightMouseDown;
+ mac->mouseDownRight = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventRightMouseUp;
+ mac->mouseDownRight = FALSE;
+ }
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ mouseButton = kCGMouseButtonCenter;
+
+ if (flags & PTR_FLAGS_DOWN)
+ {
+ mouseType = kCGEventOtherMouseDown;
+ mac->mouseDownOther = TRUE;
+ }
+ else
+ {
+ mouseType = kCGEventOtherMouseUp;
+ mac->mouseDownOther = FALSE;
+ }
+ }
+
+ CGEventRef mouseEvent =
+ CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
+ CGEventPost(kCGHIDEventTap, mouseEvent);
+ CFRelease(mouseEvent);
+ CFRelease(source);
+ }
+
+ return TRUE;
+}
+
+static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+ if (!subsystem || !client)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int mac_shadow_detect_monitors(macShadowSubsystem* subsystem)
+{
+ size_t wide, high;
+ MONITOR_DEF* monitor;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
+ subsystem->pixelWidth = CGDisplayModeGetPixelWidth(mode);
+ subsystem->pixelHeight = CGDisplayModeGetPixelHeight(mode);
+ wide = CGDisplayPixelsWide(displayId);
+ high = CGDisplayPixelsHigh(displayId);
+ CGDisplayModeRelease(mode);
+ subsystem->retina = ((subsystem->pixelWidth / wide) == 2) ? TRUE : FALSE;
+
+ if (subsystem->retina)
+ {
+ subsystem->width = wide;
+ subsystem->height = high;
+ }
+ else
+ {
+ subsystem->width = subsystem->pixelWidth;
+ subsystem->height = subsystem->pixelHeight;
+ }
+
+ subsystem->common.numMonitors = 1;
+ monitor = &(subsystem->common.monitors[0]);
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = subsystem->width;
+ monitor->bottom = subsystem->height;
+ monitor->flags = 1;
+ return 1;
+}
+
+static int mac_shadow_capture_start(macShadowSubsystem* subsystem)
+{
+ CGError err;
+ err = CGDisplayStreamStart(subsystem->stream);
+
+ if (err != kCGErrorSuccess)
+ return -1;
+
+ return 1;
+}
+
+static int mac_shadow_capture_stop(macShadowSubsystem* subsystem)
+{
+ CGError err;
+ err = CGDisplayStreamStop(subsystem->stream);
+
+ if (err != kCGErrorSuccess)
+ return -1;
+
+ return 1;
+}
+
+static int mac_shadow_capture_get_dirty_region(macShadowSubsystem* subsystem)
+{
+ size_t numRects;
+ const CGRect* rects;
+ RECTANGLE_16 invalidRect;
+ rdpShadowSurface* surface = subsystem->common.server->surface;
+ rects = CGDisplayStreamUpdateGetRects(subsystem->lastUpdate, kCGDisplayStreamUpdateDirtyRects,
+ &numRects);
+
+ if (!numRects)
+ return -1;
+
+ for (size_t index = 0; index < numRects; index++)
+ {
+ invalidRect.left = (UINT16)rects[index].origin.x;
+ invalidRect.top = (UINT16)rects[index].origin.y;
+ invalidRect.right = invalidRect.left + (UINT16)rects[index].size.width;
+ invalidRect.bottom = invalidRect.top + (UINT16)rects[index].size.height;
+
+ if (subsystem->retina)
+ {
+ /* scale invalid rect */
+ invalidRect.left /= 2;
+ invalidRect.top /= 2;
+ invalidRect.right /= 2;
+ invalidRect.bottom /= 2;
+ }
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ return 0;
+}
+
+static int freerdp_image_copy_from_retina(BYTE* pDstData, DWORD DstFormat, int nDstStep, int nXDst,
+ int nYDst, int nWidth, int nHeight, BYTE* pSrcData,
+ int nSrcStep, int nXSrc, int nYSrc)
+{
+ BYTE* pSrcPixel;
+ BYTE* pDstPixel;
+ int nSrcPad;
+ int nDstPad;
+ int srcBitsPerPixel;
+ int srcBytesPerPixel;
+ int dstBitsPerPixel;
+ int dstBytesPerPixel;
+ srcBitsPerPixel = 24;
+ srcBytesPerPixel = 8;
+
+ if (nSrcStep < 0)
+ nSrcStep = srcBytesPerPixel * nWidth;
+
+ dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat);
+ dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat);
+
+ if (nDstStep < 0)
+ nDstStep = dstBytesPerPixel * nWidth;
+
+ nSrcPad = (nSrcStep - (nWidth * srcBytesPerPixel));
+ nDstPad = (nDstStep - (nWidth * dstBytesPerPixel));
+ pSrcPixel = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)];
+ pDstPixel = &pDstData[(nYDst * nDstStep) + (nXDst * 4)];
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ for (int x = 0; x < nWidth; x++)
+ {
+ UINT32 R, G, B;
+ UINT32 color;
+ /* simple box filter scaling, could be improved with better algorithm */
+ B = pSrcPixel[0] + pSrcPixel[4] + pSrcPixel[nSrcStep + 0] + pSrcPixel[nSrcStep + 4];
+ G = pSrcPixel[1] + pSrcPixel[5] + pSrcPixel[nSrcStep + 1] + pSrcPixel[nSrcStep + 5];
+ R = pSrcPixel[2] + pSrcPixel[6] + pSrcPixel[nSrcStep + 2] + pSrcPixel[nSrcStep + 6];
+ pSrcPixel += 8;
+ color = FreeRDPGetColor(DstFormat, R >> 2, G >> 2, B >> 2, 0xFF);
+ FreeRDPWriteColor(pDstPixel, DstFormat, color);
+ pDstPixel += dstBytesPerPixel;
+ }
+
+ pSrcPixel = &pSrcPixel[nSrcPad + nSrcStep];
+ pDstPixel = &pDstPixel[nDstPad];
+ }
+
+ return 1;
+}
+
+static void (^mac_capture_stream_handler)(
+ CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef,
+ CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status, uint64_t displayTime,
+ IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
+ int x, y;
+ int count;
+ int width;
+ int height;
+ int nSrcStep;
+ BOOL empty;
+ BYTE* pSrcData;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents;
+ macShadowSubsystem* subsystem = g_Subsystem;
+ rdpShadowServer* server = subsystem->common.server;
+ rdpShadowSurface* surface = server->surface;
+ count = ArrayList_Count(server->clients);
+
+ if (count < 1)
+ return;
+
+ EnterCriticalSection(&(surface->lock));
+ mac_shadow_capture_get_dirty_region(subsystem);
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->width;
+ surfaceRect.bottom = surface->height;
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+ empty = region16_is_empty(&(surface->invalidRegion));
+ LeaveCriticalSection(&(surface->lock));
+
+ if (!empty)
+ {
+ extents = region16_extents(&(surface->invalidRegion));
+ x = extents->left;
+ y = extents->top;
+ width = extents->right - extents->left;
+ height = extents->bottom - extents->top;
+ IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ pSrcData = (BYTE*)IOSurfaceGetBaseAddress(frameSurface);
+ nSrcStep = (int)IOSurfaceGetBytesPerRow(frameSurface);
+
+ if (subsystem->retina)
+ {
+ freerdp_image_copy_from_retina(surface->data, surface->format, surface->scanline, x, y,
+ width, height, pSrcData, nSrcStep, x, y);
+ }
+ else
+ {
+ freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height,
+ pSrcData, PIXEL_FORMAT_BGRX32, nSrcStep, x, y, NULL,
+ FREERDP_FLIP_NONE);
+ }
+ LeaveCriticalSection(&(surface->lock));
+
+ IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL);
+ ArrayList_Lock(server->clients);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->common);
+
+ if (count == 1)
+ {
+ rdpShadowClient* client;
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0);
+
+ if (client)
+ {
+ subsystem->common.captureFrameRate = shadow_encoder_preferred_fps(client->encoder);
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ EnterCriticalSection(&(surface->lock));
+ region16_clear(&(surface->invalidRegion));
+ LeaveCriticalSection(&(surface->lock));
+ }
+
+ if (status != kCGDisplayStreamFrameStatusFrameComplete)
+ {
+ switch (status)
+ {
+ case kCGDisplayStreamFrameStatusFrameIdle:
+ break;
+
+ case kCGDisplayStreamFrameStatusStopped:
+ break;
+
+ case kCGDisplayStreamFrameStatusFrameBlank:
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (!subsystem->lastUpdate)
+ {
+ CFRetain(updateRef);
+ subsystem->lastUpdate = updateRef;
+ }
+ else
+ {
+ CGDisplayStreamUpdateRef tmpRef = subsystem->lastUpdate;
+ subsystem->lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef);
+ CFRelease(tmpRef);
+ }
+};
+
+static int mac_shadow_capture_init(macShadowSubsystem* subsystem)
+{
+ void* keys[2];
+ void* values[2];
+ CFDictionaryRef opts;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ subsystem->captureQueue = dispatch_queue_create("mac.shadow.capture", NULL);
+ keys[0] = (void*)kCGDisplayStreamShowCursor;
+ values[0] = (void*)kCFBooleanFalse;
+ opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1,
+ NULL, NULL);
+ subsystem->stream = CGDisplayStreamCreateWithDispatchQueue(
+ displayId, subsystem->pixelWidth, subsystem->pixelHeight, 'BGRA', opts,
+ subsystem->captureQueue, mac_capture_stream_handler);
+ CFRelease(opts);
+ return 1;
+}
+
+static int mac_shadow_screen_grab(macShadowSubsystem* subsystem)
+{
+ return 1;
+}
+
+static int mac_shadow_subsystem_process_message(macShadowSubsystem* subsystem, wMessage* message)
+{
+ rdpShadowServer* server = subsystem->common.server;
+ rdpShadowSurface* surface = server->surface;
+
+ switch (message->id)
+ {
+ case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
+ EnterCriticalSection(&(surface->lock));
+ shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem);
+ LeaveCriticalSection(&(surface->lock));
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ if (message->Free)
+ message->Free(message);
+
+ return 1;
+}
+
+static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)arg;
+ DWORD status;
+ DWORD nCount;
+ UINT64 cTime;
+ DWORD dwTimeout;
+ DWORD dwInterval;
+ UINT64 frameTime;
+ HANDLE events[32];
+ wMessage message;
+ wMessagePipe* MsgPipe;
+ MsgPipe = subsystem->common.MsgPipe;
+ nCount = 0;
+ events[nCount++] = MessageQueue_Event(MsgPipe->In);
+ subsystem->common.captureFrameRate = 16;
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ cTime = GetTickCount64();
+ dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime;
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0)
+ {
+ if (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ mac_shadow_subsystem_process_message(subsystem, &message);
+ }
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ mac_shadow_screen_grab(subsystem);
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static UINT32 mac_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ int index;
+ size_t wide, high;
+ UINT32 numMonitors = 0;
+ MONITOR_DEF* monitor;
+ CGDirectDisplayID displayId;
+ displayId = CGMainDisplayID();
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId);
+ wide = CGDisplayPixelsWide(displayId);
+ high = CGDisplayPixelsHigh(displayId);
+ CGDisplayModeRelease(mode);
+ index = 0;
+ numMonitors = 1;
+ monitor = &monitors[index];
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = (int)wide;
+ monitor->bottom = (int)high;
+ monitor->flags = 1;
+ return numMonitors;
+}
+
+static int mac_shadow_subsystem_init(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ g_Subsystem = subsystem;
+
+ mac_shadow_detect_monitors(subsystem);
+ mac_shadow_capture_init(subsystem);
+ return 1;
+}
+
+static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->lastUpdate)
+ {
+ CFRelease(subsystem->lastUpdate);
+ subsystem->lastUpdate = NULL;
+ }
+
+ return 1;
+}
+
+static int mac_shadow_subsystem_start(rdpShadowSubsystem* rdpsubsystem)
+{
+ macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem;
+ HANDLE thread;
+
+ if (!subsystem)
+ return -1;
+
+ mac_shadow_capture_start(subsystem);
+
+ if (!(thread = CreateThread(NULL, 0, mac_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return -1;
+
+ return 1;
+}
+
+static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ mac_shadow_subsystem_uninit(subsystem);
+ free(subsystem);
+}
+
+static rdpShadowSubsystem* mac_shadow_subsystem_new(void)
+{
+ macShadowSubsystem* subsystem = calloc(1, sizeof(macShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+ subsystem->common.SynchronizeEvent = mac_shadow_input_synchronize_event;
+ subsystem->common.KeyboardEvent = mac_shadow_input_keyboard_event;
+ subsystem->common.UnicodeKeyboardEvent = mac_shadow_input_unicode_keyboard_event;
+ subsystem->common.MouseEvent = mac_shadow_input_mouse_event;
+ subsystem->common.ExtendedMouseEvent = mac_shadow_input_extended_mouse_event;
+ return &subsystem->common;
+}
+
+FREERDP_API const char* ShadowSubsystemName(void)
+{
+ return "Mac";
+}
+
+FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ char name[] = "mac shadow subsystem";
+ char* arg[] = { name };
+
+ freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg);
+ pEntryPoints->New = mac_shadow_subsystem_new;
+ pEntryPoints->Free = mac_shadow_subsystem_free;
+ pEntryPoints->Init = mac_shadow_subsystem_init;
+ pEntryPoints->Uninit = mac_shadow_subsystem_uninit;
+ pEntryPoints->Start = mac_shadow_subsystem_start;
+ pEntryPoints->Stop = mac_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = mac_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/Mac/mac_shadow.h b/server/shadow/Mac/mac_shadow.h
new file mode 100644
index 0000000..2c31572
--- /dev/null
+++ b/server/shadow/Mac/mac_shadow.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_MAC_SHADOW_H
+#define FREERDP_SERVER_SHADOW_MAC_SHADOW_H
+
+#include <freerdp/server/shadow.h>
+
+typedef struct mac_shadow_subsystem macShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <dispatch/dispatch.h>
+#include <IOKit/IOKitLib.h>
+#include <IOSurface/IOSurface.h>
+#include <CoreVideo/CoreVideo.h>
+#include <CoreGraphics/CoreGraphics.h>
+
+struct mac_shadow_subsystem
+{
+ rdpShadowSubsystem common;
+
+ int width;
+ int height;
+ BOOL retina;
+ int pixelWidth;
+ int pixelHeight;
+ BOOL mouseDownLeft;
+ BOOL mouseDownRight;
+ BOOL mouseDownOther;
+ CGDisplayStreamRef stream;
+ dispatch_queue_t captureQueue;
+ CGDisplayStreamUpdateRef lastUpdate;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_MAC_SHADOW_H */
diff --git a/server/shadow/Win/CMakeLists.txt b/server/shadow/Win/CMakeLists.txt
new file mode 100644
index 0000000..f8a920e
--- /dev/null
+++ b/server/shadow/Win/CMakeLists.txt
@@ -0,0 +1,20 @@
+
+include (WarnUnmaintained)
+warn_unmaintained("windows shadow server subsystem")
+
+add_definitions(-DWITH_SHADOW_WIN)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ win_dxgi.c
+ win_dxgi.h
+ win_rdp.c
+ win_rdp.h
+ win_shadow.c
+ win_shadow.h
+ win_wds.c
+ win_wds.h
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ freerdp-client
+ freerdp
+ winpr
+)
diff --git a/server/shadow/Win/win_dxgi.c b/server/shadow/Win/win_dxgi.c
new file mode 100644
index 0000000..3c25a00
--- /dev/null
+++ b/server/shadow/Win/win_dxgi.c
@@ -0,0 +1,794 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <freerdp/log.h>
+
+#include "win_dxgi.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+#ifdef WITH_DXGI_1_2
+
+static D3D_DRIVER_TYPE DriverTypes[] = {
+ D3D_DRIVER_TYPE_HARDWARE,
+ D3D_DRIVER_TYPE_WARP,
+ D3D_DRIVER_TYPE_REFERENCE,
+};
+
+static UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
+
+static D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 };
+
+static UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
+
+static HMODULE d3d11_module = NULL;
+
+typedef HRESULT(WINAPI* fnD3D11CreateDevice)(IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType,
+ HMODULE Software, UINT Flags,
+ CONST D3D_FEATURE_LEVEL* pFeatureLevels,
+ UINT FeatureLevels, UINT SDKVersion,
+ ID3D11Device** ppDevice,
+ D3D_FEATURE_LEVEL* pFeatureLevel,
+ ID3D11DeviceContext** ppImmediateContext);
+
+static fnD3D11CreateDevice pfnD3D11CreateDevice = NULL;
+
+#undef DEFINE_GUID
+#define INITGUID
+
+#include <initguid.h>
+
+/* d3d11.h GUIDs */
+
+DEFINE_GUID(IID_ID3D11DeviceChild, 0x1841e5c8, 0x16b0, 0x489b, 0xbc, 0xc8, 0x44, 0xcf, 0xb0, 0xd5,
+ 0xde, 0xae);
+DEFINE_GUID(IID_ID3D11DepthStencilState, 0x03823efb, 0x8d8f, 0x4e1c, 0x9a, 0xa2, 0xf6, 0x4b, 0xb2,
+ 0xcb, 0xfd, 0xf1);
+DEFINE_GUID(IID_ID3D11BlendState, 0x75b68faa, 0x347d, 0x4159, 0x8f, 0x45, 0xa0, 0x64, 0x0f, 0x01,
+ 0xcd, 0x9a);
+DEFINE_GUID(IID_ID3D11RasterizerState, 0x9bb4ab81, 0xab1a, 0x4d8f, 0xb5, 0x06, 0xfc, 0x04, 0x20,
+ 0x0b, 0x6e, 0xe7);
+DEFINE_GUID(IID_ID3D11Resource, 0xdc8e63f3, 0xd12b, 0x4952, 0xb4, 0x7b, 0x5e, 0x45, 0x02, 0x6a,
+ 0x86, 0x2d);
+DEFINE_GUID(IID_ID3D11Buffer, 0x48570b85, 0xd1ee, 0x4fcd, 0xa2, 0x50, 0xeb, 0x35, 0x07, 0x22, 0xb0,
+ 0x37);
+DEFINE_GUID(IID_ID3D11Texture1D, 0xf8fb5c27, 0xc6b3, 0x4f75, 0xa4, 0xc8, 0x43, 0x9a, 0xf2, 0xef,
+ 0x56, 0x4c);
+DEFINE_GUID(IID_ID3D11Texture2D, 0x6f15aaf2, 0xd208, 0x4e89, 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3,
+ 0x4f, 0x9c);
+DEFINE_GUID(IID_ID3D11Texture3D, 0x037e866e, 0xf56d, 0x4357, 0xa8, 0xaf, 0x9d, 0xab, 0xbe, 0x6e,
+ 0x25, 0x0e);
+DEFINE_GUID(IID_ID3D11View, 0x839d1216, 0xbb2e, 0x412b, 0xb7, 0xf4, 0xa9, 0xdb, 0xeb, 0xe0, 0x8e,
+ 0xd1);
+DEFINE_GUID(IID_ID3D11ShaderResourceView, 0xb0e06fe0, 0x8192, 0x4e1a, 0xb1, 0xca, 0x36, 0xd7, 0x41,
+ 0x47, 0x10, 0xb2);
+DEFINE_GUID(IID_ID3D11RenderTargetView, 0xdfdba067, 0x0b8d, 0x4865, 0x87, 0x5b, 0xd7, 0xb4, 0x51,
+ 0x6c, 0xc1, 0x64);
+DEFINE_GUID(IID_ID3D11DepthStencilView, 0x9fdac92a, 0x1876, 0x48c3, 0xaf, 0xad, 0x25, 0xb9, 0x4f,
+ 0x84, 0xa9, 0xb6);
+DEFINE_GUID(IID_ID3D11UnorderedAccessView, 0x28acf509, 0x7f5c, 0x48f6, 0x86, 0x11, 0xf3, 0x16, 0x01,
+ 0x0a, 0x63, 0x80);
+DEFINE_GUID(IID_ID3D11VertexShader, 0x3b301d64, 0xd678, 0x4289, 0x88, 0x97, 0x22, 0xf8, 0x92, 0x8b,
+ 0x72, 0xf3);
+DEFINE_GUID(IID_ID3D11HullShader, 0x8e5c6061, 0x628a, 0x4c8e, 0x82, 0x64, 0xbb, 0xe4, 0x5c, 0xb3,
+ 0xd5, 0xdd);
+DEFINE_GUID(IID_ID3D11DomainShader, 0xf582c508, 0x0f36, 0x490c, 0x99, 0x77, 0x31, 0xee, 0xce, 0x26,
+ 0x8c, 0xfa);
+DEFINE_GUID(IID_ID3D11GeometryShader, 0x38325b96, 0xeffb, 0x4022, 0xba, 0x02, 0x2e, 0x79, 0x5b,
+ 0x70, 0x27, 0x5c);
+DEFINE_GUID(IID_ID3D11PixelShader, 0xea82e40d, 0x51dc, 0x4f33, 0x93, 0xd4, 0xdb, 0x7c, 0x91, 0x25,
+ 0xae, 0x8c);
+DEFINE_GUID(IID_ID3D11ComputeShader, 0x4f5b196e, 0xc2bd, 0x495e, 0xbd, 0x01, 0x1f, 0xde, 0xd3, 0x8e,
+ 0x49, 0x69);
+DEFINE_GUID(IID_ID3D11InputLayout, 0xe4819ddc, 0x4cf0, 0x4025, 0xbd, 0x26, 0x5d, 0xe8, 0x2a, 0x3e,
+ 0x07, 0xb7);
+DEFINE_GUID(IID_ID3D11SamplerState, 0xda6fea51, 0x564c, 0x4487, 0x98, 0x10, 0xf0, 0xd0, 0xf9, 0xb4,
+ 0xe3, 0xa5);
+DEFINE_GUID(IID_ID3D11Asynchronous, 0x4b35d0cd, 0x1e15, 0x4258, 0x9c, 0x98, 0x1b, 0x13, 0x33, 0xf6,
+ 0xdd, 0x3b);
+DEFINE_GUID(IID_ID3D11Query, 0xd6c00747, 0x87b7, 0x425e, 0xb8, 0x4d, 0x44, 0xd1, 0x08, 0x56, 0x0a,
+ 0xfd);
+DEFINE_GUID(IID_ID3D11Predicate, 0x9eb576dd, 0x9f77, 0x4d86, 0x81, 0xaa, 0x8b, 0xab, 0x5f, 0xe4,
+ 0x90, 0xe2);
+DEFINE_GUID(IID_ID3D11Counter, 0x6e8c49fb, 0xa371, 0x4770, 0xb4, 0x40, 0x29, 0x08, 0x60, 0x22, 0xb7,
+ 0x41);
+DEFINE_GUID(IID_ID3D11ClassInstance, 0xa6cd7faa, 0xb0b7, 0x4a2f, 0x94, 0x36, 0x86, 0x62, 0xa6, 0x57,
+ 0x97, 0xcb);
+DEFINE_GUID(IID_ID3D11ClassLinkage, 0xddf57cba, 0x9543, 0x46e4, 0xa1, 0x2b, 0xf2, 0x07, 0xa0, 0xfe,
+ 0x7f, 0xed);
+DEFINE_GUID(IID_ID3D11CommandList, 0xa24bc4d1, 0x769e, 0x43f7, 0x80, 0x13, 0x98, 0xff, 0x56, 0x6c,
+ 0x18, 0xe2);
+DEFINE_GUID(IID_ID3D11DeviceContext, 0xc0bfa96c, 0xe089, 0x44fb, 0x8e, 0xaf, 0x26, 0xf8, 0x79, 0x61,
+ 0x90, 0xda);
+DEFINE_GUID(IID_ID3D11VideoDecoder, 0x3C9C5B51, 0x995D, 0x48d1, 0x9B, 0x8D, 0xFA, 0x5C, 0xAE, 0xDE,
+ 0xD6, 0x5C);
+DEFINE_GUID(IID_ID3D11VideoProcessorEnumerator, 0x31627037, 0x53AB, 0x4200, 0x90, 0x61, 0x05, 0xFA,
+ 0xA9, 0xAB, 0x45, 0xF9);
+DEFINE_GUID(IID_ID3D11VideoProcessor, 0x1D7B0652, 0x185F, 0x41c6, 0x85, 0xCE, 0x0C, 0x5B, 0xE3,
+ 0xD4, 0xAE, 0x6C);
+DEFINE_GUID(IID_ID3D11AuthenticatedChannel, 0x3015A308, 0xDCBD, 0x47aa, 0xA7, 0x47, 0x19, 0x24,
+ 0x86, 0xD1, 0x4D, 0x4A);
+DEFINE_GUID(IID_ID3D11CryptoSession, 0x9B32F9AD, 0xBDCC, 0x40a6, 0xA3, 0x9D, 0xD5, 0xC8, 0x65, 0x84,
+ 0x57, 0x20);
+DEFINE_GUID(IID_ID3D11VideoDecoderOutputView, 0xC2931AEA, 0x2A85, 0x4f20, 0x86, 0x0F, 0xFB, 0xA1,
+ 0xFD, 0x25, 0x6E, 0x18);
+DEFINE_GUID(IID_ID3D11VideoProcessorInputView, 0x11EC5A5F, 0x51DC, 0x4945, 0xAB, 0x34, 0x6E, 0x8C,
+ 0x21, 0x30, 0x0E, 0xA5);
+DEFINE_GUID(IID_ID3D11VideoProcessorOutputView, 0xA048285E, 0x25A9, 0x4527, 0xBD, 0x93, 0xD6, 0x8B,
+ 0x68, 0xC4, 0x42, 0x54);
+DEFINE_GUID(IID_ID3D11VideoContext, 0x61F21C45, 0x3C0E, 0x4a74, 0x9C, 0xEA, 0x67, 0x10, 0x0D, 0x9A,
+ 0xD5, 0xE4);
+DEFINE_GUID(IID_ID3D11VideoDevice, 0x10EC4D5B, 0x975A, 0x4689, 0xB9, 0xE4, 0xD0, 0xAA, 0xC3, 0x0F,
+ 0xE3, 0x33);
+DEFINE_GUID(IID_ID3D11Device, 0xdb6f6ddb, 0xac77, 0x4e88, 0x82, 0x53, 0x81, 0x9d, 0xf9, 0xbb, 0xf1,
+ 0x40);
+
+/* dxgi.h GUIDs */
+
+DEFINE_GUID(IID_IDXGIObject, 0xaec22fb8, 0x76f3, 0x4639, 0x9b, 0xe0, 0x28, 0xeb, 0x43, 0xa6, 0x7a,
+ 0x2e);
+DEFINE_GUID(IID_IDXGIDeviceSubObject, 0x3d3e0379, 0xf9de, 0x4d58, 0xbb, 0x6c, 0x18, 0xd6, 0x29,
+ 0x92, 0xf1, 0xa6);
+DEFINE_GUID(IID_IDXGIResource, 0x035f3ab4, 0x482e, 0x4e50, 0xb4, 0x1f, 0x8a, 0x7f, 0x8b, 0xd8, 0x96,
+ 0x0b);
+DEFINE_GUID(IID_IDXGIKeyedMutex, 0x9d8e1289, 0xd7b3, 0x465f, 0x81, 0x26, 0x25, 0x0e, 0x34, 0x9a,
+ 0xf8, 0x5d);
+DEFINE_GUID(IID_IDXGISurface, 0xcafcb56c, 0x6ac3, 0x4889, 0xbf, 0x47, 0x9e, 0x23, 0xbb, 0xd2, 0x60,
+ 0xec);
+DEFINE_GUID(IID_IDXGISurface1, 0x4AE63092, 0x6327, 0x4c1b, 0x80, 0xAE, 0xBF, 0xE1, 0x2E, 0xA3, 0x2B,
+ 0x86);
+DEFINE_GUID(IID_IDXGIAdapter, 0x2411e7e1, 0x12ac, 0x4ccf, 0xbd, 0x14, 0x97, 0x98, 0xe8, 0x53, 0x4d,
+ 0xc0);
+DEFINE_GUID(IID_IDXGIOutput, 0xae02eedb, 0xc735, 0x4690, 0x8d, 0x52, 0x5a, 0x8d, 0xc2, 0x02, 0x13,
+ 0xaa);
+DEFINE_GUID(IID_IDXGISwapChain, 0x310d36a0, 0xd2e7, 0x4c0a, 0xaa, 0x04, 0x6a, 0x9d, 0x23, 0xb8,
+ 0x88, 0x6a);
+DEFINE_GUID(IID_IDXGIFactory, 0x7b7166ec, 0x21c7, 0x44ae, 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3,
+ 0x69);
+DEFINE_GUID(IID_IDXGIDevice, 0x54ec77fa, 0x1377, 0x44e6, 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8,
+ 0x4c);
+DEFINE_GUID(IID_IDXGIFactory1, 0x770aae78, 0xf26f, 0x4dba, 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3,
+ 0x87);
+DEFINE_GUID(IID_IDXGIAdapter1, 0x29038f61, 0x3839, 0x4626, 0x91, 0xfd, 0x08, 0x68, 0x79, 0x01, 0x1a,
+ 0x05);
+DEFINE_GUID(IID_IDXGIDevice1, 0x77db970f, 0x6276, 0x48ba, 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39,
+ 0x2c);
+
+/* dxgi1_2.h GUIDs */
+
+DEFINE_GUID(IID_IDXGIDisplayControl, 0xea9dbf1a, 0xc88e, 0x4486, 0x85, 0x4a, 0x98, 0xaa, 0x01, 0x38,
+ 0xf3, 0x0c);
+DEFINE_GUID(IID_IDXGIOutputDuplication, 0x191cfac3, 0xa341, 0x470d, 0xb2, 0x6e, 0xa8, 0x64, 0xf4,
+ 0x28, 0x31, 0x9c);
+DEFINE_GUID(IID_IDXGISurface2, 0xaba496dd, 0xb617, 0x4cb8, 0xa8, 0x66, 0xbc, 0x44, 0xd7, 0xeb, 0x1f,
+ 0xa2);
+DEFINE_GUID(IID_IDXGIResource1, 0x30961379, 0x4609, 0x4a41, 0x99, 0x8e, 0x54, 0xfe, 0x56, 0x7e,
+ 0xe0, 0xc1);
+DEFINE_GUID(IID_IDXGIDevice2, 0x05008617, 0xfbfd, 0x4051, 0xa7, 0x90, 0x14, 0x48, 0x84, 0xb4, 0xf6,
+ 0xa9);
+DEFINE_GUID(IID_IDXGISwapChain1, 0x790a45f7, 0x0d42, 0x4876, 0x98, 0x3a, 0x0a, 0x55, 0xcf, 0xe6,
+ 0xf4, 0xaa);
+DEFINE_GUID(IID_IDXGIFactory2, 0x50c83a1c, 0xe072, 0x4c48, 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6,
+ 0xd0);
+DEFINE_GUID(IID_IDXGIAdapter2, 0x0AA1AE0A, 0xFA0E, 0x4B84, 0x86, 0x44, 0xE0, 0x5F, 0xF8, 0xE5, 0xAC,
+ 0xB5);
+DEFINE_GUID(IID_IDXGIOutput1, 0x00cddea8, 0x939b, 0x4b83, 0xa3, 0x40, 0xa6, 0x85, 0x22, 0x66, 0x66,
+ 0xcc);
+
+const char* GetDxgiErrorString(HRESULT hr)
+{
+ switch (hr)
+ {
+ case DXGI_STATUS_OCCLUDED:
+ return "DXGI_STATUS_OCCLUDED";
+ case DXGI_STATUS_CLIPPED:
+ return "DXGI_STATUS_CLIPPED";
+ case DXGI_STATUS_NO_REDIRECTION:
+ return "DXGI_STATUS_NO_REDIRECTION";
+ case DXGI_STATUS_NO_DESKTOP_ACCESS:
+ return "DXGI_STATUS_NO_DESKTOP_ACCESS";
+ case DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE:
+ return "DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE";
+ case DXGI_STATUS_MODE_CHANGED:
+ return "DXGI_STATUS_MODE_CHANGED";
+ case DXGI_STATUS_MODE_CHANGE_IN_PROGRESS:
+ return "DXGI_STATUS_MODE_CHANGE_IN_PROGRESS";
+ case DXGI_ERROR_INVALID_CALL:
+ return "DXGI_ERROR_INVALID_CALL";
+ case DXGI_ERROR_NOT_FOUND:
+ return "DXGI_ERROR_NOT_FOUND";
+ case DXGI_ERROR_MORE_DATA:
+ return "DXGI_ERROR_MORE_DATA";
+ case DXGI_ERROR_UNSUPPORTED:
+ return "DXGI_ERROR_UNSUPPORTED";
+ case DXGI_ERROR_DEVICE_REMOVED:
+ return "DXGI_ERROR_DEVICE_REMOVED";
+ case DXGI_ERROR_DEVICE_HUNG:
+ return "DXGI_ERROR_DEVICE_HUNG";
+ case DXGI_ERROR_DEVICE_RESET:
+ return "DXGI_ERROR_DEVICE_RESET";
+ case DXGI_ERROR_WAS_STILL_DRAWING:
+ return "DXGI_ERROR_WAS_STILL_DRAWING";
+ case DXGI_ERROR_FRAME_STATISTICS_DISJOINT:
+ return "DXGI_ERROR_FRAME_STATISTICS_DISJOINT";
+ case DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE:
+ return "DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE";
+ case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+ return "DXGI_ERROR_DRIVER_INTERNAL_ERROR";
+ case DXGI_ERROR_NONEXCLUSIVE:
+ return "DXGI_ERROR_NONEXCLUSIVE";
+ case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE:
+ return "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE";
+ case DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED:
+ return "DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED";
+ case DXGI_ERROR_REMOTE_OUTOFMEMORY:
+ return "DXGI_ERROR_REMOTE_OUTOFMEMORY";
+ case DXGI_ERROR_ACCESS_LOST:
+ return "DXGI_ERROR_ACCESS_LOST";
+ case DXGI_ERROR_WAIT_TIMEOUT:
+ return "DXGI_ERROR_WAIT_TIMEOUT";
+ case DXGI_ERROR_SESSION_DISCONNECTED:
+ return "DXGI_ERROR_SESSION_DISCONNECTED";
+ case DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE:
+ return "DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE";
+ case DXGI_ERROR_CANNOT_PROTECT_CONTENT:
+ return "DXGI_ERROR_CANNOT_PROTECT_CONTENT";
+ case DXGI_ERROR_ACCESS_DENIED:
+ return "DXGI_ERROR_ACCESS_DENIED";
+ case DXGI_ERROR_NAME_ALREADY_EXISTS:
+ return "DXGI_ERROR_NAME_ALREADY_EXISTS";
+ case DXGI_ERROR_SDK_COMPONENT_MISSING:
+ return "DXGI_ERROR_SDK_COMPONENT_MISSING";
+ case DXGI_STATUS_UNOCCLUDED:
+ return "DXGI_STATUS_UNOCCLUDED";
+ case DXGI_STATUS_DDA_WAS_STILL_DRAWING:
+ return "DXGI_STATUS_DDA_WAS_STILL_DRAWING";
+ case DXGI_ERROR_MODE_CHANGE_IN_PROGRESS:
+ return "DXGI_ERROR_MODE_CHANGE_IN_PROGRESS";
+ case DXGI_DDI_ERR_WASSTILLDRAWING:
+ return "DXGI_DDI_ERR_WASSTILLDRAWING";
+ case DXGI_DDI_ERR_UNSUPPORTED:
+ return "DXGI_DDI_ERR_UNSUPPORTED";
+ case DXGI_DDI_ERR_NONEXCLUSIVE:
+ return "DXGI_DDI_ERR_NONEXCLUSIVE";
+ case 0x80070005:
+ return "DXGI_ERROR_ACCESS_DENIED";
+ }
+
+ return "DXGI_ERROR_UNKNOWN";
+}
+
+static void win_shadow_d3d11_module_init()
+{
+ if (d3d11_module)
+ return;
+
+ d3d11_module = LoadLibraryA("d3d11.dll");
+
+ if (!d3d11_module)
+ return;
+
+ pfnD3D11CreateDevice = (fnD3D11CreateDevice)GetProcAddress(d3d11_module, "D3D11CreateDevice");
+}
+
+int win_shadow_dxgi_init_duplication(winShadowSubsystem* subsystem)
+{
+ HRESULT hr;
+ UINT dTop, i = 0;
+ IDXGIOutput* pOutput;
+ DXGI_OUTPUT_DESC outputDesc = { 0 };
+ DXGI_OUTPUT_DESC* pOutputDesc;
+ D3D11_TEXTURE2D_DESC textureDesc;
+ IDXGIDevice* dxgiDevice = NULL;
+ IDXGIAdapter* dxgiAdapter = NULL;
+ IDXGIOutput* dxgiOutput = NULL;
+ IDXGIOutput1* dxgiOutput1 = NULL;
+
+ hr = subsystem->dxgiDevice->lpVtbl->QueryInterface(subsystem->dxgiDevice, &IID_IDXGIDevice,
+ (void**)&dxgiDevice);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Device::QueryInterface(IDXGIDevice) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = dxgiDevice->lpVtbl->GetParent(dxgiDevice, &IID_IDXGIAdapter, (void**)&dxgiAdapter);
+
+ if (dxgiDevice)
+ {
+ dxgiDevice->lpVtbl->Release(dxgiDevice);
+ dxgiDevice = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIDevice::GetParent(IDXGIAdapter) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ pOutput = NULL;
+
+ while (dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND)
+ {
+ pOutputDesc = &outputDesc;
+
+ hr = pOutput->lpVtbl->GetDesc(pOutput, pOutputDesc);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput::GetDesc failure: %s (0x%08lX)", GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ if (pOutputDesc->AttachedToDesktop)
+ dTop = i;
+
+ pOutput->lpVtbl->Release(pOutput);
+ i++;
+ }
+
+ dTop = 0; /* screen id */
+
+ hr = dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, dTop, &dxgiOutput);
+
+ if (dxgiAdapter)
+ {
+ dxgiAdapter->lpVtbl->Release(dxgiAdapter);
+ dxgiAdapter = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIAdapter::EnumOutputs failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ hr = dxgiOutput->lpVtbl->QueryInterface(dxgiOutput, &IID_IDXGIOutput1, (void**)&dxgiOutput1);
+
+ if (dxgiOutput)
+ {
+ dxgiOutput->lpVtbl->Release(dxgiOutput);
+ dxgiOutput = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput::QueryInterface(IDXGIOutput1) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = dxgiOutput1->lpVtbl->DuplicateOutput(dxgiOutput1, (IUnknown*)subsystem->dxgiDevice,
+ &(subsystem->dxgiOutputDuplication));
+
+ if (dxgiOutput1)
+ {
+ dxgiOutput1->lpVtbl->Release(dxgiOutput1);
+ dxgiOutput1 = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutput1::DuplicateOutput failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ textureDesc.Width = subsystem->width;
+ textureDesc.Height = subsystem->height;
+ textureDesc.MipLevels = 1;
+ textureDesc.ArraySize = 1;
+ textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ textureDesc.SampleDesc.Count = 1;
+ textureDesc.SampleDesc.Quality = 0;
+ textureDesc.Usage = D3D11_USAGE_STAGING;
+ textureDesc.BindFlags = 0;
+ textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ textureDesc.MiscFlags = 0;
+
+ hr = subsystem->dxgiDevice->lpVtbl->CreateTexture2D(subsystem->dxgiDevice, &textureDesc, NULL,
+ &(subsystem->dxgiStage));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Device::CreateTexture2D failure: %s (0x%08lX)", GetDxgiErrorString(hr),
+ hr);
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_init(winShadowSubsystem* subsystem)
+{
+ UINT i = 0;
+ HRESULT hr;
+ int status;
+ UINT DriverTypeIndex;
+ IDXGIDevice* DxgiDevice = NULL;
+ IDXGIAdapter* DxgiAdapter = NULL;
+ IDXGIOutput* DxgiOutput = NULL;
+ IDXGIOutput1* DxgiOutput1 = NULL;
+
+ win_shadow_d3d11_module_init();
+
+ if (!pfnD3D11CreateDevice)
+ return -1;
+
+ for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
+ {
+ hr = pfnD3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels,
+ NumFeatureLevels, D3D11_SDK_VERSION, &(subsystem->dxgiDevice),
+ &(subsystem->featureLevel), &(subsystem->dxgiDeviceContext));
+
+ if (SUCCEEDED(hr))
+ break;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "D3D11CreateDevice failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ status = win_shadow_dxgi_init_duplication(subsystem);
+
+ return status;
+}
+
+int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem)
+{
+ if (subsystem->dxgiStage)
+ {
+ subsystem->dxgiStage->lpVtbl->Release(subsystem->dxgiStage);
+ subsystem->dxgiStage = NULL;
+ }
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication);
+ subsystem->dxgiOutputDuplication = NULL;
+ }
+
+ if (subsystem->dxgiDeviceContext)
+ {
+ subsystem->dxgiDeviceContext->lpVtbl->Release(subsystem->dxgiDeviceContext);
+ subsystem->dxgiDeviceContext = NULL;
+ }
+
+ if (subsystem->dxgiDevice)
+ {
+ subsystem->dxgiDevice->lpVtbl->Release(subsystem->dxgiDevice);
+ subsystem->dxgiDevice = NULL;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData,
+ int* pnDstStep, int x, int y, int width, int height)
+{
+ int status;
+ HRESULT hr;
+ D3D11_BOX Box;
+ DXGI_MAPPED_RECT mappedRect;
+
+ if ((width * height) < 1)
+ return 0;
+
+ Box.top = x;
+ Box.left = y;
+ Box.right = x + width;
+ Box.bottom = y + height;
+ Box.front = 0;
+ Box.back = 1;
+
+ subsystem->dxgiDeviceContext->lpVtbl->CopySubresourceRegion(
+ subsystem->dxgiDeviceContext, (ID3D11Resource*)subsystem->dxgiStage, 0, 0, 0, 0,
+ (ID3D11Resource*)subsystem->dxgiDesktopImage, 0, &Box);
+
+ hr = subsystem->dxgiStage->lpVtbl->QueryInterface(subsystem->dxgiStage, &IID_IDXGISurface,
+ (void**)&(subsystem->dxgiSurface));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "ID3D11Texture2D::QueryInterface(IDXGISurface) failure: %s 0x%08lX",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ hr = subsystem->dxgiSurface->lpVtbl->Map(subsystem->dxgiSurface, &mappedRect, DXGI_MAP_READ);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGISurface::Map failure: %s 0x%08lX", GetDxgiErrorString(hr), hr);
+
+ if (hr == DXGI_ERROR_DEVICE_REMOVED)
+ {
+ win_shadow_dxgi_uninit(subsystem);
+
+ status = win_shadow_dxgi_init(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+
+ return -1;
+ }
+
+ subsystem->dxgiSurfaceMapped = TRUE;
+
+ *ppDstData = mappedRect.pBits;
+ *pnDstStep = mappedRect.Pitch;
+
+ return 1;
+}
+
+int win_shadow_dxgi_release_frame_data(winShadowSubsystem* subsystem)
+{
+ if (subsystem->dxgiSurface)
+ {
+ if (subsystem->dxgiSurfaceMapped)
+ {
+ subsystem->dxgiSurface->lpVtbl->Unmap(subsystem->dxgiSurface);
+ subsystem->dxgiSurfaceMapped = FALSE;
+ }
+
+ subsystem->dxgiSurface->lpVtbl->Release(subsystem->dxgiSurface);
+ subsystem->dxgiSurface = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ if (subsystem->dxgiFrameAcquired)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->ReleaseFrame(
+ subsystem->dxgiOutputDuplication);
+ subsystem->dxgiFrameAcquired = FALSE;
+ }
+ }
+
+ subsystem->pendingFrames = 0;
+
+ return 1;
+}
+
+int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem)
+{
+ UINT i = 0;
+ int status;
+ HRESULT hr = 0;
+ UINT timeout = 15;
+ UINT DataBufferSize = 0;
+ BYTE* DataBuffer = NULL;
+
+ if (subsystem->dxgiFrameAcquired)
+ {
+ win_shadow_dxgi_release_frame_data(subsystem);
+ }
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->AcquireNextFrame(
+ subsystem->dxgiOutputDuplication, timeout, &(subsystem->dxgiFrameInfo),
+ &(subsystem->dxgiResource));
+
+ if (SUCCEEDED(hr))
+ {
+ subsystem->dxgiFrameAcquired = TRUE;
+ subsystem->pendingFrames = subsystem->dxgiFrameInfo.AccumulatedFrames;
+ }
+
+ if (hr == DXGI_ERROR_WAIT_TIMEOUT)
+ return 0;
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIOutputDuplication::AcquireNextFrame failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+
+ if (hr == DXGI_ERROR_ACCESS_LOST)
+ {
+ win_shadow_dxgi_release_frame_data(subsystem);
+
+ if (subsystem->dxgiDesktopImage)
+ {
+ subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage);
+ subsystem->dxgiDesktopImage = NULL;
+ }
+
+ if (subsystem->dxgiOutputDuplication)
+ {
+ subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication);
+ subsystem->dxgiOutputDuplication = NULL;
+ }
+
+ status = win_shadow_dxgi_init_duplication(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+ else if (hr == DXGI_ERROR_INVALID_CALL)
+ {
+ win_shadow_dxgi_uninit(subsystem);
+
+ status = win_shadow_dxgi_init(subsystem);
+
+ if (status < 0)
+ return -1;
+
+ return 0;
+ }
+
+ return -1;
+ }
+
+ hr = subsystem->dxgiResource->lpVtbl->QueryInterface(
+ subsystem->dxgiResource, &IID_ID3D11Texture2D, (void**)&(subsystem->dxgiDesktopImage));
+
+ if (subsystem->dxgiResource)
+ {
+ subsystem->dxgiResource->lpVtbl->Release(subsystem->dxgiResource);
+ subsystem->dxgiResource = NULL;
+ }
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IDXGIResource::QueryInterface(ID3D11Texture2D) failure: %s (0x%08lX)",
+ GetDxgiErrorString(hr), hr);
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem)
+{
+ HRESULT hr;
+ POINT* pSrcPt;
+ RECT* pDstRect;
+ RECT* pDirtyRect;
+ UINT numMoveRects;
+ UINT numDirtyRects;
+ UINT UsedBufferSize;
+ RECTANGLE_16 invalidRect;
+ UINT MetadataBufferSize;
+ UINT MoveRectsBufferSize;
+ UINT DirtyRectsBufferSize;
+ RECT* pDirtyRectsBuffer;
+ DXGI_OUTDUPL_MOVE_RECT* pMoveRect;
+ DXGI_OUTDUPL_MOVE_RECT* pMoveRectBuffer;
+ rdpShadowSurface* surface = subsystem->server->surface;
+
+ if (subsystem->dxgiFrameInfo.AccumulatedFrames == 0)
+ return 0;
+
+ if (subsystem->dxgiFrameInfo.TotalMetadataBufferSize == 0)
+ return 0;
+
+ MetadataBufferSize = subsystem->dxgiFrameInfo.TotalMetadataBufferSize;
+
+ if (MetadataBufferSize > subsystem->MetadataBufferSize)
+ {
+ subsystem->MetadataBuffer = (BYTE*)realloc(subsystem->MetadataBuffer, MetadataBufferSize);
+
+ if (!subsystem->MetadataBuffer)
+ return -1;
+
+ subsystem->MetadataBufferSize = MetadataBufferSize;
+ }
+
+ /* GetFrameMoveRects */
+
+ UsedBufferSize = 0;
+
+ MoveRectsBufferSize = MetadataBufferSize - UsedBufferSize;
+ pMoveRectBuffer = (DXGI_OUTDUPL_MOVE_RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]);
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameMoveRects(
+ subsystem->dxgiOutputDuplication, MoveRectsBufferSize, pMoveRectBuffer,
+ &MoveRectsBufferSize);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG,
+ "IDXGIOutputDuplication::GetFrameMoveRects failure: %s (0x%08lX) Size: %u Total "
+ "%u Used: %u",
+ GetDxgiErrorString(hr), hr, MoveRectsBufferSize, MetadataBufferSize,
+ UsedBufferSize);
+ return -1;
+ }
+
+ /* GetFrameDirtyRects */
+
+ UsedBufferSize += MoveRectsBufferSize;
+
+ DirtyRectsBufferSize = MetadataBufferSize - UsedBufferSize;
+ pDirtyRectsBuffer = (RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]);
+
+ hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameDirtyRects(
+ subsystem->dxgiOutputDuplication, DirtyRectsBufferSize, pDirtyRectsBuffer,
+ &DirtyRectsBufferSize);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG,
+ "IDXGIOutputDuplication::GetFrameDirtyRects failure: %s (0x%08lX) Size: %u Total "
+ "%u Used: %u",
+ GetDxgiErrorString(hr), hr, DirtyRectsBufferSize, MetadataBufferSize,
+ UsedBufferSize);
+ return -1;
+ }
+
+ numMoveRects = MoveRectsBufferSize / sizeof(DXGI_OUTDUPL_MOVE_RECT);
+
+ for (UINT i = 0; i < numMoveRects; i++)
+ {
+ pMoveRect = &pMoveRectBuffer[i];
+ pSrcPt = &(pMoveRect->SourcePoint);
+ pDstRect = &(pMoveRect->DestinationRect);
+
+ invalidRect.left = (UINT16)pDstRect->left;
+ invalidRect.top = (UINT16)pDstRect->top;
+ invalidRect.right = (UINT16)pDstRect->right;
+ invalidRect.bottom = (UINT16)pDstRect->bottom;
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ numDirtyRects = DirtyRectsBufferSize / sizeof(RECT);
+
+ for (UINT i = 0; i < numDirtyRects; i++)
+ {
+ pDirtyRect = &pDirtyRectsBuffer[i];
+
+ invalidRect.left = (UINT16)pDirtyRect->left;
+ invalidRect.top = (UINT16)pDirtyRect->top;
+ invalidRect.right = (UINT16)pDirtyRect->right;
+ invalidRect.bottom = (UINT16)pDirtyRect->bottom;
+
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ return 1;
+}
+
+#endif
diff --git a/server/shadow/Win/win_dxgi.h b/server/shadow/Win/win_dxgi.h
new file mode 100644
index 0000000..ffe80a0
--- /dev/null
+++ b/server/shadow/Win/win_dxgi.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_WIN_DXGI_H
+#define FREERDP_SERVER_SHADOW_WIN_DXGI_H
+
+#if _WIN32_WINNT >= 0x0602
+//#define WITH_DXGI_1_2 1
+#endif
+
+#ifdef WITH_DXGI_1_2
+
+#ifndef CINTERFACE
+#define CINTERFACE
+#endif
+
+#include <D3D11.h>
+#include <dxgi1_2.h>
+
+#endif
+
+#include "win_shadow.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef WITH_DXGI_1_2
+
+ int win_shadow_dxgi_init(winShadowSubsystem* subsystem);
+ int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem);
+
+ int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData,
+ int* pnDstStep, int x, int y, int width, int height);
+
+ int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem);
+ int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_DXGI_H */
diff --git a/server/shadow/Win/win_rdp.c b/server/shadow/Win/win_rdp.c
new file mode 100644
index 0000000..3db073d
--- /dev/null
+++ b/server/shadow/Win/win_rdp.c
@@ -0,0 +1,440 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+
+#include "win_rdp.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+static void shw_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnChannelConnected: %s", e->name);
+}
+
+static void shw_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnChannelDisconnected: %s", e->name);
+}
+
+static BOOL shw_begin_paint(rdpContext* context)
+{
+ shwContext* shw;
+ rdpGdi* gdi;
+
+ WINPR_ASSERT(context);
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ shw = (shwContext*)context;
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL shw_end_paint(rdpContext* context)
+{
+ int ninvalid;
+ HGDI_RGN cinvalid;
+ RECTANGLE_16 invalidRect;
+ rdpGdi* gdi = context->gdi;
+ shwContext* shw = (shwContext*)context;
+ winShadowSubsystem* subsystem = shw->subsystem;
+ rdpShadowSurface* surface = subsystem->base.server->surface;
+ ninvalid = gdi->primary->hdc->hwnd->ninvalid;
+ cinvalid = gdi->primary->hdc->hwnd->cinvalid;
+
+ for (int index = 0; index < ninvalid; index++)
+ {
+ invalidRect.left = cinvalid[index].x;
+ invalidRect.top = cinvalid[index].y;
+ invalidRect.right = cinvalid[index].x + cinvalid[index].w;
+ invalidRect.bottom = cinvalid[index].y + cinvalid[index].h;
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ }
+
+ SetEvent(subsystem->RdpUpdateEnterEvent);
+ WaitForSingleObject(subsystem->RdpUpdateLeaveEvent, INFINITE);
+ ResetEvent(subsystem->RdpUpdateLeaveEvent);
+ return TRUE;
+}
+
+BOOL shw_desktop_resize(rdpContext* context)
+{
+ WLog_WARN(TAG, "Desktop resizing not implemented!");
+ return TRUE;
+}
+
+static BOOL shw_surface_frame_marker(rdpContext* context,
+ const SURFACE_FRAME_MARKER* surfaceFrameMarker)
+{
+ shwContext* shw = (shwContext*)context;
+ return TRUE;
+}
+
+static BOOL shw_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ WLog_WARN(TAG, "Authentication not implemented, access granted to everyone!");
+ return TRUE;
+}
+
+static int shw_verify_x509_certificate(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags)
+{
+ WLog_WARN(TAG, "Certificate checks not implemented, access granted to everyone!");
+ return 1;
+}
+
+static void shw_OnConnectionResultEventHandler(void* context, const ConnectionResultEventArgs* e)
+{
+ shwContext* shw = (shwContext*)context;
+ WINPR_ASSERT(e);
+ WLog_INFO(TAG, "OnConnectionResult: %d", e->result);
+}
+
+static BOOL shw_pre_connect(freerdp* instance)
+{
+ shwContext* shw;
+ rdpContext* context = instance->context;
+ shw = (shwContext*)context;
+ PubSub_SubscribeConnectionResult(context->pubSub, shw_OnConnectionResultEventHandler);
+ PubSub_SubscribeChannelConnected(context->pubSub, shw_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(context->pubSub, shw_OnChannelDisconnectedEventHandler);
+
+ return TRUE;
+}
+
+static BOOL shw_post_connect(freerdp* instance)
+{
+ rdpGdi* gdi;
+ shwContext* shw;
+ rdpUpdate* update;
+ rdpSettings* settings;
+
+ WINPR_ASSERT(instance);
+
+ shw = (shwContext*)instance->context;
+ WINPR_ASSERT(shw);
+
+ update = instance->context->update;
+ WINPR_ASSERT(update);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRX32))
+ return FALSE;
+
+ gdi = instance->context->gdi;
+ update->BeginPaint = shw_begin_paint;
+ update->EndPaint = shw_end_paint;
+ update->DesktopResize = shw_desktop_resize;
+ update->SurfaceFrameMarker = shw_surface_frame_marker;
+ return TRUE;
+}
+
+static DWORD WINAPI shw_client_thread(LPVOID arg)
+{
+ int index;
+ BOOL bSuccess;
+ shwContext* shw;
+ rdpContext* context;
+ rdpChannels* channels;
+
+ freerdp* instance = (freerdp*)arg;
+ WINPR_ASSERT(instance);
+
+ context = (rdpContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ shw = (shwContext*)context;
+
+ bSuccess = freerdp_connect(instance);
+ WLog_INFO(TAG, "freerdp_connect: %d", bSuccess);
+
+ if (!bSuccess)
+ {
+ ExitThread(0);
+ return 0;
+ }
+
+ channels = context->channels;
+ WINPR_ASSERT(channels);
+
+ while (1)
+ {
+ DWORD status;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD count = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles));
+
+ if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ handles[count++] = freerdp_channels_get_event_handle(instance);
+
+ if (MsgWaitForMultipleObjects(count, handles, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "MsgWaitForMultipleObjects failure: 0x%08lX", GetLastError());
+ break;
+ }
+
+ if (!freerdp_check_fds(instance))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ break;
+ }
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ {
+ break;
+ }
+
+ if (!freerdp_channels_check_fds(channels, instance))
+ {
+ WLog_ERR(TAG, "Failed to check channels file descriptor");
+ break;
+ }
+ }
+
+ freerdp_free(instance);
+ ExitThread(0);
+ return 0;
+}
+
+/**
+ * Client Interface
+ */
+
+static BOOL shw_freerdp_client_global_init(void)
+{
+ return TRUE;
+}
+
+static void shw_freerdp_client_global_uninit(void)
+{
+}
+
+static int shw_freerdp_client_start(rdpContext* context)
+{
+ shwContext* shw;
+ freerdp* instance = context->instance;
+ shw = (shwContext*)context;
+
+ if (!(shw->common.thread = CreateThread(NULL, 0, shw_client_thread, instance, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int shw_freerdp_client_stop(rdpContext* context)
+{
+ shwContext* shw = (shwContext*)context;
+ SetEvent(shw->StopEvent);
+ return 0;
+}
+
+static BOOL shw_freerdp_client_new(freerdp* instance, rdpContext* context)
+{
+ shwContext* shw;
+ rdpSettings* settings;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ shw = (shwContext*)instance->context;
+ WINPR_ASSERT(shw);
+
+ if (!(shw->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ return FALSE;
+
+ instance->LoadChannels = freerdp_client_load_channels;
+ instance->PreConnect = shw_pre_connect;
+ instance->PostConnect = shw_post_connect;
+ instance->Authenticate = shw_authenticate;
+ instance->VerifyX509Certificate = shw_verify_x509_certificate;
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ shw->settings = settings;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExternalCertificateManagement, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_OffscreenSupportLevel, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BrushSupportLevel, FALSE))
+ return FALSE;
+ ZeroMemory(freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport), 32);
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AltSecFrameMarkerSupport, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathInput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_LargePointerFlag, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, CONNECTION_TYPE_LAN))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ return TRUE;
+}
+
+static void shw_freerdp_client_free(freerdp* instance, rdpContext* context)
+{
+ shwContext* shw = (shwContext*)instance->context;
+}
+
+int shw_RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ pEntryPoints->Version = 1;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->settings = NULL;
+ pEntryPoints->ContextSize = sizeof(shwContext);
+ pEntryPoints->GlobalInit = shw_freerdp_client_global_init;
+ pEntryPoints->GlobalUninit = shw_freerdp_client_global_uninit;
+ pEntryPoints->ClientNew = shw_freerdp_client_new;
+ pEntryPoints->ClientFree = shw_freerdp_client_free;
+ pEntryPoints->ClientStart = shw_freerdp_client_start;
+ pEntryPoints->ClientStop = shw_freerdp_client_stop;
+ return 0;
+}
+
+int win_shadow_rdp_init(winShadowSubsystem* subsystem)
+{
+ rdpContext* context;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 };
+
+ clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
+ clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;
+ shw_RdpClientEntry(&clientEntryPoints);
+
+ if (!(subsystem->RdpUpdateEnterEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_enter_event;
+
+ if (!(subsystem->RdpUpdateLeaveEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail_leave_event;
+
+ if (!(context = freerdp_client_context_new(&clientEntryPoints)))
+ goto fail_context;
+
+ subsystem->shw = (shwContext*)context;
+ subsystem->shw->settings = context->settings;
+ subsystem->shw->subsystem = subsystem;
+ return 1;
+fail_context:
+ CloseHandle(subsystem->RdpUpdateLeaveEvent);
+fail_leave_event:
+ CloseHandle(subsystem->RdpUpdateEnterEvent);
+fail_enter_event:
+ return -1;
+}
+
+int win_shadow_rdp_start(winShadowSubsystem* subsystem)
+{
+ int status;
+ shwContext* shw = subsystem->shw;
+ rdpContext* context = (rdpContext*)shw;
+ status = freerdp_client_start(context);
+ return status;
+}
+
+int win_shadow_rdp_stop(winShadowSubsystem* subsystem)
+{
+ int status;
+ shwContext* shw = subsystem->shw;
+ rdpContext* context = (rdpContext*)shw;
+ status = freerdp_client_stop(context);
+ return status;
+}
+
+int win_shadow_rdp_uninit(winShadowSubsystem* subsystem)
+{
+ win_shadow_rdp_stop(subsystem);
+ return 1;
+}
diff --git a/server/shadow/Win/win_rdp.h b/server/shadow/Win/win_rdp.h
new file mode 100644
index 0000000..07dbf3b
--- /dev/null
+++ b/server/shadow/Win/win_rdp.h
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_WIN_RDP_H
+#define FREERDP_SERVER_SHADOW_WIN_RDP_H
+
+#include <freerdp/addin.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/channels/channels.h>
+
+typedef struct shw_context shwContext;
+
+#include "win_shadow.h"
+
+struct shw_context
+{
+ rdpClientContext common;
+
+ HANDLE StopEvent;
+ freerdp* instance;
+ rdpSettings* settings;
+ winShadowSubsystem* subsystem;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int win_shadow_rdp_init(winShadowSubsystem* subsystem);
+ int win_shadow_rdp_uninit(winShadowSubsystem* subsystem);
+
+ int win_shadow_rdp_start(winShadowSubsystem* subsystem);
+ int win_shadow_rdp_stop(winShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_RDP_H */
diff --git a/server/shadow/Win/win_shadow.c b/server/shadow/Win/win_shadow.c
new file mode 100644
index 0000000..d3d5a17
--- /dev/null
+++ b/server/shadow/Win/win_shadow.c
@@ -0,0 +1,560 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <windows.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+#include <freerdp/server/server-common.h>
+
+#include "win_shadow.h"
+
+#define TAG SERVER_TAG("shadow.win")
+
+/* https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event
+ * does not mention this flag is only supported if building for _WIN32_WINNT >= 0x0600
+ */
+#ifndef MOUSEEVENTF_HWHEEL
+#define MOUSEEVENTF_HWHEEL 0x1000
+#endif
+
+static BOOL win_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ WLog_WARN(TAG, "TODO: Implement!");
+ return TRUE;
+}
+
+static BOOL win_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+ UINT rc;
+ INPUT event;
+ event.type = INPUT_KEYBOARD;
+ event.ki.wVk = 0;
+ event.ki.wScan = code;
+ event.ki.dwFlags = KEYEVENTF_SCANCODE;
+ event.ki.dwExtraInfo = 0;
+ event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ UINT rc;
+ INPUT event;
+ event.type = INPUT_KEYBOARD;
+ event.ki.wVk = 0;
+ event.ki.wScan = code;
+ event.ki.dwFlags = KEYEVENTF_UNICODE;
+ event.ki.dwExtraInfo = 0;
+ event.ki.time = 0;
+
+ if (flags & KBD_FLAGS_RELEASE)
+ event.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+ UINT rc = 1;
+ INPUT event = { 0 };
+ float width;
+ float height;
+
+ event.type = INPUT_MOUSE;
+
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (flags & PTR_FLAGS_WHEEL)
+ event.mi.dwFlags = MOUSEEVENTF_WHEEL;
+ else
+ event.mi.dwFlags = MOUSEEVENTF_HWHEEL;
+ event.mi.mouseData = flags & WheelRotationMask;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ event.mi.mouseData *= -1;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+
+ /* The build target is a system that did not support MOUSEEVENTF_HWHEEL
+ * but it may run on newer systems supporting it.
+ * Ignore the return value in these cases.
+ */
+#if (_WIN32_WINNT < 0x0600)
+ if (flags & PTR_FLAGS_HWHEEL)
+ rc = 1;
+#endif
+ }
+ else
+ {
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ event.mi.dwFlags |= MOUSEEVENTF_MOVE;
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ }
+
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE;
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_LEFTUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON2)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ else if (flags & PTR_FLAGS_BUTTON3)
+ {
+ if (flags & PTR_FLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+ }
+
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL win_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+ UINT rc = 1;
+ INPUT event = { 0 };
+ float width;
+ float height;
+
+ if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2))
+ {
+ event.type = INPUT_MOUSE;
+
+ if (flags & PTR_FLAGS_MOVE)
+ {
+ width = (float)GetSystemMetrics(SM_CXSCREEN);
+ height = (float)GetSystemMetrics(SM_CYSCREEN);
+ event.mi.dx = (LONG)((float)x * (65535.0f / width));
+ event.mi.dy = (LONG)((float)y * (65535.0f / height));
+ event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
+ rc = SendInput(1, &event, sizeof(INPUT));
+ if (rc == 0)
+ return FALSE;
+ }
+
+ event.mi.dx = event.mi.dy = event.mi.dwFlags = 0;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ event.mi.dwFlags |= MOUSEEVENTF_XDOWN;
+ else
+ event.mi.dwFlags |= MOUSEEVENTF_XUP;
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ event.mi.mouseData = XBUTTON1;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ event.mi.mouseData = XBUTTON2;
+
+ rc = SendInput(1, &event, sizeof(INPUT));
+ }
+
+ if (rc == 0)
+ return FALSE;
+ return TRUE;
+}
+
+static int win_shadow_invalidate_region(winShadowSubsystem* subsystem, int x, int y, int width,
+ int height)
+{
+ rdpShadowServer* server;
+ rdpShadowSurface* surface;
+ RECTANGLE_16 invalidRect;
+ server = subsystem->base.server;
+ surface = server->surface;
+ invalidRect.left = x;
+ invalidRect.top = y;
+ invalidRect.right = x + width;
+ invalidRect.bottom = y + height;
+ EnterCriticalSection(&(surface->lock));
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ LeaveCriticalSection(&(surface->lock));
+ return 1;
+}
+
+static int win_shadow_surface_copy(winShadowSubsystem* subsystem)
+{
+ int x, y;
+ int width;
+ int height;
+ int count;
+ int status = 1;
+ int nDstStep = 0;
+ DWORD DstFormat;
+ BYTE* pDstData = NULL;
+ rdpShadowServer* server;
+ rdpShadowSurface* surface;
+ RECTANGLE_16 surfaceRect;
+ RECTANGLE_16 invalidRect;
+ const RECTANGLE_16* extents;
+ server = subsystem->base.server;
+ surface = server->surface;
+
+ if (ArrayList_Count(server->clients) < 1)
+ return 1;
+
+ surfaceRect.left = surface->x;
+ surfaceRect.top = surface->y;
+ surfaceRect.right = surface->x + surface->width;
+ surfaceRect.bottom = surface->y + surface->height;
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+
+ if (region16_is_empty(&(surface->invalidRegion)))
+ return 1;
+
+ extents = region16_extents(&(surface->invalidRegion));
+ CopyMemory(&invalidRect, extents, sizeof(RECTANGLE_16));
+ shadow_capture_align_clip_rect(&invalidRect, &surfaceRect);
+ x = invalidRect.left;
+ y = invalidRect.top;
+ width = invalidRect.right - invalidRect.left;
+ height = invalidRect.bottom - invalidRect.top;
+
+ if (0)
+ {
+ x = 0;
+ y = 0;
+ width = surface->width;
+ height = surface->height;
+ }
+
+ WLog_INFO(TAG, "SurfaceCopy x: %d y: %d width: %d height: %d right: %d bottom: %d", x, y, width,
+ height, x + width, y + height);
+#if defined(WITH_WDS_API)
+ {
+ rdpGdi* gdi;
+ shwContext* shw;
+ rdpContext* context;
+
+ WINPR_ASSERT(subsystem);
+ shw = subsystem->shw;
+ WINPR_ASSERT(shw);
+
+ context = &shw->common.context;
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ pDstData = gdi->primary_buffer;
+ nDstStep = gdi->width * 4;
+ DstFormat = gdi->dstFormat;
+ }
+#elif defined(WITH_DXGI_1_2)
+ DstFormat = PIXEL_FORMAT_BGRX32;
+ status = win_shadow_dxgi_fetch_frame_data(subsystem, &pDstData, &nDstStep, x, y, width, height);
+#endif
+
+ if (status <= 0)
+ return status;
+
+ if (!freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height,
+ pDstData, DstFormat, nDstStep, x, y, NULL, FREERDP_FLIP_NONE))
+ return ERROR_INTERNAL_ERROR;
+
+ ArrayList_Lock(server->clients);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->base);
+ ArrayList_Unlock(server->clients);
+ region16_clear(&(surface->invalidRegion));
+ return 1;
+}
+
+#if defined(WITH_WDS_API)
+
+static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ DWORD status;
+ DWORD nCount;
+ HANDLE events[32];
+ HANDLE StopEvent;
+ StopEvent = subsystem->base.server->StopEvent;
+ nCount = 0;
+ events[nCount++] = StopEvent;
+ events[nCount++] = subsystem->RdpUpdateEnterEvent;
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ if (WaitForSingleObject(subsystem->RdpUpdateEnterEvent, 0) == WAIT_OBJECT_0)
+ {
+ win_shadow_surface_copy(subsystem);
+ ResetEvent(subsystem->RdpUpdateEnterEvent);
+ SetEvent(subsystem->RdpUpdateLeaveEvent);
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+#elif defined(WITH_DXGI_1_2)
+
+static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ int fps;
+ DWORD status;
+ DWORD nCount;
+ UINT64 cTime;
+ DWORD dwTimeout;
+ DWORD dwInterval;
+ UINT64 frameTime;
+ HANDLE events[32];
+ HANDLE StopEvent;
+ StopEvent = subsystem->server->StopEvent;
+ nCount = 0;
+ events[nCount++] = StopEvent;
+ fps = 16;
+ dwInterval = 1000 / fps;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ dwTimeout = INFINITE;
+ cTime = GetTickCount64();
+ dwTimeout = (DWORD)((cTime > frameTime) ? 0 : frameTime - cTime);
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
+ {
+ break;
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ int dxgi_status;
+ dxgi_status = win_shadow_dxgi_get_next_frame(subsystem);
+
+ if (dxgi_status > 0)
+ dxgi_status = win_shadow_dxgi_get_invalid_region(subsystem);
+
+ if (dxgi_status > 0)
+ win_shadow_surface_copy(subsystem);
+
+ dwInterval = 1000 / fps;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+#endif
+
+static UINT32 win_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ HDC hdc;
+ int index;
+ int desktopWidth;
+ int desktopHeight;
+ DWORD iDevNum = 0;
+ int numMonitors = 0;
+ MONITOR_DEF* monitor;
+ DISPLAY_DEVICE displayDevice = { 0 };
+
+ displayDevice.cb = sizeof(DISPLAY_DEVICE);
+
+ if (EnumDisplayDevices(NULL, iDevNum, &displayDevice, 0))
+ {
+ hdc = CreateDC(displayDevice.DeviceName, NULL, NULL, NULL);
+ desktopWidth = GetDeviceCaps(hdc, HORZRES);
+ desktopHeight = GetDeviceCaps(hdc, VERTRES);
+ index = 0;
+ numMonitors = 1;
+ monitor = &monitors[index];
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = desktopWidth;
+ monitor->bottom = desktopHeight;
+ monitor->flags = 1;
+ DeleteDC(hdc);
+ }
+
+ return numMonitors;
+}
+
+static int win_shadow_subsystem_init(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ int status;
+ MONITOR_DEF* virtualScreen;
+ subsystem->base.numMonitors = win_shadow_enum_monitors(subsystem->base.monitors, 16);
+#if defined(WITH_WDS_API)
+ status = win_shadow_wds_init(subsystem);
+#elif defined(WITH_DXGI_1_2)
+ status = win_shadow_dxgi_init(subsystem);
+#endif
+ virtualScreen = &(subsystem->base.virtualScreen);
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ virtualScreen->right = subsystem->width;
+ virtualScreen->bottom = subsystem->height;
+ virtualScreen->flags = 1;
+ WLog_INFO(TAG, "width: %d height: %d", subsystem->width, subsystem->height);
+ return 1;
+}
+
+static int win_shadow_subsystem_uninit(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return -1;
+
+#if defined(WITH_WDS_API)
+ win_shadow_wds_uninit(subsystem);
+#elif defined(WITH_DXGI_1_2)
+ win_shadow_dxgi_uninit(subsystem);
+#endif
+ return 1;
+}
+
+static int win_shadow_subsystem_start(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+ HANDLE thread;
+
+ if (!subsystem)
+ return -1;
+
+ if (!(thread = CreateThread(NULL, 0, win_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int win_shadow_subsystem_stop(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return -1;
+
+ return 1;
+}
+
+static void win_shadow_subsystem_free(rdpShadowSubsystem* arg)
+{
+ winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
+
+ if (!subsystem)
+ return;
+
+ win_shadow_subsystem_uninit(arg);
+ free(subsystem);
+}
+
+static rdpShadowSubsystem* win_shadow_subsystem_new(void)
+{
+ winShadowSubsystem* subsystem;
+ subsystem = (winShadowSubsystem*)calloc(1, sizeof(winShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+ subsystem->base.SynchronizeEvent = win_shadow_input_synchronize_event;
+ subsystem->base.KeyboardEvent = win_shadow_input_keyboard_event;
+ subsystem->base.UnicodeKeyboardEvent = win_shadow_input_unicode_keyboard_event;
+ subsystem->base.MouseEvent = win_shadow_input_mouse_event;
+ subsystem->base.ExtendedMouseEvent = win_shadow_input_extended_mouse_event;
+ return &subsystem->base;
+}
+
+FREERDP_API const char* ShadowSubsystemName(void)
+{
+ return "Win";
+}
+
+FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ const char name[] = "windows shadow subsystem";
+ const char* arg[] = { name };
+
+ freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg);
+ pEntryPoints->New = win_shadow_subsystem_new;
+ pEntryPoints->Free = win_shadow_subsystem_free;
+ pEntryPoints->Init = win_shadow_subsystem_init;
+ pEntryPoints->Uninit = win_shadow_subsystem_uninit;
+ pEntryPoints->Start = win_shadow_subsystem_start;
+ pEntryPoints->Stop = win_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = win_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/Win/win_shadow.h b/server/shadow/Win/win_shadow.h
new file mode 100644
index 0000000..835bb66
--- /dev/null
+++ b/server/shadow/Win/win_shadow.h
@@ -0,0 +1,89 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_WIN_H
+#define FREERDP_SERVER_SHADOW_WIN_H
+
+#include <freerdp/assistance.h>
+
+#include <freerdp/server/shadow.h>
+
+typedef struct win_shadow_subsystem winShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include "win_rdp.h"
+#include "win_wds.h"
+#include "win_dxgi.h"
+
+struct win_shadow_subsystem
+{
+ rdpShadowSubsystem base;
+
+ int bpp;
+ int width;
+ int height;
+
+#ifdef WITH_WDS_API
+ HWND hWnd;
+ shwContext* shw;
+ HANDLE RdpUpdateEnterEvent;
+ HANDLE RdpUpdateLeaveEvent;
+ rdpAssistanceFile* pAssistanceFile;
+ _IRDPSessionEvents* pSessionEvents;
+ IRDPSRAPISharingSession* pSharingSession;
+ IRDPSRAPIInvitation* pInvitation;
+ IRDPSRAPIInvitationManager* pInvitationMgr;
+ IRDPSRAPISessionProperties* pSessionProperties;
+ IRDPSRAPIVirtualChannelManager* pVirtualChannelMgr;
+ IRDPSRAPIApplicationFilter* pApplicationFilter;
+ IRDPSRAPIAttendeeManager* pAttendeeMgr;
+#endif
+
+#ifdef WITH_DXGI_1_2
+ UINT pendingFrames;
+ BYTE* MetadataBuffer;
+ UINT MetadataBufferSize;
+ BOOL dxgiSurfaceMapped;
+ BOOL dxgiFrameAcquired;
+ ID3D11Device* dxgiDevice;
+ IDXGISurface* dxgiSurface;
+ ID3D11Texture2D* dxgiStage;
+ IDXGIResource* dxgiResource;
+ D3D_FEATURE_LEVEL featureLevel;
+ ID3D11Texture2D* dxgiDesktopImage;
+ DXGI_OUTDUPL_FRAME_INFO dxgiFrameInfo;
+ ID3D11DeviceContext* dxgiDeviceContext;
+ IDXGIOutputDuplication* dxgiOutputDuplication;
+#endif
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_H */
diff --git a/server/shadow/Win/win_wds.c b/server/shadow/Win/win_wds.c
new file mode 100644
index 0000000..197e61f
--- /dev/null
+++ b/server/shadow/Win/win_wds.c
@@ -0,0 +1,850 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <freerdp/log.h>
+
+#include "win_rdp.h"
+
+#include "win_wds.h"
+
+/**
+ * Windows Desktop Sharing API:
+ * http://blogs.msdn.com/b/rds/archive/2007/03/08/windows-desktop-sharing-api.aspx
+ *
+ * Windows Desktop Sharing Interfaces:
+ * http://msdn.microsoft.com/en-us/library/aa373871%28v=vs.85%29.aspx
+ *
+ * Offer Remote Assistance Sample C:
+ * http://msdn.microsoft.com/en-us/library/ms811079.aspx#remoteassistanceapi_topic2b
+ *
+ * Remote Assistance in XP: Programmatically establish an RDP session:
+ * http://www.codeproject.com/Articles/29939/Remote-Assistance-in-XP-Programmatically-establish
+ */
+
+#undef DEFINE_GUID
+#define INITGUID
+
+#include <initguid.h>
+
+#include <freerdp/assistance.h>
+
+#define TAG SERVER_TAG("shadow.win")
+
+DEFINE_GUID(CLSID_RDPSession, 0x9B78F0E6, 0x3E05, 0x4A5B, 0xB2, 0xE8, 0xE7, 0x43, 0xA8, 0x95, 0x6B,
+ 0x65);
+DEFINE_GUID(DIID__IRDPSessionEvents, 0x98a97042, 0x6698, 0x40e9, 0x8e, 0xfd, 0xb3, 0x20, 0x09, 0x90,
+ 0x00, 0x4b);
+DEFINE_GUID(IID_IRDPSRAPISharingSession, 0xeeb20886, 0xe470, 0x4cf6, 0x84, 0x2b, 0x27, 0x39, 0xc0,
+ 0xec, 0x5c, 0xfb);
+DEFINE_GUID(IID_IRDPSRAPIAttendee, 0xec0671b3, 0x1b78, 0x4b80, 0xa4, 0x64, 0x91, 0x32, 0x24, 0x75,
+ 0x43, 0xe3);
+DEFINE_GUID(IID_IRDPSRAPIAttendeeManager, 0xba3a37e8, 0x33da, 0x4749, 0x8d, 0xa0, 0x07, 0xfa, 0x34,
+ 0xda, 0x79, 0x44);
+DEFINE_GUID(IID_IRDPSRAPISessionProperties, 0x339b24f2, 0x9bc0, 0x4f16, 0x9a, 0xac, 0xf1, 0x65,
+ 0x43, 0x3d, 0x13, 0xd4);
+DEFINE_GUID(CLSID_RDPSRAPIApplicationFilter, 0xe35ace89, 0xc7e8, 0x427e, 0xa4, 0xf9, 0xb9, 0xda,
+ 0x07, 0x28, 0x26, 0xbd);
+DEFINE_GUID(CLSID_RDPSRAPIInvitationManager, 0x53d9c9db, 0x75ab, 0x4271, 0x94, 0x8a, 0x4c, 0x4e,
+ 0xb3, 0x6a, 0x8f, 0x2b);
+
+static ULONG Shadow_IRDPSessionEvents_RefCount = 0;
+
+const char* GetRDPSessionEventString(DISPID id)
+{
+ switch (id)
+ {
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED:
+ return "OnAttendeeConnected";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED:
+ return "OnAttendeeDisconnected";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE:
+ return "OnAttendeeUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ERROR:
+ return "OnError";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED:
+ return "OnConnectionEstablished";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED:
+ return "OnConnectionTerminated";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED:
+ return "OnConnectionAuthenticated";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED:
+ return "OnConnectionFailed";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST:
+ return "OnControlLevelChangeRequest";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED:
+ return "OnGraphicsStreamPaused";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED:
+ return "OnGraphicsStreamResumed";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN:
+ return "OnChannelJoin";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE:
+ return "OnChannelLeave";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED:
+ return "OnChannelDataReceived";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED:
+ return "OnChannelDataSent";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN:
+ return "OnApplicationOpen";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE:
+ return "OnApplicationClose";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE:
+ return "OnApplicationUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN:
+ return "OnWindowOpen";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE:
+ return "OnWindowClose";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE:
+ return "OnWindowUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE:
+ return "OnAppFilterUpdate";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED:
+ return "OnSharedRectChanged";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED:
+ return "OnFocusReleased";
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED:
+ return "OnSharedDesktopSettingsChanged";
+ break;
+
+ case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED:
+ return "OnViewingSizeChanged";
+ break;
+ }
+
+ return "OnUnknown";
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_QueryInterface(__RPC__in _IRDPSessionEvents* This,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [annotation][iid_is][out] */
+ _COM_Outptr_ void** ppvObject)
+{
+ *ppvObject = NULL;
+
+ if (IsEqualIID(riid, &DIID__IRDPSessionEvents) || IsEqualIID(riid, &IID_IDispatch) ||
+ IsEqualIID(riid, &IID_IUnknown))
+ {
+ *ppvObject = This;
+ }
+
+ if (!(*ppvObject))
+ return E_NOINTERFACE;
+
+ This->lpVtbl->AddRef(This);
+ return S_OK;
+}
+
+static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_AddRef(__RPC__in _IRDPSessionEvents* This)
+{
+ Shadow_IRDPSessionEvents_RefCount++;
+ return Shadow_IRDPSessionEvents_RefCount;
+}
+
+static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Release(__RPC__in _IRDPSessionEvents* This)
+{
+ if (!Shadow_IRDPSessionEvents_RefCount)
+ return 0;
+
+ Shadow_IRDPSessionEvents_RefCount--;
+ return Shadow_IRDPSessionEvents_RefCount;
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_GetTypeInfoCount(__RPC__in _IRDPSessionEvents* This,
+ /* [out] */ __RPC__out UINT* pctinfo)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfoCount");
+ *pctinfo = 1;
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE
+Shadow_IRDPSessionEvents_GetTypeInfo(__RPC__in _IRDPSessionEvents* This,
+ /* [in] */ UINT iTInfo,
+ /* [in] */ LCID lcid,
+ /* [out] */ __RPC__deref_out_opt ITypeInfo** ppTInfo)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfo");
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_GetIDsOfNames(
+ __RPC__in _IRDPSessionEvents* This,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR* rgszNames,
+ /* [range][in] */ __RPC__in_range(0, 16384) UINT cNames,
+ /* [in] */ LCID lcid,
+ /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID* rgDispId)
+{
+ WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetIDsOfNames");
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Invoke(_IRDPSessionEvents* This,
+ /* [annotation][in] */
+ _In_ DISPID dispIdMember,
+ /* [annotation][in] */
+ _In_ REFIID riid,
+ /* [annotation][in] */
+ _In_ LCID lcid,
+ /* [annotation][in] */
+ _In_ WORD wFlags,
+ /* [annotation][out][in] */
+ _In_ DISPPARAMS* pDispParams,
+ /* [annotation][out] */
+ _Out_opt_ VARIANT* pVarResult,
+ /* [annotation][out] */
+ _Out_opt_ EXCEPINFO* pExcepInfo,
+ /* [annotation][out] */
+ _Out_opt_ UINT* puArgErr)
+{
+ HRESULT hr;
+ VARIANT vr;
+ UINT uArgErr;
+ WLog_INFO(TAG, "%s (%ld)", GetRDPSessionEventString(dispIdMember), dispIdMember);
+
+ switch (dispIdMember)
+ {
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED:
+ {
+ int level;
+ IDispatch* pDispatch;
+ IRDPSRAPIAttendee* pAttendee;
+ vr.vt = VT_DISPATCH;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pDispatch = vr.pdispVal;
+ hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee,
+ (void**)&pAttendee);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ level = CTRL_LEVEL_VIEW;
+ // level = CTRL_LEVEL_INTERACTIVE;
+ hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pAttendee->lpVtbl->Release(pAttendee);
+ }
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_ERROR:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST:
+ {
+ int level;
+ IDispatch* pDispatch;
+ IRDPSRAPIAttendee* pAttendee;
+ vr.vt = VT_INT;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 1, VT_INT, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s DispGetParam(1, VT_INT) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ level = vr.intVal;
+ vr.vt = VT_DISPATCH;
+ vr.pdispVal = NULL;
+ hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pDispatch = vr.pdispVal;
+ hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee,
+ (void**)&pAttendee);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level);
+
+ if (FAILED(hr))
+ {
+ WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX",
+ GetRDPSessionEventString(dispIdMember), hr);
+ return hr;
+ }
+
+ pAttendee->lpVtbl->Release(pAttendee);
+ }
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED:
+ break;
+
+ case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED:
+ break;
+
+ case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED:
+ break;
+ }
+
+ return S_OK;
+}
+
+static _IRDPSessionEventsVtbl Shadow_IRDPSessionEventsVtbl = {
+ /* IUnknown */
+ Shadow_IRDPSessionEvents_QueryInterface, Shadow_IRDPSessionEvents_AddRef,
+ Shadow_IRDPSessionEvents_Release,
+
+ /* IDispatch */
+ Shadow_IRDPSessionEvents_GetTypeInfoCount, Shadow_IRDPSessionEvents_GetTypeInfo,
+ Shadow_IRDPSessionEvents_GetIDsOfNames, Shadow_IRDPSessionEvents_Invoke
+};
+
+static _IRDPSessionEvents Shadow_IRDPSessionEvents = { &Shadow_IRDPSessionEventsVtbl };
+
+static LRESULT CALLBACK ShadowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_CLOSE:
+ DestroyWindow(hwnd);
+ break;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+
+ default:
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ return 0;
+}
+
+int win_shadow_wds_wnd_init(winShadowSubsystem* subsystem)
+{
+ HMODULE hModule;
+ HINSTANCE hInstance;
+ WNDCLASSEX wndClassEx = { 0 };
+ hModule = GetModuleHandle(NULL);
+
+ wndClassEx.cbSize = sizeof(WNDCLASSEX);
+ wndClassEx.style = 0;
+ wndClassEx.lpfnWndProc = ShadowWndProc;
+ wndClassEx.cbClsExtra = 0;
+ wndClassEx.cbWndExtra = 0;
+ wndClassEx.hInstance = hModule;
+ wndClassEx.hIcon = NULL;
+ wndClassEx.hCursor = NULL;
+ wndClassEx.hbrBackground = NULL;
+ wndClassEx.lpszMenuName = _T("ShadowWndMenu");
+ wndClassEx.lpszClassName = _T("ShadowWndClass");
+ wndClassEx.hIconSm = NULL;
+
+ if (!RegisterClassEx(&wndClassEx))
+ {
+ WLog_ERR(TAG, "RegisterClassEx failure");
+ return -1;
+ }
+
+ hInstance = wndClassEx.hInstance;
+ subsystem->hWnd = CreateWindowEx(0, wndClassEx.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0,
+ hInstance, NULL);
+
+ if (!subsystem->hWnd)
+ {
+ WLog_INFO(TAG, "CreateWindowEx failure");
+ return -1;
+ }
+
+ return 1;
+}
+
+int win_shadow_wds_init(winShadowSubsystem* subsystem)
+{
+ int status;
+ HRESULT hr;
+ DWORD dwCookie;
+ long left, top;
+ long right, bottom;
+ long width, height;
+ IUnknown* pUnknown;
+ rdpSettings* settings;
+ BSTR bstrAuthString;
+ BSTR bstrGroupName;
+ BSTR bstrPassword;
+ BSTR bstrPropertyName;
+ VARIANT varPropertyValue;
+ rdpAssistanceFile* file;
+ IConnectionPoint* pCP;
+ IConnectionPointContainer* pCPC;
+ win_shadow_wds_wnd_init(subsystem);
+ hr = OleInitialize(NULL);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "OleInitialize() failure");
+ return -1;
+ }
+
+ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "CoInitialize() failure");
+ return -1;
+ }
+
+ hr = CoCreateInstance(&CLSID_RDPSession, NULL, CLSCTX_ALL, &IID_IRDPSRAPISharingSession,
+ (void**)&(subsystem->pSharingSession));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "CoCreateInstance(IRDPSRAPISharingSession) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ pUnknown = (IUnknown*)subsystem->pSharingSession;
+ hr = pUnknown->lpVtbl->QueryInterface(pUnknown, &IID_IConnectionPointContainer, (void**)&pCPC);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "QueryInterface(IID_IConnectionPointContainer) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ pCPC->lpVtbl->FindConnectionPoint(pCPC, &DIID__IRDPSessionEvents, &pCP);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(
+ TAG,
+ "IConnectionPointContainer::FindConnectionPoint(_IRDPSessionEvents) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ dwCookie = 0;
+ subsystem->pSessionEvents = &Shadow_IRDPSessionEvents;
+ subsystem->pSessionEvents->lpVtbl->AddRef(subsystem->pSessionEvents);
+ hr = pCP->lpVtbl->Advise(pCP, (IUnknown*)subsystem->pSessionEvents, &dwCookie);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IConnectionPoint::Advise(Shadow_IRDPSessionEvents) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->put_ColorDepth(subsystem->pSharingSession, 32);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::put_ColorDepth() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->GetDesktopSharedRect(subsystem->pSharingSession, &left,
+ &top, &right, &bottom);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::GetDesktopSharedRect() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ width = right - left;
+ height = bottom - top;
+ WLog_INFO(
+ TAG,
+ "GetDesktopSharedRect(): left: %ld top: %ld right: %ld bottom: %ld width: %ld height: %ld",
+ left, top, right, bottom, width, height);
+ hr = subsystem->pSharingSession->lpVtbl->get_VirtualChannelManager(
+ subsystem->pSharingSession, &(subsystem->pVirtualChannelMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_VirtualChannelManager() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_ApplicationFilter(
+ subsystem->pSharingSession, &(subsystem->pApplicationFilter));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_ApplicationFilter() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Attendees(subsystem->pSharingSession,
+ &(subsystem->pAttendeeMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Attendees() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Properties(subsystem->pSharingSession,
+ &(subsystem->pSessionProperties));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Properties() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"PortId");
+ varPropertyValue.vt = VT_I4;
+ varPropertyValue.intVal = 40000;
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortId) failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"DrvConAttach");
+ varPropertyValue.vt = VT_BOOL;
+ varPropertyValue.boolVal = VARIANT_TRUE;
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(DrvConAttach) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ bstrPropertyName = SysAllocString(L"PortProtocol");
+ varPropertyValue.vt = VT_I4;
+ // varPropertyValue.intVal = 0; // AF_UNSPEC
+ varPropertyValue.intVal = 2; // AF_INET
+ // varPropertyValue.intVal = 23; // AF_INET6
+ hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties,
+ bstrPropertyName, varPropertyValue);
+ SysFreeString(bstrPropertyName);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortProtocol) failure: 0x%08lX",
+ hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->Open(subsystem->pSharingSession);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::Open() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ hr = subsystem->pSharingSession->lpVtbl->get_Invitations(subsystem->pSharingSession,
+ &(subsystem->pInvitationMgr));
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Invitations() failure");
+ return -1;
+ }
+
+ bstrAuthString = SysAllocString(L"Shadow");
+ bstrGroupName = SysAllocString(L"ShadowGroup");
+ bstrPassword = SysAllocString(L"Shadow123!");
+ hr = subsystem->pInvitationMgr->lpVtbl->CreateInvitation(
+ subsystem->pInvitationMgr, bstrAuthString, bstrGroupName, bstrPassword, 5,
+ &(subsystem->pInvitation));
+ SysFreeString(bstrAuthString);
+ SysFreeString(bstrGroupName);
+ SysFreeString(bstrPassword);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPIInvitationManager::CreateInvitation() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ file = subsystem->pAssistanceFile = freerdp_assistance_file_new();
+
+ if (!file)
+ {
+ WLog_ERR(TAG, "freerdp_assistance_file_new() failed");
+ return -1;
+ }
+
+ {
+ int status2 = -1;
+ char* ConnectionString2;
+ BSTR bstrConnectionString;
+ hr = subsystem->pInvitation->lpVtbl->get_ConnectionString(subsystem->pInvitation,
+ &bstrConnectionString);
+
+ if (FAILED(hr))
+ {
+ WLog_ERR(TAG, "IRDPSRAPIInvitation::get_ConnectionString() failure: 0x%08lX", hr);
+ return -1;
+ }
+
+ ConnectionString2 = ConvertWCharToUtf8Alloc(bstrConnectionString, NULL);
+ SysFreeString(bstrConnectionString);
+ status2 = freerdp_assistance_set_connection_string2(file, ConnectionString2, "Shadow123!");
+ free(ConnectionString2);
+
+ if ((!ConnectionString2) || (status2 < 1))
+ {
+ WLog_ERR(TAG, "failed to convert connection string");
+ return -1;
+ }
+ }
+
+ freerdp_assistance_print_file(file, WLog_Get(TAG), WLOG_INFO);
+ status = win_shadow_rdp_init(subsystem);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "win_shadow_rdp_init() failure: %d", status);
+ return status;
+ }
+
+ settings = subsystem->shw->settings;
+ status = freerdp_assistance_populate_settings_from_assistance_file(file, settings);
+ freerdp_settings_set_string(settings, FreeRDP_Domain, "RDP");
+ freerdp_settings_set_string(settings, FreeRDP_Username, "Shadow");
+ freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE);
+ freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, width);
+ freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, height);
+ status = win_shadow_rdp_start(subsystem);
+
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "win_shadow_rdp_start() failure: %d", status);
+ return status;
+ }
+
+ return 1;
+}
+
+int win_shadow_wds_uninit(winShadowSubsystem* subsystem)
+{
+ if (subsystem->pSharingSession)
+ {
+ subsystem->pSharingSession->lpVtbl->Close(subsystem->pSharingSession);
+ subsystem->pSharingSession->lpVtbl->Release(subsystem->pSharingSession);
+ subsystem->pSharingSession = NULL;
+ }
+
+ if (subsystem->pVirtualChannelMgr)
+ {
+ subsystem->pVirtualChannelMgr->lpVtbl->Release(subsystem->pVirtualChannelMgr);
+ subsystem->pVirtualChannelMgr = NULL;
+ }
+
+ if (subsystem->pApplicationFilter)
+ {
+ subsystem->pApplicationFilter->lpVtbl->Release(subsystem->pApplicationFilter);
+ subsystem->pApplicationFilter = NULL;
+ }
+
+ if (subsystem->pAttendeeMgr)
+ {
+ subsystem->pAttendeeMgr->lpVtbl->Release(subsystem->pAttendeeMgr);
+ subsystem->pAttendeeMgr = NULL;
+ }
+
+ if (subsystem->pSessionProperties)
+ {
+ subsystem->pSessionProperties->lpVtbl->Release(subsystem->pSessionProperties);
+ subsystem->pSessionProperties = NULL;
+ }
+
+ if (subsystem->pInvitationMgr)
+ {
+ subsystem->pInvitationMgr->lpVtbl->Release(subsystem->pInvitationMgr);
+ subsystem->pInvitationMgr = NULL;
+ }
+
+ if (subsystem->pInvitation)
+ {
+ subsystem->pInvitation->lpVtbl->Release(subsystem->pInvitation);
+ subsystem->pInvitation = NULL;
+ }
+
+ if (subsystem->pAssistanceFile)
+ {
+ freerdp_assistance_file_free(subsystem->pAssistanceFile);
+ subsystem->pAssistanceFile = NULL;
+ }
+
+ if (subsystem->hWnd)
+ {
+ DestroyWindow(subsystem->hWnd);
+ subsystem->hWnd = NULL;
+ }
+
+ if (subsystem->shw)
+ {
+ win_shadow_rdp_uninit(subsystem);
+ subsystem->shw = NULL;
+ }
+
+ return 1;
+}
diff --git a/server/shadow/Win/win_wds.h b/server/shadow/Win/win_wds.h
new file mode 100644
index 0000000..f8f3d80
--- /dev/null
+++ b/server/shadow/Win/win_wds.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_WIN_WDS_H
+#define FREERDP_SERVER_SHADOW_WIN_WDS_H
+
+#define WITH_WDS_API 1
+
+#ifndef CINTERFACE
+#define CINTERFACE
+#endif
+
+#include <rdpencomapi.h>
+
+#ifndef DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED
+#define DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED 340
+#endif
+
+#include "win_shadow.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int win_shadow_wds_init(winShadowSubsystem* subsystem);
+ int win_shadow_wds_uninit(winShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_WIN_WDS_H */
diff --git a/server/shadow/X11/CMakeLists.txt b/server/shadow/X11/CMakeLists.txt
new file mode 100644
index 0000000..0d88a0e
--- /dev/null
+++ b/server/shadow/X11/CMakeLists.txt
@@ -0,0 +1,72 @@
+find_package(X11 REQUIRED)
+if(X11_FOUND)
+ add_definitions(-DWITH_X11)
+ include_directories(${X11_INCLUDE_DIR})
+ list(APPEND LIBS ${X11_LIBRARIES})
+endif()
+
+if(X11_XShm_FOUND)
+ add_definitions(-DWITH_XSHM)
+ include_directories(${X11_XShm_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xext_LIB})
+endif()
+
+if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT)
+ list(APPEND LIBS ${X11_Xext_LIB})
+endif()
+
+if(X11_Xinerama_FOUND)
+ add_definitions(-DWITH_XINERAMA)
+ include_directories(${X11_Xinerama_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xinerama_LIB})
+endif()
+
+if(X11_Xdamage_FOUND)
+ add_definitions(-DWITH_XDAMAGE)
+ include_directories(${X11_Xdamage_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xdamage_LIB})
+endif()
+
+if(X11_Xfixes_FOUND)
+ add_definitions(-DWITH_XFIXES)
+ include_directories(${X11_Xfixes_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_Xfixes_LIB})
+endif()
+
+if(X11_XTest_FOUND)
+ add_definitions(-DWITH_XTEST)
+ include_directories(${X11_XTest_INCLUDE_PATH})
+ list(APPEND LIBS ${X11_XTest_LIB})
+endif()
+
+# XCursor and XRandr are currently not used so don't link them
+#if(X11_Xcursor_FOUND)
+# add_definitions(-DWITH_XCURSOR)
+# include_directories(${X11_Xcursor_INCLUDE_PATH})
+# list(APPEND LIBS ${X11_Xcursor_LIB})
+#endif()
+
+#if(X11_Xrandr_FOUND)
+# add_definitions(-DWITH_XRANDR)
+# include_directories(${X11_Xrandr_INCLUDE_PATH})
+# list(APPEND LIBS ${X11_Xrandr_LIB})
+#endif()
+
+find_package(PAM)
+if(PAM_FOUND)
+ add_definitions(-DWITH_PAM)
+ include_directories(${PAM_INCLUDE_DIR})
+ list(APPEND LIBS ${PAM_LIBRARY})
+else()
+ message("building without PAM authentication support")
+endif()
+
+add_definitions(-DWITH_SHADOW_X11)
+add_library(freerdp-shadow-subsystem-impl STATIC
+ x11_shadow.h
+ x11_shadow.c
+)
+target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE
+ ${LIBS}
+)
diff --git a/server/shadow/X11/x11_shadow.c b/server/shadow/X11/x11_shadow.c
new file mode 100644
index 0000000..5c1fab1
--- /dev/null
+++ b/server/shadow/X11/x11_shadow.c
@@ -0,0 +1,1513 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/select.h>
+#include <sys/signal.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/image.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/region.h>
+
+#include "x11_shadow.h"
+
+#define TAG SERVER_TAG("shadow.x11")
+
+static UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors);
+
+#ifdef WITH_PAM
+
+#include <security/pam_appl.h>
+
+typedef struct
+{
+ const char* user;
+ const char* domain;
+ const char* password;
+} SHADOW_PAM_AUTH_DATA;
+
+typedef struct
+{
+ char* service_name;
+ pam_handle_t* handle;
+ struct pam_conv pamc;
+ SHADOW_PAM_AUTH_DATA appdata;
+} SHADOW_PAM_AUTH_INFO;
+
+static int x11_shadow_pam_conv(int num_msg, const struct pam_message** msg,
+ struct pam_response** resp, void* appdata_ptr)
+{
+ int pam_status = PAM_CONV_ERR;
+ SHADOW_PAM_AUTH_DATA* appdata = NULL;
+ struct pam_response* response = NULL;
+ WINPR_ASSERT(num_msg >= 0);
+ appdata = (SHADOW_PAM_AUTH_DATA*)appdata_ptr;
+ WINPR_ASSERT(appdata);
+
+ if (!(response = (struct pam_response*)calloc((size_t)num_msg, sizeof(struct pam_response))))
+ return PAM_BUF_ERR;
+
+ for (int index = 0; index < num_msg; index++)
+ {
+ switch (msg[index]->msg_style)
+ {
+ case PAM_PROMPT_ECHO_ON:
+ response[index].resp = _strdup(appdata->user);
+
+ if (!response[index].resp)
+ goto out_fail;
+
+ response[index].resp_retcode = PAM_SUCCESS;
+ break;
+
+ case PAM_PROMPT_ECHO_OFF:
+ response[index].resp = _strdup(appdata->password);
+
+ if (!response[index].resp)
+ goto out_fail;
+
+ response[index].resp_retcode = PAM_SUCCESS;
+ break;
+
+ default:
+ pam_status = PAM_CONV_ERR;
+ goto out_fail;
+ }
+ }
+
+ *resp = response;
+ return PAM_SUCCESS;
+out_fail:
+
+ for (int index = 0; index < num_msg; ++index)
+ {
+ if (response[index].resp)
+ {
+ memset(response[index].resp, 0, strlen(response[index].resp));
+ free(response[index].resp);
+ }
+ }
+
+ memset(response, 0, sizeof(struct pam_response) * (size_t)num_msg);
+ free(response);
+ *resp = NULL;
+ return pam_status;
+}
+
+static BOOL x11_shadow_pam_get_service_name(SHADOW_PAM_AUTH_INFO* info)
+{
+ const char* base = "/etc/pam.d";
+ const char* hints[] = { "lightdm", "gdm", "xdm", "login", "sshd" };
+
+ for (size_t x = 0; x < ARRAYSIZE(hints); x++)
+ {
+ char path[MAX_PATH];
+ const char* hint = hints[x];
+
+ _snprintf(path, sizeof(path), "%s/%s", base, hint);
+ if (winpr_PathFileExists(path))
+ {
+
+ info->service_name = _strdup(hint);
+ return info->service_name != NULL;
+ }
+ }
+ WLog_WARN(TAG, "Could not determine PAM service name");
+ return FALSE;
+}
+
+static int x11_shadow_pam_authenticate(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ const char* user, const char* domain, const char* password)
+{
+ int pam_status = 0;
+ SHADOW_PAM_AUTH_INFO info = { 0 };
+ WINPR_UNUSED(subsystem);
+ WINPR_UNUSED(client);
+
+ if (!x11_shadow_pam_get_service_name(&info))
+ return -1;
+
+ info.appdata.user = user;
+ info.appdata.domain = domain;
+ info.appdata.password = password;
+ info.pamc.conv = &x11_shadow_pam_conv;
+ info.pamc.appdata_ptr = &info.appdata;
+ pam_status = pam_start(info.service_name, 0, &info.pamc, &info.handle);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_start failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ pam_status = pam_authenticate(info.handle, 0);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_authenticate failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ pam_status = pam_acct_mgmt(info.handle, 0);
+
+ if (pam_status != PAM_SUCCESS)
+ {
+ WLog_ERR(TAG, "pam_acct_mgmt failure: %s", pam_strerror(info.handle, pam_status));
+ return -1;
+ }
+
+ return 1;
+}
+
+#endif
+
+static BOOL x11_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT32 flags)
+{
+ /* TODO: Implement */
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT8 code)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ DWORD vkcode = 0;
+ DWORD keycode = 0;
+ DWORD scancode = 0;
+ BOOL extended = FALSE;
+
+ if (!client || !subsystem)
+ return FALSE;
+
+ if (flags & KBD_FLAGS_EXTENDED)
+ extended = TRUE;
+
+ scancode = code;
+ if (extended)
+ scancode |= KBDEXT;
+
+ vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
+
+ if (extended)
+ vkcode |= KBDEXT;
+
+ keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_XKB);
+
+ if (keycode != 0)
+ {
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+
+ if (flags & KBD_FLAGS_RELEASE)
+ XTestFakeKeyEvent(x11->display, keycode, False, CurrentTime);
+ else
+ XTestFakeKeyEvent(x11->display, keycode, True, CurrentTime);
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+ }
+
+#endif
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags,
+ UINT16 code)
+{
+ /* TODO: Implement */
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
+ UINT16 flags, UINT16 x, UINT16 y)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ unsigned int button = 0;
+ BOOL down = FALSE;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ server = subsystem->server;
+
+ if (!server)
+ return FALSE;
+
+ surface = server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ x11->lastMouseClient = client;
+ x += surface->x;
+ y += surface->y;
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+
+ if (flags & PTR_FLAGS_WHEEL)
+ {
+ BOOL negative = FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ negative = TRUE;
+
+ button = (negative) ? 5 : 4;
+ XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime);
+ XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime);
+ }
+ else if (flags & PTR_FLAGS_HWHEEL)
+ {
+ BOOL negative = FALSE;
+
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ negative = TRUE;
+
+ button = (negative) ? 7 : 6;
+ XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime);
+ XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime);
+ }
+ else
+ {
+ if (flags & PTR_FLAGS_MOVE)
+ XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime);
+
+ if (flags & PTR_FLAGS_BUTTON1)
+ button = 1;
+ else if (flags & PTR_FLAGS_BUTTON2)
+ button = 3;
+ else if (flags & PTR_FLAGS_BUTTON3)
+ button = 2;
+
+ if (flags & PTR_FLAGS_DOWN)
+ down = TRUE;
+
+ if (button)
+ XTestFakeButtonEvent(x11->display, button, down, CurrentTime);
+ }
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+#endif
+ return TRUE;
+}
+
+static BOOL x11_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
+ rdpShadowClient* client, UINT16 flags, UINT16 x,
+ UINT16 y)
+{
+#ifdef WITH_XTEST
+ x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem;
+ UINT button = 0;
+ BOOL down = FALSE;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem || !client)
+ return FALSE;
+
+ server = subsystem->server;
+
+ if (!server)
+ return FALSE;
+
+ surface = server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ x11->lastMouseClient = client;
+ x += surface->x;
+ y += surface->y;
+ XLockDisplay(x11->display);
+ XTestGrabControl(x11->display, True);
+ XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime);
+
+ if (flags & PTR_XFLAGS_BUTTON1)
+ button = 8;
+ else if (flags & PTR_XFLAGS_BUTTON2)
+ button = 9;
+
+ if (flags & PTR_XFLAGS_DOWN)
+ down = TRUE;
+
+ if (button)
+ XTestFakeButtonEvent(x11->display, button, down, CurrentTime);
+
+ XTestGrabControl(x11->display, False);
+ XFlush(x11->display);
+ XUnlockDisplay(x11->display);
+#endif
+ return TRUE;
+}
+
+static void x11_shadow_message_free(UINT32 id, SHADOW_MSG_OUT* msg)
+{
+ switch (id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ free(msg);
+ break;
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->xorMaskData);
+ free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->andMaskData);
+ free(msg);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", id);
+ free(msg);
+ break;
+ }
+}
+
+static int x11_shadow_pointer_position_update(x11ShadowSubsystem* subsystem)
+{
+ UINT32 msgId = SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID;
+ rdpShadowServer* server = NULL;
+ SHADOW_MSG_OUT_POINTER_POSITION_UPDATE templateMsg;
+ int count = 0;
+
+ if (!subsystem || !subsystem->common.server || !subsystem->common.server->clients)
+ return -1;
+
+ templateMsg.xPos = subsystem->common.pointerX;
+ templateMsg.yPos = subsystem->common.pointerY;
+ templateMsg.common.Free = x11_shadow_message_free;
+ server = subsystem->common.server;
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg = NULL;
+ rdpShadowClient* client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index);
+
+ /* Skip the client which send us the latest mouse event */
+ if (client == subsystem->lastMouseClient)
+ continue;
+
+ msg = malloc(sizeof(templateMsg));
+
+ if (!msg)
+ {
+ count = -1;
+ break;
+ }
+
+ memcpy(msg, &templateMsg, sizeof(templateMsg));
+
+ if (shadow_client_post_msg(client, NULL, msgId, (SHADOW_MSG_OUT*)msg, NULL))
+ count++;
+ }
+
+ ArrayList_Unlock(server->clients);
+ return count;
+}
+
+static int x11_shadow_pointer_alpha_update(x11ShadowSubsystem* subsystem)
+{
+ SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg = NULL;
+ UINT32 msgId = SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID;
+ msg = (SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)calloc(1,
+ sizeof(SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE));
+
+ if (!msg)
+ return -1;
+
+ msg->xHot = subsystem->cursorHotX;
+ msg->yHot = subsystem->cursorHotY;
+ msg->width = subsystem->cursorWidth;
+ msg->height = subsystem->cursorHeight;
+
+ if (shadow_subsystem_pointer_convert_alpha_pointer_data(subsystem->cursorPixels, TRUE,
+ msg->width, msg->height, msg) < 0)
+ {
+ free(msg);
+ return -1;
+ }
+
+ msg->common.Free = x11_shadow_message_free;
+ return shadow_client_boardcast_msg(subsystem->common.server, NULL, msgId, (SHADOW_MSG_OUT*)msg,
+ NULL)
+ ? 1
+ : -1;
+}
+
+static int x11_shadow_query_cursor(x11ShadowSubsystem* subsystem, BOOL getImage)
+{
+ int x = 0;
+ int y = 0;
+ int n = 0;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ server = subsystem->common.server;
+ surface = server->surface;
+
+ if (getImage)
+ {
+#ifdef WITH_XFIXES
+ UINT32* pDstPixel = NULL;
+ XFixesCursorImage* ci = NULL;
+ XLockDisplay(subsystem->display);
+ ci = XFixesGetCursorImage(subsystem->display);
+ XUnlockDisplay(subsystem->display);
+
+ if (!ci)
+ return -1;
+
+ x = ci->x;
+ y = ci->y;
+
+ if (ci->width > subsystem->cursorMaxWidth)
+ return -1;
+
+ if (ci->height > subsystem->cursorMaxHeight)
+ return -1;
+
+ subsystem->cursorHotX = ci->xhot;
+ subsystem->cursorHotY = ci->yhot;
+ subsystem->cursorWidth = ci->width;
+ subsystem->cursorHeight = ci->height;
+ subsystem->cursorId = ci->cursor_serial;
+ n = ci->width * ci->height;
+ pDstPixel = (UINT32*)subsystem->cursorPixels;
+
+ for (int k = 0; k < n; k++)
+ {
+ /* XFixesCursorImage.pixels is in *unsigned long*, which may be 8 bytes */
+ *pDstPixel++ = (UINT32)ci->pixels[k];
+ }
+
+ XFree(ci);
+ x11_shadow_pointer_alpha_update(subsystem);
+#endif
+ }
+ else
+ {
+ UINT32 mask = 0;
+ int win_x = 0;
+ int win_y = 0;
+ int root_x = 0;
+ int root_y = 0;
+ Window root = 0;
+ Window child = 0;
+ XLockDisplay(subsystem->display);
+
+ if (!XQueryPointer(subsystem->display, subsystem->root_window, &root, &child, &root_x,
+ &root_y, &win_x, &win_y, &mask))
+ {
+ XUnlockDisplay(subsystem->display);
+ return -1;
+ }
+
+ XUnlockDisplay(subsystem->display);
+ x = root_x;
+ y = root_y;
+ }
+
+ /* Convert to offset based on current surface */
+ if (surface)
+ {
+ x -= surface->x;
+ y -= surface->y;
+ }
+
+ if ((x != (INT64)subsystem->common.pointerX) || (y != (INT64)subsystem->common.pointerY))
+ {
+ subsystem->common.pointerX = x;
+ subsystem->common.pointerY = y;
+ x11_shadow_pointer_position_update(subsystem);
+ }
+
+ return 1;
+}
+
+static int x11_shadow_handle_xevent(x11ShadowSubsystem* subsystem, XEvent* xevent)
+{
+ if (xevent->type == MotionNotify)
+ {
+ }
+
+#ifdef WITH_XFIXES
+ else if (xevent->type == subsystem->xfixes_cursor_notify_event)
+ {
+ x11_shadow_query_cursor(subsystem, TRUE);
+ }
+
+#endif
+ else
+ {
+ }
+
+ return 1;
+}
+
+static void x11_shadow_validate_region(x11ShadowSubsystem* subsystem, int x, int y, int width,
+ int height)
+{
+ XRectangle region;
+
+ if (!subsystem->use_xfixes || !subsystem->use_xdamage)
+ return;
+
+ region.x = x;
+ region.y = y;
+ region.width = width;
+ region.height = height;
+#if defined(WITH_XFIXES) && defined(WITH_XDAMAGE)
+ XLockDisplay(subsystem->display);
+ XFixesSetRegion(subsystem->display, subsystem->xdamage_region, &region, 1);
+ XDamageSubtract(subsystem->display, subsystem->xdamage, subsystem->xdamage_region, None);
+ XUnlockDisplay(subsystem->display);
+#endif
+}
+
+static int x11_shadow_blend_cursor(x11ShadowSubsystem* subsystem)
+{
+ UINT32 nXSrc = 0;
+ UINT32 nYSrc = 0;
+ INT64 nXDst = 0;
+ INT64 nYDst = 0;
+ UINT32 nWidth = 0;
+ UINT32 nHeight = 0;
+ UINT32 nSrcStep = 0;
+ UINT32 nDstStep = 0;
+ BYTE* pSrcData = NULL;
+ BYTE* pDstData = NULL;
+ BYTE A = 0;
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+ rdpShadowSurface* surface = NULL;
+
+ if (!subsystem)
+ return -1;
+
+ surface = subsystem->common.server->surface;
+ nXSrc = 0;
+ nYSrc = 0;
+ nWidth = subsystem->cursorWidth;
+ nHeight = subsystem->cursorHeight;
+ nXDst = subsystem->common.pointerX - subsystem->cursorHotX;
+ nYDst = subsystem->common.pointerY - subsystem->cursorHotY;
+
+ if (nXDst >= surface->width)
+ return 1;
+
+ if (nXDst < 0)
+ {
+ nXDst *= -1;
+
+ if (nXDst >= nWidth)
+ return 1;
+
+ nXSrc = (UINT32)nXDst;
+ nWidth -= nXDst;
+ nXDst = 0;
+ }
+
+ if (nYDst >= surface->height)
+ return 1;
+
+ if (nYDst < 0)
+ {
+ nYDst *= -1;
+
+ if (nYDst >= nHeight)
+ return 1;
+
+ nYSrc = (UINT32)nYDst;
+ nHeight -= nYDst;
+ nYDst = 0;
+ }
+
+ if ((nXDst + nWidth) > surface->width)
+ nWidth = surface->width - nXDst;
+
+ if ((nYDst + nHeight) > surface->height)
+ nHeight = surface->height - nYDst;
+
+ pSrcData = subsystem->cursorPixels;
+ nSrcStep = subsystem->cursorWidth * 4;
+ pDstData = surface->data;
+ nDstStep = surface->scanline;
+
+ for (int y = 0; y < nHeight; y++)
+ {
+ const BYTE* pSrcPixel = &pSrcData[((nYSrc + y) * nSrcStep) + (nXSrc * 4)];
+ BYTE* pDstPixel = &pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)];
+
+ for (int x = 0; x < nWidth; x++)
+ {
+ B = *pSrcPixel++;
+ G = *pSrcPixel++;
+ R = *pSrcPixel++;
+ A = *pSrcPixel++;
+
+ if (A == 0xFF)
+ {
+ pDstPixel[0] = B;
+ pDstPixel[1] = G;
+ pDstPixel[2] = R;
+ }
+ else
+ {
+ pDstPixel[0] = B + (pDstPixel[0] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ pDstPixel[1] = G + (pDstPixel[1] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ pDstPixel[2] = R + (pDstPixel[2] * (0xFF - A) + (0xFF / 2)) / 0xFF;
+ }
+
+ pDstPixel[3] = 0xFF;
+ pDstPixel += 4;
+ }
+ }
+
+ return 1;
+}
+
+static BOOL x11_shadow_check_resize(x11ShadowSubsystem* subsystem)
+{
+ XWindowAttributes attr;
+ XLockDisplay(subsystem->display);
+ XGetWindowAttributes(subsystem->display, subsystem->root_window, &attr);
+ XUnlockDisplay(subsystem->display);
+
+ if (attr.width != (INT64)subsystem->width || attr.height != (INT64)subsystem->height)
+ {
+ MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen);
+
+ /* Screen size changed. Refresh monitor definitions and trigger screen resize */
+ subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16);
+ shadow_screen_resize(subsystem->common.server->screen);
+ subsystem->width = attr.width;
+ subsystem->height = attr.height;
+
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ virtualScreen->right = subsystem->width - 1;
+ virtualScreen->bottom = subsystem->height - 1;
+ virtualScreen->flags = 1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int x11_shadow_error_handler_for_capture(Display* display, XErrorEvent* event)
+{
+ char msg[256];
+ XGetErrorText(display, event->error_code, (char*)&msg, sizeof(msg));
+ WLog_ERR(TAG, "X11 error: %s Error code: %x, request code: %x, minor code: %x", msg,
+ event->error_code, event->request_code, event->minor_code);
+
+ /* Ignore BAD MATCH error during image capture. Abort in other case */
+ if (event->error_code != BadMatch)
+ {
+ abort();
+ }
+
+ return 0;
+}
+
+static int x11_shadow_screen_grab(x11ShadowSubsystem* subsystem)
+{
+ int rc = 0;
+ size_t count = 0;
+ int status = -1;
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ XImage* image = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents = NULL;
+ server = subsystem->common.server;
+ surface = server->surface;
+ count = ArrayList_Count(server->clients);
+
+ if (count < 1)
+ return 1;
+
+ EnterCriticalSection(&surface->lock);
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->width;
+ surfaceRect.bottom = surface->height;
+ LeaveCriticalSection(&surface->lock);
+
+ XLockDisplay(subsystem->display);
+ /*
+ * Ignore BadMatch error during image capture. The screen size may be
+ * changed outside. We will resize to correct resolution at next frame
+ */
+ XSetErrorHandler(x11_shadow_error_handler_for_capture);
+#if defined(WITH_XDAMAGE)
+ if (subsystem->use_xshm)
+ {
+ image = subsystem->fb_image;
+ XCopyArea(subsystem->display, subsystem->root_window, subsystem->fb_pixmap,
+ subsystem->xshm_gc, 0, 0, subsystem->width, subsystem->height, 0, 0);
+
+ EnterCriticalSection(&surface->lock);
+ status = shadow_capture_compare(surface->data, surface->scanline, surface->width,
+ surface->height, (BYTE*)&(image->data[surface->width * 4]),
+ image->bytes_per_line, &invalidRect);
+ LeaveCriticalSection(&surface->lock);
+ }
+ else
+#endif
+ {
+ EnterCriticalSection(&surface->lock);
+ image = XGetImage(subsystem->display, subsystem->root_window, surface->x, surface->y,
+ surface->width, surface->height, AllPlanes, ZPixmap);
+
+ if (image)
+ {
+ status = shadow_capture_compare(surface->data, surface->scanline, surface->width,
+ surface->height, (BYTE*)image->data,
+ image->bytes_per_line, &invalidRect);
+ }
+ LeaveCriticalSection(&surface->lock);
+ if (!image)
+ {
+ /*
+ * BadMatch error happened. The size may have been changed again.
+ * Give up this frame and we will resize again in next frame
+ */
+ goto fail_capture;
+ }
+ }
+
+ /* Restore the default error handler */
+ XSetErrorHandler(NULL);
+ XSync(subsystem->display, False);
+ XUnlockDisplay(subsystem->display);
+
+ if (status)
+ {
+ BOOL empty = 0;
+ EnterCriticalSection(&surface->lock);
+ region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
+ region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
+ empty = region16_is_empty(&(surface->invalidRegion));
+ LeaveCriticalSection(&surface->lock);
+
+ if (!empty)
+ {
+ BOOL success = 0;
+ EnterCriticalSection(&surface->lock);
+ extents = region16_extents(&(surface->invalidRegion));
+ x = extents->left;
+ y = extents->top;
+ width = extents->right - extents->left;
+ height = extents->bottom - extents->top;
+ WINPR_ASSERT(image);
+ WINPR_ASSERT(image->bytes_per_line >= 0);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(height >= 0);
+ success = freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y,
+ (UINT32)width, (UINT32)height, (BYTE*)image->data,
+ PIXEL_FORMAT_BGRX32, (UINT32)image->bytes_per_line, x, y,
+ NULL, FREERDP_FLIP_NONE);
+ LeaveCriticalSection(&surface->lock);
+ if (!success)
+ goto fail_capture;
+
+ // x11_shadow_blend_cursor(subsystem);
+ count = ArrayList_Count(server->clients);
+ shadow_subsystem_frame_update(&subsystem->common);
+
+ if (count == 1)
+ {
+ rdpShadowClient* client = NULL;
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0);
+
+ if (client)
+ subsystem->common.captureFrameRate =
+ shadow_encoder_preferred_fps(client->encoder);
+ }
+
+ EnterCriticalSection(&surface->lock);
+ region16_clear(&(surface->invalidRegion));
+ LeaveCriticalSection(&surface->lock);
+ }
+ }
+
+ rc = 1;
+fail_capture:
+ if (!subsystem->use_xshm && image)
+ XDestroyImage(image);
+
+ if (rc != 1)
+ {
+ XSetErrorHandler(NULL);
+ XSync(subsystem->display, False);
+ XUnlockDisplay(subsystem->display);
+ }
+
+ return rc;
+}
+
+static int x11_shadow_subsystem_process_message(x11ShadowSubsystem* subsystem, wMessage* message)
+{
+ switch (message->id)
+ {
+ case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
+ shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem);
+ break;
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ if (message->Free)
+ message->Free(message);
+
+ return 1;
+}
+
+static DWORD WINAPI x11_shadow_subsystem_thread(LPVOID arg)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)arg;
+ XEvent xevent;
+ DWORD status = 0;
+ DWORD nCount = 0;
+ UINT64 cTime = 0;
+ DWORD dwTimeout = 0;
+ DWORD dwInterval = 0;
+ UINT64 frameTime = 0;
+ HANDLE events[32];
+ wMessage message;
+ wMessagePipe* MsgPipe = NULL;
+ MsgPipe = subsystem->common.MsgPipe;
+ nCount = 0;
+ events[nCount++] = subsystem->common.event;
+ events[nCount++] = MessageQueue_Event(MsgPipe->In);
+ subsystem->common.captureFrameRate = 16;
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime = GetTickCount64() + dwInterval;
+
+ while (1)
+ {
+ cTime = GetTickCount64();
+ dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime;
+ status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0)
+ {
+ if (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ break;
+
+ x11_shadow_subsystem_process_message(subsystem, &message);
+ }
+ }
+
+ if (WaitForSingleObject(subsystem->common.event, 0) == WAIT_OBJECT_0)
+ {
+ XLockDisplay(subsystem->display);
+
+ if (XEventsQueued(subsystem->display, QueuedAlready))
+ {
+ XNextEvent(subsystem->display, &xevent);
+ x11_shadow_handle_xevent(subsystem, &xevent);
+ }
+
+ XUnlockDisplay(subsystem->display);
+ }
+
+ if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
+ {
+ x11_shadow_check_resize(subsystem);
+ x11_shadow_screen_grab(subsystem);
+ x11_shadow_query_cursor(subsystem, FALSE);
+ dwInterval = 1000 / subsystem->common.captureFrameRate;
+ frameTime += dwInterval;
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static int x11_shadow_subsystem_base_init(x11ShadowSubsystem* subsystem)
+{
+ if (subsystem->display)
+ return 1; /* initialize once */
+
+ if (!getenv("DISPLAY"))
+ setenv("DISPLAY", ":0", 1);
+
+ if (!XInitThreads())
+ return -1;
+
+ subsystem->display = XOpenDisplay(NULL);
+
+ if (!subsystem->display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ return -1;
+ }
+
+ subsystem->xfds = ConnectionNumber(subsystem->display);
+ subsystem->number = DefaultScreen(subsystem->display);
+ subsystem->screen = ScreenOfDisplay(subsystem->display, subsystem->number);
+ subsystem->depth = DefaultDepthOfScreen(subsystem->screen);
+ subsystem->width = WidthOfScreen(subsystem->screen);
+ subsystem->height = HeightOfScreen(subsystem->screen);
+ subsystem->root_window = RootWindow(subsystem->display, subsystem->number);
+ return 1;
+}
+
+static int x11_shadow_xfixes_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XFIXES
+ int xfixes_event = 0;
+ int xfixes_error = 0;
+ int major = 0;
+ int minor = 0;
+
+ if (!XFixesQueryExtension(subsystem->display, &xfixes_event, &xfixes_error))
+ return -1;
+
+ if (!XFixesQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+
+ subsystem->xfixes_cursor_notify_event = xfixes_event + XFixesCursorNotify;
+ XFixesSelectCursorInput(subsystem->display, subsystem->root_window,
+ XFixesDisplayCursorNotifyMask);
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xinerama_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XINERAMA
+ int xinerama_event = 0;
+ int xinerama_error = 0;
+ x11_shadow_subsystem_base_init(subsystem);
+
+ if (!XineramaQueryExtension(subsystem->display, &xinerama_event, &xinerama_error))
+ return -1;
+
+#if defined(WITH_XDAMAGE)
+ int major = 0;
+ int minor = 0;
+ if (!XDamageQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+#endif
+
+ if (!XineramaIsActive(subsystem->display))
+ return -1;
+
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xdamage_init(x11ShadowSubsystem* subsystem)
+{
+#ifdef WITH_XDAMAGE
+ int major = 0;
+ int minor = 0;
+ int damage_event = 0;
+ int damage_error = 0;
+
+ if (!subsystem->use_xfixes)
+ return -1;
+
+ if (!XDamageQueryExtension(subsystem->display, &damage_event, &damage_error))
+ return -1;
+
+ if (!XDamageQueryVersion(subsystem->display, &major, &minor))
+ return -1;
+
+ if (major < 1)
+ return -1;
+
+ subsystem->xdamage_notify_event = damage_event + XDamageNotify;
+ subsystem->xdamage =
+ XDamageCreate(subsystem->display, subsystem->root_window, XDamageReportDeltaRectangles);
+
+ if (!subsystem->xdamage)
+ return -1;
+
+#ifdef WITH_XFIXES
+ subsystem->xdamage_region = XFixesCreateRegion(subsystem->display, NULL, 0);
+
+ if (!subsystem->xdamage_region)
+ return -1;
+
+#endif
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int x11_shadow_xshm_init(x11ShadowSubsystem* subsystem)
+{
+ Bool pixmaps = 0;
+ int major = 0;
+ int minor = 0;
+ XGCValues values;
+
+ if (!XShmQueryExtension(subsystem->display))
+ return -1;
+
+ if (!XShmQueryVersion(subsystem->display, &major, &minor, &pixmaps))
+ return -1;
+
+ if (!pixmaps)
+ return -1;
+
+ subsystem->fb_shm_info.shmid = -1;
+ subsystem->fb_shm_info.shmaddr = (char*)-1;
+ subsystem->fb_shm_info.readOnly = False;
+ subsystem->fb_image =
+ XShmCreateImage(subsystem->display, subsystem->visual, subsystem->depth, ZPixmap, NULL,
+ &(subsystem->fb_shm_info), subsystem->width, subsystem->height);
+
+ if (!subsystem->fb_image)
+ {
+ WLog_ERR(TAG, "XShmCreateImage failed");
+ return -1;
+ }
+
+ subsystem->fb_shm_info.shmid = shmget(
+ IPC_PRIVATE, 1ull * subsystem->fb_image->bytes_per_line * subsystem->fb_image->height,
+ IPC_CREAT | 0600);
+
+ if (subsystem->fb_shm_info.shmid == -1)
+ {
+ WLog_ERR(TAG, "shmget failed");
+ return -1;
+ }
+
+ subsystem->fb_shm_info.shmaddr = shmat(subsystem->fb_shm_info.shmid, 0, 0);
+ subsystem->fb_image->data = subsystem->fb_shm_info.shmaddr;
+
+ if (subsystem->fb_shm_info.shmaddr == ((char*)-1))
+ {
+ WLog_ERR(TAG, "shmat failed");
+ return -1;
+ }
+
+ if (!XShmAttach(subsystem->display, &(subsystem->fb_shm_info)))
+ return -1;
+
+ XSync(subsystem->display, False);
+ shmctl(subsystem->fb_shm_info.shmid, IPC_RMID, 0);
+ subsystem->fb_pixmap =
+ XShmCreatePixmap(subsystem->display, subsystem->root_window, subsystem->fb_image->data,
+ &(subsystem->fb_shm_info), subsystem->fb_image->width,
+ subsystem->fb_image->height, subsystem->fb_image->depth);
+ XSync(subsystem->display, False);
+
+ if (!subsystem->fb_pixmap)
+ return -1;
+
+ values.subwindow_mode = IncludeInferiors;
+ values.graphics_exposures = False;
+#if defined(WITH_XDAMAGE)
+ subsystem->xshm_gc = XCreateGC(subsystem->display, subsystem->root_window,
+ GCSubwindowMode | GCGraphicsExposures, &values);
+ XSetFunction(subsystem->display, subsystem->xshm_gc, GXcopy);
+#endif
+ XSync(subsystem->display, False);
+ return 1;
+}
+
+UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ Display* display = NULL;
+ int displayWidth = 0;
+ int displayHeight = 0;
+ int numMonitors = 0;
+
+ if (!getenv("DISPLAY"))
+ setenv("DISPLAY", ":0", 1);
+
+ display = XOpenDisplay(NULL);
+
+ if (!display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ return -1;
+ }
+
+ displayWidth = WidthOfScreen(DefaultScreenOfDisplay(display));
+ displayHeight = HeightOfScreen(DefaultScreenOfDisplay(display));
+#ifdef WITH_XINERAMA
+ {
+#if defined(WITH_XDAMAGE)
+ int major = 0;
+ int minor = 0;
+#endif
+ int xinerama_event = 0;
+ int xinerama_error = 0;
+ XineramaScreenInfo* screens = NULL;
+
+ const Bool xinerama = XineramaQueryExtension(display, &xinerama_event, &xinerama_error);
+ const Bool damage =
+#if defined(WITH_XDAMAGE)
+ XDamageQueryVersion(display, &major, &minor);
+#else
+ False;
+#endif
+
+ if (xinerama && damage && XineramaIsActive(display))
+ {
+ screens = XineramaQueryScreens(display, &numMonitors);
+
+ if (numMonitors > (INT64)maxMonitors)
+ numMonitors = (int)maxMonitors;
+
+ if (screens && (numMonitors > 0))
+ {
+ for (int index = 0; index < numMonitors; index++)
+ {
+ MONITOR_DEF* monitor = &monitors[index];
+ const XineramaScreenInfo* screen = &screens[index];
+
+ monitor->left = screen->x_org;
+ monitor->top = screen->y_org;
+ monitor->right = monitor->left + screen->width - 1;
+ monitor->bottom = monitor->top + screen->height - 1;
+ monitor->flags = (index == 0) ? 1 : 0;
+ }
+ }
+
+ XFree(screens);
+ }
+ }
+#endif
+ XCloseDisplay(display);
+
+ if (numMonitors < 1)
+ {
+ MONITOR_DEF* monitor = &monitors[0];
+ numMonitors = 1;
+
+ monitor->left = 0;
+ monitor->top = 0;
+ monitor->right = displayWidth - 1;
+ monitor->bottom = displayHeight - 1;
+ monitor->flags = 1;
+ }
+
+ errno = 0;
+ return numMonitors;
+}
+
+static int x11_shadow_subsystem_init(rdpShadowSubsystem* sub)
+{
+ int pf_count = 0;
+ int vi_count = 0;
+ int nextensions = 0;
+ char** extensions = NULL;
+ XVisualInfo* vi = NULL;
+ XVisualInfo* vis = NULL;
+ XVisualInfo template = { 0 };
+ XPixmapFormatValues* pf = NULL;
+ XPixmapFormatValues* pfs = NULL;
+
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16);
+ x11_shadow_subsystem_base_init(subsystem);
+
+ if ((subsystem->depth != 24) && (subsystem->depth != 32))
+ {
+ WLog_ERR(TAG, "unsupported X11 server color depth: %" PRIu32, subsystem->depth);
+ return -1;
+ }
+
+ extensions = XListExtensions(subsystem->display, &nextensions);
+
+ if (!extensions || (nextensions < 0))
+ return -1;
+
+ for (int i = 0; i < nextensions; i++)
+ {
+ if (strcmp(extensions[i], "Composite") == 0)
+ subsystem->composite = TRUE;
+ }
+
+ XFreeExtensionList(extensions);
+
+ if (subsystem->composite)
+ subsystem->use_xdamage = FALSE;
+
+ pfs = XListPixmapFormats(subsystem->display, &pf_count);
+
+ if (!pfs)
+ {
+ WLog_ERR(TAG, "XListPixmapFormats failed");
+ return -1;
+ }
+
+ for (int i = 0; i < pf_count; i++)
+ {
+ pf = pfs + i;
+
+ if (pf->depth == (INT64)subsystem->depth)
+ {
+ subsystem->bpp = pf->bits_per_pixel;
+ subsystem->scanline_pad = pf->scanline_pad;
+ break;
+ }
+ }
+
+ XFree(pfs);
+ template.class = TrueColor;
+ template.screen = subsystem->number;
+ vis = XGetVisualInfo(subsystem->display, VisualClassMask | VisualScreenMask, &template,
+ &vi_count);
+
+ if (!vis)
+ {
+ WLog_ERR(TAG, "XGetVisualInfo failed");
+ return -1;
+ }
+
+ for (int i = 0; i < vi_count; i++)
+ {
+ vi = vis + i;
+
+ if (vi->depth == (INT64)subsystem->depth)
+ {
+ subsystem->visual = vi->visual;
+ break;
+ }
+ }
+
+ XFree(vis);
+ XSelectInput(subsystem->display, subsystem->root_window, SubstructureNotifyMask);
+ subsystem->cursorMaxWidth = 256;
+ subsystem->cursorMaxHeight = 256;
+ subsystem->cursorPixels =
+ winpr_aligned_malloc(subsystem->cursorMaxWidth * subsystem->cursorMaxHeight * 4ull, 16);
+
+ if (!subsystem->cursorPixels)
+ return -1;
+
+ x11_shadow_query_cursor(subsystem, TRUE);
+
+ if (subsystem->use_xfixes)
+ {
+ if (x11_shadow_xfixes_init(subsystem) < 0)
+ subsystem->use_xfixes = FALSE;
+ }
+
+ if (subsystem->use_xinerama)
+ {
+ if (x11_shadow_xinerama_init(subsystem) < 0)
+ subsystem->use_xinerama = FALSE;
+ }
+
+ if (subsystem->use_xshm)
+ {
+ if (x11_shadow_xshm_init(subsystem) < 0)
+ subsystem->use_xshm = FALSE;
+ }
+
+ if (subsystem->use_xdamage)
+ {
+ if (x11_shadow_xdamage_init(subsystem) < 0)
+ subsystem->use_xdamage = FALSE;
+ }
+
+ if (!(subsystem->common.event =
+ CreateFileDescriptorEvent(NULL, FALSE, FALSE, subsystem->xfds, WINPR_FD_READ)))
+ return -1;
+
+ {
+ MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen);
+ virtualScreen->left = 0;
+ virtualScreen->top = 0;
+ WINPR_ASSERT(subsystem->width <= INT32_MAX);
+ WINPR_ASSERT(subsystem->height <= INT32_MAX);
+ virtualScreen->right = (INT32)subsystem->width - 1;
+ virtualScreen->bottom = (INT32)subsystem->height - 1;
+ virtualScreen->flags = 1;
+ WLog_INFO(TAG,
+ "X11 Extensions: XFixes: %" PRId32 " Xinerama: %" PRId32 " XDamage: %" PRId32
+ " XShm: %" PRId32 "",
+ subsystem->use_xfixes, subsystem->use_xinerama, subsystem->use_xdamage,
+ subsystem->use_xshm);
+ }
+ return 1;
+}
+
+static int x11_shadow_subsystem_uninit(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->display)
+ {
+ XCloseDisplay(subsystem->display);
+ subsystem->display = NULL;
+ }
+
+ if (subsystem->common.event)
+ {
+ CloseHandle(subsystem->common.event);
+ subsystem->common.event = NULL;
+ }
+
+ if (subsystem->cursorPixels)
+ {
+ winpr_aligned_free(subsystem->cursorPixels);
+ subsystem->cursorPixels = NULL;
+ }
+
+ return 1;
+}
+
+static int x11_shadow_subsystem_start(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (!(subsystem->thread =
+ CreateThread(NULL, 0, x11_shadow_subsystem_thread, (void*)subsystem, 0, NULL)))
+ {
+ WLog_ERR(TAG, "Failed to create thread");
+ return -1;
+ }
+
+ return 1;
+}
+
+static int x11_shadow_subsystem_stop(rdpShadowSubsystem* sub)
+{
+ x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub;
+
+ if (!subsystem)
+ return -1;
+
+ if (subsystem->thread)
+ {
+ if (MessageQueue_PostQuit(subsystem->common.MsgPipe->In, 0))
+ WaitForSingleObject(subsystem->thread, INFINITE);
+
+ CloseHandle(subsystem->thread);
+ subsystem->thread = NULL;
+ }
+
+ return 1;
+}
+
+static rdpShadowSubsystem* x11_shadow_subsystem_new(void)
+{
+ x11ShadowSubsystem* subsystem = NULL;
+ subsystem = (x11ShadowSubsystem*)calloc(1, sizeof(x11ShadowSubsystem));
+
+ if (!subsystem)
+ return NULL;
+
+#ifdef WITH_PAM
+ subsystem->common.Authenticate = x11_shadow_pam_authenticate;
+#endif
+ subsystem->common.SynchronizeEvent = x11_shadow_input_synchronize_event;
+ subsystem->common.KeyboardEvent = x11_shadow_input_keyboard_event;
+ subsystem->common.UnicodeKeyboardEvent = x11_shadow_input_unicode_keyboard_event;
+ subsystem->common.MouseEvent = x11_shadow_input_mouse_event;
+ subsystem->common.ExtendedMouseEvent = x11_shadow_input_extended_mouse_event;
+ subsystem->composite = FALSE;
+ subsystem->use_xshm = FALSE; /* temporarily disabled */
+ subsystem->use_xfixes = TRUE;
+ subsystem->use_xdamage = FALSE;
+ subsystem->use_xinerama = TRUE;
+ return (rdpShadowSubsystem*)subsystem;
+}
+
+static void x11_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ x11_shadow_subsystem_uninit(subsystem);
+ free(subsystem);
+}
+
+FREERDP_ENTRY_POINT(FREERDP_API const char* ShadowSubsystemName(void))
+{
+ return "X11";
+}
+
+FREERDP_ENTRY_POINT(FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints))
+{
+ if (!pEntryPoints)
+ return -1;
+
+ pEntryPoints->New = x11_shadow_subsystem_new;
+ pEntryPoints->Free = x11_shadow_subsystem_free;
+ pEntryPoints->Init = x11_shadow_subsystem_init;
+ pEntryPoints->Uninit = x11_shadow_subsystem_uninit;
+ pEntryPoints->Start = x11_shadow_subsystem_start;
+ pEntryPoints->Stop = x11_shadow_subsystem_stop;
+ pEntryPoints->EnumMonitors = x11_shadow_enum_monitors;
+ return 1;
+}
diff --git a/server/shadow/X11/x11_shadow.h b/server/shadow/X11/x11_shadow.h
new file mode 100644
index 0000000..aca2d63
--- /dev/null
+++ b/server/shadow/X11/x11_shadow.h
@@ -0,0 +1,114 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_X11_H
+#define FREERDP_SERVER_SHADOW_X11_H
+
+#include <freerdp/server/shadow.h>
+
+typedef struct x11_shadow_subsystem x11ShadowSubsystem;
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <X11/Xlib.h>
+
+#ifdef WITH_XSHM
+#include <X11/extensions/XShm.h>
+#endif
+
+#ifdef WITH_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#ifdef WITH_XTEST
+#include <X11/extensions/XTest.h>
+#endif
+
+#ifdef WITH_XDAMAGE
+#include <X11/extensions/Xdamage.h>
+#endif
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+struct x11_shadow_subsystem
+{
+ rdpShadowSubsystem common;
+
+ HANDLE thread;
+
+ UINT32 bpp;
+ int xfds;
+ UINT32 depth;
+ UINT32 width;
+ UINT32 height;
+ int number;
+ XImage* image;
+ Screen* screen;
+ Visual* visual;
+ Display* display;
+ UINT32 scanline_pad;
+ BOOL composite;
+
+ BOOL use_xshm;
+ BOOL use_xfixes;
+ BOOL use_xdamage;
+ BOOL use_xinerama;
+
+ XImage* fb_image;
+ Pixmap fb_pixmap;
+ Window root_window;
+ XShmSegmentInfo fb_shm_info;
+
+ UINT32 cursorHotX;
+ UINT32 cursorHotY;
+ UINT32 cursorWidth;
+ UINT32 cursorHeight;
+ UINT32 cursorId;
+ BYTE* cursorPixels;
+ UINT32 cursorMaxWidth;
+ UINT32 cursorMaxHeight;
+ rdpShadowClient* lastMouseClient;
+
+#ifdef WITH_XDAMAGE
+ GC xshm_gc;
+ Damage xdamage;
+ int xdamage_notify_event;
+ XserverRegion xdamage_region;
+#endif
+
+#ifdef WITH_XFIXES
+ int xfixes_cursor_notify_event;
+#endif
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_X11_H */
diff --git a/server/shadow/freerdp-shadow-cli.1.in b/server/shadow/freerdp-shadow-cli.1.in
new file mode 100644
index 0000000..890fb7a
--- /dev/null
+++ b/server/shadow/freerdp-shadow-cli.1.in
@@ -0,0 +1,85 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A utility for sharing a X display via RDP.
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB/port:\fP\fI<port number>\fP]
+[\fB/ipc-socket:\fP\fI<ipc-socket>\fP]
+[\fB/monitors:\fP\fI<0,1,2,...>\fP]
+[\fB/rect:\fP\fI<x,y,w,h>\fP]
+[\fB+auth\fP]
+[\fB-may-view\fP]
+[\fB-may-interact\fP]
+[\fB/sec:\fP\fI<rdp|tls|nla|ext>\fP]
+[\fB-sec-rdp\fP]
+[\fB-sec-tls\fP]
+[\fB-sec-nla\fP]
+[\fB-sec-ext\fP]
+[\fB/sam-file:\fP\fI<file>\fP]
+[\fB/version\fP]
+[\fB/help\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+can be used to share a running X display like with VNC but by using the RDP
+instead. It is also possibly to share only parts (rect) of the display.
+.SH OPTIONS
+.IP /ipc-socket:<ipc-socket>
+If this option is set an ipc socket with the path \fIipc-socket\fP is used
+instead of a TCP socket.
+.IP /port:<port>
+Set the port to use. Default is 3389.
+This option is ignored if ipc-socket is used.
+.IP /monitors:<1,2,3,...>
+Select the monitor(s) to share.
+.IP /rect:<x,y,w,h>
+Select rectangle within monitor to share.
+.IP -auth
+Disable authentication. If authentication is enabled PAM is used with the
+X11 subsystem. Running as root is not necessary, however if run as user only
+the same user that started @MANPAGE_NAME@ can authenticate.
+.br
+\fBWarning\fP: If authentication is disabled \fIeveryone\fP can connect.
+.IP -may-view
+Clients may view without prompt.
+.IP -may-interact
+Clients may interact without prompt.
+.IP /sec:<rdp|tls|nla|ext>
+Force a specific protocol security
+.IP -sec-rdp
+Disable RDP security (default:on)
+.IP -sec-tls
+Disable TLS protocol security (default:on)
+.IP -sec-nla
+Disable NLA protocol security (default:on)
+.IP +sec-ext
+Use NLA extended protocol security (default:off)
+.IP /sam-file:<file>
+NTLM SAM file for NLA authentication
+.IP /version
+Print the version and exit.
+.IP /help
+Print the help and exit.
+
+.SH EXAMPLES
+@MANPAGE_NAME@ /port:12345
+
+When run as user within a X session (for example from an xterm) a socket on
+12345 is opened and the current display is shared via RDP.
+
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/server/shadow/freerdp-shadow.pc.in b/server/shadow/freerdp-shadow.pc.in
new file mode 100644
index 0000000..805e2af
--- /dev/null
+++ b/server/shadow/freerdp-shadow.pc.in
@@ -0,0 +1,15 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-shadow@FREERDP_API_VERSION@ -lfreerdp-shadow-subsystem@FREERDP_API_VERSION@
+
+Name: FreeRDP shadow
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_API_VERSION@
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/shadow/shadow.c b/server/shadow/shadow.c
new file mode 100644
index 0000000..14c9014
--- /dev/null
+++ b/server/shadow/shadow.c
@@ -0,0 +1,179 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/cmdline.h>
+#include <winpr/winsock.h>
+
+#include <winpr/tools/makecert.h>
+
+#include <freerdp/server/shadow.h>
+#include <freerdp/settings.h>
+
+#include <freerdp/log.h>
+#define TAG SERVER_TAG("shadow")
+
+int main(int argc, char** argv)
+{
+ int status = 0;
+ DWORD dwExitCode = 0;
+ COMMAND_LINE_ARGUMENT_A shadow_args[] = {
+ { "log-filters", COMMAND_LINE_VALUE_REQUIRED, "<tag>:<level>[,<tag>:<level>[,...]]", NULL,
+ NULL, -1, NULL, "Set logger filters, see wLog(7) for details" },
+ { "log-level", COMMAND_LINE_VALUE_REQUIRED, "[OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE]", NULL,
+ NULL, -1, NULL, "Set the default log level, see wLog(7) for details" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" },
+ { "ipc-socket", COMMAND_LINE_VALUE_REQUIRED, "<ipc-socket>", NULL, NULL, -1, NULL,
+ "Server IPC socket" },
+ { "bind-address", COMMAND_LINE_VALUE_REQUIRED, "<bind-address>[,<another address>, ...]",
+ NULL, NULL, -1, NULL,
+ "An address to bind to. Use '[<ipv6>]' for IPv6 addresses, e.g. '[::1]' for "
+ "localhost" },
+ { "monitors", COMMAND_LINE_VALUE_OPTIONAL, "<0,1,2...>", NULL, NULL, -1, NULL,
+ "Select or list monitors" },
+ { "max-connections", COMMAND_LINE_VALUE_REQUIRED, "<number>", 0, NULL, -1, NULL,
+ "maximum connections allowed to server, 0 to deactivate" },
+ { "rect", COMMAND_LINE_VALUE_REQUIRED, "<x,y,w,h>", NULL, NULL, -1, NULL,
+ "Select rectangle within monitor to share" },
+ { "auth", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Clients must authenticate" },
+ { "remote-guard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Remote credential guard" },
+ { "may-view", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Clients may view without prompt" },
+ { "may-interact", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Clients may interact without prompt" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED, "<rdp|tls|nla|ext>", NULL, NULL, -1, NULL,
+ "force specific protocol security" },
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "rdp protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "tls protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "nla protocol security" },
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "nla extended protocol security" },
+ { "sam-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "NTLM SAM file for NLA authentication" },
+ { "keytab", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Kerberos keytab file for NLA authentication" },
+ { "ccache", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "Kerberos host ccache file for NLA authentication" },
+ { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
+ "file where tls secrets shall be stored" },
+ { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX progressive codec" },
+ { "gfx-rfx", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX RFX codec" },
+ { "gfx-planar", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX planar codec" },
+ { "gfx-avc420", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX AVC420 codec" },
+ { "gfx-avc444", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Allow GFX AVC444 codec" },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1,
+ NULL, "Print version" },
+ { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL,
+ -1, NULL, "Print the build configuration" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "Print help" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+ };
+
+ shadow_subsystem_set_entry_builtin(NULL);
+
+ rdpShadowServer* server = shadow_server_new();
+
+ if (!server)
+ {
+ status = -1;
+ WLog_ERR(TAG, "Server new failed");
+ goto fail;
+ }
+
+ rdpSettings* settings = server->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ goto fail;
+
+ /* By default allow all GFX modes.
+ * This can be changed with command line flags [+|-]gfx-CODEC
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressiveV2, TRUE))
+ goto fail;
+
+ /* TODO: We do not implement relative mouse callbacks, so deactivate it for now */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent, FALSE))
+ goto fail;
+
+ if ((status = shadow_server_parse_command_line(server, argc, argv, shadow_args)) < 0)
+ {
+ shadow_server_command_line_status_print(server, argc, argv, status, shadow_args);
+ goto fail;
+ }
+
+ if ((status = shadow_server_init(server)) < 0)
+ {
+ WLog_ERR(TAG, "Server initialization failed.");
+ goto fail;
+ }
+
+ if ((status = shadow_server_start(server)) < 0)
+ {
+ WLog_ERR(TAG, "Failed to start server.");
+ goto fail;
+ }
+
+#ifdef _WIN32
+ {
+ MSG msg = { 0 };
+ while (GetMessage(&msg, 0, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+#endif
+
+ WaitForSingleObject(server->thread, INFINITE);
+
+ if (!GetExitCodeThread(server->thread, &dwExitCode))
+ status = -1;
+ else
+ status = (int)dwExitCode;
+
+fail:
+ shadow_server_uninit(server);
+ shadow_server_free(server);
+ return status;
+}
diff --git a/server/shadow/shadow.h b/server/shadow/shadow.h
new file mode 100644
index 0000000..33eb805
--- /dev/null
+++ b/server/shadow/shadow.h
@@ -0,0 +1,44 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_SHADOW_H
+#define FREERDP_SERVER_SHADOW_SHADOW_H
+
+#include <freerdp/server/shadow.h>
+
+#include "shadow_client.h"
+#include "shadow_input.h"
+#include "shadow_screen.h"
+#include "shadow_surface.h"
+#include "shadow_encoder.h"
+#include "shadow_capture.h"
+#include "shadow_channels.h"
+#include "shadow_subsystem.h"
+#include "shadow_lobby.h"
+#include "shadow_mcevent.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SHADOW_H */
diff --git a/server/shadow/shadow_audin.c b/server/shadow/shadow_audin.c
new file mode 100644
index 0000000..9e7f6ef
--- /dev/null
+++ b/server/shadow/shadow_audin.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.com>
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 <freerdp/log.h>
+#include "shadow.h"
+
+#include "shadow_audin.h"
+#include <freerdp/server/server-common.h>
+
+#if defined(CHANNEL_AUDIN_SERVER)
+#include <freerdp/server/audin.h>
+#endif
+
+#define TAG SERVER_TAG("shadow")
+
+#if defined(CHANNEL_AUDIN_SERVER)
+
+static UINT AudinServerData(audin_server_context* audin, const SNDIN_DATA* data)
+{
+ rdpShadowClient* client = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ WINPR_ASSERT(audin);
+ WINPR_ASSERT(data);
+
+ client = audin->userdata;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+ subsystem = client->server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ if (!client->mayInteract)
+ return CHANNEL_RC_OK;
+
+ if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client,
+ audin_server_get_negotiated_format(client->audin), data->Data))
+ return ERROR_INTERNAL_ERROR;
+
+ return CHANNEL_RC_OK;
+}
+
+#endif
+
+BOOL shadow_client_audin_init(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context* audin = client->audin = audin_server_context_new(client->vcm);
+
+ if (!audin)
+ return FALSE;
+
+ audin->userdata = client;
+
+ audin->Data = AudinServerData;
+
+ if (client->subsystem->audinFormats)
+ {
+ if (!audin_server_set_formats(client->audin, client->subsystem->nAudinFormats,
+ client->subsystem->audinFormats))
+ goto fail;
+ }
+ else
+ {
+ if (!audin_server_set_formats(client->audin, -1, NULL))
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ audin_server_context_free(audin);
+ client->audin = NULL;
+#endif
+ return FALSE;
+}
+
+void shadow_client_audin_uninit(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+#if defined(CHANNEL_AUDIN_SERVER)
+ audin_server_context_free(client->audin);
+ client->audin = NULL;
+#endif
+}
diff --git a/server/shadow/shadow_audin.h b/server/shadow/shadow_audin.h
new file mode 100644
index 0000000..0499987
--- /dev/null
+++ b/server/shadow/shadow_audin.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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_SHADOW_AUDIN_H
+#define FREERDP_SERVER_SHADOW_AUDIN_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_audin_init(rdpShadowClient* client);
+ void shadow_client_audin_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_AUDIN_H */
diff --git a/server/shadow/shadow_capture.c b/server/shadow/shadow_capture.c
new file mode 100644
index 0000000..4711ded
--- /dev/null
+++ b/server/shadow/shadow_capture.c
@@ -0,0 +1,263 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/log.h>
+
+#include "shadow_surface.h"
+
+#include "shadow_capture.h"
+
+#define TAG SERVER_TAG("shadow")
+
+int shadow_capture_align_clip_rect(RECTANGLE_16* rect, RECTANGLE_16* clip)
+{
+ int dx = 0;
+ int dy = 0;
+ dx = (rect->left % 16);
+
+ if (dx != 0)
+ {
+ rect->left -= dx;
+ rect->right += dx;
+ }
+
+ dx = (rect->right % 16);
+
+ if (dx != 0)
+ {
+ rect->right += (16 - dx);
+ }
+
+ dy = (rect->top % 16);
+
+ if (dy != 0)
+ {
+ rect->top -= dy;
+ rect->bottom += dy;
+ }
+
+ dy = (rect->bottom % 16);
+
+ if (dy != 0)
+ {
+ rect->bottom += (16 - dy);
+ }
+
+ if (rect->left < clip->left)
+ rect->left = clip->left;
+
+ if (rect->top < clip->top)
+ rect->top = clip->top;
+
+ if (rect->right > clip->right)
+ rect->right = clip->right;
+
+ if (rect->bottom > clip->bottom)
+ rect->bottom = clip->bottom;
+
+ return 1;
+}
+
+int shadow_capture_compare(BYTE* pData1, UINT32 nStep1, UINT32 nWidth, UINT32 nHeight, BYTE* pData2,
+ UINT32 nStep2, RECTANGLE_16* rect)
+{
+ BOOL equal = 0;
+ BOOL allEqual = 0;
+ UINT32 tw = 0;
+ UINT32 th = 0;
+ UINT32 nrow = 0;
+ UINT32 ncol = 0;
+ UINT32 l = 0;
+ UINT32 t = 0;
+ UINT32 r = 0;
+ UINT32 b = 0;
+ BYTE* p1 = NULL;
+ BYTE* p2 = NULL;
+ BOOL rows[1024];
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ BOOL cols[1024] = { FALSE };
+#endif
+ allEqual = TRUE;
+ ZeroMemory(rect, sizeof(RECTANGLE_16));
+ FillMemory(rows, sizeof(rows), 0xFF);
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ FillMemory(cols, sizeof(cols), 0xFF);
+#endif
+ nrow = (nHeight + 15) / 16;
+ ncol = (nWidth + 15) / 16;
+ l = ncol + 1;
+ r = 0;
+ t = nrow + 1;
+ b = 0;
+
+ for (UINT32 ty = 0; ty < nrow; ty++)
+ {
+ th = ((ty + 1) == nrow) ? (nHeight % 16) : 16;
+
+ if (!th)
+ th = 16;
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ {
+ equal = TRUE;
+ tw = ((tx + 1) == ncol) ? (nWidth % 16) : 16;
+
+ if (!tw)
+ tw = 16;
+
+ p1 = &pData1[(ty * 16 * nStep1) + (tx * 16 * 4)];
+ p2 = &pData2[(ty * 16 * nStep2) + (tx * 16 * 4)];
+
+ for (UINT32 k = 0; k < th; k++)
+ {
+ if (memcmp(p1, p2, tw * 4) != 0)
+ {
+ equal = FALSE;
+ break;
+ }
+
+ p1 += nStep1;
+ p2 += nStep2;
+ }
+
+ if (!equal)
+ {
+ rows[ty] = FALSE;
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ cols[tx] = FALSE;
+#endif
+
+ if (l > tx)
+ l = tx;
+
+ if (r < tx)
+ r = tx;
+ }
+ }
+
+ if (!rows[ty])
+ {
+ allEqual = FALSE;
+
+ if (t > ty)
+ t = ty;
+
+ if (b < ty)
+ b = ty;
+ }
+ }
+
+ if (allEqual)
+ return 0;
+
+ WINPR_ASSERT(l * 16 <= UINT16_MAX);
+ WINPR_ASSERT(t * 16 <= UINT16_MAX);
+ WINPR_ASSERT((r + 1) * 16 <= UINT16_MAX);
+ WINPR_ASSERT((b + 1) * 16 <= UINT16_MAX);
+ rect->left = (UINT16)l * 16;
+ rect->top = (UINT16)t * 16;
+ rect->right = (UINT16)(r + 1) * 16;
+ rect->bottom = (UINT16)(b + 1) * 16;
+
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ if (rect->right > nWidth)
+ rect->right = (UINT16)nWidth;
+
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ if (rect->bottom > nHeight)
+ rect->bottom = (UINT16)nHeight;
+
+#ifdef WITH_DEBUG_SHADOW_CAPTURE
+ size_t size = ncol + 1;
+ char* col_str = calloc(size, sizeof(char));
+
+ if (!col_str)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return 1;
+ }
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "-");
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X');
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "-");
+
+ WLog_INFO(TAG, "%s", col_str);
+
+ for (UINT32 ty = 0; ty < nrow; ty++)
+ {
+ for (UINT32 tx = 0; tx < ncol; tx++)
+ sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X');
+
+ WLog_INFO(TAG, "%s", col_str);
+ WLog_INFO(TAG, "|%s|", rows[ty] ? "O" : "X");
+ }
+
+ WLog_INFO(TAG,
+ "left: %" PRIu32 " top: %" PRIu32 " right: %" PRIu32 " bottom: %" PRIu32
+ " ncol: %" PRIu32 " nrow: %" PRIu32,
+ l, t, r, b, ncol, nrow);
+ free(col_str);
+#endif
+ return 1;
+}
+
+rdpShadowCapture* shadow_capture_new(rdpShadowServer* server)
+{
+ WINPR_ASSERT(server);
+
+ rdpShadowCapture* capture = (rdpShadowCapture*)calloc(1, sizeof(rdpShadowCapture));
+
+ if (!capture)
+ return NULL;
+
+ capture->server = server;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(capture->lock), 4000))
+ {
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ shadow_capture_free(capture);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+ }
+
+ return capture;
+}
+
+void shadow_capture_free(rdpShadowCapture* capture)
+{
+ if (!capture)
+ return;
+
+ DeleteCriticalSection(&(capture->lock));
+ free(capture);
+}
diff --git a/server/shadow/shadow_capture.h b/server/shadow/shadow_capture.h
new file mode 100644
index 0000000..31de550
--- /dev/null
+++ b/server/shadow/shadow_capture.h
@@ -0,0 +1,52 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_CAPTURE_H
+#define FREERDP_SERVER_SHADOW_CAPTURE_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/winpr.h>
+#include <winpr/synch.h>
+
+struct rdp_shadow_capture
+{
+ rdpShadowServer* server;
+
+ int width;
+ int height;
+
+ CRITICAL_SECTION lock;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_capture_free(rdpShadowCapture* capture);
+
+ WINPR_ATTR_MALLOC(shadow_capture_free, 1)
+ rdpShadowCapture* shadow_capture_new(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CAPTURE_H */
diff --git a/server/shadow/shadow_channels.c b/server/shadow/shadow_channels.c
new file mode 100644
index 0000000..07c5105
--- /dev/null
+++ b/server/shadow/shadow_channels.c
@@ -0,0 +1,66 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_channels.h"
+
+UINT shadow_client_channels_post_connect(rdpShadowClient* client)
+{
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ shadow_client_encomsp_init(client);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, REMDESK_SVC_CHANNEL_NAME))
+ {
+ shadow_client_remdesk_init(client);
+ }
+
+ if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, RDPSND_CHANNEL_NAME))
+ {
+ shadow_client_rdpsnd_init(client);
+ }
+
+ shadow_client_audin_init(client);
+
+ if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ shadow_client_rdpgfx_init(client);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+void shadow_client_channels_free(rdpShadowClient* client)
+{
+ if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ shadow_client_rdpgfx_uninit(client);
+ }
+
+ shadow_client_audin_uninit(client);
+
+ shadow_client_rdpsnd_uninit(client);
+
+ shadow_client_remdesk_uninit(client);
+
+ shadow_client_encomsp_uninit(client);
+}
diff --git a/server/shadow/shadow_channels.h b/server/shadow/shadow_channels.h
new file mode 100644
index 0000000..261041e
--- /dev/null
+++ b/server/shadow/shadow_channels.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_CHANNELS_H
+#define FREERDP_SERVER_SHADOW_CHANNELS_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include "shadow_encomsp.h"
+#include "shadow_remdesk.h"
+#include "shadow_rdpsnd.h"
+#include "shadow_audin.h"
+#include "shadow_rdpgfx.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ UINT shadow_client_channels_post_connect(rdpShadowClient* client);
+ void shadow_client_channels_free(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CHANNELS_H */
diff --git a/server/shadow/shadow_client.c b/server/shadow/shadow_client.c
new file mode 100644
index 0000000..0fd5236
--- /dev/null
+++ b/server/shadow/shadow_client.c
@@ -0,0 +1,2612 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 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 <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/sysinfo.h>
+#include <winpr/interlocked.h>
+
+#include <freerdp/log.h>
+#include <freerdp/channels/drdynvc.h>
+
+#include "shadow.h"
+
+#define TAG CLIENT_TAG("shadow")
+
+typedef struct
+{
+ BOOL gfxOpened;
+ BOOL gfxSurfaceCreated;
+} SHADOW_GFX_STATUS;
+
+static INLINE BOOL shadow_client_rdpgfx_new_surface(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_CREATE_SURFACE_PDU createSurface;
+ RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU surfaceToOutput;
+ RdpgfxServerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+ settings = ((rdpContext*)client)->settings;
+ WINPR_ASSERT(settings);
+
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ createSurface.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ createSurface.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ createSurface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888;
+ createSurface.surfaceId = client->surfaceId;
+ surfaceToOutput.outputOriginX = 0;
+ surfaceToOutput.outputOriginY = 0;
+ surfaceToOutput.surfaceId = client->surfaceId;
+ surfaceToOutput.reserved = 0;
+ IFCALLRET(context->CreateSurface, error, context, &createSurface);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "CreateSurface failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ IFCALLRET(context->MapSurfaceToOutput, error, context, &surfaceToOutput);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "MapSurfaceToOutput failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL shadow_client_rdpgfx_release_surface(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_DELETE_SURFACE_PDU pdu;
+ RdpgfxServerContext* context = NULL;
+
+ WINPR_ASSERT(client);
+
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+
+ pdu.surfaceId = client->surfaceId++;
+ IFCALLRET(context->DeleteSurface, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "DeleteSurface failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static INLINE BOOL shadow_client_rdpgfx_reset_graphic(rdpShadowClient* client)
+{
+ UINT error = CHANNEL_RC_OK;
+ RDPGFX_RESET_GRAPHICS_PDU pdu = { 0 };
+ RdpgfxServerContext* context = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->rdpgfx);
+
+ context = client->rdpgfx;
+ WINPR_ASSERT(context);
+
+ settings = client->context.settings;
+ WINPR_ASSERT(settings);
+
+ pdu.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ pdu.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ pdu.monitorCount = client->subsystem->numMonitors;
+ pdu.monitorDefArray = client->subsystem->monitors;
+ IFCALLRET(context->ResetGraphics, error, context, &pdu);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "ResetGraphics failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+
+ client->first_frame = TRUE;
+ return TRUE;
+}
+
+static INLINE void shadow_client_free_queued_message(void* obj)
+{
+ wMessage* message = (wMessage*)obj;
+
+ WINPR_ASSERT(message);
+ if (message->Free)
+ {
+ message->Free(message);
+ message->Free = NULL;
+ }
+}
+
+static void shadow_client_context_free(freerdp_peer* peer, rdpContext* context)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ rdpShadowServer* server = NULL;
+
+ WINPR_UNUSED(peer);
+ if (!client)
+ return;
+
+ server = client->server;
+ if (server && server->clients)
+ ArrayList_Remove(server->clients, (void*)client);
+
+ shadow_encoder_free(client->encoder);
+
+ /* Clear queued messages and free resource */
+ MessageQueue_Free(client->MsgQueue);
+ WTSCloseServer((HANDLE)client->vcm);
+ region16_uninit(&(client->invalidRegion));
+ DeleteCriticalSection(&(client->lock));
+
+ client->MsgQueue = NULL;
+ client->encoder = NULL;
+ client->vcm = NULL;
+}
+
+static BOOL shadow_client_context_new(freerdp_peer* peer, rdpContext* context)
+{
+ BOOL NSCodec = 0;
+ const char bind_address[] = "bind-address,";
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ rdpSettings* settings = NULL;
+ const rdpSettings* srvSettings = NULL;
+ rdpShadowServer* server = NULL;
+ const wObject cb = { NULL, NULL, NULL, shadow_client_free_queued_message, NULL };
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ server = (rdpShadowServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+
+ srvSettings = server->settings;
+ WINPR_ASSERT(srvSettings);
+
+ client->surfaceId = 1;
+ client->server = server;
+ client->subsystem = server->subsystem;
+ WINPR_ASSERT(client->subsystem);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth,
+ freerdp_settings_get_uint32(srvSettings, FreeRDP_ColorDepth)))
+ return FALSE;
+ NSCodec = freerdp_settings_get_bool(srvSettings, FreeRDP_NSCodec);
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, NSCodec))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec,
+ freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec)))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264,
+ freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264)))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowSkipAlpha, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowColorSubsampling, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowDynamicColorFidelity, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+
+ if (server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
+ strnlen(bind_address, sizeof(bind_address))) != 0))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_LyncRdpMode, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ }
+
+ client->inLobby = TRUE;
+ client->mayView = server->mayView;
+ client->mayInteract = server->mayInteract;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(client->lock), 4000))
+ goto fail;
+
+ region16_init(&(client->invalidRegion));
+ client->vcm = WTSOpenServerA(peer->context);
+
+ if (!client->vcm || client->vcm == INVALID_HANDLE_VALUE)
+ goto fail;
+
+ if (!(client->MsgQueue = MessageQueue_New(&cb)))
+ goto fail;
+
+ if (!(client->encoder = shadow_encoder_new(client)))
+ goto fail;
+
+ if (!ArrayList_Append(server->clients, (void*)client))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ shadow_client_context_free(peer, context);
+ return FALSE;
+}
+
+static INLINE void shadow_client_mark_invalid(rdpShadowClient* client, UINT32 numRects,
+ const RECTANGLE_16* rects)
+{
+ RECTANGLE_16 screenRegion;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(rects || (numRects == 0));
+
+ settings = client->context.settings;
+ WINPR_ASSERT(settings);
+
+ EnterCriticalSection(&(client->lock));
+
+ /* Mark client invalid region. No rectangle means full screen */
+ if (numRects > 0)
+ {
+ for (UINT32 index = 0; index < numRects; index++)
+ {
+ region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &rects[index]);
+ }
+ }
+ else
+ {
+ screenRegion.left = 0;
+ screenRegion.top = 0;
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ screenRegion.right = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ screenRegion.bottom = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &screenRegion);
+ }
+
+ LeaveCriticalSection(&(client->lock));
+}
+
+/**
+ * Function description
+ * Recalculate client desktop size and update to rdpSettings
+ *
+ * @return TRUE if width/height changed.
+ */
+static INLINE BOOL shadow_client_recalc_desktop_size(rdpShadowClient* client)
+{
+ INT32 width = 0;
+ INT32 height = 0;
+ rdpShadowServer* server = NULL;
+ rdpSettings* settings = NULL;
+ RECTANGLE_16 viewport = { 0 };
+
+ WINPR_ASSERT(client);
+ server = client->server;
+ settings = client->context.settings;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->surface);
+ WINPR_ASSERT(settings);
+
+ WINPR_ASSERT(server->surface->width <= UINT16_MAX);
+ WINPR_ASSERT(server->surface->height <= UINT16_MAX);
+ viewport.right = (UINT16)server->surface->width;
+ viewport.bottom = (UINT16)server->surface->height;
+
+ if (server->shareSubRect)
+ {
+ rectangles_intersection(&viewport, &(server->subRect), &viewport);
+ }
+
+ width = viewport.right - viewport.left;
+ height = viewport.bottom - viewport.top;
+
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != (UINT32)width ||
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != (UINT32)height)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL shadow_client_capabilities(freerdp_peer* peer)
+{
+ rdpShadowSubsystem* subsystem = NULL;
+ rdpShadowClient* client = NULL;
+ BOOL ret = TRUE;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+
+ subsystem = client->server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ IFCALLRET(subsystem->ClientCapabilities, ret, subsystem, client);
+
+ if (!ret)
+ WLog_WARN(TAG, "subsystem->ClientCapabilities failed");
+
+ return ret;
+}
+
+static void shadow_reset_desktop_resize(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+ client->resizeRequested = FALSE;
+}
+
+static BOOL shadow_send_desktop_resize(rdpShadowClient* client)
+{
+ BOOL rc = 0;
+ rdpUpdate* update = NULL;
+ rdpSettings* settings = NULL;
+ const freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(client);
+
+ settings = client->context.settings;
+ peer = client->context.peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(client->server);
+ WINPR_ASSERT(client->server->surface);
+
+ const UINT32 resizeWidth = client->server->surface->width;
+ const UINT32 resizeHeight = client->server->surface->height;
+
+ if (client->resizeRequested)
+ {
+ if ((resizeWidth == client->resizeWidth) && (resizeHeight == client->resizeHeight))
+ {
+ const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ WLog_WARN(TAG,
+ "detected previous resize request for resolution %" PRIu32 "x%" PRIu32
+ ", still have %" PRIu32 "x%" PRIu32 ", disconnecting peer",
+ resizeWidth, resizeHeight, w, h);
+ return FALSE;
+ }
+ }
+
+ update = client->context.update;
+ WINPR_ASSERT(update);
+ WINPR_ASSERT(update->DesktopResize);
+
+ // Update peer resolution, required so that during disconnect/reconnect the correct resolution
+ // is sent to the client.
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, resizeWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, resizeHeight))
+ return FALSE;
+ rc = update->DesktopResize(update->context);
+ WLog_INFO(TAG, "Client %s resize requested (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")",
+ peer->hostname, resizeWidth, resizeHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+ client->resizeRequested = TRUE;
+ client->resizeWidth = resizeWidth;
+ client->resizeHeight = resizeHeight;
+
+ return rc;
+}
+
+static BOOL shadow_client_post_connect(freerdp_peer* peer)
+{
+ int authStatus = 0;
+ rdpSettings* settings = NULL;
+ rdpShadowClient* client = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ server = client->server;
+ WINPR_ASSERT(server);
+
+ subsystem = server->subsystem;
+ WINPR_ASSERT(subsystem);
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 24)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 16)) /* disable 24bpp */
+ return FALSE;
+ }
+
+ const UINT32 MultifragMaxRequestSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ if (MultifragMaxRequestSize < 0x3F0000)
+ {
+ BOOL rc = freerdp_settings_set_bool(
+ settings, FreeRDP_NSCodec,
+ FALSE); /* NSCodec compressor does not support fragmentation yet */
+ WINPR_ASSERT(rc);
+ }
+
+ WLog_INFO(TAG, "Client from %s is activated (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")",
+ peer->hostname, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
+
+ if (shadow_client_channels_post_connect(client) != CHANNEL_RC_OK)
+ return FALSE;
+
+ shadow_client_mark_invalid(client, 0, NULL);
+ authStatus = -1;
+
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ const char* Password = freerdp_settings_get_string(settings, FreeRDP_Password);
+
+ if (Username && Password)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE))
+ return FALSE;
+ }
+
+ if (server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ {
+ if (subsystem->Authenticate)
+ {
+ authStatus = subsystem->Authenticate(subsystem, client, Username, Domain, Password);
+ }
+
+ if (authStatus < 0)
+ {
+ WLog_ERR(TAG, "client authentication failure: %d", authStatus);
+ return FALSE;
+ }
+ }
+
+ if (subsystem->ClientConnect)
+ {
+ return subsystem->ClientConnect(subsystem, client);
+ }
+
+ return TRUE;
+}
+
+/* Convert rects in sub rect coordinate to client/surface coordinate */
+static INLINE void shadow_client_convert_rects(rdpShadowClient* client, RECTANGLE_16* dst,
+ const RECTANGLE_16* src, UINT32 numRects)
+{
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->server);
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src || (numRects == 0));
+
+ if (client->server->shareSubRect)
+ {
+ UINT16 offsetX = client->server->subRect.left;
+ UINT16 offsetY = client->server->subRect.top;
+
+ for (UINT32 i = 0; i < numRects; i++)
+ {
+ const RECTANGLE_16* s = &src[i];
+ RECTANGLE_16* d = &dst[i];
+
+ d->left = s->left + offsetX;
+ d->right = s->right + offsetX;
+ d->top = s->top + offsetY;
+ d->bottom = s->bottom + offsetY;
+ }
+ }
+ else
+ {
+ if (src != dst)
+ {
+ CopyMemory(dst, src, numRects * sizeof(RECTANGLE_16));
+ }
+ }
+}
+
+static BOOL shadow_client_refresh_request(rdpShadowClient* client)
+{
+ wMessage message = { 0 };
+ wMessagePipe* MsgPipe = NULL;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->subsystem);
+
+ MsgPipe = client->subsystem->MsgPipe;
+ WINPR_ASSERT(MsgPipe);
+
+ message.id = SHADOW_MSG_IN_REFRESH_REQUEST_ID;
+ message.wParam = NULL;
+ message.lParam = NULL;
+ message.context = (void*)client;
+ message.Free = NULL;
+ return MessageQueue_Dispatch(MsgPipe->In, &message);
+}
+
+static BOOL shadow_client_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ RECTANGLE_16* rects = NULL;
+
+ /* It is invalid if we have area count but no actual area */
+ if (count && !areas)
+ return FALSE;
+
+ if (count)
+ {
+ rects = (RECTANGLE_16*)calloc(count, sizeof(RECTANGLE_16));
+
+ if (!rects)
+ {
+ return FALSE;
+ }
+
+ shadow_client_convert_rects(client, rects, areas, count);
+ shadow_client_mark_invalid(client, count, rects);
+ free(rects);
+ }
+ else
+ {
+ shadow_client_mark_invalid(client, 0, NULL);
+ }
+
+ return shadow_client_refresh_request(client);
+}
+
+static BOOL shadow_client_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ RECTANGLE_16 region;
+
+ WINPR_ASSERT(client);
+
+ client->suppressOutput = allow ? FALSE : TRUE;
+
+ if (allow)
+ {
+ if (area)
+ {
+ shadow_client_convert_rects(client, &region, area, 1);
+ shadow_client_mark_invalid(client, 1, &region);
+ }
+ else
+ {
+ shadow_client_mark_invalid(client, 0, NULL);
+ }
+ }
+
+ return shadow_client_refresh_request(client);
+}
+
+static BOOL shadow_client_activate(freerdp_peer* peer)
+{
+ rdpSettings* settings = NULL;
+ rdpShadowClient* client = NULL;
+
+ WINPR_ASSERT(peer);
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Resize client if necessary */
+ if (shadow_client_recalc_desktop_size(client))
+ return shadow_send_desktop_resize(client);
+
+ shadow_reset_desktop_resize(client);
+ client->activated = TRUE;
+ client->inLobby = client->mayView ? FALSE : TRUE;
+
+ if (shadow_encoder_reset(client->encoder) < 0)
+ {
+ WLog_ERR(TAG, "Failed to reset encoder");
+ return FALSE;
+ }
+
+ /* Update full screen in next update */
+ return shadow_client_refresh_rect(&client->context, 0, NULL);
+}
+
+static BOOL shadow_client_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ BOOL rc = FALSE;
+ char* user = NULL;
+ char* domain = NULL;
+ char* password = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(automatic);
+
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(identity);
+
+ WINPR_ASSERT(peer->context);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (identity->Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE)
+ {
+ if (identity->User)
+ user = ConvertWCharNToUtf8Alloc(identity->User, identity->UserLength, NULL);
+
+ if (identity->Domain)
+ domain = ConvertWCharNToUtf8Alloc(identity->Domain, identity->DomainLength, NULL);
+
+ if (identity->Password)
+ password = ConvertWCharNToUtf8Alloc(identity->Password, identity->PasswordLength, NULL);
+ }
+ else
+ {
+ if (identity->User)
+ user = _strdup((char*)identity->User);
+
+ if (identity->Domain)
+ domain = _strdup((char*)identity->Domain);
+
+ if (identity->Password)
+ password = _strdup((char*)identity->Password);
+ }
+
+ if ((identity->User && !user) || (identity->Domain && !domain) ||
+ (identity->Password && !password))
+ goto fail;
+
+ if (user)
+ freerdp_settings_set_string(settings, FreeRDP_Username, user);
+
+ if (domain)
+ freerdp_settings_set_string(settings, FreeRDP_Domain, domain);
+
+ if (password)
+ freerdp_settings_set_string(settings, FreeRDP_Password, password);
+
+ rc = TRUE;
+fail:
+ free(user);
+ free(domain);
+ free(password);
+ return rc;
+}
+
+static INLINE void shadow_client_common_frame_acknowledge(rdpShadowClient* client, UINT32 frameId)
+{
+ /*
+ * Record the last client acknowledged frame id to
+ * calculate how much frames are in progress.
+ * Some rdp clients (win7 mstsc) skips frame ACK if it is
+ * inactive, we should not expect ACK for each frame.
+ * So it is OK to calculate inflight frame count according to
+ * a latest acknowledged frame id.
+ */
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->lastAckframeId = frameId;
+}
+
+static BOOL shadow_client_surface_frame_acknowledge(rdpContext* context, UINT32 frameId)
+{
+ rdpShadowClient* client = (rdpShadowClient*)context;
+ shadow_client_common_frame_acknowledge(client, frameId);
+ /*
+ * Reset queueDepth for legacy none RDPGFX acknowledge
+ */
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->queueDepth = QUEUE_DEPTH_UNAVAILABLE;
+ return TRUE;
+}
+
+static UINT
+shadow_client_rdpgfx_frame_acknowledge(RdpgfxServerContext* context,
+ const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge)
+{
+ rdpShadowClient* client = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(frameAcknowledge);
+
+ client = (rdpShadowClient*)context->custom;
+ shadow_client_common_frame_acknowledge(client, frameAcknowledge->frameId);
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(client->encoder);
+ client->encoder->queueDepth = frameAcknowledge->queueDepth;
+ return CHANNEL_RC_OK;
+}
+
+static BOOL shadow_are_caps_filtered(const rdpSettings* settings, UINT32 caps)
+{
+ const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81,
+ RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101,
+ RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103,
+ RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105,
+ RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR,
+ RDPGFX_CAPVERSION_107 };
+
+ WINPR_ASSERT(settings);
+ const UINT32 filter = freerdp_settings_get_uint32(settings, FreeRDP_GfxCapsFilter);
+
+ for (UINT32 x = 0; x < ARRAYSIZE(capList); x++)
+ {
+ if (caps == capList[x])
+ return (filter & (1 << x)) != 0;
+ }
+
+ return TRUE;
+}
+
+static UINT shadow_client_send_caps_confirm(RdpgfxServerContext* context, rdpShadowClient* client,
+ const RDPGFX_CAPS_CONFIRM_PDU* pdu)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(pdu);
+
+ WINPR_ASSERT(context->CapsConfirm);
+ UINT rc = context->CapsConfirm(context, pdu);
+ client->areGfxCapsReady = (rc == CHANNEL_RC_OK);
+ return rc;
+}
+
+static BOOL shadow_client_caps_test_version(RdpgfxServerContext* context, rdpShadowClient* client,
+ BOOL h264, const RDPGFX_CAPSET* capsSets,
+ UINT32 capsSetCount, UINT32 capsVersion, UINT* rc)
+{
+ const rdpSettings* srvSettings = NULL;
+ rdpSettings* clientSettings = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(capsSets || (capsSetCount == 0));
+ WINPR_ASSERT(rc);
+
+ WINPR_ASSERT(context->rdpcontext);
+ srvSettings = context->rdpcontext->settings;
+ WINPR_ASSERT(srvSettings);
+
+ clientSettings = client->context.settings;
+ WINPR_ASSERT(clientSettings);
+
+ if (shadow_are_caps_filtered(srvSettings, capsVersion))
+ return FALSE;
+
+ for (UINT32 index = 0; index < capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsSets[index];
+
+ if (currentCaps->version == capsVersion)
+ {
+ UINT32 flags = 0;
+ BOOL planar = FALSE;
+ BOOL rfx = FALSE;
+ BOOL avc444v2 = FALSE;
+ BOOL avc444 = FALSE;
+ BOOL avc420 = FALSE;
+ BOOL progressive = FALSE;
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu = { 0 };
+ pdu.capsSet = &caps;
+
+ flags = pdu.capsSet->flags;
+
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE))
+ return FALSE;
+
+ avc444v2 = avc444 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444v2) || !h264)
+ avc444v2 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, avc444v2))
+ return FALSE;
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444) || !h264)
+ avc444 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, avc444))
+ return FALSE;
+ if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264) || !h264)
+ avc420 = FALSE;
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, avc420))
+ return FALSE;
+
+ progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressive);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressive, progressive))
+ return FALSE;
+ progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressiveV2);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressiveV2, progressive))
+ return FALSE;
+
+ rfx = freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_RemoteFxCodec, rfx))
+ return FALSE;
+
+ planar = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxPlanar);
+ if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxPlanar, planar))
+ return FALSE;
+
+ if (!avc444v2 && !avc444 && !avc420)
+ pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
+
+ *rc = shadow_client_send_caps_confirm(context, client, &pdu);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
+ const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ const rdpSettings* srvSettings = NULL;
+ rdpSettings* clientSettings = NULL;
+ BOOL h264 = FALSE;
+
+ UINT32 flags = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capsAdvertise);
+
+ rdpShadowClient* client = (rdpShadowClient*)context->custom;
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context->rdpcontext);
+
+ srvSettings = context->rdpcontext->settings;
+ WINPR_ASSERT(srvSettings);
+
+ clientSettings = client->context.settings;
+ WINPR_ASSERT(clientSettings);
+
+#ifdef WITH_GFX_H264
+ h264 =
+ (shadow_encoder_prepare(client->encoder, FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444) >= 0);
+#else
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+#endif
+
+ /* Request full screen update for new gfx channel */
+ if (!shadow_client_refresh_rect(&client->context, 0, NULL))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_107, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106_ERR,
+ &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_105, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_104, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_103, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_102, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_101, &rc))
+ return rc;
+
+ if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets,
+ capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_10, &rc))
+ return rc;
+
+ if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_81))
+ {
+ for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index];
+
+ if (currentCaps->version == RDPGFX_CAPVERSION_81)
+ {
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu;
+ pdu.capsSet = &caps;
+
+ flags = pdu.capsSet->flags;
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient,
+ (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE);
+
+#ifndef WITH_GFX_H264
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+ pdu.capsSet->flags &= ~RDPGFX_CAPS_FLAG_AVC420_ENABLED;
+#else
+
+ if (h264)
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264,
+ (flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED) ? TRUE
+ : FALSE);
+ else
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+#endif
+
+ return shadow_client_send_caps_confirm(context, client, &pdu);
+ }
+ }
+ }
+
+ if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_8))
+ {
+ for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++)
+ {
+ const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index];
+
+ if (currentCaps->version == RDPGFX_CAPVERSION_8)
+ {
+ RDPGFX_CAPSET caps = *currentCaps;
+ RDPGFX_CAPS_CONFIRM_PDU pdu;
+ pdu.capsSet = &caps;
+ flags = pdu.capsSet->flags;
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE);
+
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient,
+ (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE);
+ freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache,
+ (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE);
+
+ return shadow_client_send_caps_confirm(context, client, &pdu);
+ }
+ }
+ }
+
+ return CHANNEL_RC_UNSUPPORTED_VERSION;
+}
+
+static INLINE UINT32 rdpgfx_estimate_h264_avc420(RDPGFX_AVC420_BITMAP_STREAM* havc420)
+{
+ /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */
+ WINPR_ASSERT(havc420);
+ return sizeof(UINT32) /* numRegionRects */
+ + 10 /* regionRects + quantQualityVals */
+ * havc420->meta.numRegionRects +
+ havc420->length;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client, const BYTE* pSrcData,
+ UINT32 nSrcStep, UINT32 SrcFormat, UINT16 nXSrc,
+ UINT16 nYSrc, UINT16 nWidth, UINT16 nHeight)
+{
+ UINT32 id = 0;
+ UINT error = CHANNEL_RC_OK;
+ const rdpContext* context = (const rdpContext*)client;
+ const rdpSettings* settings = NULL;
+ rdpShadowEncoder* encoder = NULL;
+ RDPGFX_SURFACE_COMMAND cmd = { 0 };
+ RDPGFX_START_FRAME_PDU cmdstart = { 0 };
+ RDPGFX_END_FRAME_PDU cmdend = { 0 };
+ SYSTEMTIME sTime = { 0 };
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!settings || !encoder)
+ return FALSE;
+
+ if (client->first_frame)
+ {
+ rfx_context_reset(encoder->rfx, nWidth, nHeight);
+ client->first_frame = FALSE;
+ }
+
+ cmdstart.frameId = shadow_encoder_create_frame_id(encoder);
+ GetSystemTime(&sTime);
+ cmdstart.timestamp = (UINT32)(sTime.wHour << 22U | sTime.wMinute << 16U | sTime.wSecond << 10U |
+ sTime.wMilliseconds);
+ cmdend.frameId = cmdstart.frameId;
+ cmd.surfaceId = client->surfaceId;
+ cmd.format = PIXEL_FORMAT_BGRX32;
+ cmd.left = nXSrc;
+ cmd.top = nYSrc;
+ cmd.right = cmd.left + nWidth;
+ cmd.bottom = cmd.top + nHeight;
+ cmd.width = nWidth;
+ cmd.height = nHeight;
+
+ id = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+#ifdef WITH_GFX_H264
+ const BOOL GfxH264 = freerdp_settings_get_bool(settings, FreeRDP_GfxH264);
+ const BOOL GfxAVC444 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444);
+ const BOOL GfxAVC444v2 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444v2);
+ if (GfxAVC444 || GfxAVC444v2)
+ {
+ INT32 rc = 0;
+ RDPGFX_AVC444_BITMAP_STREAM avc444 = { 0 };
+ RECTANGLE_16 regionRect = { 0 };
+ BYTE version = GfxAVC444v2 ? 2 : 1;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC444) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC444");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ rc = avc444_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight,
+ version, &regionRect, &avc444.LC, &avc444.bitstream[0].data,
+ &avc444.bitstream[0].length, &avc444.bitstream[1].data,
+ &avc444.bitstream[1].length, &avc444.bitstream[0].meta,
+ &avc444.bitstream[1].meta);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "avc420_compress failed for avc444");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ avc444.cbAvc420EncodedBitstream1 = rdpgfx_estimate_h264_avc420(&avc444.bitstream[0]);
+ cmd.codecId = GfxAVC444v2 ? RDPGFX_CODECID_AVC444v2 : RDPGFX_CODECID_AVC444;
+ cmd.extra = (void*)&avc444;
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ free_h264_metablock(&avc444.bitstream[0].meta);
+ free_h264_metablock(&avc444.bitstream[1].meta);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (GfxH264)
+ {
+ INT32 rc = 0;
+ RDPGFX_AVC420_BITMAP_STREAM avc420 = { 0 };
+ RECTANGLE_16 regionRect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC420) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC420");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ rc = avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight,
+ &regionRect, &avc420.data, &avc420.length, &avc420.meta);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "avc420_compress failed");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ cmd.codecId = RDPGFX_CODECID_AVC420;
+ cmd.extra = (void*)&avc420;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+ free_h264_metablock(&avc420.meta);
+
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else
+#endif
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (id != 0))
+ {
+ BOOL rc = 0;
+ wStream* s = NULL;
+ RFX_RECT rect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX");
+ return FALSE;
+ }
+
+ s = Stream_New(NULL, 1024);
+ WINPR_ASSERT(s);
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ rect.x = (UINT16)cmd.left;
+ rect.y = (UINT16)cmd.top;
+ rect.width = (UINT16)cmd.right - cmd.left;
+ rect.height = (UINT16)cmd.bottom - cmd.top;
+
+ rc = rfx_compose_message(encoder->rfx, s, &rect, 1, pSrcData, nWidth, nHeight, nSrcStep);
+
+ if (!rc)
+ {
+ WLog_ERR(TAG, "rfx_compose_message failed");
+ Stream_Free(s, TRUE);
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ const size_t pos = Stream_GetPosition(s);
+ WINPR_ASSERT(pos <= UINT32_MAX);
+
+ cmd.codecId = RDPGFX_CODECID_CAVIDEO;
+ cmd.data = Stream_Buffer(s);
+ cmd.length = (UINT32)pos;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ Stream_Free(s, TRUE);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_GfxProgressive))
+ {
+ INT32 rc = 0;
+ REGION16 region;
+ RECTANGLE_16 regionRect;
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PROGRESSIVE) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PROGRESSIVE");
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cmd.left <= UINT16_MAX);
+ WINPR_ASSERT(cmd.top <= UINT16_MAX);
+ WINPR_ASSERT(cmd.right <= UINT16_MAX);
+ WINPR_ASSERT(cmd.bottom <= UINT16_MAX);
+ regionRect.left = (UINT16)cmd.left;
+ regionRect.top = (UINT16)cmd.top;
+ regionRect.right = (UINT16)cmd.right;
+ regionRect.bottom = (UINT16)cmd.bottom;
+ region16_init(&region);
+ region16_union_rect(&region, &region, &regionRect);
+ rc = progressive_compress(encoder->progressive, pSrcData, nSrcStep * nHeight, cmd.format,
+ nWidth, nHeight, nSrcStep, &region, &cmd.data, &cmd.length);
+ region16_uninit(&region);
+ if (rc < 0)
+ {
+ WLog_ERR(TAG, "progressive_compress failed");
+ return FALSE;
+ }
+
+ /* rc > 0 means new data */
+ if (rc > 0)
+ {
+ cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ }
+
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_GfxPlanar))
+ {
+ BOOL rc = 0;
+ const UINT32 w = cmd.right - cmd.left;
+ const UINT32 h = cmd.bottom - cmd.top;
+ const BYTE* src =
+ &pSrcData[cmd.top * nSrcStep + cmd.left * FreeRDPGetBytesPerPixel(SrcFormat)];
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR");
+ return FALSE;
+ }
+
+ rc = freerdp_bitmap_planar_context_reset(encoder->planar, w, h);
+ WINPR_ASSERT(rc);
+ freerdp_planar_topdown_image(encoder->planar, TRUE);
+
+ cmd.data = freerdp_bitmap_compress_planar(encoder->planar, src, SrcFormat, w, h, nSrcStep,
+ NULL, &cmd.length);
+ WINPR_ASSERT(cmd.data || (cmd.length == 0));
+
+ cmd.codecId = RDPGFX_CODECID_PLANAR;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ free(cmd.data);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ else
+ {
+ BOOL rc = 0;
+ const UINT32 w = cmd.right - cmd.left;
+ const UINT32 h = cmd.bottom - cmd.top;
+ const UINT32 length = w * 4 * h;
+ BYTE* data = malloc(length);
+
+ WINPR_ASSERT(data);
+
+ rc = freerdp_image_copy(data, PIXEL_FORMAT_BGRA32, 0, 0, 0, w, h, pSrcData, SrcFormat,
+ nSrcStep, cmd.left, cmd.top, NULL, 0);
+ WINPR_ASSERT(rc);
+
+ cmd.data = data;
+ cmd.length = length;
+ cmd.codecId = RDPGFX_CODECID_UNCOMPRESSED;
+
+ IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart,
+ &cmdend);
+ free(data);
+ if (error)
+ {
+ WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_surface_bits(rdpShadowClient* client, BYTE* pSrcData,
+ UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc,
+ UINT16 nWidth, UINT16 nHeight)
+{
+ BOOL ret = TRUE;
+ BOOL first = 0;
+ BOOL last = 0;
+ wStream* s = NULL;
+ size_t numMessages = 0;
+ UINT32 frameId = 0;
+ rdpUpdate* update = NULL;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ rdpShadowEncoder* encoder = NULL;
+ SURFACE_BITS_COMMAND cmd = { 0 };
+ UINT32 nsID = 0;
+ UINT32 rfxID = 0;
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ update = context->update;
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!update || !settings || !encoder)
+ return FALSE;
+
+ if (encoder->frameAck)
+ frameId = shadow_encoder_create_frame_id(encoder);
+
+ nsID = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId);
+ rfxID = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId);
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (rfxID != 0))
+ {
+ RFX_RECT rect = { 0 };
+
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX");
+ return FALSE;
+ }
+
+ s = encoder->bs;
+ rect.x = nXSrc;
+ rect.y = nYSrc;
+ rect.width = nWidth;
+ rect.height = nHeight;
+
+ const UINT32 MultifragMaxRequestSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ RFX_MESSAGE_LIST* messages =
+ rfx_encode_messages(encoder->rfx, &rect, 1, pSrcData,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ nSrcStep, &numMessages, MultifragMaxRequestSize);
+ if (!messages)
+ {
+ WLog_ERR(TAG, "rfx_encode_messages failed");
+ return FALSE;
+ }
+
+ cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS;
+ WINPR_ASSERT(rfxID <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)rfxID;
+ cmd.destLeft = 0;
+ cmd.destTop = 0;
+ cmd.destRight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ cmd.destBottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ cmd.bmp.bpp = 32;
+ cmd.bmp.flags = 0;
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX);
+ WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX);
+ cmd.bmp.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ cmd.bmp.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ cmd.skipCompression = TRUE;
+
+ for (size_t i = 0; i < numMessages; i++)
+ {
+ Stream_SetPosition(s, 0);
+
+ const RFX_MESSAGE* msg = rfx_message_list_get(messages, i);
+ if (!rfx_write_message(encoder->rfx, s, msg))
+ {
+ rfx_message_list_free(messages);
+ WLog_ERR(TAG, "rfx_write_message failed");
+ ret = FALSE;
+ break;
+ }
+
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ first = (i == 0) ? TRUE : FALSE;
+ last = ((i + 1) == numMessages) ? TRUE : FALSE;
+
+ if (!encoder->frameAck)
+ IFCALLRET(update->SurfaceBits, ret, update->context, &cmd);
+ else
+ IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last,
+ frameId);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "Send surface bits(RemoteFxCodec) failed");
+ break;
+ }
+ }
+
+ rfx_message_list_free(messages);
+ }
+ if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec) && (nsID != 0))
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_NSCODEC) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_NSCODEC");
+ return FALSE;
+ }
+
+ s = encoder->bs;
+ Stream_SetPosition(s, 0);
+ pSrcData = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)];
+ nsc_compose_message(encoder->nsc, s, pSrcData, nWidth, nHeight, nSrcStep);
+ cmd.cmdType = CMDTYPE_SET_SURFACE_BITS;
+ cmd.bmp.bpp = 32;
+ WINPR_ASSERT(nsID <= UINT16_MAX);
+ cmd.bmp.codecID = (UINT16)nsID;
+ cmd.destLeft = nXSrc;
+ cmd.destTop = nYSrc;
+ cmd.destRight = cmd.destLeft + nWidth;
+ cmd.destBottom = cmd.destTop + nHeight;
+ cmd.bmp.width = nWidth;
+ cmd.bmp.height = nHeight;
+ WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX);
+ cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s);
+ cmd.bmp.bitmapData = Stream_Buffer(s);
+ first = TRUE;
+ last = TRUE;
+
+ if (!encoder->frameAck)
+ IFCALLRET(update->SurfaceBits, ret, update->context, &cmd);
+ else
+ IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, frameId);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "Send surface bits(NSCodec) failed");
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_bitmap_update(rdpShadowClient* client, BYTE* pSrcData,
+ UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc,
+ UINT16 nWidth, UINT16 nHeight)
+{
+ BOOL ret = TRUE;
+ BYTE* data = NULL;
+ BYTE* buffer = NULL;
+ UINT32 k = 0;
+ UINT32 yIdx = 0;
+ UINT32 xIdx = 0;
+ UINT32 rows = 0;
+ UINT32 cols = 0;
+ UINT32 DstSize = 0;
+ UINT32 SrcFormat = 0;
+ BITMAP_DATA* bitmap = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ UINT32 totalBitmapSize = 0;
+ UINT32 updateSizeEstimate = 0;
+ BITMAP_DATA* bitmapData = NULL;
+ BITMAP_UPDATE bitmapUpdate;
+ rdpShadowEncoder* encoder = NULL;
+
+ if (!context || !pSrcData)
+ return FALSE;
+
+ update = context->update;
+ settings = context->settings;
+ encoder = client->encoder;
+
+ if (!update || !settings || !encoder)
+ return FALSE;
+
+ const UINT32 maxUpdateSize =
+ freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize);
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32)
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_INTERLEAVED) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_INTERLEAVED");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0)
+ {
+ WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR");
+ return FALSE;
+ }
+ }
+
+ SrcFormat = PIXEL_FORMAT_BGRX32;
+
+ if ((nXSrc % 4) != 0)
+ {
+ nWidth += (nXSrc % 4);
+ nXSrc -= (nXSrc % 4);
+ }
+
+ if ((nYSrc % 4) != 0)
+ {
+ nHeight += (nYSrc % 4);
+ nYSrc -= (nYSrc % 4);
+ }
+
+ rows = (nHeight / 64) + ((nHeight % 64) ? 1 : 0);
+ cols = (nWidth / 64) + ((nWidth % 64) ? 1 : 0);
+ k = 0;
+ totalBitmapSize = 0;
+ bitmapUpdate.number = rows * cols;
+
+ if (!(bitmapData = (BITMAP_DATA*)calloc(bitmapUpdate.number, sizeof(BITMAP_DATA))))
+ return FALSE;
+
+ bitmapUpdate.rectangles = bitmapData;
+
+ if ((nWidth % 4) != 0)
+ {
+ nWidth += (4 - (nWidth % 4));
+ }
+
+ if ((nHeight % 4) != 0)
+ {
+ nHeight += (4 - (nHeight % 4));
+ }
+
+ for (yIdx = 0; yIdx < rows; yIdx++)
+ {
+ for (xIdx = 0; xIdx < cols; xIdx++)
+ {
+ bitmap = &bitmapData[k];
+ bitmap->width = 64;
+ bitmap->height = 64;
+ bitmap->destLeft = nXSrc + (xIdx * 64);
+ bitmap->destTop = nYSrc + (yIdx * 64);
+
+ if ((INT64)(bitmap->destLeft + bitmap->width) > (nXSrc + nWidth))
+ bitmap->width = (UINT32)(nXSrc + nWidth) - bitmap->destLeft;
+
+ if ((INT64)(bitmap->destTop + bitmap->height) > (nYSrc + nHeight))
+ bitmap->height = (UINT32)(nYSrc + nHeight) - bitmap->destTop;
+
+ bitmap->destRight = bitmap->destLeft + bitmap->width - 1;
+ bitmap->destBottom = bitmap->destTop + bitmap->height - 1;
+ bitmap->compressed = TRUE;
+
+ if ((bitmap->width < 4) || (bitmap->height < 4))
+ continue;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32)
+ {
+ UINT32 bitsPerPixel = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ UINT32 bytesPerPixel = (bitsPerPixel + 7) / 8;
+ DstSize = 64 * 64 * 4;
+ buffer = encoder->grid[k];
+ interleaved_compress(encoder->interleaved, buffer, &DstSize, bitmap->width,
+ bitmap->height, pSrcData, SrcFormat, nSrcStep,
+ bitmap->destLeft, bitmap->destTop, NULL, bitsPerPixel);
+ bitmap->bitmapDataStream = buffer;
+ bitmap->bitmapLength = DstSize;
+ bitmap->bitsPerPixel = bitsPerPixel;
+ bitmap->cbScanWidth = bitmap->width * bytesPerPixel;
+ bitmap->cbUncompressedSize = bitmap->width * bitmap->height * bytesPerPixel;
+ }
+ else
+ {
+ UINT32 dstSize = 0;
+ buffer = encoder->grid[k];
+ data = &pSrcData[(bitmap->destTop * nSrcStep) + (bitmap->destLeft * 4)];
+
+ buffer =
+ freerdp_bitmap_compress_planar(encoder->planar, data, SrcFormat, bitmap->width,
+ bitmap->height, nSrcStep, buffer, &dstSize);
+ bitmap->bitmapDataStream = buffer;
+ bitmap->bitmapLength = dstSize;
+ bitmap->bitsPerPixel = 32;
+ bitmap->cbScanWidth = bitmap->width * 4;
+ bitmap->cbUncompressedSize = bitmap->width * bitmap->height * 4;
+ }
+
+ bitmap->cbCompFirstRowSize = 0;
+ bitmap->cbCompMainBodySize = bitmap->bitmapLength;
+ totalBitmapSize += bitmap->bitmapLength;
+ k++;
+ }
+ }
+
+ bitmapUpdate.number = k;
+ updateSizeEstimate = totalBitmapSize + (k * bitmapUpdate.number) + 16;
+
+ if (updateSizeEstimate > maxUpdateSize)
+ {
+ UINT32 i = 0;
+ UINT32 j = 0;
+ UINT32 updateSize = 0;
+ UINT32 newUpdateSize = 0;
+ BITMAP_DATA* fragBitmapData = NULL;
+
+ if (k > 0)
+ fragBitmapData = (BITMAP_DATA*)calloc(k, sizeof(BITMAP_DATA));
+
+ if (!fragBitmapData)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for fragBitmapData");
+ ret = FALSE;
+ goto out;
+ }
+
+ bitmapUpdate.rectangles = fragBitmapData;
+ i = j = 0;
+ updateSize = 1024;
+
+ while (i < k)
+ {
+ newUpdateSize = updateSize + (bitmapData[i].bitmapLength + 16);
+
+ if (newUpdateSize < maxUpdateSize)
+ {
+ CopyMemory(&fragBitmapData[j++], &bitmapData[i++], sizeof(BITMAP_DATA));
+ updateSize = newUpdateSize;
+ }
+
+ if ((newUpdateSize >= maxUpdateSize) || (i + 1) >= k)
+ {
+ bitmapUpdate.number = j;
+ IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "BitmapUpdate failed");
+ break;
+ }
+
+ updateSize = 1024;
+ j = 0;
+ }
+ }
+
+ free(fragBitmapData);
+ }
+ else
+ {
+ IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate);
+
+ if (!ret)
+ {
+ WLog_ERR(TAG, "BitmapUpdate failed");
+ }
+ }
+
+out:
+ free(bitmapData);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return TRUE on success (or nothing need to be updated)
+ */
+static BOOL shadow_client_send_surface_update(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus)
+{
+ BOOL ret = TRUE;
+ INT64 nXSrc = 0;
+ INT64 nYSrc = 0;
+ INT64 nWidth = 0;
+ INT64 nHeight = 0;
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ REGION16 invalidRegion;
+ RECTANGLE_16 surfaceRect;
+ const RECTANGLE_16* extents = NULL;
+ BYTE* pSrcData = NULL;
+ UINT32 nSrcStep = 0;
+ UINT32 SrcFormat = 0;
+ UINT32 numRects = 0;
+ const RECTANGLE_16* rects = NULL;
+
+ if (!context || !pStatus)
+ return FALSE;
+
+ settings = context->settings;
+ server = client->server;
+
+ if (!settings || !server)
+ return FALSE;
+
+ surface = client->inLobby ? server->lobby : server->surface;
+
+ if (!surface)
+ return FALSE;
+
+ EnterCriticalSection(&(client->lock));
+ region16_init(&invalidRegion);
+ region16_copy(&invalidRegion, &(client->invalidRegion));
+ region16_clear(&(client->invalidRegion));
+ LeaveCriticalSection(&(client->lock));
+
+ EnterCriticalSection(&surface->lock);
+ rects = region16_rects(&(surface->invalidRegion), &numRects);
+
+ for (UINT32 index = 0; index < numRects; index++)
+ region16_union_rect(&invalidRegion, &invalidRegion, &rects[index]);
+
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ WINPR_ASSERT(surface->width <= UINT16_MAX);
+ WINPR_ASSERT(surface->height <= UINT16_MAX);
+ surfaceRect.right = (UINT16)surface->width;
+ surfaceRect.bottom = (UINT16)surface->height;
+ region16_intersect_rect(&invalidRegion, &invalidRegion, &surfaceRect);
+
+ if (server->shareSubRect)
+ {
+ region16_intersect_rect(&invalidRegion, &invalidRegion, &(server->subRect));
+ }
+
+ if (region16_is_empty(&invalidRegion))
+ {
+ /* No image region need to be updated. Success */
+ goto out;
+ }
+
+ extents = region16_extents(&invalidRegion);
+ nXSrc = extents->left;
+ nYSrc = extents->top;
+ nWidth = extents->right - extents->left;
+ nHeight = extents->bottom - extents->top;
+ pSrcData = surface->data;
+ nSrcStep = surface->scanline;
+ SrcFormat = surface->format;
+
+ /* Move to new pSrcData / nXSrc / nYSrc according to sub rect */
+ if (server->shareSubRect)
+ {
+ INT32 subX = 0;
+ INT32 subY = 0;
+ subX = server->subRect.left;
+ subY = server->subRect.top;
+ nXSrc -= subX;
+ nYSrc -= subY;
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ pSrcData = &pSrcData[((UINT16)subY * nSrcStep) + ((UINT16)subX * 4U)];
+ }
+
+ // WLog_INFO(TAG, "shadow_client_send_surface_update: x: %" PRId64 " y: %" PRId64 " width: %"
+ // PRId64 " height: %" PRId64 " right: %" PRId64 " bottom: %" PRId64, nXSrc, nYSrc, nWidth,
+ // nHeight, nXSrc + nWidth, nYSrc + nHeight);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ if (pStatus->gfxOpened && client->areGfxCapsReady)
+ {
+ /* GFX/h264 always full screen encoded */
+ nWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ nHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ /* Create primary surface if have not */
+ if (!pStatus->gfxSurfaceCreated)
+ {
+ /* Only init surface when we have h264 supported */
+ if (!(ret = shadow_client_rdpgfx_reset_graphic(client)))
+ goto out;
+
+ if (!(ret = shadow_client_rdpgfx_new_surface(client)))
+ goto out;
+
+ pStatus->gfxSurfaceCreated = TRUE;
+ }
+
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_surface_gfx(client, pSrcData, nSrcStep, SrcFormat, 0, 0,
+ (UINT16)nWidth, (UINT16)nHeight);
+ }
+ else
+ {
+ ret = TRUE;
+ }
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodec))
+ {
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_surface_bits(client, pSrcData, nSrcStep, (UINT16)nXSrc,
+ (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight);
+ }
+ else
+ {
+ WINPR_ASSERT(nXSrc >= 0);
+ WINPR_ASSERT(nXSrc <= UINT16_MAX);
+ WINPR_ASSERT(nYSrc >= 0);
+ WINPR_ASSERT(nYSrc <= UINT16_MAX);
+ WINPR_ASSERT(nWidth >= 0);
+ WINPR_ASSERT(nWidth <= UINT16_MAX);
+ WINPR_ASSERT(nHeight >= 0);
+ WINPR_ASSERT(nHeight <= UINT16_MAX);
+ ret = shadow_client_send_bitmap_update(client, pSrcData, nSrcStep, (UINT16)nXSrc,
+ (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight);
+ }
+
+out:
+ LeaveCriticalSection(&surface->lock);
+ region16_uninit(&invalidRegion);
+ return ret;
+}
+
+/**
+ * Function description
+ * Notify client for resize. The new desktop width/height
+ * should have already been updated in rdpSettings.
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_send_resize(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus)
+{
+ rdpContext* context = (rdpContext*)client;
+ rdpSettings* settings = NULL;
+ freerdp_peer* peer = NULL;
+
+ if (!context || !pStatus)
+ return FALSE;
+
+ peer = context->peer;
+ settings = context->settings;
+
+ if (!peer || !settings)
+ return FALSE;
+
+ /**
+ * Unset client activated flag to avoid sending update message during
+ * resize. DesktopResize will reactive the client and
+ * shadow_client_activate would be invoked later.
+ */
+ client->activated = FALSE;
+
+ /* Close Gfx surfaces */
+ if (pStatus->gfxSurfaceCreated)
+ {
+ if (!shadow_client_rdpgfx_release_surface(client))
+ return FALSE;
+
+ pStatus->gfxSurfaceCreated = FALSE;
+ }
+
+ /* Send Resize */
+ if (!shadow_send_desktop_resize(client))
+ return FALSE;
+ shadow_reset_desktop_resize(client);
+
+ /* Clear my invalidRegion. shadow_client_activate refreshes fullscreen */
+ EnterCriticalSection(&(client->lock));
+ region16_clear(&(client->invalidRegion));
+ LeaveCriticalSection(&(client->lock));
+ return TRUE;
+}
+
+/**
+ * Function description
+ * Mark invalid region for client
+ *
+ * @return TRUE on success
+ */
+static BOOL shadow_client_surface_update(rdpShadowClient* client, REGION16* region)
+{
+ UINT32 numRects = 0;
+ const RECTANGLE_16* rects = NULL;
+ rects = region16_rects(region, &numRects);
+ shadow_client_mark_invalid(client, numRects, rects);
+ return TRUE;
+}
+
+/**
+ * Function description
+ * Only union invalid region from server surface
+ *
+ * @return TRUE on success
+ */
+static INLINE BOOL shadow_client_no_surface_update(rdpShadowClient* client,
+ SHADOW_GFX_STATUS* pStatus)
+{
+ rdpShadowServer* server = NULL;
+ rdpShadowSurface* surface = NULL;
+ WINPR_UNUSED(pStatus);
+ WINPR_ASSERT(client);
+ server = client->server;
+ WINPR_ASSERT(server);
+ surface = client->inLobby ? server->lobby : server->surface;
+ return shadow_client_surface_update(client, &(surface->invalidRegion));
+}
+
+static int shadow_client_subsystem_process_message(rdpShadowClient* client, wMessage* message)
+{
+ rdpContext* context = (rdpContext*)client;
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(message);
+ WINPR_ASSERT(context);
+ update = context->update;
+ WINPR_ASSERT(update);
+
+ /* FIXME: the pointer updates appear to be broken when used with bulk compression and mstsc */
+
+ switch (message->id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ {
+ POINTER_POSITION_UPDATE pointerPosition;
+ const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg =
+ (const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE*)message->wParam;
+ pointerPosition.xPos = msg->xPos;
+ pointerPosition.yPos = msg->yPos;
+
+ WINPR_ASSERT(client->server);
+ if (client->server->shareSubRect)
+ {
+ pointerPosition.xPos -= client->server->subRect.left;
+ pointerPosition.yPos -= client->server->subRect.top;
+ }
+
+ if (client->activated)
+ {
+ if ((msg->xPos != client->pointerX) || (msg->yPos != client->pointerY))
+ {
+ WINPR_ASSERT(update->pointer);
+ IFCALL(update->pointer->PointerPosition, context, &pointerPosition);
+ client->pointerX = msg->xPos;
+ client->pointerY = msg->yPos;
+ }
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ {
+ POINTER_NEW_UPDATE pointerNew = { 0 };
+ POINTER_COLOR_UPDATE* pointerColor = { 0 };
+ POINTER_CACHED_UPDATE pointerCached = { 0 };
+ const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg =
+ (const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)message->wParam;
+
+ WINPR_ASSERT(msg);
+ pointerNew.xorBpp = 24;
+ pointerColor = &(pointerNew.colorPtrAttr);
+ pointerColor->cacheIndex = 0;
+ pointerColor->hotSpotX = msg->xHot;
+ pointerColor->hotSpotY = msg->yHot;
+ pointerColor->width = msg->width;
+ pointerColor->height = msg->height;
+ pointerColor->lengthAndMask = msg->lengthAndMask;
+ pointerColor->lengthXorMask = msg->lengthXorMask;
+ pointerColor->xorMaskData = msg->xorMaskData;
+ pointerColor->andMaskData = msg->andMaskData;
+ pointerCached.cacheIndex = pointerColor->cacheIndex;
+
+ if (client->activated)
+ {
+ IFCALL(update->pointer->PointerNew, context, &pointerNew);
+ IFCALL(update->pointer->PointerCached, context, &pointerCached);
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID:
+ {
+ const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES* msg =
+ (const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES*)message->wParam;
+
+ WINPR_ASSERT(msg);
+
+ if (client->activated && client->rdpsnd && client->rdpsnd->Activated)
+ {
+ client->rdpsnd->src_format = msg->audio_format;
+ IFCALL(client->rdpsnd->SendSamples, client->rdpsnd, msg->buf, msg->nFrames,
+ msg->wTimestamp);
+ }
+
+ break;
+ }
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID:
+ {
+ const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME* msg =
+ (const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME*)message->wParam;
+
+ if (client->activated && client->rdpsnd && client->rdpsnd->Activated)
+ {
+ IFCALL(client->rdpsnd->SetVolume, client->rdpsnd, msg->left, msg->right);
+ }
+
+ break;
+ }
+
+ default:
+ WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
+ break;
+ }
+
+ shadow_client_free_queued_message(message);
+ return 1;
+}
+
+static DWORD WINAPI shadow_client_thread(LPVOID arg)
+{
+ rdpShadowClient* client = (rdpShadowClient*)arg;
+ BOOL rc = FALSE;
+ DWORD status = 0;
+ wMessage message = { 0 };
+ wMessage pointerPositionMsg = { 0 };
+ wMessage pointerAlphaMsg = { 0 };
+ wMessage audioVolumeMsg = { 0 };
+ HANDLE ChannelEvent = 0;
+ void* UpdateSubscriber = NULL;
+ HANDLE UpdateEvent = 0;
+ freerdp_peer* peer = NULL;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpShadowServer* server = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+ wMessageQueue* MsgQueue = NULL;
+ /* This should only be visited in client thread */
+ SHADOW_GFX_STATUS gfxstatus = { 0 };
+ rdpUpdate* update = NULL;
+
+ WINPR_ASSERT(client);
+
+ MsgQueue = client->MsgQueue;
+ WINPR_ASSERT(MsgQueue);
+
+ server = client->server;
+ WINPR_ASSERT(server);
+ subsystem = server->subsystem;
+ context = (rdpContext*)client;
+ peer = context->peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ peer->Capabilities = shadow_client_capabilities;
+ peer->PostConnect = shadow_client_post_connect;
+ peer->Activate = shadow_client_activate;
+ peer->Logon = shadow_client_logon;
+ shadow_input_register_callbacks(peer->context->input);
+
+ rc = peer->Initialize(peer);
+ if (!rc)
+ goto out;
+
+ update = peer->context->update;
+ WINPR_ASSERT(update);
+
+ update->RefreshRect = shadow_client_refresh_rect;
+ update->SuppressOutput = shadow_client_suppress_output;
+ update->SurfaceFrameAcknowledge = shadow_client_surface_frame_acknowledge;
+
+ if ((!client->vcm) || (!subsystem->updateEvent))
+ goto out;
+
+ UpdateSubscriber = shadow_multiclient_get_subscriber(subsystem->updateEvent);
+
+ if (!UpdateSubscriber)
+ goto out;
+
+ UpdateEvent = shadow_multiclient_getevent(UpdateSubscriber);
+ WINPR_ASSERT(UpdateEvent);
+
+ ChannelEvent = WTSVirtualChannelManagerGetEventHandle(client->vcm);
+ WINPR_ASSERT(ChannelEvent);
+
+ rc = freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent, TRUE);
+ WINPR_ASSERT(rc);
+ rc = freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE);
+ WINPR_ASSERT(rc);
+ while (1)
+ {
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ events[nCount++] = UpdateEvent;
+ {
+ DWORD tmp = peer->GetEventHandles(peer, &events[nCount], 64 - nCount);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP transport event handles");
+ goto fail;
+ }
+
+ nCount += tmp;
+ }
+ events[nCount++] = ChannelEvent;
+ events[nCount++] = MessageQueue_Event(MsgQueue);
+
+ HANDLE gfxevent = rdpgfx_server_get_event_handle(client->rdpgfx);
+
+ if (gfxevent)
+ events[nCount++] = gfxevent;
+
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ goto fail;
+
+ if (WaitForSingleObject(UpdateEvent, 0) == WAIT_OBJECT_0)
+ {
+ /* The UpdateEvent means to start sending current frame. It is
+ * triggered from subsystem implementation and it should ensure
+ * that the screen and primary surface meta data (width, height,
+ * scanline, invalid region, etc) is not changed until it is reset
+ * (at shadow_multiclient_consume). As best practice, subsystem
+ * implementation should invoke shadow_subsystem_frame_update which
+ * triggers the event and then wait for completion */
+ if (client->activated && !client->suppressOutput)
+ {
+ /* Send screen update or resize to this client */
+
+ /* Check resize */
+ if (shadow_client_recalc_desktop_size(client))
+ {
+ /* Screen size changed, do resize */
+ if (!shadow_client_send_resize(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to send resize message");
+ break;
+ }
+ }
+ else
+ {
+ /* Send frame */
+ if (!shadow_client_send_surface_update(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to send surface update");
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Our client don't receive graphic updates. Just save the invalid region */
+ if (!shadow_client_no_surface_update(client, &gfxstatus))
+ {
+ WLog_ERR(TAG, "Failed to handle surface update");
+ break;
+ }
+ }
+
+ /*
+ * The return value of shadow_multiclient_consume is whether or not
+ * the subscriber really consumes the event. It's not cared currently.
+ */
+ (void)shadow_multiclient_consume(UpdateSubscriber);
+ }
+
+ WINPR_ASSERT(peer->CheckFileDescriptor);
+ if (!peer->CheckFileDescriptor(peer))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ goto fail;
+ }
+
+ if (client->activated &&
+ WTSVirtualChannelManagerIsChannelJoined(client->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ switch (WTSVirtualChannelManagerGetDrdynvcState(client->vcm))
+ {
+ /* Dynamic channel status may have been changed after processing */
+ case DRDYNVC_STATE_NONE:
+
+ /* Call this routine to Initialize drdynvc channel */
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm))
+ {
+ WLog_ERR(TAG, "Failed to initialize drdynvc channel");
+ goto fail;
+ }
+
+ break;
+
+ case DRDYNVC_STATE_READY:
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin))
+ {
+ if (!IFCALLRESULT(FALSE, client->audin->Open, client->audin))
+ {
+ WLog_ERR(TAG, "Failed to initialize audin channel");
+ goto fail;
+ }
+ }
+#endif
+
+ /* Init RDPGFX dynamic channel */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline) &&
+ client->rdpgfx && !gfxstatus.gfxOpened)
+ {
+ client->rdpgfx->FrameAcknowledge = shadow_client_rdpgfx_frame_acknowledge;
+ client->rdpgfx->CapsAdvertise = shadow_client_rdpgfx_caps_advertise;
+
+ if (!client->rdpgfx->Open(client->rdpgfx))
+ {
+ WLog_WARN(TAG, "Failed to open GraphicsPipeline");
+ if (!freerdp_settings_set_bool(settings,
+ FreeRDP_SupportGraphicsPipeline, FALSE))
+ goto fail;
+ }
+ else
+ {
+ gfxstatus.gfxOpened = TRUE;
+ WLog_INFO(TAG, "Gfx Pipeline Opened");
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
+ {
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelManagerCheckFileDescriptor failure");
+ goto fail;
+ }
+ }
+
+ if (gfxevent)
+ {
+ if (WaitForSingleObject(gfxevent, 0) == WAIT_OBJECT_0)
+ {
+ rdpgfx_server_handle_messages(client->rdpgfx);
+ }
+ }
+
+ if (WaitForSingleObject(MessageQueue_Event(MsgQueue), 0) == WAIT_OBJECT_0)
+ {
+ /* Drain messages. Pointer update could be accumulated. */
+ pointerPositionMsg.id = 0;
+ pointerPositionMsg.Free = NULL;
+ pointerAlphaMsg.id = 0;
+ pointerAlphaMsg.Free = NULL;
+ audioVolumeMsg.id = 0;
+ audioVolumeMsg.Free = NULL;
+
+ while (MessageQueue_Peek(MsgQueue, &message, TRUE))
+ {
+ if (message.id == WMQ_QUIT)
+ {
+ break;
+ }
+
+ switch (message.id)
+ {
+ case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&pointerPositionMsg);
+ pointerPositionMsg = message;
+ break;
+
+ case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&pointerAlphaMsg);
+ pointerAlphaMsg = message;
+ break;
+
+ case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID:
+ /* Abandon previous message */
+ shadow_client_free_queued_message(&audioVolumeMsg);
+ audioVolumeMsg = message;
+ break;
+
+ default:
+ shadow_client_subsystem_process_message(client, &message);
+ break;
+ }
+ }
+
+ if (message.id == WMQ_QUIT)
+ {
+ /* Release stored message */
+ shadow_client_free_queued_message(&pointerPositionMsg);
+ shadow_client_free_queued_message(&pointerAlphaMsg);
+ shadow_client_free_queued_message(&audioVolumeMsg);
+ goto fail;
+ }
+ else
+ {
+ /* Process accumulated messages if needed */
+ if (pointerPositionMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &pointerPositionMsg);
+ }
+
+ if (pointerAlphaMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &pointerAlphaMsg);
+ }
+
+ if (audioVolumeMsg.id)
+ {
+ shadow_client_subsystem_process_message(client, &audioVolumeMsg);
+ }
+ }
+ }
+ }
+
+fail:
+
+ /* Free channels early because we establish channels in post connect */
+#if defined(CHANNEL_AUDIN_SERVER)
+ if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin))
+ {
+ if (!IFCALLRESULT(FALSE, client->audin->Close, client->audin))
+ {
+ WLog_WARN(TAG, "AUDIN shutdown failure!");
+ }
+ }
+#endif
+
+ if (gfxstatus.gfxOpened)
+ {
+ if (gfxstatus.gfxSurfaceCreated)
+ {
+ if (!shadow_client_rdpgfx_release_surface(client))
+ WLog_WARN(TAG, "GFX release surface failure!");
+ }
+
+ WINPR_ASSERT(client->rdpgfx);
+ WINPR_ASSERT(client->rdpgfx->Close);
+ rc = client->rdpgfx->Close(client->rdpgfx);
+ WINPR_ASSERT(rc);
+ }
+
+ shadow_client_channels_free(client);
+
+ if (UpdateSubscriber)
+ {
+ shadow_multiclient_release_subscriber(UpdateSubscriber);
+ UpdateSubscriber = NULL;
+ }
+
+ if (peer->connected && subsystem->ClientDisconnect)
+ {
+ subsystem->ClientDisconnect(subsystem, client);
+ }
+
+out:
+ WINPR_ASSERT(peer->Disconnect);
+ peer->Disconnect(peer);
+ freerdp_peer_context_free(peer);
+ freerdp_peer_free(peer);
+ ExitThread(0);
+ return 0;
+}
+
+BOOL shadow_client_accepted(freerdp_listener* listener, freerdp_peer* peer)
+{
+ rdpShadowClient* client = NULL;
+ rdpShadowServer* server = NULL;
+
+ if (!listener || !peer)
+ return FALSE;
+
+ server = (rdpShadowServer*)listener->info;
+ WINPR_ASSERT(server);
+
+ peer->ContextExtra = (void*)server;
+ peer->ContextSize = sizeof(rdpShadowClient);
+ peer->ContextNew = shadow_client_context_new;
+ peer->ContextFree = shadow_client_context_free;
+
+ if (!freerdp_peer_context_new_ex(peer, server->settings))
+ return FALSE;
+
+ client = (rdpShadowClient*)peer->context;
+ WINPR_ASSERT(client);
+
+ if (!(client->thread = CreateThread(NULL, 0, shadow_client_thread, client, 0, NULL)))
+ {
+ freerdp_peer_context_free(peer);
+ return FALSE;
+ }
+ else
+ {
+ /* Close the thread handle to make it detached. */
+ CloseHandle(client->thread);
+ client->thread = NULL;
+ }
+
+ return TRUE;
+}
+
+static void shadow_msg_out_addref(wMessage* message)
+{
+ SHADOW_MSG_OUT* msg = NULL;
+
+ WINPR_ASSERT(message);
+ msg = (SHADOW_MSG_OUT*)message->wParam;
+ WINPR_ASSERT(msg);
+
+ InterlockedIncrement(&(msg->refCount));
+}
+
+static void shadow_msg_out_release(wMessage* message)
+{
+ SHADOW_MSG_OUT* msg = NULL;
+
+ WINPR_ASSERT(message);
+ msg = (SHADOW_MSG_OUT*)message->wParam;
+ WINPR_ASSERT(msg);
+
+ if (InterlockedDecrement(&(msg->refCount)) <= 0)
+ {
+ IFCALL(msg->Free, message->id, msg);
+ }
+}
+
+static BOOL shadow_client_dispatch_msg(rdpShadowClient* client, wMessage* message)
+{
+ if (!client || !message)
+ return FALSE;
+
+ /* Add reference when it is posted */
+ shadow_msg_out_addref(message);
+
+ WINPR_ASSERT(client->MsgQueue);
+ if (MessageQueue_Dispatch(client->MsgQueue, message))
+ return TRUE;
+ else
+ {
+ /* Release the reference since post failed */
+ shadow_msg_out_release(message);
+ return FALSE;
+ }
+}
+
+BOOL shadow_client_post_msg(rdpShadowClient* client, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam)
+{
+ wMessage message = { 0 };
+ message.context = context;
+ message.id = type;
+ message.wParam = (void*)msg;
+ message.lParam = lParam;
+ message.Free = shadow_msg_out_release;
+ return shadow_client_dispatch_msg(client, &message);
+}
+
+int shadow_client_boardcast_msg(rdpShadowServer* server, void* context, UINT32 type,
+ SHADOW_MSG_OUT* msg, void* lParam)
+{
+ wMessage message = { 0 };
+ rdpShadowClient* client = NULL;
+ int count = 0;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(msg);
+
+ message.context = context;
+ message.id = type;
+ message.wParam = (void*)msg;
+ message.lParam = lParam;
+ message.Free = shadow_msg_out_release;
+ /* First add reference as we reference it in this function.
+ * Therefore it would not be free'ed during post. */
+ shadow_msg_out_addref(&message);
+
+ WINPR_ASSERT(server->clients);
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index);
+
+ if (shadow_client_dispatch_msg(client, &message))
+ {
+ count++;
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ /* Release the reference for this function */
+ shadow_msg_out_release(&message);
+ return count;
+}
+
+int shadow_client_boardcast_quit(rdpShadowServer* server, int nExitCode)
+{
+ wMessageQueue* queue = NULL;
+ int count = 0;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->clients);
+
+ ArrayList_Lock(server->clients);
+
+ for (size_t index = 0; index < ArrayList_Count(server->clients); index++)
+ {
+ queue = ((rdpShadowClient*)ArrayList_GetItem(server->clients, index))->MsgQueue;
+
+ if (MessageQueue_PostQuit(queue, nExitCode))
+ {
+ count++;
+ }
+ }
+
+ ArrayList_Unlock(server->clients);
+ return count;
+}
diff --git a/server/shadow/shadow_client.h b/server/shadow/shadow_client.h
new file mode 100644
index 0000000..9334924
--- /dev/null
+++ b/server/shadow/shadow_client.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_CLIENT_H
+#define FREERDP_SERVER_SHADOW_CLIENT_H
+
+#include <freerdp/server/shadow.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_accepted(freerdp_listener* instance, freerdp_peer* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_CLIENT_H */
diff --git a/server/shadow/shadow_encoder.c b/server/shadow/shadow_encoder.c
new file mode 100644
index 0000000..fc1747c
--- /dev/null
+++ b/server/shadow/shadow_encoder.c
@@ -0,0 +1,520 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "shadow.h"
+
+#include "shadow_encoder.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("shadow")
+
+UINT32 shadow_encoder_preferred_fps(rdpShadowEncoder* encoder)
+{
+ /* Return preferred fps calculated according to the last
+ * sent frame id and last client-acknowledged frame id.
+ */
+ return encoder->fps;
+}
+
+UINT32 shadow_encoder_inflight_frames(rdpShadowEncoder* encoder)
+{
+ /* Return inflight frame count.
+ * If queueDepth is SUSPEND_FRAME_ACKNOWLEDGEMENT, count = 0
+ * Otherwise, calculate count =
+ * <last sent frame id> - <last client-acknowledged frame id>
+ * Note: This function is exported so that subsystem could
+ * implement its own strategy to tune fps.
+ */
+ return (encoder->queueDepth == SUSPEND_FRAME_ACKNOWLEDGEMENT)
+ ? 0
+ : encoder->frameId - encoder->lastAckframeId;
+}
+
+UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder)
+{
+ UINT32 frameId = 0;
+ UINT32 inFlightFrames = shadow_encoder_inflight_frames(encoder);
+
+ /*
+ * Calculate preferred fps according to how much frames are
+ * in-progress. Note that it only works when subsytem implementation
+ * calls shadow_encoder_preferred_fps and takes the suggestion.
+ */
+ if (inFlightFrames > 1)
+ {
+ encoder->fps = (100 / (inFlightFrames + 1) * encoder->maxFps) / 100;
+ }
+ else
+ {
+ encoder->fps += 2;
+
+ if (encoder->fps > encoder->maxFps)
+ encoder->fps = encoder->maxFps;
+ }
+
+ if (encoder->fps < 1)
+ encoder->fps = 1;
+
+ frameId = ++encoder->frameId;
+ return frameId;
+}
+
+static int shadow_encoder_init_grid(rdpShadowEncoder* encoder)
+{
+ UINT32 tileSize = 0;
+ UINT32 tileCount = 0;
+ encoder->gridWidth = ((encoder->width + (encoder->maxTileWidth - 1)) / encoder->maxTileWidth);
+ encoder->gridHeight =
+ ((encoder->height + (encoder->maxTileHeight - 1)) / encoder->maxTileHeight);
+ tileSize = encoder->maxTileWidth * encoder->maxTileHeight * 4;
+ tileCount = encoder->gridWidth * encoder->gridHeight;
+ encoder->gridBuffer = (BYTE*)calloc(tileSize, tileCount);
+
+ if (!encoder->gridBuffer)
+ return -1;
+
+ encoder->grid = (BYTE**)calloc(tileCount, sizeof(BYTE*));
+
+ if (!encoder->grid)
+ return -1;
+
+ for (UINT32 i = 0; i < encoder->gridHeight; i++)
+ {
+ for (UINT32 j = 0; j < encoder->gridWidth; j++)
+ {
+ UINT32 k = (i * encoder->gridWidth) + j;
+ encoder->grid[k] = &(encoder->gridBuffer[k * tileSize]);
+ }
+ }
+
+ return 0;
+}
+
+static int shadow_encoder_uninit_grid(rdpShadowEncoder* encoder)
+{
+ if (encoder->gridBuffer)
+ {
+ free(encoder->gridBuffer);
+ encoder->gridBuffer = NULL;
+ }
+
+ if (encoder->grid)
+ {
+ free(encoder->grid);
+ encoder->grid = NULL;
+ }
+
+ encoder->gridWidth = 0;
+ encoder->gridHeight = 0;
+ return 0;
+}
+
+static int shadow_encoder_init_rfx(rdpShadowEncoder* encoder)
+{
+ if (!encoder->rfx)
+ encoder->rfx = rfx_context_new_ex(
+ TRUE, freerdp_settings_get_uint32(encoder->server->settings, FreeRDP_ThreadingFlags));
+
+ if (!encoder->rfx)
+ goto fail;
+
+ if (!rfx_context_reset(encoder->rfx, encoder->width, encoder->height))
+ goto fail;
+
+ rfx_context_set_mode(encoder->rfx, encoder->server->rfxMode);
+ rfx_context_set_pixel_format(encoder->rfx, PIXEL_FORMAT_BGRX32);
+ encoder->codecs |= FREERDP_CODEC_REMOTEFX;
+ return 1;
+fail:
+ rfx_context_free(encoder->rfx);
+ return -1;
+}
+
+static int shadow_encoder_init_nsc(rdpShadowEncoder* encoder)
+{
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+
+ if (!encoder->nsc)
+ encoder->nsc = nsc_context_new();
+
+ if (!encoder->nsc)
+ goto fail;
+
+ if (!nsc_context_reset(encoder->nsc, encoder->width, encoder->height))
+ goto fail;
+
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_COLOR_LOSS_LEVEL,
+ freerdp_settings_get_uint32(settings, FreeRDP_NSCodecColorLossLevel)))
+ goto fail;
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_ALLOW_SUBSAMPLING,
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowSubsampling)))
+ goto fail;
+ if (!nsc_context_set_parameters(
+ encoder->nsc, NSC_DYNAMIC_COLOR_FIDELITY,
+ !freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowDynamicColorFidelity)))
+ goto fail;
+ if (!nsc_context_set_parameters(encoder->nsc, NSC_COLOR_FORMAT, PIXEL_FORMAT_BGRX32))
+ goto fail;
+ encoder->codecs |= FREERDP_CODEC_NSCODEC;
+ return 1;
+fail:
+ nsc_context_free(encoder->nsc);
+ return -1;
+}
+
+static int shadow_encoder_init_planar(rdpShadowEncoder* encoder)
+{
+ DWORD planarFlags = 0;
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DrawAllowSkipAlpha))
+ planarFlags |= PLANAR_FORMAT_HEADER_NA;
+
+ planarFlags |= PLANAR_FORMAT_HEADER_RLE;
+
+ if (!encoder->planar)
+ {
+ encoder->planar = freerdp_bitmap_planar_context_new(planarFlags, encoder->maxTileWidth,
+ encoder->maxTileHeight);
+ }
+
+ if (!encoder->planar)
+ goto fail;
+
+ if (!freerdp_bitmap_planar_context_reset(encoder->planar, encoder->maxTileWidth,
+ encoder->maxTileHeight))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_PLANAR;
+ return 1;
+fail:
+ freerdp_bitmap_planar_context_free(encoder->planar);
+ return -1;
+}
+
+static int shadow_encoder_init_interleaved(rdpShadowEncoder* encoder)
+{
+ if (!encoder->interleaved)
+ encoder->interleaved = bitmap_interleaved_context_new(TRUE);
+
+ if (!encoder->interleaved)
+ goto fail;
+
+ if (!bitmap_interleaved_context_reset(encoder->interleaved))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_INTERLEAVED;
+ return 1;
+fail:
+ bitmap_interleaved_context_free(encoder->interleaved);
+ return -1;
+}
+
+static int shadow_encoder_init_h264(rdpShadowEncoder* encoder)
+{
+ if (!encoder->h264)
+ encoder->h264 = h264_context_new(TRUE);
+
+ if (!encoder->h264)
+ goto fail;
+
+ if (!h264_context_reset(encoder->h264, encoder->width, encoder->height))
+ goto fail;
+
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_RATECONTROL,
+ encoder->server->h264RateControlMode))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_BITRATE,
+ encoder->server->h264BitRate))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_FRAMERATE,
+ encoder->server->h264FrameRate))
+ goto fail;
+ if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_QP, encoder->server->h264QP))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444;
+ return 1;
+fail:
+ h264_context_free(encoder->h264);
+ return -1;
+}
+
+static int shadow_encoder_init_progressive(rdpShadowEncoder* encoder)
+{
+ WINPR_ASSERT(encoder);
+ if (!encoder->progressive)
+ encoder->progressive = progressive_context_new(TRUE);
+
+ if (!encoder->progressive)
+ goto fail;
+
+ if (!progressive_context_reset(encoder->progressive))
+ goto fail;
+
+ encoder->codecs |= FREERDP_CODEC_PROGRESSIVE;
+ return 1;
+fail:
+ progressive_context_free(encoder->progressive);
+ return -1;
+}
+
+static int shadow_encoder_init(rdpShadowEncoder* encoder)
+{
+ encoder->width = encoder->server->screen->width;
+ encoder->height = encoder->server->screen->height;
+ encoder->maxTileWidth = 64;
+ encoder->maxTileHeight = 64;
+ shadow_encoder_init_grid(encoder);
+
+ if (!encoder->bs)
+ encoder->bs = Stream_New(NULL, encoder->maxTileWidth * encoder->maxTileHeight * 4ULL);
+
+ if (!encoder->bs)
+ return -1;
+
+ return 1;
+}
+
+static int shadow_encoder_uninit_rfx(rdpShadowEncoder* encoder)
+{
+ if (encoder->rfx)
+ {
+ rfx_context_free(encoder->rfx);
+ encoder->rfx = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_REMOTEFX;
+ return 1;
+}
+
+static int shadow_encoder_uninit_nsc(rdpShadowEncoder* encoder)
+{
+ if (encoder->nsc)
+ {
+ nsc_context_free(encoder->nsc);
+ encoder->nsc = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_NSCODEC;
+ return 1;
+}
+
+static int shadow_encoder_uninit_planar(rdpShadowEncoder* encoder)
+{
+ if (encoder->planar)
+ {
+ freerdp_bitmap_planar_context_free(encoder->planar);
+ encoder->planar = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_PLANAR;
+ return 1;
+}
+
+static int shadow_encoder_uninit_interleaved(rdpShadowEncoder* encoder)
+{
+ if (encoder->interleaved)
+ {
+ bitmap_interleaved_context_free(encoder->interleaved);
+ encoder->interleaved = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_INTERLEAVED;
+ return 1;
+}
+
+static int shadow_encoder_uninit_h264(rdpShadowEncoder* encoder)
+{
+ if (encoder->h264)
+ {
+ h264_context_free(encoder->h264);
+ encoder->h264 = NULL;
+ }
+
+ encoder->codecs &= (UINT32) ~(FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444);
+ return 1;
+}
+
+static int shadow_encoder_uninit_progressive(rdpShadowEncoder* encoder)
+{
+ WINPR_ASSERT(encoder);
+ if (encoder->progressive)
+ {
+ progressive_context_free(encoder->progressive);
+ encoder->progressive = NULL;
+ }
+
+ encoder->codecs &= (UINT32)~FREERDP_CODEC_PROGRESSIVE;
+ return 1;
+}
+
+static int shadow_encoder_uninit(rdpShadowEncoder* encoder)
+{
+ shadow_encoder_uninit_grid(encoder);
+
+ if (encoder->bs)
+ {
+ Stream_Free(encoder->bs, TRUE);
+ encoder->bs = NULL;
+ }
+
+ shadow_encoder_uninit_rfx(encoder);
+
+ shadow_encoder_uninit_nsc(encoder);
+
+ shadow_encoder_uninit_planar(encoder);
+
+ shadow_encoder_uninit_interleaved(encoder);
+ shadow_encoder_uninit_h264(encoder);
+
+ shadow_encoder_uninit_progressive(encoder);
+
+ return 1;
+}
+
+int shadow_encoder_reset(rdpShadowEncoder* encoder)
+{
+ int status = 0;
+ UINT32 codecs = encoder->codecs;
+ rdpContext* context = (rdpContext*)encoder->client;
+ rdpSettings* settings = context->settings;
+ status = shadow_encoder_uninit(encoder);
+
+ if (status < 0)
+ return -1;
+
+ status = shadow_encoder_init(encoder);
+
+ if (status < 0)
+ return -1;
+
+ status = shadow_encoder_prepare(encoder, codecs);
+
+ if (status < 0)
+ return -1;
+
+ encoder->fps = 16;
+ encoder->maxFps = 32;
+ encoder->frameId = 0;
+ encoder->lastAckframeId = 0;
+ encoder->frameAck = freerdp_settings_get_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled);
+ return 1;
+}
+
+int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs)
+{
+ int status = 0;
+
+ if ((codecs & FREERDP_CODEC_REMOTEFX) && !(encoder->codecs & FREERDP_CODEC_REMOTEFX))
+ {
+ WLog_DBG(TAG, "initializing RemoteFX encoder");
+ status = shadow_encoder_init_rfx(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_NSCODEC) && !(encoder->codecs & FREERDP_CODEC_NSCODEC))
+ {
+ WLog_DBG(TAG, "initializing NSCodec encoder");
+ status = shadow_encoder_init_nsc(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_PLANAR) && !(encoder->codecs & FREERDP_CODEC_PLANAR))
+ {
+ WLog_DBG(TAG, "initializing planar bitmap encoder");
+ status = shadow_encoder_init_planar(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_INTERLEAVED) && !(encoder->codecs & FREERDP_CODEC_INTERLEAVED))
+ {
+ WLog_DBG(TAG, "initializing interleaved bitmap encoder");
+ status = shadow_encoder_init_interleaved(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)) &&
+ !(encoder->codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)))
+ {
+ WLog_DBG(TAG, "initializing H.264 encoder");
+ status = shadow_encoder_init_h264(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ if ((codecs & FREERDP_CODEC_PROGRESSIVE) && !(encoder->codecs & FREERDP_CODEC_PROGRESSIVE))
+ {
+ WLog_DBG(TAG, "initializing progressive encoder");
+ status = shadow_encoder_init_progressive(encoder);
+
+ if (status < 0)
+ return -1;
+ }
+
+ return 1;
+}
+
+rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client)
+{
+ rdpShadowEncoder* encoder = NULL;
+ rdpShadowServer* server = client->server;
+ encoder = (rdpShadowEncoder*)calloc(1, sizeof(rdpShadowEncoder));
+
+ if (!encoder)
+ return NULL;
+
+ encoder->client = client;
+ encoder->server = server;
+ encoder->fps = 16;
+ encoder->maxFps = 32;
+
+ if (shadow_encoder_init(encoder) < 0)
+ {
+ free(encoder);
+ return NULL;
+ }
+
+ return encoder;
+}
+
+void shadow_encoder_free(rdpShadowEncoder* encoder)
+{
+ if (!encoder)
+ return;
+
+ shadow_encoder_uninit(encoder);
+ free(encoder);
+}
diff --git a/server/shadow/shadow_encoder.h b/server/shadow/shadow_encoder.h
new file mode 100644
index 0000000..dfe00f3
--- /dev/null
+++ b/server/shadow/shadow_encoder.h
@@ -0,0 +1,81 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_ENCODER_H
+#define FREERDP_SERVER_SHADOW_ENCODER_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/codecs.h>
+
+#include <freerdp/server/shadow.h>
+
+struct rdp_shadow_encoder
+{
+ rdpShadowClient* client;
+ rdpShadowServer* server;
+
+ UINT32 width;
+ UINT32 height;
+ UINT32 codecs;
+
+ BYTE** grid;
+ UINT32 gridWidth;
+ UINT32 gridHeight;
+ BYTE* gridBuffer;
+ UINT32 maxTileWidth;
+ UINT32 maxTileHeight;
+
+ wStream* bs;
+
+ RFX_CONTEXT* rfx;
+ NSC_CONTEXT* nsc;
+ BITMAP_PLANAR_CONTEXT* planar;
+ BITMAP_INTERLEAVED_CONTEXT* interleaved;
+ H264_CONTEXT* h264;
+ PROGRESSIVE_CONTEXT* progressive;
+
+ UINT32 fps;
+ UINT32 maxFps;
+ BOOL frameAck;
+ UINT32 frameId;
+ UINT32 lastAckframeId;
+ UINT32 queueDepth;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_encoder_reset(rdpShadowEncoder* encoder);
+ int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs);
+ UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder);
+
+ void shadow_encoder_free(rdpShadowEncoder* encoder);
+
+ WINPR_ATTR_MALLOC(shadow_encoder_free, 1)
+ rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_ENCODER_H */
diff --git a/server/shadow/shadow_encomsp.c b/server/shadow/shadow_encomsp.c
new file mode 100644
index 0000000..8a3a468
--- /dev/null
+++ b/server/shadow/shadow_encomsp.c
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+#include "shadow.h"
+
+#include "shadow_encomsp.h"
+
+#define TAG SERVER_TAG("shadow")
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+encomsp_change_participant_control_level(EncomspServerContext* context,
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu)
+{
+ BOOL inLobby = 0;
+ BOOL mayView = 0;
+ BOOL mayInteract = 0;
+ rdpShadowClient* client = (rdpShadowClient*)context->custom;
+
+ WLog_INFO(TAG,
+ "ChangeParticipantControlLevel: ParticipantId: %" PRIu32 " Flags: 0x%04" PRIX16 "",
+ pdu->ParticipantId, pdu->Flags);
+
+ mayView = (pdu->Flags & ENCOMSP_MAY_VIEW) ? TRUE : FALSE;
+ mayInteract = (pdu->Flags & ENCOMSP_MAY_INTERACT) ? TRUE : FALSE;
+
+ if (mayInteract && !mayView)
+ mayView = TRUE; /* may interact implies may view */
+
+ if (mayInteract)
+ {
+ if (!client->mayInteract)
+ {
+ /* request interact + view */
+ client->mayInteract = TRUE;
+ client->mayView = TRUE;
+ }
+ }
+ else if (mayView)
+ {
+ if (client->mayInteract)
+ {
+ /* release interact */
+ client->mayInteract = FALSE;
+ }
+ else if (!client->mayView)
+ {
+ /* request view */
+ client->mayView = TRUE;
+ }
+ }
+ else
+ {
+ if (client->mayInteract)
+ {
+ /* release interact + view */
+ client->mayView = FALSE;
+ client->mayInteract = FALSE;
+ }
+ else if (client->mayView)
+ {
+ /* release view */
+ client->mayView = FALSE;
+ client->mayInteract = FALSE;
+ }
+ }
+
+ inLobby = client->mayView ? FALSE : TRUE;
+
+ if (inLobby != client->inLobby)
+ {
+ shadow_encoder_reset(client->encoder);
+ client->inLobby = inLobby;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+int shadow_client_encomsp_init(rdpShadowClient* client)
+{
+ EncomspServerContext* encomsp = NULL;
+
+ encomsp = client->encomsp = encomsp_server_context_new(client->vcm);
+
+ encomsp->rdpcontext = &client->context;
+
+ encomsp->custom = (void*)client;
+
+ encomsp->ChangeParticipantControlLevel = encomsp_change_participant_control_level;
+
+ if (client->encomsp)
+ client->encomsp->Start(client->encomsp);
+
+ return 1;
+}
+
+void shadow_client_encomsp_uninit(rdpShadowClient* client)
+{
+ if (client->encomsp)
+ {
+ client->encomsp->Stop(client->encomsp);
+ encomsp_server_context_free(client->encomsp);
+ client->encomsp = NULL;
+ }
+}
diff --git a/server/shadow/shadow_encomsp.h b/server/shadow/shadow_encomsp.h
new file mode 100644
index 0000000..6562e25
--- /dev/null
+++ b/server/shadow/shadow_encomsp.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_ENCOMSP_H
+#define FREERDP_SERVER_SHADOW_ENCOMSP_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_encomsp_init(rdpShadowClient* client);
+ void shadow_client_encomsp_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_ENCOMSP_H */
diff --git a/server/shadow/shadow_input.c b/server/shadow/shadow_input.c
new file mode 100644
index 0000000..97268b8
--- /dev/null
+++ b/server/shadow/shadow_input.c
@@ -0,0 +1,114 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+static BOOL shadow_input_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->SynchronizeEvent, subsystem, client, flags);
+}
+
+static BOOL shadow_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->KeyboardEvent, subsystem, client, flags, code);
+}
+
+static BOOL shadow_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->UnicodeKeyboardEvent, subsystem, client, flags, code);
+}
+
+static BOOL shadow_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (client->server->shareSubRect)
+ {
+ x += client->server->subRect.left;
+ y += client->server->subRect.top;
+ }
+
+ if (!(flags & PTR_FLAGS_WHEEL))
+ {
+ client->pointerX = x;
+ client->pointerY = y;
+
+ if ((client->pointerX == subsystem->pointerX) && (client->pointerY == subsystem->pointerY))
+ {
+ flags &= ~PTR_FLAGS_MOVE;
+
+ if (!(flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3)))
+ return TRUE;
+ }
+ }
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->MouseEvent, subsystem, client, flags, x, y);
+}
+
+static BOOL shadow_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ rdpShadowClient* client = (rdpShadowClient*)input->context;
+ rdpShadowSubsystem* subsystem = client->server->subsystem;
+
+ if (client->server->shareSubRect)
+ {
+ x += client->server->subRect.left;
+ y += client->server->subRect.top;
+ }
+
+ client->pointerX = x;
+ client->pointerY = y;
+
+ if (!client->mayInteract)
+ return TRUE;
+
+ return IFCALLRESULT(TRUE, subsystem->ExtendedMouseEvent, subsystem, client, flags, x, y);
+}
+
+void shadow_input_register_callbacks(rdpInput* input)
+{
+ input->SynchronizeEvent = shadow_input_synchronize_event;
+ input->KeyboardEvent = shadow_input_keyboard_event;
+ input->UnicodeKeyboardEvent = shadow_input_unicode_keyboard_event;
+ input->MouseEvent = shadow_input_mouse_event;
+ input->ExtendedMouseEvent = shadow_input_extended_mouse_event;
+}
diff --git a/server/shadow/shadow_input.h b/server/shadow/shadow_input.h
new file mode 100644
index 0000000..8bde31c
--- /dev/null
+++ b/server/shadow/shadow_input.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_INPUT_H
+#define FREERDP_SERVER_SHADOW_INPUT_H
+
+#include <freerdp/server/shadow.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_input_register_callbacks(rdpInput* input);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_INPUT_H */
diff --git a/server/shadow/shadow_lobby.c b/server/shadow/shadow_lobby.c
new file mode 100644
index 0000000..8a9fade
--- /dev/null
+++ b/server/shadow/shadow_lobby.c
@@ -0,0 +1,85 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <rdtk/rdtk.h>
+
+#include "shadow.h"
+
+#include "shadow_lobby.h"
+
+BOOL shadow_client_init_lobby(rdpShadowServer* server)
+{
+ int width = 0;
+ int height = 0;
+ rdtkEngine* engine = NULL;
+ rdtkSurface* surface = NULL;
+ RECTANGLE_16 invalidRect;
+ rdpShadowSurface* lobby = server->lobby;
+
+ if (!lobby)
+ return FALSE;
+
+ if (!(engine = rdtk_engine_new()))
+ {
+ return FALSE;
+ }
+
+ if (!(surface =
+ rdtk_surface_new(engine, lobby->data, lobby->width, lobby->height, lobby->scanline)))
+ {
+ rdtk_engine_free(engine);
+ return FALSE;
+ }
+
+ invalidRect.left = 0;
+ invalidRect.top = 0;
+ WINPR_ASSERT(lobby->width <= UINT16_MAX);
+ WINPR_ASSERT(lobby->height <= UINT16_MAX);
+ invalidRect.right = (UINT16)lobby->width;
+ invalidRect.bottom = (UINT16)lobby->height;
+ if (server->shareSubRect)
+ {
+ /* If we have shared sub rect setting, only fill shared rect */
+ rectangles_intersection(&invalidRect, &(server->subRect), &invalidRect);
+ }
+
+ width = invalidRect.right - invalidRect.left;
+ height = invalidRect.bottom - invalidRect.top;
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ rdtk_surface_fill(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height,
+ 0x3BB9FF);
+
+ rdtk_label_draw(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height, NULL,
+ "Welcome", 0, 0);
+ // rdtk_button_draw(surface, 16, 64, 128, 32, NULL, "button");
+ // rdtk_text_field_draw(surface, 16, 128, 128, 32, NULL, "text field");
+
+ rdtk_surface_free(surface);
+
+ rdtk_engine_free(engine);
+
+ region16_union_rect(&(lobby->invalidRegion), &(lobby->invalidRegion), &invalidRect);
+
+ return TRUE;
+}
diff --git a/server/shadow/shadow_lobby.h b/server/shadow/shadow_lobby.h
new file mode 100644
index 0000000..37ad9cf
--- /dev/null
+++ b/server/shadow/shadow_lobby.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_LOBBY_H
+#define FREERDP_SERVER_SHADOW_LOBBY_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <rdtk/rdtk.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL shadow_client_init_lobby(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_LOBBY_H */
diff --git a/server/shadow/shadow_mcevent.c b/server/shadow/shadow_mcevent.c
new file mode 100644
index 0000000..4609cee
--- /dev/null
+++ b/server/shadow/shadow_mcevent.c
@@ -0,0 +1,355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <freerdp/log.h>
+#include "shadow.h"
+
+#define TAG SERVER_TAG("shadow.mcevent")
+
+struct rdp_shadow_multiclient_event
+{
+ HANDLE event; /* Kickoff event */
+ HANDLE barrierEvent; /* Represents that all clients have consumed event */
+ HANDLE doneEvent; /* Event handling finished. Server could continue */
+ wArrayList* subscribers;
+ CRITICAL_SECTION lock;
+ int consuming;
+ int waiting;
+
+ /* For debug */
+ int eventid;
+};
+
+struct rdp_shadow_multiclient_subscriber
+{
+ rdpShadowMultiClientEvent* ref;
+ BOOL pleaseHandle; /* Indicate if server expects my handling in this turn */
+};
+
+rdpShadowMultiClientEvent* shadow_multiclient_new(void)
+{
+ rdpShadowMultiClientEvent* event =
+ (rdpShadowMultiClientEvent*)calloc(1, sizeof(rdpShadowMultiClientEvent));
+ if (!event)
+ goto out_error;
+
+ event->event = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->event)
+ goto out_free;
+
+ event->barrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->barrierEvent)
+ goto out_free_event;
+
+ event->doneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!event->doneEvent)
+ goto out_free_barrierEvent;
+
+ event->subscribers = ArrayList_New(TRUE);
+ if (!event->subscribers)
+ goto out_free_doneEvent;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(event->lock), 4000))
+ goto out_free_subscribers;
+
+ event->consuming = 0;
+ event->waiting = 0;
+ event->eventid = 0;
+ SetEvent(event->doneEvent);
+ return event;
+
+out_free_subscribers:
+ ArrayList_Free(event->subscribers);
+out_free_doneEvent:
+ CloseHandle(event->doneEvent);
+out_free_barrierEvent:
+ CloseHandle(event->barrierEvent);
+out_free_event:
+ CloseHandle(event->event);
+out_free:
+ free(event);
+out_error:
+ return (rdpShadowMultiClientEvent*)NULL;
+}
+
+void shadow_multiclient_free(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ DeleteCriticalSection(&(event->lock));
+
+ ArrayList_Free(event->subscribers);
+ CloseHandle(event->doneEvent);
+ CloseHandle(event->barrierEvent);
+ CloseHandle(event->event);
+ free(event);
+
+ return;
+}
+
+static void _Publish(rdpShadowMultiClientEvent* event)
+{
+ wArrayList* subscribers = NULL;
+ struct rdp_shadow_multiclient_subscriber* subscriber = NULL;
+
+ subscribers = event->subscribers;
+
+ WINPR_ASSERT(event->consuming == 0);
+
+ /* Count subscribing clients */
+ ArrayList_Lock(subscribers);
+ for (size_t i = 0; i < ArrayList_Count(subscribers); i++)
+ {
+ subscriber = (struct rdp_shadow_multiclient_subscriber*)ArrayList_GetItem(subscribers, i);
+ /* Set flag to subscriber: I acknowledge and please handle */
+ subscriber->pleaseHandle = TRUE;
+ event->consuming++;
+ }
+ ArrayList_Unlock(subscribers);
+
+ if (event->consuming > 0)
+ {
+ event->eventid = (event->eventid & 0xff) + 1;
+ WLog_VRB(TAG, "Server published event %d. %d clients.\n", event->eventid, event->consuming);
+ ResetEvent(event->doneEvent);
+ SetEvent(event->event);
+ }
+
+ return;
+}
+
+static void _WaitForSubscribers(rdpShadowMultiClientEvent* event)
+{
+ if (event->consuming > 0)
+ {
+ /* Wait for clients done */
+ WLog_VRB(TAG, "Server wait event %d. %d clients.\n", event->eventid, event->consuming);
+ LeaveCriticalSection(&(event->lock));
+ WaitForSingleObject(event->doneEvent, INFINITE);
+ EnterCriticalSection(&(event->lock));
+ WLog_VRB(TAG, "Server quit event %d. %d clients.\n", event->eventid, event->consuming);
+ }
+
+ /* Last subscriber should have already reset the event */
+ WINPR_ASSERT(WaitForSingleObject(event->event, 0) != WAIT_OBJECT_0);
+
+ return;
+}
+
+void shadow_multiclient_publish(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _Publish(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+void shadow_multiclient_wait(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _WaitForSubscribers(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event)
+{
+ if (!event)
+ return;
+
+ EnterCriticalSection(&(event->lock));
+ _Publish(event);
+ _WaitForSubscribers(event);
+ LeaveCriticalSection(&(event->lock));
+
+ return;
+}
+
+static BOOL _Consume(struct rdp_shadow_multiclient_subscriber* subscriber, BOOL wait)
+{
+ rdpShadowMultiClientEvent* event = subscriber->ref;
+ BOOL ret = FALSE;
+
+ if (WaitForSingleObject(event->event, 0) == WAIT_OBJECT_0 && subscriber->pleaseHandle)
+ {
+ /* Consume my share. Server is waiting for us */
+ event->consuming--;
+ ret = TRUE;
+ }
+
+ WINPR_ASSERT(event->consuming >= 0);
+
+ if (event->consuming == 0)
+ {
+ /* Last client reset event before notify clients to continue */
+ ResetEvent(event->event);
+
+ if (event->waiting > 0)
+ {
+ /* Notify other clients to continue */
+ SetEvent(event->barrierEvent);
+ }
+ else
+ {
+ /* Only one client. Notify server directly */
+ SetEvent(event->doneEvent);
+ }
+ }
+ else /* (event->consuming > 0) */
+ {
+ if (wait)
+ {
+ /*
+ * This client need to wait. That means the client will
+ * continue waiting for other clients to finish.
+ * The last client should reset barrierEvent.
+ */
+ event->waiting++;
+ LeaveCriticalSection(&(event->lock));
+ WaitForSingleObject(event->barrierEvent, INFINITE);
+ EnterCriticalSection(&(event->lock));
+ event->waiting--;
+ if (event->waiting == 0)
+ {
+ /*
+ * This is last client waiting for barrierEvent.
+ * We can now discard barrierEvent and notify
+ * server to continue.
+ */
+ ResetEvent(event->barrierEvent);
+ SetEvent(event->doneEvent);
+ }
+ }
+ }
+
+ return ret;
+}
+
+void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event)
+{
+ struct rdp_shadow_multiclient_subscriber* subscriber = NULL;
+
+ if (!event)
+ return NULL;
+
+ EnterCriticalSection(&(event->lock));
+
+ subscriber = (struct rdp_shadow_multiclient_subscriber*)calloc(
+ 1, sizeof(struct rdp_shadow_multiclient_subscriber));
+ if (!subscriber)
+ goto out_error;
+
+ subscriber->ref = event;
+ subscriber->pleaseHandle = FALSE;
+
+ if (!ArrayList_Append(event->subscribers, subscriber))
+ goto out_free;
+
+ WLog_VRB(TAG, "Get subscriber %p. Wait event %d. %d clients.\n", (void*)subscriber,
+ event->eventid, event->consuming);
+ (void)_Consume(subscriber, TRUE);
+ WLog_VRB(TAG, "Get subscriber %p. Quit event %d. %d clients.\n", (void*)subscriber,
+ event->eventid, event->consuming);
+
+ LeaveCriticalSection(&(event->lock));
+
+ return subscriber;
+
+out_free:
+ free(subscriber);
+out_error:
+ LeaveCriticalSection(&(event->lock));
+ return NULL;
+}
+
+/*
+ * Consume my share and release my register
+ * If we have update event and pleaseHandle flag
+ * We need to consume. Anyway we need to clear
+ * pleaseHandle flag
+ */
+void shadow_multiclient_release_subscriber(void* subscriber)
+{
+ struct rdp_shadow_multiclient_subscriber* s = NULL;
+ rdpShadowMultiClientEvent* event = NULL;
+
+ if (!subscriber)
+ return;
+
+ s = (struct rdp_shadow_multiclient_subscriber*)subscriber;
+ event = s->ref;
+
+ EnterCriticalSection(&(event->lock));
+
+ WLog_VRB(TAG, "Release Subscriber %p. Drop event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+ (void)_Consume(s, FALSE);
+ WLog_VRB(TAG, "Release Subscriber %p. Quit event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+
+ ArrayList_Remove(event->subscribers, subscriber);
+
+ LeaveCriticalSection(&(event->lock));
+
+ free(subscriber);
+
+ return;
+}
+
+BOOL shadow_multiclient_consume(void* subscriber)
+{
+ struct rdp_shadow_multiclient_subscriber* s = NULL;
+ rdpShadowMultiClientEvent* event = NULL;
+ BOOL ret = FALSE;
+
+ if (!subscriber)
+ return ret;
+
+ s = (struct rdp_shadow_multiclient_subscriber*)subscriber;
+ event = s->ref;
+
+ EnterCriticalSection(&(event->lock));
+
+ WLog_VRB(TAG, "Subscriber %p wait event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+ ret = _Consume(s, TRUE);
+ WLog_VRB(TAG, "Subscriber %p quit event %d. %d clients.\n", subscriber, event->eventid,
+ event->consuming);
+
+ LeaveCriticalSection(&(event->lock));
+
+ return ret;
+}
+
+HANDLE shadow_multiclient_getevent(void* subscriber)
+{
+ if (!subscriber)
+ return (HANDLE)NULL;
+
+ return ((struct rdp_shadow_multiclient_subscriber*)subscriber)->ref->event;
+}
diff --git a/server/shadow/shadow_mcevent.h b/server/shadow/shadow_mcevent.h
new file mode 100644
index 0000000..c78b920
--- /dev/null
+++ b/server/shadow/shadow_mcevent.h
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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_SHADOW_MCEVENT_H
+#define FREERDP_SERVER_SHADOW_MCEVENT_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/collections.h>
+
+/*
+ * This file implemented a model that an event is consumed
+ * by multiple clients. All clients should wait others before continue
+ * Server should wait for all clients before continue
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_multiclient_free(rdpShadowMultiClientEvent* event);
+
+ WINPR_ATTR_MALLOC(shadow_multiclient_free, 1)
+ rdpShadowMultiClientEvent* shadow_multiclient_new(void);
+
+ void shadow_multiclient_publish(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_wait(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event);
+ void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event);
+ void shadow_multiclient_release_subscriber(void* subscriber);
+ BOOL shadow_multiclient_consume(void* subscriber);
+ HANDLE shadow_multiclient_getevent(void* subscriber);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_MCEVENT_H */
diff --git a/server/shadow/shadow_rdpgfx.c b/server/shadow/shadow_rdpgfx.c
new file mode 100644
index 0000000..16bb236
--- /dev/null
+++ b/server/shadow/shadow_rdpgfx.c
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+#include "shadow.h"
+
+#include "shadow_rdpgfx.h"
+
+#define TAG SERVER_TAG("shadow")
+
+int shadow_client_rdpgfx_init(rdpShadowClient* client)
+{
+ WINPR_ASSERT(client);
+
+ RdpgfxServerContext* rdpgfx = client->rdpgfx = rdpgfx_server_context_new(client->vcm);
+ if (!rdpgfx)
+ {
+ return 0;
+ }
+
+ rdpgfx->rdpcontext = &client->context;
+
+ rdpgfx->custom = client;
+
+ if (!IFCALLRESULT(CHANNEL_RC_OK, rdpgfx->Initialize, rdpgfx, TRUE))
+ return -1;
+
+ return 1;
+}
+
+void shadow_client_rdpgfx_uninit(rdpShadowClient* client)
+{
+ if (client->rdpgfx)
+ {
+ rdpgfx_server_context_free(client->rdpgfx);
+ client->rdpgfx = NULL;
+ }
+}
diff --git a/server/shadow/shadow_rdpgfx.h b/server/shadow/shadow_rdpgfx.h
new file mode 100644
index 0000000..ce81376
--- /dev/null
+++ b/server/shadow/shadow_rdpgfx.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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_SHADOW_RDPGFX_H
+#define FREERDP_SERVER_SHADOW_RDPGFX_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_rdpgfx_init(rdpShadowClient* client);
+ void shadow_client_rdpgfx_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_RDPGFX_H */
diff --git a/server/shadow/shadow_rdpsnd.c b/server/shadow/shadow_rdpsnd.c
new file mode 100644
index 0000000..16899e4
--- /dev/null
+++ b/server/shadow/shadow_rdpsnd.c
@@ -0,0 +1,87 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <freerdp/log.h>
+#include <freerdp/codec/dsp.h>
+#include <freerdp/server/server-common.h>
+
+#include "shadow.h"
+
+#include "shadow_rdpsnd.h"
+
+#define TAG SERVER_TAG("shadow")
+
+static void rdpsnd_activated(RdpsndServerContext* context)
+{
+ for (size_t i = 0; i < context->num_client_formats; i++)
+ {
+ for (size_t j = 0; j < context->num_server_formats; j++)
+ {
+ if (audio_format_compatible(&context->server_formats[j], &context->client_formats[i]))
+ {
+ context->SelectFormat(context, i);
+ return;
+ }
+ }
+ }
+
+ WLog_ERR(TAG, "Could not agree on a audio format with the server\n");
+}
+
+int shadow_client_rdpsnd_init(rdpShadowClient* client)
+{
+ RdpsndServerContext* rdpsnd = NULL;
+ rdpsnd = client->rdpsnd = rdpsnd_server_context_new(client->vcm);
+
+ if (!rdpsnd)
+ {
+ return 0;
+ }
+
+ rdpsnd->data = client;
+
+ if (client->subsystem->rdpsndFormats)
+ {
+ rdpsnd->server_formats = client->subsystem->rdpsndFormats;
+ rdpsnd->num_server_formats = client->subsystem->nRdpsndFormats;
+ }
+ else
+ {
+ rdpsnd->num_server_formats = server_rdpsnd_get_formats(&rdpsnd->server_formats);
+ }
+
+ if (rdpsnd->num_server_formats > 0)
+ rdpsnd->src_format = &rdpsnd->server_formats[0];
+
+ rdpsnd->Activated = rdpsnd_activated;
+ rdpsnd->Initialize(rdpsnd, TRUE);
+ return 1;
+}
+
+void shadow_client_rdpsnd_uninit(rdpShadowClient* client)
+{
+ if (client->rdpsnd)
+ {
+ client->rdpsnd->Stop(client->rdpsnd);
+ rdpsnd_server_context_free(client->rdpsnd);
+ client->rdpsnd = NULL;
+ }
+}
diff --git a/server/shadow/shadow_rdpsnd.h b/server/shadow/shadow_rdpsnd.h
new file mode 100644
index 0000000..ae34ea3
--- /dev/null
+++ b/server/shadow/shadow_rdpsnd.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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_SHADOW_RDPSND_H
+#define FREERDP_SERVER_SHADOW_RDPSND_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_rdpsnd_init(rdpShadowClient* client);
+ void shadow_client_rdpsnd_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_RDPSND_H */
diff --git a/server/shadow/shadow_remdesk.c b/server/shadow/shadow_remdesk.c
new file mode 100644
index 0000000..19fb9f3
--- /dev/null
+++ b/server/shadow/shadow_remdesk.c
@@ -0,0 +1,50 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_remdesk.h"
+
+int shadow_client_remdesk_init(rdpShadowClient* client)
+{
+ RemdeskServerContext* remdesk = NULL;
+
+ remdesk = client->remdesk = remdesk_server_context_new(client->vcm);
+ remdesk->rdpcontext = &client->context;
+
+ remdesk->custom = (void*)client;
+
+ if (client->remdesk)
+ client->remdesk->Start(client->remdesk);
+
+ return 1;
+}
+
+void shadow_client_remdesk_uninit(rdpShadowClient* client)
+{
+ if (client->remdesk)
+ {
+ client->remdesk->Stop(client->remdesk);
+ remdesk_server_context_free(client->remdesk);
+ client->remdesk = NULL;
+ }
+}
diff --git a/server/shadow/shadow_remdesk.h b/server/shadow/shadow_remdesk.h
new file mode 100644
index 0000000..88fdba0
--- /dev/null
+++ b/server/shadow/shadow_remdesk.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_REMDESK_H
+#define FREERDP_SERVER_SHADOW_REMDESK_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ int shadow_client_remdesk_init(rdpShadowClient* client);
+ void shadow_client_remdesk_uninit(rdpShadowClient* client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_REMDESK_H */
diff --git a/server/shadow/shadow_screen.c b/server/shadow/shadow_screen.c
new file mode 100644
index 0000000..8a9e0b3
--- /dev/null
+++ b/server/shadow/shadow_screen.c
@@ -0,0 +1,163 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+
+#include "shadow_surface.h"
+
+#include "shadow_screen.h"
+#include "shadow_lobby.h"
+
+rdpShadowScreen* shadow_screen_new(rdpShadowServer* server)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(server->subsystem);
+
+ rdpShadowScreen* screen = (rdpShadowScreen*)calloc(1, sizeof(rdpShadowScreen));
+
+ if (!screen)
+ goto fail;
+
+ screen->server = server;
+ rdpShadowSubsystem* subsystem = server->subsystem;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(screen->lock), 4000))
+ goto fail;
+
+ region16_init(&(screen->invalidRegion));
+
+ WINPR_ASSERT(subsystem->selectedMonitor < ARRAYSIZE(subsystem->monitors));
+ const MONITOR_DEF* primary = &(subsystem->monitors[subsystem->selectedMonitor]);
+ WINPR_ASSERT(primary);
+
+ INT64 x = primary->left;
+ INT64 y = primary->top;
+ INT64 width = primary->right - primary->left + 1;
+ INT64 height = primary->bottom - primary->top + 1;
+
+ WINPR_ASSERT(x >= 0);
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y >= 0);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+
+ screen->width = (UINT16)width;
+ screen->height = (UINT16)height;
+
+ screen->primary =
+ shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height);
+
+ if (!screen->primary)
+ goto fail;
+
+ server->surface = screen->primary;
+
+ screen->lobby = shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height);
+
+ if (!screen->lobby)
+ goto fail;
+
+ server->lobby = screen->lobby;
+
+ if (!shadow_client_init_lobby(server))
+ goto fail;
+
+ return screen;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ shadow_screen_free(screen);
+ WINPR_PRAGMA_DIAG_POP
+
+ return NULL;
+}
+
+void shadow_screen_free(rdpShadowScreen* screen)
+{
+ if (!screen)
+ return;
+
+ DeleteCriticalSection(&(screen->lock));
+
+ region16_uninit(&(screen->invalidRegion));
+
+ if (screen->primary)
+ {
+ shadow_surface_free(screen->primary);
+ screen->primary = NULL;
+ }
+
+ if (screen->lobby)
+ {
+ shadow_surface_free(screen->lobby);
+ screen->lobby = NULL;
+ }
+
+ free(screen);
+}
+
+BOOL shadow_screen_resize(rdpShadowScreen* screen)
+{
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ int height = 0;
+ MONITOR_DEF* primary = NULL;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ if (!screen)
+ return FALSE;
+
+ subsystem = screen->server->subsystem;
+ primary = &(subsystem->monitors[subsystem->selectedMonitor]);
+
+ x = primary->left;
+ y = primary->top;
+ width = primary->right - primary->left + 1;
+ height = primary->bottom - primary->top + 1;
+
+ WINPR_ASSERT(x >= 0);
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y >= 0);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ WINPR_ASSERT(width >= 0);
+ WINPR_ASSERT(width <= UINT16_MAX);
+ WINPR_ASSERT(height >= 0);
+ WINPR_ASSERT(height <= UINT16_MAX);
+ if (shadow_surface_resize(screen->primary, (UINT16)x, (UINT16)y, (UINT16)width,
+ (UINT16)height) &&
+ shadow_surface_resize(screen->lobby, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height))
+ {
+ if (((UINT32)width != screen->width) || ((UINT32)height != screen->height))
+ {
+ /* screen size is changed. Store new size and reinit lobby */
+ screen->width = (UINT32)width;
+ screen->height = (UINT32)height;
+ shadow_client_init_lobby(screen->server);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/shadow/shadow_screen.h b/server/shadow/shadow_screen.h
new file mode 100644
index 0000000..a7bf789
--- /dev/null
+++ b/server/shadow/shadow_screen.h
@@ -0,0 +1,55 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_SCREEN_H
+#define FREERDP_SERVER_SHADOW_SCREEN_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+struct rdp_shadow_screen
+{
+ rdpShadowServer* server;
+
+ UINT32 width;
+ UINT32 height;
+
+ CRITICAL_SECTION lock;
+ REGION16 invalidRegion;
+
+ rdpShadowSurface* primary;
+ rdpShadowSurface* lobby;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_screen_free(rdpShadowScreen* screen);
+
+ WINPR_ATTR_MALLOC(shadow_screen_free, 1)
+ rdpShadowScreen* shadow_screen_new(rdpShadowServer* server);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SCREEN_H */
diff --git a/server/shadow/shadow_server.c b/server/shadow/shadow_server.c
new file mode 100644
index 0000000..067dcc6
--- /dev/null
+++ b/server/shadow/shadow_server.c
@@ -0,0 +1,1028 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 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 <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/cmdline.h>
+#include <winpr/winsock.h>
+
+#include <freerdp/log.h>
+#include <freerdp/version.h>
+
+#include <winpr/tools/makecert.h>
+
+#ifndef _WIN32
+#include <sys/select.h>
+#include <signal.h>
+#endif
+
+#include "shadow.h"
+
+#define TAG SERVER_TAG("shadow")
+
+static const char bind_address[] = "bind-address,";
+
+static int shadow_server_print_command_line_help(int argc, char** argv,
+ COMMAND_LINE_ARGUMENT_A* largs)
+{
+ char* str = NULL;
+ size_t length = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ if ((argc < 1) || !largs || !argv)
+ return -1;
+
+ printf("Usage: %s [options]\n", argv[0]);
+ printf("\n");
+ printf("Syntax:\n");
+ printf(" /flag (enables flag)\n");
+ printf(" /option:<value> (specifies option with value)\n");
+ printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
+ printf("\n");
+ arg = largs;
+
+ do
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
+ {
+ printf(" %s", "/");
+ printf("%-20s\n", arg->Name);
+ printf("\t%s\n", arg->Text);
+ }
+ else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ printf(" %s", "/");
+
+ if (arg->Format)
+ {
+ length = (strlen(arg->Name) + strlen(arg->Format) + 2);
+ str = (char*)malloc(length + 1);
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s:%s", arg->Name, arg->Format);
+ printf("%-20s\n", str);
+ free(str);
+ }
+ else
+ {
+ printf("%-20s\n", arg->Name);
+ }
+
+ printf("\t%s\n", arg->Text);
+ }
+ else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ length = strlen(arg->Name) + 32;
+ str = (char*)malloc(length + 1);
+
+ if (!str)
+ return -1;
+
+ sprintf_s(str, length + 1, "%s (default:%s)", arg->Name, arg->Default ? "on" : "off");
+ printf(" %s", arg->Default ? "-" : "+");
+ printf("%-20s\n", str);
+ free(str);
+ printf("\t%s\n", arg->Text);
+ }
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ return 1;
+}
+
+int shadow_server_command_line_status_print(rdpShadowServer* server, int argc, char** argv,
+ int status, COMMAND_LINE_ARGUMENT_A* cargs)
+{
+ WINPR_UNUSED(server);
+
+ if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
+ {
+ printf("FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ return COMMAND_LINE_STATUS_PRINT_VERSION;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
+ {
+ printf("%s\n", freerdp_get_build_config());
+ return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT)
+ {
+ return COMMAND_LINE_STATUS_PRINT;
+ }
+ else if (status < 0)
+ {
+ if (shadow_server_print_command_line_help(argc, argv, cargs) < 0)
+ return -1;
+
+ return COMMAND_LINE_STATUS_PRINT_HELP;
+ }
+
+ return 1;
+}
+
+int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv,
+ COMMAND_LINE_ARGUMENT_A* cargs)
+{
+ int status = 0;
+ DWORD flags = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ rdpSettings* settings = server->settings;
+
+ if ((argc < 2) || !argv || !cargs)
+ return 1;
+
+ CommandLineClearArgumentsA(cargs);
+ flags = COMMAND_LINE_SEPARATOR_COLON;
+ flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ status = CommandLineParseArgumentsA(argc, argv, cargs, flags, server, NULL, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = cargs;
+ errno = 0;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "port")
+ {
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
+ return -1;
+
+ server->port = (DWORD)val;
+ }
+ CommandLineSwitchCase(arg, "ipc-socket")
+ {
+ /* /bind-address is incompatible */
+ if (server->ipcSocket)
+ return -1;
+ server->ipcSocket = _strdup(arg->Value);
+
+ if (!server->ipcSocket)
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "bind-address")
+ {
+ int rc = 0;
+ size_t len = strlen(arg->Value) + sizeof(bind_address);
+ /* /ipc-socket is incompatible */
+ if (server->ipcSocket)
+ return -1;
+ server->ipcSocket = calloc(len, sizeof(CHAR));
+
+ if (!server->ipcSocket)
+ return -1;
+
+ rc = _snprintf(server->ipcSocket, len, "%s%s", bind_address, arg->Value);
+ if ((rc < 0) || ((size_t)rc != len - 1))
+ return -1;
+ }
+ CommandLineSwitchCase(arg, "may-view")
+ {
+ server->mayView = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "may-interact")
+ {
+ server->mayInteract = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "max-connections")
+ {
+ errno = 0;
+ unsigned long val = strtoul(arg->Value, NULL, 0);
+
+ if ((errno != 0) || (val > UINT32_MAX))
+ return -1;
+ server->maxClientsConnected = val;
+ }
+ CommandLineSwitchCase(arg, "rect")
+ {
+ char* p = NULL;
+ char* tok[4];
+ long x = -1;
+ long y = -1;
+ long w = -1;
+ long h = -1;
+ char* str = _strdup(arg->Value);
+
+ if (!str)
+ return -1;
+
+ tok[0] = p = str;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[1] = p;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[2] = p;
+ p = strchr(p + 1, ',');
+
+ if (!p)
+ {
+ free(str);
+ return -1;
+ }
+
+ *p++ = '\0';
+ tok[3] = p;
+ x = strtol(tok[0], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ y = strtol(tok[1], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ w = strtol(tok[2], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ h = strtol(tok[3], NULL, 0);
+
+ if (errno != 0)
+ goto fail;
+
+ fail:
+ free(str);
+
+ if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
+ return -1;
+
+ if ((x > UINT16_MAX) || (y > UINT16_MAX) || (x + w > UINT16_MAX) ||
+ (y + h > UINT16_MAX))
+ return -1;
+ server->subRect.left = (UINT16)x;
+ server->subRect.top = (UINT16)y;
+ server->subRect.right = (UINT16)(x + w);
+ server->subRect.bottom = (UINT16)(y + h);
+ server->shareSubRect = TRUE;
+ }
+ CommandLineSwitchCase(arg, "auth")
+ {
+ server->authentication = arg->Value ? TRUE : FALSE;
+ }
+ CommandLineSwitchCase(arg, "remote-guard")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteCredentialGuard,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec")
+ {
+ if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("tls", arg->Value) == 0) /* TLS */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("nla", arg->Value) == 0) /* NLA */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
+ }
+ }
+ CommandLineSwitchCase(arg, "sec-rdp")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-tls")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-nla")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-ext")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sam-file")
+ {
+ freerdp_settings_set_string(settings, FreeRDP_NtlmSamFile, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "log-level")
+ {
+ wLog* root = WLog_GetRoot();
+
+ if (!WLog_SetStringLogLevel(root, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "log-filters")
+ {
+ if (!WLog_AddStringLogFilters(arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-progressive")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-rfx")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-planar")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxPlanar, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-avc420")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-avc444")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2,
+ arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, arg->Value ? TRUE : FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "keytab")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKeytab, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ccache")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosCache, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ arg = CommandLineFindArgumentA(cargs, "monitors");
+
+ if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ {
+ UINT32 numMonitors = 0;
+ MONITOR_DEF monitors[16] = { 0 };
+ numMonitors = shadow_enum_monitors(monitors, 16);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ /* Select monitors */
+ long val = strtol(arg->Value, NULL, 0);
+
+ if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
+ status = COMMAND_LINE_STATUS_PRINT;
+
+ server->selectedMonitor = (UINT32)val;
+ }
+ else
+ {
+ /* List monitors */
+
+ for (UINT32 index = 0; index < numMonitors; index++)
+ {
+ const MONITOR_DEF* monitor = &monitors[index];
+ const INT64 width = monitor->right - monitor->left + 1;
+ const INT64 height = monitor->bottom - monitor->top + 1;
+ WLog_INFO(TAG, " %s [%d] %" PRId64 "x%" PRId64 "\t+%" PRId32 "+%" PRId32 "",
+ (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left,
+ monitor->top);
+ }
+
+ status = COMMAND_LINE_STATUS_PRINT;
+ }
+ }
+
+ /* If we want to disable authentication we need to ensure that NLA security
+ * is not activated. Only TLS and RDP security allow anonymous login.
+ */
+ if (!server->authentication)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ return status;
+}
+
+static DWORD WINAPI shadow_server_thread(LPVOID arg)
+{
+ rdpShadowServer* server = (rdpShadowServer*)arg;
+ BOOL running = TRUE;
+ DWORD status = 0;
+ freerdp_listener* listener = server->listener;
+ shadow_subsystem_start(server->subsystem);
+
+ while (running)
+ {
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ events[nCount++] = server->StopEvent;
+ nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
+
+ if (nCount <= 1)
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ switch (status)
+ {
+ case WAIT_FAILED:
+ case WAIT_OBJECT_0:
+ running = FALSE;
+ break;
+
+ default:
+ {
+ if (!listener->CheckFileDescriptor(listener))
+ {
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+ running = FALSE;
+ }
+ else
+ {
+#ifdef _WIN32
+ Sleep(100); /* FIXME: listener event handles */
+#endif
+ }
+ }
+ break;
+ }
+ }
+
+ listener->Close(listener);
+ shadow_subsystem_stop(server->subsystem);
+
+ /* Signal to the clients that server is being stopped and wait for them
+ * to disconnect. */
+ if (shadow_client_boardcast_quit(server, 0))
+ {
+ while (ArrayList_Count(server->clients) > 0)
+ {
+ Sleep(100);
+ }
+ }
+
+ ExitThread(0);
+ return 0;
+}
+
+static BOOL open_port(rdpShadowServer* server, char* address)
+{
+ BOOL status = 0;
+ char* modaddr = address;
+
+ if (modaddr)
+ {
+ if (modaddr[0] == '[')
+ {
+ char* end = strchr(address, ']');
+ if (!end)
+ {
+ WLog_ERR(TAG, "Could not parse bind-address %s", address);
+ return -1;
+ }
+ *end++ = '\0';
+ if (strlen(end) > 0)
+ {
+ WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end);
+ return -1;
+ }
+ modaddr++;
+ }
+ }
+ status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
+
+ if (!status)
+ {
+ WLog_ERR(TAG,
+ "Problem creating TCP listener. (Port already used or insufficient permissions?)");
+ }
+
+ return status;
+}
+
+int shadow_server_start(rdpShadowServer* server)
+{
+ BOOL ipc = 0;
+ BOOL status = 0;
+ WSADATA wsaData;
+
+ if (!server)
+ return -1;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ return -1;
+
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ server->screen = shadow_screen_new(server);
+
+ if (!server->screen)
+ {
+ WLog_ERR(TAG, "screen_new failed");
+ return -1;
+ }
+
+ server->capture = shadow_capture_new(server);
+
+ if (!server->capture)
+ {
+ WLog_ERR(TAG, "capture_new failed");
+ return -1;
+ }
+
+ /* Bind magic:
+ *
+ * emtpy ... bind TCP all
+ * <local path> ... bind local (IPC)
+ * bind-socket,<address> ... bind TCP to specified interface
+ */
+ ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
+ strnlen(bind_address, sizeof(bind_address))) != 0);
+ if (!ipc)
+ {
+ size_t count = 0;
+ char** list = CommandLineParseCommaSeparatedValuesEx(NULL, server->ipcSocket, &count);
+ if (!list || (count <= 1))
+ {
+ if (server->ipcSocket == NULL)
+ {
+ if (!open_port(server, NULL))
+ {
+ free(list);
+ return -1;
+ }
+ }
+ else
+ {
+ free(list);
+ return -1;
+ }
+ }
+
+ WINPR_ASSERT(list || (count == 0));
+ for (size_t x = 1; x < count; x++)
+ {
+ BOOL success = open_port(server, list[x]);
+ if (!success)
+ {
+ free(list);
+ return -1;
+ }
+ }
+ free(list);
+ }
+ else
+ {
+ status = server->listener->OpenLocal(server->listener, server->ipcSocket);
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or "
+ "insufficient permissions?)");
+ return -1;
+ }
+ }
+
+ if (!(server->thread = CreateThread(NULL, 0, shadow_server_thread, (void*)server, 0, NULL)))
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+int shadow_server_stop(rdpShadowServer* server)
+{
+ if (!server)
+ return -1;
+
+ if (server->thread)
+ {
+ SetEvent(server->StopEvent);
+ WaitForSingleObject(server->thread, INFINITE);
+ CloseHandle(server->thread);
+ server->thread = NULL;
+ if (server->listener && server->listener->Close)
+ server->listener->Close(server->listener);
+ }
+
+ if (server->screen)
+ {
+ shadow_screen_free(server->screen);
+ server->screen = NULL;
+ }
+
+ if (server->capture)
+ {
+ shadow_capture_free(server->capture);
+ server->capture = NULL;
+ }
+
+ return 0;
+}
+
+static int shadow_server_init_config_path(rdpShadowServer* server)
+{
+#ifdef _WIN32
+
+ if (!server->ConfigPath)
+ {
+ server->ConfigPath = GetEnvironmentSubPath("LOCALAPPDATA", "freerdp");
+ }
+
+#endif
+#ifdef __APPLE__
+
+ if (!server->ConfigPath)
+ {
+ char* userLibraryPath;
+ char* userApplicationSupportPath;
+ userLibraryPath = GetKnownSubPath(KNOWN_PATH_HOME, "Library");
+
+ if (userLibraryPath)
+ {
+ if (!winpr_PathFileExists(userLibraryPath) && !winpr_PathMakePath(userLibraryPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", userLibraryPath);
+ free(userLibraryPath);
+ return -1;
+ }
+
+ userApplicationSupportPath = GetCombinedPath(userLibraryPath, "Application Support");
+
+ if (userApplicationSupportPath)
+ {
+ if (!winpr_PathFileExists(userApplicationSupportPath) &&
+ !winpr_PathMakePath(userApplicationSupportPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", userApplicationSupportPath);
+ free(userLibraryPath);
+ free(userApplicationSupportPath);
+ return -1;
+ }
+
+ server->ConfigPath = GetCombinedPath(userApplicationSupportPath, "freerdp");
+ }
+
+ free(userLibraryPath);
+ free(userApplicationSupportPath);
+ }
+ }
+
+#endif
+
+ if (!server->ConfigPath)
+ {
+ char* configHome = NULL;
+ configHome = GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME);
+
+ if (configHome)
+ {
+ if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", configHome);
+ free(configHome);
+ return -1;
+ }
+
+ server->ConfigPath = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, "freerdp");
+ free(configHome);
+ }
+ }
+
+ if (!server->ConfigPath)
+ return -1; /* no usable config path */
+
+ return 1;
+}
+
+static BOOL shadow_server_create_certificate(rdpShadowServer* server, const char* filepath)
+{
+ BOOL rc = FALSE;
+ char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" };
+ const size_t makecert_argc = ARRAYSIZE(makecert_argv);
+
+ MAKECERT_CONTEXT* makecert = makecert_context_new();
+
+ if (!makecert)
+ goto out_fail;
+
+ if (makecert_context_process(makecert, makecert_argc, makecert_argv) < 0)
+ goto out_fail;
+
+ if (makecert_context_set_output_file_name(makecert, "shadow") != 1)
+ goto out_fail;
+
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(filepath);
+ if (!winpr_PathFileExists(server->CertificateFile))
+ {
+ if (makecert_context_output_certificate_file(makecert, filepath) != 1)
+ goto out_fail;
+ }
+
+ if (!winpr_PathFileExists(server->PrivateKeyFile))
+ {
+ if (makecert_context_output_private_key_file(makecert, filepath) != 1)
+ goto out_fail;
+ }
+ rc = TRUE;
+out_fail:
+ makecert_context_free(makecert);
+ return rc;
+}
+static BOOL shadow_server_init_certificate(rdpShadowServer* server)
+{
+ char* filepath = NULL;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(server);
+
+ if (!winpr_PathFileExists(server->ConfigPath) && !winpr_PathMakePath(server->ConfigPath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath);
+ return FALSE;
+ }
+
+ if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow")))
+ return FALSE;
+
+ if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, 0))
+ {
+ if (!CreateDirectoryA(filepath, 0))
+ {
+ WLog_ERR(TAG, "Failed to create directory '%s'", filepath);
+ goto out_fail;
+ }
+ }
+
+ server->CertificateFile = GetCombinedPath(filepath, "shadow.crt");
+ server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key");
+
+ if (!server->CertificateFile || !server->PrivateKeyFile)
+ goto out_fail;
+
+ if ((!winpr_PathFileExists(server->CertificateFile)) ||
+ (!winpr_PathFileExists(server->PrivateKeyFile)))
+ {
+ if (!shadow_server_create_certificate(server, filepath))
+ goto out_fail;
+ }
+
+ rdpSettings* settings = server->settings;
+ WINPR_ASSERT(settings);
+
+ rdpPrivateKey* key = freerdp_key_new_from_file(server->PrivateKeyFile);
+ if (!key)
+ goto out_fail;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ goto out_fail;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
+ if (!cert)
+ goto out_fail;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ goto out_fail;
+
+ if (!freerdp_certificate_is_rdp_security_compatible(cert))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
+ goto out_fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
+ goto out_fail;
+ }
+ ret = TRUE;
+out_fail:
+ free(filepath);
+ return ret;
+}
+
+static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
+{
+ WINPR_ASSERT(listener);
+
+ rdpShadowServer* server = (rdpShadowServer*)listener->info;
+ WINPR_ASSERT(server);
+
+ if (server->maxClientsConnected > 0)
+ {
+ const size_t count = ArrayList_Count(server->clients);
+ if (count >= server->maxClientsConnected)
+ {
+ WLog_WARN(TAG, "connection limit [%" PRIuz "] reached, discarding client",
+ server->maxClientsConnected);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+int shadow_server_init(rdpShadowServer* server)
+{
+ int status = 0;
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+
+ if (!(server->clients = ArrayList_New(TRUE)))
+ goto fail;
+
+ if (!(server->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+
+ if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
+ goto fail;
+
+ status = shadow_server_init_config_path(server);
+
+ if (status < 0)
+ goto fail;
+
+ if (!shadow_server_init_certificate(server))
+ goto fail;
+
+ server->listener = freerdp_listener_new();
+
+ if (!server->listener)
+ goto fail;
+
+ server->listener->info = (void*)server;
+ server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
+ server->listener->PeerAccepted = shadow_client_accepted;
+ server->subsystem = shadow_subsystem_new();
+
+ if (!server->subsystem)
+ goto fail;
+
+ status = shadow_subsystem_init(server->subsystem, server);
+ if (status < 0)
+ goto fail;
+
+ return status;
+
+fail:
+ shadow_server_uninit(server);
+ WLog_ERR(TAG, "Failed to initialize shadow server");
+ return -1;
+}
+
+int shadow_server_uninit(rdpShadowServer* server)
+{
+ if (!server)
+ return -1;
+
+ shadow_server_stop(server);
+ shadow_subsystem_uninit(server->subsystem);
+ shadow_subsystem_free(server->subsystem);
+ server->subsystem = NULL;
+ freerdp_listener_free(server->listener);
+ server->listener = NULL;
+ free(server->CertificateFile);
+ server->CertificateFile = NULL;
+ free(server->PrivateKeyFile);
+ server->PrivateKeyFile = NULL;
+ free(server->ConfigPath);
+ server->ConfigPath = NULL;
+ DeleteCriticalSection(&(server->lock));
+ CloseHandle(server->StopEvent);
+ server->StopEvent = NULL;
+ ArrayList_Free(server->clients);
+ server->clients = NULL;
+ return 1;
+}
+
+rdpShadowServer* shadow_server_new(void)
+{
+ rdpShadowServer* server = NULL;
+ server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer));
+
+ if (!server)
+ return NULL;
+
+ server->port = 3389;
+ server->mayView = TRUE;
+ server->mayInteract = TRUE;
+ server->rfxMode = RLGR3;
+ server->h264RateControlMode = H264_RATECONTROL_VBR;
+ server->h264BitRate = 10000000;
+ server->h264FrameRate = 30;
+ server->h264QP = 0;
+ server->authentication = TRUE;
+ server->settings = freerdp_settings_new(FREERDP_SETTINGS_SERVER_MODE);
+ return server;
+}
+
+void shadow_server_free(rdpShadowServer* server)
+{
+ if (!server)
+ return;
+
+ free(server->ipcSocket);
+ server->ipcSocket = NULL;
+ freerdp_settings_free(server->settings);
+ server->settings = NULL;
+ free(server);
+}
diff --git a/server/shadow/shadow_subsystem.c b/server/shadow/shadow_subsystem.c
new file mode 100644
index 0000000..ca73c72
--- /dev/null
+++ b/server/shadow/shadow_subsystem.c
@@ -0,0 +1,286 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_subsystem.h"
+
+static pfnShadowSubsystemEntry pSubsystemEntry = NULL;
+
+void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry)
+{
+ pSubsystemEntry = pEntry;
+}
+
+static int shadow_subsystem_load_entry_points(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+ ZeroMemory(pEntryPoints, sizeof(RDP_SHADOW_ENTRY_POINTS));
+
+ if (!pSubsystemEntry)
+ return -1;
+
+ if (pSubsystemEntry(pEntryPoints) < 0)
+ return -1;
+
+ return 1;
+}
+
+rdpShadowSubsystem* shadow_subsystem_new(void)
+{
+ RDP_SHADOW_ENTRY_POINTS ep;
+ rdpShadowSubsystem* subsystem = NULL;
+
+ shadow_subsystem_load_entry_points(&ep);
+
+ if (!ep.New)
+ return NULL;
+
+ subsystem = ep.New();
+
+ if (!subsystem)
+ return NULL;
+
+ CopyMemory(&(subsystem->ep), &ep, sizeof(RDP_SHADOW_ENTRY_POINTS));
+
+ return subsystem;
+}
+
+void shadow_subsystem_free(rdpShadowSubsystem* subsystem)
+{
+ if (subsystem && subsystem->ep.Free)
+ subsystem->ep.Free(subsystem);
+}
+
+int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server)
+{
+ int status = -1;
+
+ if (!subsystem || !subsystem->ep.Init)
+ return -1;
+
+ subsystem->server = server;
+ subsystem->selectedMonitor = server->selectedMonitor;
+
+ if (!(subsystem->MsgPipe = MessagePipe_New()))
+ goto fail;
+
+ if (!(subsystem->updateEvent = shadow_multiclient_new()))
+ goto fail;
+
+ if ((status = subsystem->ep.Init(subsystem)) >= 0)
+ return status;
+
+fail:
+ if (subsystem->MsgPipe)
+ {
+ MessagePipe_Free(subsystem->MsgPipe);
+ subsystem->MsgPipe = NULL;
+ }
+
+ if (subsystem->updateEvent)
+ {
+ shadow_multiclient_free(subsystem->updateEvent);
+ subsystem->updateEvent = NULL;
+ }
+
+ return status;
+}
+
+static void shadow_subsystem_free_queued_message(void* obj)
+{
+ wMessage* message = (wMessage*)obj;
+ if (message->Free)
+ {
+ message->Free(message);
+ message->Free = NULL;
+ }
+}
+
+void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
+{
+ if (!subsystem)
+ return;
+
+ if (subsystem->ep.Uninit)
+ subsystem->ep.Uninit(subsystem);
+
+ if (subsystem->MsgPipe)
+ {
+ wObject* obj1 = NULL;
+ wObject* obj2 = NULL;
+ /* Release resource in messages before free */
+ obj1 = MessageQueue_Object(subsystem->MsgPipe->In);
+
+ obj1->fnObjectFree = shadow_subsystem_free_queued_message;
+ MessageQueue_Clear(subsystem->MsgPipe->In);
+
+ obj2 = MessageQueue_Object(subsystem->MsgPipe->Out);
+ obj2->fnObjectFree = shadow_subsystem_free_queued_message;
+ MessageQueue_Clear(subsystem->MsgPipe->Out);
+ MessagePipe_Free(subsystem->MsgPipe);
+ subsystem->MsgPipe = NULL;
+ }
+
+ if (subsystem->updateEvent)
+ {
+ shadow_multiclient_free(subsystem->updateEvent);
+ subsystem->updateEvent = NULL;
+ }
+}
+
+int shadow_subsystem_start(rdpShadowSubsystem* subsystem)
+{
+ int status = 0;
+
+ if (!subsystem || !subsystem->ep.Start)
+ return -1;
+
+ status = subsystem->ep.Start(subsystem);
+
+ return status;
+}
+
+int shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
+{
+ int status = 0;
+
+ if (!subsystem || !subsystem->ep.Stop)
+ return -1;
+
+ status = subsystem->ep.Stop(subsystem);
+
+ return status;
+}
+
+UINT32 shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
+{
+ UINT32 numMonitors = 0;
+ RDP_SHADOW_ENTRY_POINTS ep;
+
+ if (shadow_subsystem_load_entry_points(&ep) < 0)
+ return 0;
+
+ numMonitors = ep.EnumMonitors(monitors, maxMonitors);
+
+ return numMonitors;
+}
+
+/**
+ * Common function for subsystem implementation.
+ * This function convert 32bit ARGB format pixels to xormask data
+ * and andmask data and fill into SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE
+ * Caller should free the andMaskData and xorMaskData later.
+ */
+int shadow_subsystem_pointer_convert_alpha_pointer_data(
+ BYTE* pixels, BOOL premultiplied, UINT32 width, UINT32 height,
+ SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* pointerColor)
+{
+ BYTE* pSrc8 = NULL;
+ BYTE* pDst8 = NULL;
+ UINT32 xorStep = 0;
+ UINT32 andStep = 0;
+ UINT32 andBit = 0;
+ BYTE* andBits = NULL;
+ UINT32 andPixel = 0;
+ BYTE A = 0;
+ BYTE R = 0;
+ BYTE G = 0;
+ BYTE B = 0;
+
+ xorStep = (width * 3);
+ xorStep += (xorStep % 2);
+
+ andStep = ((width + 7) / 8);
+ andStep += (andStep % 2);
+
+ pointerColor->lengthXorMask = height * xorStep;
+ pointerColor->xorMaskData = (BYTE*)calloc(1, pointerColor->lengthXorMask);
+
+ if (!pointerColor->xorMaskData)
+ return -1;
+
+ pointerColor->lengthAndMask = height * andStep;
+ pointerColor->andMaskData = (BYTE*)calloc(1, pointerColor->lengthAndMask);
+
+ if (!pointerColor->andMaskData)
+ {
+ free(pointerColor->xorMaskData);
+ pointerColor->xorMaskData = NULL;
+ return -1;
+ }
+
+ for (UINT32 y = 0; y < height; y++)
+ {
+ pSrc8 = &pixels[(width * 4) * (height - 1 - y)];
+ pDst8 = &(pointerColor->xorMaskData[y * xorStep]);
+
+ andBit = 0x80;
+ andBits = &(pointerColor->andMaskData[andStep * y]);
+
+ for (UINT32 x = 0; x < width; x++)
+ {
+ B = *pSrc8++;
+ G = *pSrc8++;
+ R = *pSrc8++;
+ A = *pSrc8++;
+
+ andPixel = 0;
+
+ if (A < 64)
+ A = 0; /* pixel cannot be partially transparent */
+
+ if (!A)
+ {
+ /* transparent pixel: XOR = black, AND = 1 */
+ andPixel = 1;
+ B = G = R = 0;
+ }
+ else
+ {
+ if (premultiplied)
+ {
+ B = (B * 0xFF) / A;
+ G = (G * 0xFF) / A;
+ R = (R * 0xFF) / A;
+ }
+ }
+
+ *pDst8++ = B;
+ *pDst8++ = G;
+ *pDst8++ = R;
+
+ if (andPixel)
+ *andBits |= andBit;
+ if (!(andBit >>= 1))
+ {
+ andBits++;
+ andBit = 0x80;
+ }
+ }
+ }
+
+ return 1;
+}
+
+void shadow_subsystem_frame_update(rdpShadowSubsystem* subsystem)
+{
+ shadow_multiclient_publish_and_wait(subsystem->updateEvent);
+}
diff --git a/server/shadow/shadow_subsystem.h b/server/shadow/shadow_subsystem.h
new file mode 100644
index 0000000..206dfdd
--- /dev/null
+++ b/server/shadow/shadow_subsystem.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_SUBSYSTEM_H
+#define FREERDP_SERVER_SHADOW_SUBSYSTEM_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_subsystem_free(rdpShadowSubsystem* subsystem);
+
+ WINPR_ATTR_MALLOC(shadow_subsystem_free, 1)
+ rdpShadowSubsystem* shadow_subsystem_new(void);
+
+ int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server);
+ void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem);
+
+ int shadow_subsystem_start(rdpShadowSubsystem* subsystem);
+ int shadow_subsystem_stop(rdpShadowSubsystem* subsystem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SUBSYSTEM_H */
diff --git a/server/shadow/shadow_subsystem_builtin.c b/server/shadow/shadow_subsystem_builtin.c
new file mode 100644
index 0000000..627458e
--- /dev/null
+++ b/server/shadow/shadow_subsystem_builtin.c
@@ -0,0 +1,75 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/server/shadow.h>
+
+typedef struct
+{
+ const char* (*name)(void);
+ pfnShadowSubsystemEntry entry;
+} RDP_SHADOW_SUBSYSTEM;
+
+extern int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints);
+extern const char* ShadowSubsystemName(void);
+
+static RDP_SHADOW_SUBSYSTEM g_Subsystems[] = {
+
+ { ShadowSubsystemName, ShadowSubsystemEntry }
+};
+
+static size_t g_SubsystemCount = ARRAYSIZE(g_Subsystems);
+
+static pfnShadowSubsystemEntry shadow_subsystem_load_static_entry(const char* name)
+{
+ if (!name)
+ {
+ if (g_SubsystemCount > 0)
+ {
+ const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[0];
+ WINPR_ASSERT(cur->entry);
+
+ return cur->entry;
+ }
+
+ return NULL;
+ }
+
+ for (size_t index = 0; index < g_SubsystemCount; index++)
+ {
+ const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[index];
+ WINPR_ASSERT(cur->name);
+ WINPR_ASSERT(cur->entry);
+
+ if (strcmp(name, cur->name()) == 0)
+ return cur->entry;
+ }
+
+ return NULL;
+}
+
+void shadow_subsystem_set_entry_builtin(const char* name)
+{
+ pfnShadowSubsystemEntry entry = shadow_subsystem_load_static_entry(name);
+
+ if (entry)
+ shadow_subsystem_set_entry(entry);
+
+ return;
+}
diff --git a/server/shadow/shadow_surface.c b/server/shadow/shadow_surface.c
new file mode 100644
index 0000000..16aecce
--- /dev/null
+++ b/server/shadow/shadow_surface.c
@@ -0,0 +1,104 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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.
+ */
+
+#include <freerdp/config.h>
+
+#include "shadow.h"
+
+#include "shadow_surface.h"
+#define ALIGN_SCREEN_SIZE(size, align) \
+ ((((size) % (align)) != 0) ? ((size) + (align) - ((size) % (align))) : (size))
+
+rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height)
+{
+ rdpShadowSurface* surface = NULL;
+ surface = (rdpShadowSurface*)calloc(1, sizeof(rdpShadowSurface));
+
+ if (!surface)
+ return NULL;
+
+ surface->server = server;
+ surface->x = x;
+ surface->y = y;
+ surface->width = width;
+ surface->height = height;
+ surface->scanline = ALIGN_SCREEN_SIZE(surface->width, 32) * 4;
+ surface->format = PIXEL_FORMAT_BGRX32;
+ surface->data = (BYTE*)calloc(ALIGN_SCREEN_SIZE(surface->height, 32), surface->scanline);
+
+ if (!surface->data)
+ {
+ free(surface);
+ return NULL;
+ }
+
+ if (!InitializeCriticalSectionAndSpinCount(&(surface->lock), 4000))
+ {
+ free(surface->data);
+ free(surface);
+ return NULL;
+ }
+
+ region16_init(&(surface->invalidRegion));
+ return surface;
+}
+
+void shadow_surface_free(rdpShadowSurface* surface)
+{
+ if (!surface)
+ return;
+
+ free(surface->data);
+ DeleteCriticalSection(&(surface->lock));
+ region16_uninit(&(surface->invalidRegion));
+ free(surface);
+}
+
+BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height)
+{
+ BYTE* buffer = NULL;
+ UINT32 scanline = ALIGN_SCREEN_SIZE(width, 4) * 4;
+
+ if (!surface)
+ return FALSE;
+
+ if ((width == surface->width) && (height == surface->height))
+ {
+ /* We don't need to reset frame buffer, just update left top */
+ surface->x = x;
+ surface->y = y;
+ return TRUE;
+ }
+
+ buffer = (BYTE*)realloc(surface->data, 1ull * scanline * ALIGN_SCREEN_SIZE(height, 4ull));
+
+ if (buffer)
+ {
+ surface->x = x;
+ surface->y = y;
+ surface->width = width;
+ surface->height = height;
+ surface->scanline = scanline;
+ surface->data = buffer;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/server/shadow/shadow_surface.h b/server/shadow/shadow_surface.h
new file mode 100644
index 0000000..277df0a
--- /dev/null
+++ b/server/shadow/shadow_surface.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@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_SHADOW_SURFACE_H
+#define FREERDP_SERVER_SHADOW_SURFACE_H
+
+#include <freerdp/server/shadow.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void shadow_surface_free(rdpShadowSurface* surface);
+
+ WINPR_ATTR_MALLOC(shadow_surface_free, 1)
+ rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height);
+
+ BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width,
+ UINT32 height);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_SHADOW_SURFACE_H */