summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--client/CMakeLists.txt113
-rw-r--r--client/FreeRDP-ClientConfig.cmake.in13
-rw-r--r--client/SDL/CMakeLists.txt128
-rw-r--r--client/SDL/aad/CMakeLists.txt75
-rw-r--r--client/SDL/aad/dummy.cpp0
-rw-r--r--client/SDL/aad/qt/webview_impl.cpp105
-rw-r--r--client/SDL/aad/sdl_config.hpp.in3
-rw-r--r--client/SDL/aad/sdl_webview.cpp129
-rw-r--r--client/SDL/aad/sdl_webview.hpp38
-rw-r--r--client/SDL/aad/webview_impl.hpp24
-rw-r--r--client/SDL/aad/wrapper/README1
-rw-r--r--client/SDL/aad/wrapper/webview.h2781
-rw-r--r--client/SDL/aad/wrapper/webview_impl.cpp82
-rw-r--r--client/SDL/dialogs/CMakeLists.txt75
-rw-r--r--client/SDL/dialogs/font/OFL.txt93
-rw-r--r--client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttfbin0 -> 580356 bytes
-rw-r--r--client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttfbin0 -> 529700 bytes
-rw-r--r--client/SDL/dialogs/font/README.txt100
-rw-r--r--client/SDL/dialogs/res/CMakeLists.txt89
-rw-r--r--client/SDL/dialogs/res/convert_res_to_c.cpp184
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.cpp25
-rw-r--r--client/SDL/dialogs/res/sdl_resource_file.hpp33
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.cpp78
-rw-r--r--client/SDL/dialogs/res/sdl_resource_manager.hpp46
-rw-r--r--client/SDL/dialogs/sdl_button.cpp71
-rw-r--r--client/SDL/dialogs/sdl_button.hpp26
-rw-r--r--client/SDL/dialogs/sdl_buttons.cpp105
-rw-r--r--client/SDL/dialogs/sdl_buttons.hpp37
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.cpp536
-rw-r--r--client/SDL/dialogs/sdl_connection_dialog.hpp129
-rw-r--r--client/SDL/dialogs/sdl_dialogs.cpp621
-rw-r--r--client/SDL/dialogs/sdl_dialogs.hpp53
-rw-r--r--client/SDL/dialogs/sdl_input.cpp177
-rw-r--r--client/SDL/dialogs/sdl_input.hpp73
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.cpp299
-rw-r--r--client/SDL/dialogs/sdl_input_widgets.hpp44
-rw-r--r--client/SDL/dialogs/sdl_select.cpp74
-rw-r--r--client/SDL/dialogs/sdl_select.hpp46
-rw-r--r--client/SDL/dialogs/sdl_selectlist.cpp208
-rw-r--r--client/SDL/dialogs/sdl_selectlist.hpp42
-rw-r--r--client/SDL/dialogs/sdl_widget.cpp280
-rw-r--r--client/SDL/dialogs/sdl_widget.hpp88
-rw-r--r--client/SDL/dialogs/test/CMakeLists.txt30
-rw-r--r--client/SDL/dialogs/test/TestSDLDialogs.cpp99
-rw-r--r--client/SDL/man/CMakeLists.txt12
-rw-r--r--client/SDL/man/sdl-freerdp-config.1.xml.in81
-rw-r--r--client/SDL/man/sdl-freerdp-envvar.1.xml.in15
-rw-r--r--client/SDL/man/sdl-freerdp-examples.1.xml.in95
-rw-r--r--client/SDL/man/sdl-freerdp.1.xml.in67
-rw-r--r--client/SDL/sdl_channels.cpp83
-rw-r--r--client/SDL/sdl_channels.hpp29
-rw-r--r--client/SDL/sdl_disp.cpp471
-rw-r--r--client/SDL/sdl_disp.hpp82
-rw-r--r--client/SDL/sdl_freerdp.cpp1704
-rw-r--r--client/SDL/sdl_freerdp.hpp88
-rw-r--r--client/SDL/sdl_kbd.cpp568
-rw-r--r--client/SDL/sdl_kbd.hpp56
-rw-r--r--client/SDL/sdl_monitor.cpp331
-rw-r--r--client/SDL/sdl_monitor.hpp28
-rw-r--r--client/SDL/sdl_pointer.cpp197
-rw-r--r--client/SDL/sdl_pointer.hpp27
-rw-r--r--client/SDL/sdl_touch.cpp285
-rw-r--r--client/SDL/sdl_touch.hpp36
-rw-r--r--client/SDL/sdl_types.hpp46
-rw-r--r--client/SDL/sdl_utils.cpp465
-rw-r--r--client/SDL/sdl_utils.hpp113
-rw-r--r--client/SDL/sdl_window.cpp203
-rw-r--r--client/SDL/sdl_window.hpp62
-rw-r--r--client/Sample/CMakeLists.txt77
-rw-r--r--client/Sample/ModuleOptions.cmake4
-rw-r--r--client/Sample/tf_channels.c79
-rw-r--r--client/Sample/tf_channels.h33
-rw-r--r--client/Sample/tf_freerdp.c411
-rw-r--r--client/Sample/tf_freerdp.h37
-rw-r--r--client/Wayland/CMakeLists.txt62
-rw-r--r--client/Wayland/wlf_channels.c79
-rw-r--r--client/Wayland/wlf_channels.h37
-rw-r--r--client/Wayland/wlf_cliprdr.c1009
-rw-r--r--client/Wayland/wlf_cliprdr.h36
-rw-r--r--client/Wayland/wlf_disp.c455
-rw-r--r--client/Wayland/wlf_disp.h38
-rw-r--r--client/Wayland/wlf_input.c458
-rw-r--r--client/Wayland/wlf_input.h45
-rw-r--r--client/Wayland/wlf_pointer.c167
-rw-r--r--client/Wayland/wlf_pointer.h28
-rw-r--r--client/Wayland/wlfreerdp.1.in38
-rw-r--r--client/Wayland/wlfreerdp.c807
-rw-r--r--client/Wayland/wlfreerdp.h59
-rw-r--r--client/X11/CMakeLists.txt245
-rw-r--r--client/X11/ModuleOptions.cmake4
-rw-r--r--client/X11/cli/CMakeLists.txt49
-rw-r--r--client/X11/cli/xfreerdp.c86
-rw-r--r--client/X11/man/CMakeLists.txt11
-rw-r--r--client/X11/man/xfreerdp-channels.1.xml0
-rw-r--r--client/X11/man/xfreerdp-envvar.1.xml15
-rw-r--r--client/X11/man/xfreerdp-examples.1.xml95
-rw-r--r--client/X11/man/xfreerdp.1.xml.in64
-rw-r--r--client/X11/resource/close.xbm11
-rw-r--r--client/X11/resource/lock.xbm11
-rw-r--r--client/X11/resource/minimize.xbm11
-rw-r--r--client/X11/resource/restore.xbm11
-rw-r--r--client/X11/resource/unlock.xbm11
-rw-r--r--client/X11/xf_channels.c132
-rw-r--r--client/X11/xf_channels.h37
-rw-r--r--client/X11/xf_client.c2000
-rw-r--r--client/X11/xf_client.h53
-rw-r--r--client/X11/xf_cliprdr.c2517
-rw-r--r--client/X11/xf_cliprdr.h38
-rw-r--r--client/X11/xf_disp.c550
-rw-r--r--client/X11/xf_disp.h40
-rw-r--r--client/X11/xf_event.c1314
-rw-r--r--client/X11/xf_event.h46
-rw-r--r--client/X11/xf_floatbar.c933
-rw-r--r--client/X11/xf_floatbar.h37
-rw-r--r--client/X11/xf_gfx.c476
-rw-r--r--client/X11/xf_gfx.h45
-rw-r--r--client/X11/xf_graphics.c532
-rw-r--r--client/X11/xf_graphics.h33
-rw-r--r--client/X11/xf_input.c946
-rw-r--r--client/X11/xf_input.h33
-rw-r--r--client/X11/xf_keyboard.c746
-rw-r--r--client/X11/xf_keyboard.h65
-rw-r--r--client/X11/xf_monitor.c654
-rw-r--r--client/X11/xf_monitor.h48
-rw-r--r--client/X11/xf_rail.c1184
-rw-r--r--client/X11/xf_rail.h47
-rw-r--r--client/X11/xf_tsmf.c471
-rw-r--r--client/X11/xf_tsmf.h29
-rw-r--r--client/X11/xf_utils.c123
-rw-r--r--client/X11/xf_utils.h78
-rw-r--r--client/X11/xf_video.c132
-rw-r--r--client/X11/xf_video.h35
-rw-r--r--client/X11/xf_window.c1323
-rw-r--r--client/X11/xf_window.h207
-rw-r--r--client/X11/xfreerdp.h389
-rw-r--r--client/common/CMakeLists.txt110
-rw-r--r--client/common/client.c2156
-rw-r--r--client/common/client_cliprdr_file.c2556
-rw-r--r--client/common/cmdline.c5922
-rw-r--r--client/common/cmdline.h519
-rw-r--r--client/common/file.c2707
-rw-r--r--client/common/geometry.c43
-rw-r--r--client/common/man/CMakeLists.txt3
-rw-r--r--client/common/man/generate_argument_docbook.c210
-rw-r--r--client/common/smartcard_cli.c60
-rw-r--r--client/common/test/CMakeLists.txt30
-rw-r--r--client/common/test/TestClientChannels.c87
-rw-r--r--client/common/test/TestClientCmdLine.c263
-rw-r--r--client/common/test/TestClientRdpFile.c600
-rw-r--r--client/freerdp-client.pc.in15
150 files changed, 47554 insertions, 0 deletions
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
new file mode 100644
index 0000000..fcb0d40
--- /dev/null
+++ b/client/CMakeLists.txt
@@ -0,0 +1,113 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Clients
+#
+# 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.
+
+# Clients
+
+if(WITH_CLIENT_COMMON)
+ add_subdirectory(common)
+endif()
+
+if(FREERDP_VENDOR AND WITH_CLIENT)
+ if(WIN32 AND NOT UWP)
+ add_subdirectory(Windows)
+ else()
+ if(WITH_SAMPLE)
+ add_subdirectory(Sample)
+ endif()
+ endif()
+
+ if(WITH_CLIENT_SDL)
+ find_package(SDL2)
+ if (SDL2_FOUND)
+ add_subdirectory(SDL)
+ else()
+ message(WARNING "SDL2 requested but not found, continuing build without SDL client")
+ endif()
+ endif()
+
+ if(WITH_X11)
+ add_subdirectory(X11)
+ endif()
+
+ if(WITH_WAYLAND AND WAYLAND_FOUND)
+ add_subdirectory(Wayland)
+ endif()
+
+ if(APPLE)
+ if(IOS)
+ if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/iOS")
+ message(STATUS "Adding iOS client")
+ add_subdirectory(iOS)
+ endif()
+ else()
+ option(WITH_CLIENT_MAC "Build native mac client" ON)
+ if (WITH_CLIENT_MAC)
+ add_subdirectory(Mac)
+ endif()
+ endif()
+ endif()
+
+ if(ANDROID)
+ message(STATUS "Android client module is built with Android Studio project")
+ endif()
+endif()
+
+# Pick up other clients
+if(WITH_CLIENT)
+ 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_CLIENT ${FILEPATH})
+ set(FREERDP_CLIENT_ENABLED 0)
+ include(${FILEPATH})
+ if(FREERDP_CLIENT_ENABLED)
+ if(NOT (${FREERDP_CLIENT_VENDOR} MATCHES "FreeRDP"))
+ list(APPEND FREERDP_EXTRA_CLIENTS ${FREERDP_CLIENT})
+ if(${FREERDP_CLIENT_VENDOR} MATCHES "${VENDOR}")
+ set(CLIENT_VENDOR_PATH "client/${FREERDP_CLIENT}" PARENT_SCOPE)
+ endif()
+ endif()
+ endif()
+ endif()
+ endforeach()
+
+ foreach(FREERDP_CLIENT ${FREERDP_EXTRA_CLIENTS})
+ add_subdirectory(${FREERDP_CLIENT})
+ endforeach()
+endif()
+
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-client.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-client${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-client${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-client)
+
+SetFreeRDPCMakeInstallDir(FREERDP_CLIENT_CMAKE_INSTALL_DIR "FreeRDP-Client${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ClientConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ClientConfigVersion.cmake
+ DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR})
+
+install(EXPORT FreeRDP-ClientTargets DESTINATION ${FREERDP_CLIENT_CMAKE_INSTALL_DIR})
diff --git a/client/FreeRDP-ClientConfig.cmake.in b/client/FreeRDP-ClientConfig.cmake.in
new file mode 100644
index 0000000..35b74c1
--- /dev/null
+++ b/client/FreeRDP-ClientConfig.cmake.in
@@ -0,0 +1,13 @@
+include(CMakeFindDependencyMacro)
+find_dependency(WinPR @FREERDP_VERSION@)
+find_dependency(FreeRDP @FREERDP_VERSION@)
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Client_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Client_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Client_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Client_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ClientTargets.cmake")
diff --git a/client/SDL/CMakeLists.txt b/client/SDL/CMakeLists.txt
new file mode 100644
index 0000000..6d2b778
--- /dev/null
+++ b/client/SDL/CMakeLists.txt
@@ -0,0 +1,128 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP SDL Client
+#
+# Copyright 2022 Armin Novak <anovak@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.
+
+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(sdl-freerdp
+ LANGUAGES CXX
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+option(WITH_DEBUG_SDL_EVENTS "[dangerous, not for release builds!] Debug SDL events" ${DEFAULT_DEBUG_OPTION})
+option(WITH_DEBUG_SDL_KBD_EVENTS "[dangerous, not for release builds!] Debug SDL keyboard events" ${DEFAULT_DEBUG_OPTION})
+option(WITH_WIN_CONSOLE "Build ${PROJECT_NAME} with console support" ON)
+option(WITH_SDL_LINK_SHARED "link SDL dynamic or static" ON)
+
+if(WITH_WIN_CONSOLE)
+ set(WIN32_GUI_FLAG "")
+else()
+ set(WIN32_GUI_FLAG "WIN32")
+endif()
+
+
+if (WITH_DEBUG_SDL_EVENTS)
+ add_definitions(-DWITH_DEBUG_SDL_EVENTS)
+endif()
+if (WITH_DEBUG_SDL_KBD_EVENTS)
+ add_definitions(-DWITH_DEBUG_SDL_KBD_EVENTS)
+endif()
+
+find_package(SDL2 REQUIRED COMPONENTS)
+include_directories(${SDL2_INCLUDE_DIR})
+include_directories(${SDL2_INCLUDE_DIRS})
+find_package(cJSON)
+
+set(LIBS "")
+if (cJSON_FOUND)
+ include_directories(${CJSON_INCLUDE_DIRS})
+ list(APPEND LIBS ${CJSON_LIBRARIES})
+ add_compile_definitions(CJSON_FOUND)
+endif()
+
+find_package(Threads REQUIRED)
+
+add_subdirectory(dialogs)
+set(SRCS
+ sdl_types.hpp
+ sdl_utils.cpp
+ sdl_utils.hpp
+ sdl_kbd.cpp
+ sdl_kbd.hpp
+ sdl_touch.cpp
+ sdl_touch.hpp
+ sdl_pointer.cpp
+ sdl_pointer.hpp
+ sdl_disp.cpp
+ sdl_disp.hpp
+ sdl_monitor.cpp
+ sdl_monitor.hpp
+ sdl_freerdp.hpp
+ sdl_freerdp.cpp
+ sdl_channels.hpp
+ sdl_channels.cpp
+ sdl_window.hpp
+ sdl_window.cpp
+)
+
+add_subdirectory(aad)
+list(APPEND LIBS
+ winpr
+ freerdp
+ freerdp-client
+ Threads::Threads
+ sdl_client_res
+ dialogs
+ aad-view
+ )
+
+if (NOT WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
+else()
+ list(APPEND LIBS ${SDL2_LIBRARIES})
+endif()
+
+add_executable(${PROJECT_NAME}
+ ${WIN32_GUI_FLAG}
+ ${SRCS}
+ )
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+endif()
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/SDL")
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+add_subdirectory(man)
diff --git a/client/SDL/aad/CMakeLists.txt b/client/SDL/aad/CMakeLists.txt
new file mode 100644
index 0000000..2286542
--- /dev/null
+++ b/client/SDL/aad/CMakeLists.txt
@@ -0,0 +1,75 @@
+set(WITH_WEBVIEW_DEFAULT OFF)
+if (UNIX AND NOT APPLE)
+ set(WITH_WEBVIEW_DEFAULT ON)
+endif()
+
+option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" ${WITH_WEBVIEW_DEFAULT})
+if (WITH_WEBVIEW)
+ option(WITH_WEBVIEW_QT "Build with QtWebEngine support for AAD login broweser popup" OFF)
+
+ set(SRCS
+ sdl_webview.hpp
+ webview_impl.hpp
+ sdl_webview.cpp
+ )
+ set(LIBS
+ winpr
+ )
+
+ if (WITH_WEBVIEW_QT)
+ find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED)
+
+ list(APPEND SRCS
+ qt/webview_impl.cpp
+ )
+
+ list(APPEND LIBS
+ Qt5::WebEngineWidgets
+ )
+ else()
+ list(APPEND SRCS
+ wrapper/webview.h
+ wrapper/webview_impl.cpp
+ )
+
+ if (WIN32)
+ find_package(unofficial-webview2 CONFIG REQUIRED)
+ list(APPEND LIBS
+ unofficial::webview2::webview2
+ )
+ elseif(APPLE)
+ find_library(WEBKIT Webkit REQUIRED)
+ list(APPEND LIBS
+ ${WEBKIT}
+ )
+ else()
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.0 REQUIRED)
+ include_directories(${WEBVIEW_GTK_INCLUDE_DIRS})
+ list(APPEND LIBS
+ ${WEBVIEW_GTK_LIBRARIES}
+ )
+ endif()
+ endif()
+else()
+ set(SRCS
+ dummy.cpp
+ )
+endif()
+
+configure_file(sdl_config.hpp.in sdl_config.hpp @ONLY)
+
+add_library(aad-view STATIC
+ ${SRCS}
+)
+target_include_directories(aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
+target_link_libraries(aad-view
+ PRIVATE
+ ${LIBS}
+)
+target_compile_definitions(
+ aad-view
+ PUBLIC
+ ${DEFINITIONS}
+)
+
diff --git a/client/SDL/aad/dummy.cpp b/client/SDL/aad/dummy.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/SDL/aad/dummy.cpp
diff --git a/client/SDL/aad/qt/webview_impl.cpp b/client/SDL/aad/qt/webview_impl.cpp
new file mode 100644
index 0000000..e70cc46
--- /dev/null
+++ b/client/SDL/aad/qt/webview_impl.cpp
@@ -0,0 +1,105 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <QApplication>
+#include <QWebEngineView>
+#include <QWebEngineProfile>
+#include <QWebEngineUrlScheme>
+#include <QWebEngineUrlSchemeHandler>
+#include <QWebEngineUrlRequestJob>
+
+#include <string>
+#include <cstdlib>
+#include <cstdarg>
+#include <winpr/string.h>
+#include <winpr/assert.h>
+#include <freerdp/log.h>
+#include <freerdp/build-config.h>
+
+#include "../webview_impl.hpp"
+
+#define TAG CLIENT_TAG("sdl.webview")
+
+class SchemeHandler : public QWebEngineUrlSchemeHandler
+{
+ public:
+ explicit SchemeHandler(QObject* parent = nullptr) : QWebEngineUrlSchemeHandler(parent)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob* request) override
+ {
+ QUrl url = request->requestUrl();
+
+ int rc = -1;
+ for (auto& param : url.query().split('&'))
+ {
+ QStringList pair = param.split('=');
+
+ if (pair.size() != 2 || pair[0] != QLatin1String("code"))
+ continue;
+
+ auto qc = pair[1];
+ m_code = qc.toStdString();
+ rc = 0;
+ break;
+ }
+ qApp->exit(rc);
+ }
+
+ [[nodiscard]] std::string code() const
+ {
+ return m_code;
+ }
+
+ private:
+ std::string m_code{};
+};
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
+{
+ int argc = 1;
+ const auto vendor = QString::fromUtf8(FREERDP_VENDOR_STRING);
+ const auto product = QString::fromUtf8(FREERDP_PRODUCT_STRING);
+ QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
+
+ std::string wtitle = title;
+ char* argv[] = { wtitle.data() };
+ QCoreApplication::setOrganizationName(vendor);
+ QCoreApplication::setApplicationName(product);
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QApplication app(argc, argv);
+
+ SchemeHandler handler;
+ QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler);
+
+ QWebEngineView webview;
+ webview.load(QUrl(QString::fromStdString(url)));
+ webview.show();
+
+ if (app.exec() != 0)
+ return false;
+
+ auto val = handler.code();
+ if (val.empty())
+ return false;
+ code = val;
+
+ return !code.empty();
+}
diff --git a/client/SDL/aad/sdl_config.hpp.in b/client/SDL/aad/sdl_config.hpp.in
new file mode 100644
index 0000000..34d0751
--- /dev/null
+++ b/client/SDL/aad/sdl_config.hpp.in
@@ -0,0 +1,3 @@
+#pragma once
+
+#cmakedefine WITH_WEBVIEW
diff --git a/client/SDL/aad/sdl_webview.cpp b/client/SDL/aad/sdl_webview.cpp
new file mode 100644
index 0000000..b4df75b
--- /dev/null
+++ b/client/SDL/aad/sdl_webview.cpp
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 <string>
+#include <sstream>
+#include <cstdlib>
+#include <winpr/string.h>
+#include <freerdp/log.h>
+
+#include "sdl_webview.hpp"
+#include "webview_impl.hpp"
+
+#define TAG CLIENT_TAG("SDL.webview")
+
+static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope,
+ const char* req_cnf, char** token)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(scope);
+ WINPR_ASSERT(req_cnf);
+ WINPR_ASSERT(token);
+
+ WINPR_UNUSED(instance);
+
+ std::string client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
+ std::string redirect_uri =
+ "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
+
+ *token = nullptr;
+
+ auto url =
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
+ "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
+
+ const std::string title = "FreeRDP WebView - AAD access token";
+ std::string code;
+ auto rc = webview_impl_run(title, url, code);
+ if (!rc || code.empty())
+ return FALSE;
+
+ auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
+ "&scope=" + scope + "&redirect_uri=" + redirect_uri +
+ "&req_cnf=" + req_cnf;
+ return client_common_get_access_token(instance, token_request.c_str(), token);
+}
+
+static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
+{
+ WINPR_ASSERT(token);
+
+ std::string client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ std::string redirect_uri =
+ "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2fa85cf173-4192-42f8-81fa-777a763e6e2c";
+ std::string scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
+
+ *token = nullptr;
+
+ auto url =
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
+ "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri;
+ const std::string title = "FreeRDP WebView - AVD access token";
+ std::string code;
+ auto rc = webview_impl_run(title, url, code);
+ if (!rc || code.empty())
+ return FALSE;
+
+ auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
+ "&scope=" + scope + "&redirect_uri=" + redirect_uri;
+ return client_common_get_access_token(instance, token_request.c_str(), token);
+}
+
+BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+ switch (tokenType)
+ {
+ case ACCESS_TOKEN_TYPE_AAD:
+ {
+ if (count < 2)
+ {
+ WLog_ERR(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", aborting",
+ count);
+ return FALSE;
+ }
+ else if (count > 2)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ va_list ap;
+ va_start(ap, count);
+ const char* scope = va_arg(ap, const char*);
+ const char* req_cnf = va_arg(ap, const char*);
+ const BOOL rc = sdl_webview_get_rdsaad_access_token(instance, scope, req_cnf, token);
+ va_end(ap);
+ return rc;
+ }
+ case ACCESS_TOKEN_TYPE_AVD:
+ if (count != 0)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ return sdl_webview_get_avd_access_token(instance, token);
+ default:
+ WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
+ return FALSE;
+ }
+}
diff --git a/client/SDL/aad/sdl_webview.hpp b/client/SDL/aad/sdl_webview.hpp
new file mode 100644
index 0000000..49461d6
--- /dev/null
+++ b/client/SDL/aad/sdl_webview.hpp
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+
+#include <sdl_config.hpp>
+
+#if defined(WITH_WEBVIEW)
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/client/SDL/aad/webview_impl.hpp b/client/SDL/aad/webview_impl.hpp
new file mode 100644
index 0000000..25bca3c
--- /dev/null
+++ b/client/SDL/aad/webview_impl.hpp
@@ -0,0 +1,24 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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.
+ */
+
+#pragma once
+
+#include <string>
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code);
diff --git a/client/SDL/aad/wrapper/README b/client/SDL/aad/wrapper/README
new file mode 100644
index 0000000..da906ba
--- /dev/null
+++ b/client/SDL/aad/wrapper/README
@@ -0,0 +1 @@
+upstream at https://github.com/webview/webview/
diff --git a/client/SDL/aad/wrapper/webview.h b/client/SDL/aad/wrapper/webview.h
new file mode 100644
index 0000000..4919265
--- /dev/null
+++ b/client/SDL/aad/wrapper/webview.h
@@ -0,0 +1,2781 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017 Serge Zaitsev
+ * Copyright (c) 2022 Steffen André Langnes
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef WEBVIEW_H
+#define WEBVIEW_H
+
+#ifndef WEBVIEW_API
+#define WEBVIEW_API extern
+#endif
+
+#ifndef WEBVIEW_VERSION_MAJOR
+// The current library major version.
+#define WEBVIEW_VERSION_MAJOR 0
+#endif
+
+#ifndef WEBVIEW_VERSION_MINOR
+// The current library minor version.
+#define WEBVIEW_VERSION_MINOR 10
+#endif
+
+#ifndef WEBVIEW_VERSION_PATCH
+// The current library patch version.
+#define WEBVIEW_VERSION_PATCH 0
+#endif
+
+#ifndef WEBVIEW_VERSION_PRE_RELEASE
+// SemVer 2.0.0 pre-release labels prefixed with "-".
+#define WEBVIEW_VERSION_PRE_RELEASE ""
+#endif
+
+#ifndef WEBVIEW_VERSION_BUILD_METADATA
+// SemVer 2.0.0 build metadata prefixed with "+".
+#define WEBVIEW_VERSION_BUILD_METADATA ""
+#endif
+
+// Utility macro for stringifying a macro argument.
+#define WEBVIEW_STRINGIFY(x) #x
+
+// Utility macro for stringifying the result of a macro argument expansion.
+#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x)
+
+// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
+#define WEBVIEW_VERSION_NUMBER \
+ WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \
+ "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY( \
+ WEBVIEW_VERSION_PATCH)
+
+// Holds the elements of a MAJOR.MINOR.PATCH version number.
+typedef struct
+{
+ // Major version.
+ unsigned int major;
+ // Minor version.
+ unsigned int minor;
+ // Patch version.
+ unsigned int patch;
+} webview_version_t;
+
+// Holds the library's version information.
+typedef struct
+{
+ // The elements of the version number.
+ webview_version_t version;
+ // SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
+ char version_number[32];
+ // SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise
+ // an empty string.
+ char pre_release[48];
+ // SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string.
+ char build_metadata[48];
+} webview_version_info_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef void* webview_t;
+
+ // Creates a new webview instance. If debug is non-zero - developer tools will
+ // be enabled (if the platform supports them). Window parameter can be a
+ // pointer to the native window handle. If it's non-null - then child WebView
+ // is embedded into the given parent window. Otherwise a new window is created.
+ // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
+ // passed here. Returns null on failure. Creation can fail for various reasons
+ // such as when required runtime dependencies are missing or when window creation
+ // fails.
+ WEBVIEW_API webview_t webview_create(int debug, void* window);
+
+ // Destroys a webview and closes the native window.
+ WEBVIEW_API void webview_destroy(webview_t w);
+
+ // Runs the main loop until it's terminated. After this function exits - you
+ // must destroy the webview.
+ WEBVIEW_API void webview_run(webview_t w);
+
+ // Stops the main loop. It is safe to call this function from another other
+ // background thread.
+ WEBVIEW_API void webview_terminate(webview_t w);
+
+ // Posts a function to be executed on the main thread. You normally do not need
+ // to call this function, unless you want to tweak the native window.
+ WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t w, void* arg), void* arg);
+
+ // Returns a native window handle pointer. When using GTK backend the pointer
+ // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
+ // pointer, when using Win32 backend the pointer is HWND pointer.
+ WEBVIEW_API void* webview_get_window(webview_t w);
+
+ // Updates the title of the native window. Must be called from the UI thread.
+ WEBVIEW_API void webview_set_title(webview_t w, const char* title);
+
+// Window size hints
+#define WEBVIEW_HINT_NONE 0 // Width and height are default size
+#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds
+#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds
+#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user
+ // Updates native window size. See WEBVIEW_HINT constants.
+ WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints);
+
+ // Navigates webview to the given URL. URL may be a properly encoded data URI.
+ // Examples:
+ // webview_navigate(w, "https://github.com/webview/webview");
+ // webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E");
+ // webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=");
+ WEBVIEW_API void webview_navigate(webview_t w, const char* url);
+
+ // Set webview HTML directly.
+ // Example: webview_set_html(w, "<h1>Hello</h1>");
+ WEBVIEW_API void webview_set_html(webview_t w, const char* html);
+
+ // Injects JavaScript code at the initialization of the new page. Every time
+ // the webview will open a the new page - this initialization code will be
+ // executed. It is guaranteed that code is executed before window.onload.
+ WEBVIEW_API void webview_init(webview_t w, const char* js);
+
+ // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
+ // the result of the expression is ignored. Use RPC bindings if you want to
+ // receive notifications about the results of the evaluation.
+ WEBVIEW_API void webview_eval(webview_t w, const char* js);
+
+ // Binds a native C callback so that it will appear under the given name as a
+ // global JavaScript function. Internally it uses webview_init(). Callback
+ // receives a request string and a user-provided argument pointer. Request
+ // string is a JSON array of all the arguments passed to the JavaScript
+ // function.
+ WEBVIEW_API void webview_bind(webview_t w, const char* name,
+ void (*fn)(const char* seq, const char* req, void* arg),
+ void* arg);
+
+ // Removes a native C callback that was previously set by webview_bind.
+ WEBVIEW_API void webview_unbind(webview_t w, const char* name);
+
+ // Allows to return a value from the native binding. Original request pointer
+ // must be provided to help internal RPC engine match requests with responses.
+ // If status is zero - result is expected to be a valid JSON result value.
+ // If status is not zero - result is an error JSON object.
+ WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result);
+
+ // Get the library's version information.
+ // @since 0.10
+ WEBVIEW_API const webview_version_info_t* webview_version();
+
+#ifdef __cplusplus
+}
+
+#ifndef WEBVIEW_HEADER
+
+#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
+#if defined(__APPLE__)
+#define WEBVIEW_COCOA
+#elif defined(__unix__)
+#define WEBVIEW_GTK
+#elif defined(_WIN32)
+#define WEBVIEW_EDGE
+#else
+#error "please, specify webview backend"
+#endif
+#endif
+
+#ifndef WEBVIEW_DEPRECATED
+#if __cplusplus >= 201402L
+#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]]
+#elif defined(_MSC_VER)
+#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason))
+#else
+#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason)))
+#endif
+#endif
+
+#ifndef WEBVIEW_DEPRECATED_PRIVATE
+#define WEBVIEW_DEPRECATED_PRIVATE WEBVIEW_DEPRECATED("Private API should not be used")
+#endif
+
+#include <array>
+#include <atomic>
+#include <functional>
+#include <future>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+#include <locale>
+#include <codecvt>
+#include <cstring>
+
+namespace webview
+{
+
+ using dispatch_fn_t = std::function<void()>;
+
+ namespace detail
+ {
+
+ // The library's version information.
+ constexpr const webview_version_info_t library_version_info{
+ { WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH },
+ WEBVIEW_VERSION_NUMBER,
+ WEBVIEW_VERSION_PRE_RELEASE,
+ WEBVIEW_VERSION_BUILD_METADATA
+ };
+
+ inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
+ const char** value, size_t* valuesz)
+ {
+ enum
+ {
+ JSON_STATE_VALUE,
+ JSON_STATE_LITERAL,
+ JSON_STATE_STRING,
+ JSON_STATE_ESCAPE,
+ JSON_STATE_UTF8
+ } state = JSON_STATE_VALUE;
+ const char* k = nullptr;
+ int index = 1;
+ int depth = 0;
+ int utf8_bytes = 0;
+
+ *value = nullptr;
+ *valuesz = 0;
+
+ if (key == nullptr)
+ {
+ index = static_cast<decltype(index)>(keysz);
+ if (index < 0)
+ {
+ return -1;
+ }
+ keysz = 0;
+ }
+
+ for (; sz > 0; s++, sz--)
+ {
+ enum
+ {
+ JSON_ACTION_NONE,
+ JSON_ACTION_START,
+ JSON_ACTION_END,
+ JSON_ACTION_START_STRUCT,
+ JSON_ACTION_END_STRUCT
+ } action = JSON_ACTION_NONE;
+ auto c = static_cast<unsigned char>(*s);
+ switch (state)
+ {
+ case JSON_STATE_VALUE:
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':')
+ {
+ continue;
+ }
+ else if (c == '"')
+ {
+ action = JSON_ACTION_START;
+ state = JSON_STATE_STRING;
+ }
+ else if (c == '{' || c == '[')
+ {
+ action = JSON_ACTION_START_STRUCT;
+ }
+ else if (c == '}' || c == ']')
+ {
+ action = JSON_ACTION_END_STRUCT;
+ }
+ else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
+ (c >= '0' && c <= '9'))
+ {
+ action = JSON_ACTION_START;
+ state = JSON_STATE_LITERAL;
+ }
+ else
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_LITERAL:
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+ c == ']' || c == '}' || c == ':')
+ {
+ state = JSON_STATE_VALUE;
+ s--;
+ sz++;
+ action = JSON_ACTION_END;
+ }
+ else if (c < 32 || c > 126)
+ {
+ return -1;
+ } // fallthrough
+ case JSON_STATE_STRING:
+ if (c < 32 || (c > 126 && c < 192))
+ {
+ return -1;
+ }
+ else if (c == '"')
+ {
+ action = JSON_ACTION_END;
+ state = JSON_STATE_VALUE;
+ }
+ else if (c == '\\')
+ {
+ state = JSON_STATE_ESCAPE;
+ }
+ else if (c >= 192 && c < 224)
+ {
+ utf8_bytes = 1;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 224 && c < 240)
+ {
+ utf8_bytes = 2;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 240 && c < 247)
+ {
+ utf8_bytes = 3;
+ state = JSON_STATE_UTF8;
+ }
+ else if (c >= 128 && c < 192)
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_ESCAPE:
+ if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' ||
+ c == 'r' || c == 't' || c == 'u')
+ {
+ state = JSON_STATE_STRING;
+ }
+ else
+ {
+ return -1;
+ }
+ break;
+ case JSON_STATE_UTF8:
+ if (c < 128 || c > 191)
+ {
+ return -1;
+ }
+ utf8_bytes--;
+ if (utf8_bytes == 0)
+ {
+ state = JSON_STATE_STRING;
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ if (action == JSON_ACTION_END_STRUCT)
+ {
+ depth--;
+ }
+
+ if (depth == 1)
+ {
+ if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT)
+ {
+ if (index == 0)
+ {
+ *value = s;
+ }
+ else if (keysz > 0 && index == 1)
+ {
+ k = s;
+ }
+ else
+ {
+ index--;
+ }
+ }
+ else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT)
+ {
+ if (*value != nullptr && index == 0)
+ {
+ *valuesz = (size_t)(s + 1 - *value);
+ return 0;
+ }
+ else if (keysz > 0 && k != nullptr)
+ {
+ if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0)
+ {
+ index = 0;
+ }
+ else
+ {
+ index = 2;
+ }
+ k = nullptr;
+ }
+ }
+ }
+
+ if (action == JSON_ACTION_START_STRUCT)
+ {
+ depth++;
+ }
+ }
+ return -1;
+ }
+
+ inline std::string json_escape(const std::string& s)
+ {
+ // TODO: implement
+ return '"' + s + '"';
+ }
+
+ inline int json_unescape(const char* s, size_t n, char* out)
+ {
+ int r = 0;
+ if (*s++ != '"')
+ {
+ return -1;
+ }
+ while (n > 2)
+ {
+ char c = *s;
+ if (c == '\\')
+ {
+ s++;
+ n--;
+ switch (*s)
+ {
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case '\\':
+ c = '\\';
+ break;
+ case '/':
+ c = '/';
+ break;
+ case '\"':
+ c = '\"';
+ break;
+ default: // TODO: support unicode decoding
+ return -1;
+ }
+ }
+ if (out != nullptr)
+ {
+ *out++ = c;
+ }
+ s++;
+ n--;
+ r++;
+ }
+ if (*s != '"')
+ {
+ return -1;
+ }
+ if (out != nullptr)
+ {
+ *out = '\0';
+ }
+ return r;
+ }
+
+ inline std::string json_parse(const std::string& s, const std::string& key, const int index)
+ {
+ const char* value = nullptr;
+ size_t value_sz = 0;
+ if (key.empty())
+ {
+ json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
+ }
+ else
+ {
+ json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz);
+ }
+ if (value != nullptr)
+ {
+ if (value[0] != '"')
+ {
+ return { value, value_sz };
+ }
+ int n = json_unescape(value, value_sz, nullptr);
+ if (n > 0)
+ {
+ char* decoded = new char[n + 1];
+ json_unescape(value, value_sz, decoded);
+ std::string result(decoded, n);
+ delete[] decoded;
+ return result;
+ }
+ }
+ return "";
+ }
+
+ } // namespace detail
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz,
+ const char** value, size_t* valuesz)
+ {
+ return detail::json_parse_c(s, sz, key, keysz, value, valuesz);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline std::string json_escape(const std::string& s)
+ {
+ return detail::json_escape(s);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline int json_unescape(const char* s, size_t n, char* out)
+ {
+ return detail::json_unescape(s, n, out);
+ }
+
+ WEBVIEW_DEPRECATED_PRIVATE
+ inline std::string json_parse(const std::string& s, const std::string& key, const int index)
+ {
+ return detail::json_parse(s, key, index);
+ }
+
+} // namespace webview
+
+#if defined(WEBVIEW_GTK)
+//
+// ====================================================================
+//
+// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
+// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
+//
+// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
+//
+// ====================================================================
+//
+#include <JavaScriptCore/JavaScript.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+namespace webview
+{
+ namespace detail
+ {
+
+ class gtk_webkit_engine
+ {
+ public:
+ gtk_webkit_engine(bool debug, void* window) : m_window(static_cast<GtkWidget*>(window))
+ {
+ if (gtk_init_check(nullptr, nullptr) == FALSE)
+ {
+ return;
+ }
+ m_window = static_cast<GtkWidget*>(window);
+ if (m_window == nullptr)
+ {
+ m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ }
+ g_signal_connect(G_OBJECT(m_window), "destroy",
+ G_CALLBACK(+[](GtkWidget*, gpointer arg) {
+ static_cast<gtk_webkit_engine*>(arg)->terminate();
+ }),
+ this);
+ // Initialize webview widget
+ m_webview = webkit_web_view_new();
+ WebKitUserContentManager* manager =
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+ g_signal_connect(manager, "script-message-received::external",
+ G_CALLBACK(+[](WebKitUserContentManager*,
+ WebKitJavascriptResult* r, gpointer arg) {
+ auto* w = static_cast<gtk_webkit_engine*>(arg);
+ char* s = get_string_from_js_result(r);
+ w->on_message(s);
+ g_free(s);
+ }),
+ this);
+ webkit_user_content_manager_register_script_message_handler(manager, "external");
+ init("window.external={invoke:function(s){window.webkit.messageHandlers."
+ "external.postMessage(s);}}");
+
+ gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
+ gtk_widget_grab_focus(GTK_WIDGET(m_webview));
+
+ WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
+ webkit_settings_set_javascript_can_access_clipboard(settings, true);
+ if (debug)
+ {
+ webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
+ webkit_settings_set_enable_developer_extras(settings, true);
+ }
+
+ gtk_widget_show_all(m_window);
+ }
+ virtual ~gtk_webkit_engine() = default;
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void run()
+ {
+ gtk_main();
+ }
+ void terminate()
+ {
+ gtk_main_quit();
+ }
+ void dispatch(std::function<void()> f)
+ {
+ g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void* f) -> int {
+ (*static_cast<dispatch_fn_t*>(f))();
+ return G_SOURCE_REMOVE;
+ }),
+ new std::function<void()>(f),
+ [](void* f) { delete static_cast<dispatch_fn_t*>(f); });
+ }
+
+ void set_title(const std::string& title)
+ {
+ gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
+ }
+
+ void set_size(int width, int height, int hints)
+ {
+ gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
+ if (hints == WEBVIEW_HINT_NONE)
+ {
+ gtk_window_resize(GTK_WINDOW(m_window), width, height);
+ }
+ else if (hints == WEBVIEW_HINT_FIXED)
+ {
+ gtk_widget_set_size_request(m_window, width, height);
+ }
+ else
+ {
+ GdkGeometry g;
+ g.min_width = g.max_width = width;
+ g.min_height = g.max_height = height;
+ GdkWindowHints h =
+ (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
+ // This defines either MIN_SIZE, or MAX_SIZE, but not both:
+ gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
+ }
+ }
+
+ void navigate(const std::string& url)
+ {
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ g_signal_connect(WEBKIT_WEB_VIEW(m_webview), "load-changed",
+ G_CALLBACK(on_load_changed), this);
+ navigateCallbackArg = arg;
+ navigateCallback = std::move(callback);
+ }
+
+ void add_scheme_handler(const std::string& scheme,
+ std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ auto view = WEBKIT_WEB_VIEW(m_webview);
+ auto context = webkit_web_view_get_context(view);
+
+ scheme_handlers.insert({ scheme, { .arg = arg, .fkt = callback } });
+ webkit_web_context_register_uri_scheme(context, scheme.c_str(), scheme_handler,
+ static_cast<gpointer>(this), nullptr);
+ }
+
+ void set_html(const std::string& html)
+ {
+ webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), nullptr);
+ }
+
+ void init(const std::string& js)
+ {
+ WebKitUserContentManager* manager =
+ webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+ webkit_user_content_manager_add_script(
+ manager, webkit_user_script_new(
+ js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr));
+ }
+
+ void eval(const std::string& js)
+ {
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr,
+ nullptr, nullptr);
+ }
+
+ private:
+ virtual void on_message(const std::string& msg) = 0;
+
+ struct handler_t
+ {
+ void* arg;
+ std::function<void(const std::string&, void*)> fkt;
+ };
+
+ std::map<std::string, handler_t> scheme_handlers;
+
+ void scheme_handler_call(const std::string& scheme, const std::string& url)
+ {
+ auto handler = scheme_handlers.find(scheme);
+ if (handler != scheme_handlers.end())
+ {
+ const auto& arg = handler->second;
+ arg.fkt(url, arg.arg);
+ }
+ }
+
+ static void scheme_handler(WebKitURISchemeRequest* request, gpointer user_data)
+ {
+ auto _this = static_cast<gtk_webkit_engine*>(user_data);
+
+ auto scheme = webkit_uri_scheme_request_get_scheme(request);
+ auto uri = webkit_uri_scheme_request_get_uri(request);
+ _this->scheme_handler_call(scheme, uri);
+ }
+
+ static char* get_string_from_js_result(WebKitJavascriptResult* r)
+ {
+ char* s = nullptr;
+#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
+ JSCValue* value = webkit_javascript_result_get_js_value(r);
+ s = jsc_value_to_string(value);
+#else
+ JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
+ JSValueRef value = webkit_javascript_result_get_value(r);
+ JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
+ size_t n = JSStringGetMaximumUTF8CStringSize(js);
+ s = g_new(char, n);
+ JSStringGetUTF8CString(js, s, n);
+ JSStringRelease(js);
+#endif
+ return s;
+ }
+
+ GtkWidget* m_window;
+ GtkWidget* m_webview;
+
+ void* navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> navigateCallback = nullptr;
+
+ static void on_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event,
+ gpointer arg)
+ {
+ if (load_event == WEBKIT_LOAD_FINISHED)
+ {
+ auto inst = static_cast<gtk_webkit_engine*>(arg);
+ inst->navigateCallback(webkit_web_view_get_uri(web_view),
+ inst->navigateCallbackArg);
+ }
+ }
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::gtk_webkit_engine;
+
+} // namespace webview
+
+#elif defined(WEBVIEW_COCOA)
+
+//
+// ====================================================================
+//
+// This implementation uses Cocoa WKWebView backend on macOS. It is
+// written using ObjC runtime and uses WKWebView class as a browser runtime.
+// You should pass "-framework Webkit" flag to the compiler.
+//
+// ====================================================================
+//
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/NSObjCRuntime.h>
+#include <objc/objc-runtime.h>
+
+namespace webview
+{
+ namespace detail
+ {
+ namespace objc
+ {
+
+ // A convenient template function for unconditionally casting the specified
+ // C-like function into a function that can be called with the given return
+ // type and arguments. Caller takes full responsibility for ensuring that
+ // the function call is valid. It is assumed that the function will not
+ // throw exceptions.
+ template <typename Result, typename Callable, typename... Args>
+ Result invoke(Callable callable, Args... args) noexcept
+ {
+ return reinterpret_cast<Result (*)(Args...)>(callable)(args...);
+ }
+
+ // Calls objc_msgSend.
+ template <typename Result, typename... Args> Result msg_send(Args... args) noexcept
+ {
+ return invoke<Result>(objc_msgSend, args...);
+ }
+
+ } // namespace objc
+
+ enum NSBackingStoreType : NSUInteger
+ {
+ NSBackingStoreBuffered = 2
+ };
+
+ enum NSWindowStyleMask : NSUInteger
+ {
+ NSWindowStyleMaskTitled = 1,
+ NSWindowStyleMaskClosable = 2,
+ NSWindowStyleMaskMiniaturizable = 4,
+ NSWindowStyleMaskResizable = 8
+ };
+
+ enum NSApplicationActivationPolicy : NSInteger
+ {
+ NSApplicationActivationPolicyRegular = 0
+ };
+
+ enum WKUserScriptInjectionTime : NSInteger
+ {
+ WKUserScriptInjectionTimeAtDocumentStart = 0
+ };
+
+ enum NSModalResponse : NSInteger
+ {
+ NSModalResponseOK = 1
+ };
+
+ // Convenient conversion of string literals.
+ inline id operator"" _cls(const char* s, std::size_t)
+ {
+ return (id)objc_getClass(s);
+ }
+ inline SEL operator"" _sel(const char* s, std::size_t)
+ {
+ return sel_registerName(s);
+ }
+ inline id operator"" _str(const char* s, std::size_t)
+ {
+ return objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, s);
+ }
+
+ class cocoa_wkwebview_engine
+ {
+ public:
+ cocoa_wkwebview_engine(bool debug, void* window)
+ : m_debug{ debug }, m_parent_window{ window }
+ {
+ auto app = get_shared_application();
+ auto delegate = create_app_delegate();
+ objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ objc::msg_send<void>(app, "setDelegate:"_sel, delegate);
+
+ // See comments related to application lifecycle in create_app_delegate().
+ if (window)
+ {
+ on_application_did_finish_launching(delegate, app);
+ }
+ else
+ {
+ // Start the main run loop so that the app delegate gets the
+ // NSApplicationDidFinishLaunchingNotification notification after the run
+ // loop has started in order to perform further initialization.
+ // We need to return from this constructor so this run loop is only
+ // temporary.
+ objc::msg_send<void>(app, "run"_sel);
+ }
+ }
+ virtual ~cocoa_wkwebview_engine() = default;
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void terminate()
+ {
+ auto app = get_shared_application();
+ objc::msg_send<void>(app, "terminate:"_sel, nullptr);
+ }
+ void run()
+ {
+ auto app = get_shared_application();
+ objc::msg_send<void>(app, "run"_sel);
+ }
+ void dispatch(std::function<void()> f)
+ {
+ dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
+ (dispatch_function_t)([](void* arg) {
+ auto f = static_cast<dispatch_fn_t*>(arg);
+ (*f)();
+ delete f;
+ }));
+ }
+ void set_title(const std::string& title)
+ {
+ objc::msg_send<void>(
+ m_window, "setTitle:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
+ }
+ void set_size(int width, int height, int hints)
+ {
+ auto style = static_cast<NSWindowStyleMask>(NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable);
+ if (hints != WEBVIEW_HINT_FIXED)
+ {
+ style = static_cast<NSWindowStyleMask>(style | NSWindowStyleMaskResizable);
+ }
+ objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
+
+ if (hints == WEBVIEW_HINT_MIN)
+ {
+ objc::msg_send<void>(m_window, "setContentMinSize:"_sel,
+ CGSizeMake(width, height));
+ }
+ else if (hints == WEBVIEW_HINT_MAX)
+ {
+ objc::msg_send<void>(m_window, "setContentMaxSize:"_sel,
+ CGSizeMake(width, height));
+ }
+ else
+ {
+ objc::msg_send<void>(m_window, "setFrame:display:animate:"_sel,
+ CGRectMake(0, 0, width, height), YES, NO);
+ }
+ objc::msg_send<void>(m_window, "center"_sel);
+ }
+ void navigate(const std::string& url)
+ {
+ auto nsurl = objc::msg_send<id>(
+ "NSURL"_cls, "URLWithString:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
+
+ objc::msg_send<void>(
+ m_webview, "loadRequest:"_sel,
+ objc::msg_send<id>("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ m_navigateCallback = callback;
+ m_navigateCallbackArg = arg;
+ }
+
+ void set_html(const std::string& html)
+ {
+ objc::msg_send<void>(
+ m_webview, "loadHTMLString:baseURL:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, html.c_str()),
+ nullptr);
+ }
+ void init(const std::string& js)
+ {
+ // Equivalent Obj-C:
+ // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString
+ // stringWithUTF8String:js.c_str()]
+ // injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
+ objc::msg_send<void>(
+ m_manager, "addUserScript:"_sel,
+ objc::msg_send<id>(
+ objc::msg_send<id>("WKUserScript"_cls, "alloc"_sel),
+ "initWithSource:injectionTime:forMainFrameOnly:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
+ WKUserScriptInjectionTimeAtDocumentStart, YES));
+ }
+ void eval(const std::string& js)
+ {
+ objc::msg_send<void>(
+ m_webview, "evaluateJavaScript:completionHandler:"_sel,
+ objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
+ nullptr);
+ }
+
+ private:
+ virtual void on_message(const std::string& msg) = 0;
+ id create_app_delegate()
+ {
+ // Note: Avoid registering the class name "AppDelegate" as it is the
+ // default name in projects created with Xcode, and using the same name
+ // causes objc_registerClassPair to crash.
+ auto cls =
+ objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewAppDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
+ class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
+ (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
+ // If the library was not initialized with an existing window then the user
+ // is likely managing the application lifecycle and we would not get the
+ // "applicationDidFinishLaunching:" message and therefore do not need to
+ // add this method.
+ if (!m_parent_window)
+ {
+ class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
+ (IMP)(+[](id self, SEL, id notification) {
+ auto app = objc::msg_send<id>(notification, "object"_sel);
+ auto w = get_associated_webview(self);
+ w->on_application_did_finish_launching(self, app);
+ }),
+ "v@:@");
+ }
+ objc_registerClassPair(cls);
+ return objc::msg_send<id>((id)cls, "new"_sel);
+ }
+ id create_script_message_handler()
+ {
+ auto cls = objc_allocateClassPair((Class) "NSResponder"_cls,
+ "WebkitScriptMessageHandler", 0);
+ class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
+ class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
+ (IMP)(+[](id self, SEL, id, id msg) {
+ auto w = get_associated_webview(self);
+ w->on_message(objc::msg_send<const char*>(
+ objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
+ }),
+ "v@:@@");
+ objc_registerClassPair(cls);
+ auto instance = objc::msg_send<id>((id)cls, "new"_sel);
+ objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ return instance;
+ }
+ static id create_webkit_ui_delegate()
+ {
+ auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitUIDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("WKUIDelegate"));
+ class_addMethod(
+ cls,
+ "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
+ (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) {
+ auto allows_multiple_selection =
+ objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
+ auto allows_directories =
+ objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
+
+ // Show a panel for selecting files.
+ auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
+ objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
+ objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
+ allows_directories);
+ objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
+ allows_multiple_selection);
+ auto modal_response =
+ objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
+
+ // Get the URLs for the selected files. If the modal was canceled
+ // then we pass null to the completion handler to signify
+ // cancellation.
+ id urls = modal_response == NSModalResponseOK
+ ? objc::msg_send<id>(panel, "URLs"_sel)
+ : nullptr;
+
+ // Invoke the completion handler block.
+ auto sig = objc::msg_send<id>("NSMethodSignature"_cls,
+ "signatureWithObjCTypes:"_sel, "v@?@");
+ auto invocation = objc::msg_send<id>(
+ "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
+ objc::msg_send<void>(invocation, "setTarget:"_sel, completion_handler);
+ objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls, 1);
+ objc::msg_send<void>(invocation, "invoke"_sel);
+ }),
+ "v@:@@@@");
+ objc_registerClassPair(cls);
+ return objc::msg_send<id>((id)cls, "new"_sel);
+ }
+ id create_webkit_navigation_delegate()
+ {
+ auto cls =
+ objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0);
+ class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate"));
+ class_addMethod(cls, "webView:didFinishNavigation:"_sel,
+ (IMP)(+[](id delegate, SEL sel, id webview, id navigation) {
+ auto w = get_associated_webview(delegate);
+ auto url = objc::msg_send<id>(webview, "URL"_sel);
+ auto nstr = objc::msg_send<id>(url, "absoluteString"_sel);
+ auto str = objc::msg_send<char*>(nstr, "UTF8String"_sel);
+ w->m_navigateCallback(str, w->m_navigateCallbackArg);
+ }),
+ "v@:@");
+ objc_registerClassPair(cls);
+ auto instance = objc::msg_send<id>((id)cls, "new"_sel);
+ objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN);
+ return instance;
+ }
+ static id get_shared_application()
+ {
+ return objc::msg_send<id>("NSApplication"_cls, "sharedApplication"_sel);
+ }
+ static cocoa_wkwebview_engine* get_associated_webview(id object)
+ {
+ auto w = (cocoa_wkwebview_engine*)objc_getAssociatedObject(object, "webview");
+ assert(w);
+ return w;
+ }
+ static id get_main_bundle() noexcept
+ {
+ return objc::msg_send<id>("NSBundle"_cls, "mainBundle"_sel);
+ }
+ static bool is_app_bundled() noexcept
+ {
+ auto bundle = get_main_bundle();
+ if (!bundle)
+ {
+ return false;
+ }
+ auto bundle_path = objc::msg_send<id>(bundle, "bundlePath"_sel);
+ auto bundled = objc::msg_send<BOOL>(bundle_path, "hasSuffix:"_sel, ".app"_str);
+ return !!bundled;
+ }
+ void on_application_did_finish_launching(id /*delegate*/, id app)
+ {
+ // See comments related to application lifecycle in create_app_delegate().
+ if (!m_parent_window)
+ {
+ // Stop the main run loop so that we can return
+ // from the constructor.
+ objc::msg_send<void>(app, "stop:"_sel, nullptr);
+ }
+
+ // Activate the app if it is not bundled.
+ // Bundled apps launched from Finder are activated automatically but
+ // otherwise not. Activating the app even when it has been launched from
+ // Finder does not seem to be harmful but calling this function is rarely
+ // needed as proper activation is normally taken care of for us.
+ // Bundled apps have a default activation policy of
+ // NSApplicationActivationPolicyRegular while non-bundled apps have a
+ // default activation policy of NSApplicationActivationPolicyProhibited.
+ if (!is_app_bundled())
+ {
+ // "setActivationPolicy:" must be invoked before
+ // "activateIgnoringOtherApps:" for activation to work.
+ objc::msg_send<void>(app, "setActivationPolicy:"_sel,
+ NSApplicationActivationPolicyRegular);
+ // Activate the app regardless of other active apps.
+ // This can be obtrusive so we only do it when necessary.
+ objc::msg_send<void>(app, "activateIgnoringOtherApps:"_sel, YES);
+ }
+
+ // Main window
+ if (!m_parent_window)
+ {
+ m_window = objc::msg_send<id>("NSWindow"_cls, "alloc"_sel);
+ auto style = NSWindowStyleMaskTitled;
+ m_window = objc::msg_send<id>(
+ m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
+ CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO);
+ }
+ else
+ {
+ m_window = (id)m_parent_window;
+ }
+
+ // Webview
+ auto config = objc::msg_send<id>("WKWebViewConfiguration"_cls, "new"_sel);
+ m_manager = objc::msg_send<id>(config, "userContentController"_sel);
+ m_webview = objc::msg_send<id>("WKWebView"_cls, "alloc"_sel);
+
+ if (m_debug)
+ {
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
+ objc::msg_send<id>(
+ objc::msg_send<id>(config, "preferences"_sel), "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "developerExtrasEnabled"_str);
+ }
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "fullScreenEnabled"_str);
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "javaScriptCanAccessClipboard"_str);
+
+ // Equivalent Obj-C:
+ // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
+ objc::msg_send<id>(objc::msg_send<id>(config, "preferences"_sel),
+ "setValue:forKey:"_sel,
+ objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES),
+ "DOMPasteAllowed"_str);
+
+ auto ui_delegate = create_webkit_ui_delegate();
+ objc::msg_send<void>(m_webview, "initWithFrame:configuration:"_sel,
+ CGRectMake(0, 0, 0, 0), config);
+ objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, ui_delegate);
+
+ auto navigation_delegate = create_webkit_navigation_delegate();
+ objc::msg_send<void>(m_webview, "setNavigationDelegate:"_sel, navigation_delegate);
+ auto script_message_handler = create_script_message_handler();
+ objc::msg_send<void>(m_manager, "addScriptMessageHandler:name:"_sel,
+ script_message_handler, "external"_str);
+
+ init(R""(
+ window.external = {
+ invoke: function(s) {
+ window.webkit.messageHandlers.external.postMessage(s);
+ },
+ };
+ )"");
+ objc::msg_send<void>(m_window, "setContentView:"_sel, m_webview);
+ objc::msg_send<void>(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
+ }
+ bool m_debug;
+ void* m_parent_window;
+ id m_window;
+ id m_webview;
+ id m_manager;
+ void* m_navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> m_navigateCallback = 0;
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::cocoa_wkwebview_engine;
+
+} // namespace webview
+
+#elif defined(WEBVIEW_EDGE)
+
+//
+// ====================================================================
+//
+// This implementation uses Win32 API to create a native window. It
+// uses Edge/Chromium webview2 backend as a browser engine.
+//
+// ====================================================================
+//
+
+#define WIN32_LEAN_AND_MEAN
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "WebView2.h"
+
+#ifdef _MSC_VER
+#pragma comment(lib, "advapi32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "shell32.lib")
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(lib, "user32.lib")
+#pragma comment(lib, "version.lib")
+#endif
+
+namespace webview
+{
+ namespace detail
+ {
+
+ using msg_cb_t = std::function<void(const std::string)>;
+
+ // Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string.
+ inline std::wstring widen_string(const std::string& input)
+ {
+ if (input.empty())
+ {
+ return std::wstring();
+ }
+ UINT cp = CP_UTF8;
+ DWORD flags = MB_ERR_INVALID_CHARS;
+ auto input_c = input.c_str();
+ auto input_length = static_cast<int>(input.size());
+ auto required_length =
+ MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0);
+ if (required_length > 0)
+ {
+ std::wstring output(static_cast<std::size_t>(required_length), L'\0');
+ if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0],
+ required_length) > 0)
+ {
+ return output;
+ }
+ }
+ // Failed to convert string from UTF-8 to UTF-16
+ return std::wstring();
+ }
+
+ // Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string.
+ inline std::string narrow_string(const std::wstring& input)
+ {
+ if (input.empty())
+ {
+ return std::string();
+ }
+ UINT cp = CP_UTF8;
+ DWORD flags = WC_ERR_INVALID_CHARS;
+ auto input_c = input.c_str();
+ auto input_length = static_cast<int>(input.size());
+ auto required_length =
+ WideCharToMultiByte(cp, flags, input_c, input_length, nullptr, 0, nullptr, nullptr);
+ if (required_length > 0)
+ {
+ std::string output(static_cast<std::size_t>(required_length), '\0');
+ if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0],
+ required_length, nullptr, nullptr) > 0)
+ {
+ return output;
+ }
+ }
+ // Failed to convert string from UTF-16 to UTF-8
+ return std::string();
+ }
+
+ // Parses a version string with 1-4 integral components, e.g. "1.2.3.4".
+ // Missing or invalid components default to 0, and excess components are ignored.
+ template <typename T>
+ std::array<unsigned int, 4> parse_version(const std::basic_string<T>& version) noexcept
+ {
+ auto parse_component = [](auto sb, auto se) -> unsigned int {
+ try
+ {
+ auto n = std::stol(std::basic_string<T>(sb, se));
+ return n < 0 ? 0 : n;
+ }
+ catch (std::exception&)
+ {
+ return 0;
+ }
+ };
+ auto end = version.end();
+ auto sb = version.begin(); // subrange begin
+ auto se = sb; // subrange end
+ unsigned int ci = 0; // component index
+ std::array<unsigned int, 4> components{};
+ while (sb != end && se != end && ci < components.size())
+ {
+ if (*se == static_cast<T>('.'))
+ {
+ components[ci++] = parse_component(sb, se);
+ sb = ++se;
+ continue;
+ }
+ ++se;
+ }
+ if (sb < se && ci < components.size())
+ {
+ components[ci] = parse_component(sb, se);
+ }
+ return components;
+ }
+
+ template <typename T, std::size_t Length>
+ auto parse_version(const T (&version)[Length]) noexcept
+ {
+ return parse_version(std::basic_string<T>(version, Length));
+ }
+
+ std::wstring get_file_version_string(const std::wstring& file_path) noexcept
+ {
+ DWORD dummy_handle; // Unused
+ DWORD info_buffer_length = GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle);
+ if (info_buffer_length == 0)
+ {
+ return std::wstring();
+ }
+ std::vector<char> info_buffer;
+ info_buffer.reserve(info_buffer_length);
+ if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length, info_buffer.data()))
+ {
+ return std::wstring();
+ }
+ auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion";
+ LPWSTR version = nullptr;
+ unsigned int version_length = 0;
+ if (!VerQueryValueW(info_buffer.data(), sub_block, reinterpret_cast<LPVOID*>(&version),
+ &version_length))
+ {
+ return std::wstring();
+ }
+ if (!version || version_length == 0)
+ {
+ return std::wstring();
+ }
+ return std::wstring(version, version_length);
+ }
+
+ // A wrapper around COM library initialization. Calls CoInitializeEx in the
+ // constructor and CoUninitialize in the destructor.
+ class com_init_wrapper
+ {
+ public:
+ com_init_wrapper(DWORD dwCoInit)
+ {
+ // We can safely continue as long as COM was either successfully
+ // initialized or already initialized.
+ // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with
+ // a different concurrency model.
+ switch (CoInitializeEx(nullptr, dwCoInit))
+ {
+ case S_OK:
+ case S_FALSE:
+ m_initialized = true;
+ break;
+ }
+ }
+
+ ~com_init_wrapper()
+ {
+ if (m_initialized)
+ {
+ CoUninitialize();
+ m_initialized = false;
+ }
+ }
+
+ com_init_wrapper(const com_init_wrapper& other) = delete;
+ com_init_wrapper& operator=(const com_init_wrapper& other) = delete;
+ com_init_wrapper(com_init_wrapper&& other) = delete;
+ com_init_wrapper& operator=(com_init_wrapper&& other) = delete;
+
+ bool is_initialized() const
+ {
+ return m_initialized;
+ }
+
+ private:
+ bool m_initialized = false;
+ };
+
+ // Holds a symbol name and associated type for code clarity.
+ template <typename T> class library_symbol
+ {
+ public:
+ using type = T;
+
+ constexpr explicit library_symbol(const char* name) : m_name(name)
+ {
+ }
+ constexpr const char* get_name() const
+ {
+ return m_name;
+ }
+
+ private:
+ const char* m_name;
+ };
+
+ // Loads a native shared library and allows one to get addresses for those
+ // symbols.
+ class native_library
+ {
+ public:
+ explicit native_library(const wchar_t* name) : m_handle(LoadLibraryW(name))
+ {
+ }
+
+ ~native_library()
+ {
+ if (m_handle)
+ {
+ FreeLibrary(m_handle);
+ m_handle = nullptr;
+ }
+ }
+
+ native_library(const native_library& other) = delete;
+ native_library& operator=(const native_library& other) = delete;
+ native_library(native_library&& other) = default;
+ native_library& operator=(native_library&& other) = default;
+
+ // Returns true if the library is currently loaded; otherwise false.
+ operator bool() const
+ {
+ return is_loaded();
+ }
+
+ // Get the address for the specified symbol or nullptr if not found.
+ template <typename Symbol> typename Symbol::type get(const Symbol& symbol) const
+ {
+ if (is_loaded())
+ {
+ return reinterpret_cast<typename Symbol::type>(
+ GetProcAddress(m_handle, symbol.get_name()));
+ }
+ return nullptr;
+ }
+
+ // Returns true if the library is currently loaded; otherwise false.
+ bool is_loaded() const
+ {
+ return !!m_handle;
+ }
+
+ void detach()
+ {
+ m_handle = nullptr;
+ }
+
+ private:
+ HMODULE m_handle = nullptr;
+ };
+
+ struct user32_symbols
+ {
+ using DPI_AWARENESS_CONTEXT = HANDLE;
+ using SetProcessDpiAwarenessContext_t = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT);
+ using SetProcessDPIAware_t = BOOL(WINAPI*)();
+
+ static constexpr auto SetProcessDpiAwarenessContext =
+ library_symbol<SetProcessDpiAwarenessContext_t>("SetProcessDpiAwarenessContext");
+ static constexpr auto SetProcessDPIAware =
+ library_symbol<SetProcessDPIAware_t>("SetProcessDPIAware");
+ };
+
+ struct shcore_symbols
+ {
+ typedef enum
+ {
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+ } PROCESS_DPI_AWARENESS;
+ using SetProcessDpiAwareness_t = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS);
+
+ static constexpr auto SetProcessDpiAwareness =
+ library_symbol<SetProcessDpiAwareness_t>("SetProcessDpiAwareness");
+ };
+
+ class reg_key
+ {
+ public:
+ explicit reg_key(HKEY root_key, const wchar_t* sub_key, DWORD options,
+ REGSAM sam_desired)
+ {
+ HKEY handle;
+ auto status = RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle);
+ if (status == ERROR_SUCCESS)
+ {
+ m_handle = handle;
+ }
+ }
+
+ explicit reg_key(HKEY root_key, const std::wstring& sub_key, DWORD options,
+ REGSAM sam_desired)
+ : reg_key(root_key, sub_key.c_str(), options, sam_desired)
+ {
+ }
+
+ virtual ~reg_key()
+ {
+ if (m_handle)
+ {
+ RegCloseKey(m_handle);
+ m_handle = nullptr;
+ }
+ }
+
+ reg_key(const reg_key& other) = delete;
+ reg_key& operator=(const reg_key& other) = delete;
+ reg_key(reg_key&& other) = delete;
+ reg_key& operator=(reg_key&& other) = delete;
+
+ bool is_open() const
+ {
+ return !!m_handle;
+ }
+ bool get_handle() const
+ {
+ return m_handle;
+ }
+
+ std::wstring query_string(const wchar_t* name) const
+ {
+ DWORD buf_length = 0;
+ // Get the size of the data in bytes.
+ auto status =
+ RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr, &buf_length);
+ if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA)
+ {
+ return std::wstring();
+ }
+ // Read the data.
+ std::wstring result(buf_length / sizeof(wchar_t), 0);
+ auto buf = reinterpret_cast<LPBYTE>(&result[0]);
+ status = RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length);
+ if (status != ERROR_SUCCESS)
+ {
+ return std::wstring();
+ }
+ // Remove trailing null-characters.
+ for (std::size_t length = result.size(); length > 0; --length)
+ {
+ if (result[length - 1] != 0)
+ {
+ result.resize(length);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private:
+ HKEY m_handle = nullptr;
+ };
+
+ inline bool enable_dpi_awareness()
+ {
+ auto user32 = native_library(L"user32.dll");
+ if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext))
+ {
+ if (fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+ {
+ return true;
+ }
+ return GetLastError() == ERROR_ACCESS_DENIED;
+ }
+ if (auto shcore = native_library(L"shcore.dll"))
+ {
+ if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness))
+ {
+ auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE);
+ return result == S_OK || result == E_ACCESSDENIED;
+ }
+ }
+ if (auto fn = user32.get(user32_symbols::SetProcessDPIAware))
+ {
+ return !!fn();
+ }
+ return true;
+ }
+
+// Enable built-in WebView2Loader implementation by default.
+#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
+#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1
+#endif
+
+// Link WebView2Loader.dll explicitly by default only if the built-in
+// implementation is enabled.
+#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
+#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
+#endif
+
+// Explicit linking of WebView2Loader.dll should be used along with
+// the built-in implementation.
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1
+#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
+#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1.
+#endif
+
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ // Gets the last component of a Windows native file path.
+ // For example, if the path is "C:\a\b" then the result is "b".
+ template <typename T>
+ std::basic_string<T> get_last_native_path_component(const std::basic_string<T>& path)
+ {
+ if (auto pos = path.find_last_of(static_cast<T>('\\'));
+ pos != std::basic_string<T>::npos)
+ {
+ return path.substr(pos + 1);
+ }
+ return std::basic_string<T>();
+ }
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+ template <typename T> struct cast_info_t
+ {
+ using type = T;
+ IID iid;
+ };
+
+ namespace mswebview2
+ {
+ static constexpr IID IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{
+ 0x6C4819F3, 0xC9B7, 0x4260, 0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C
+ };
+ static constexpr IID IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
+ 0x4E8A3389, 0xC9D8, 0x4BD2, 0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D
+ };
+ static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{
+ 0x15E1C6A3, 0xC72A, 0x4DF3, 0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD
+ };
+ static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{
+ 0x57213F19, 0x00E6, 0x49FA, 0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2
+ };
+
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ enum class webview2_runtime_type
+ {
+ installed = 0,
+ embedded = 1
+ };
+
+ namespace webview2_symbols
+ {
+ using CreateWebViewEnvironmentWithOptionsInternal_t = HRESULT(STDMETHODCALLTYPE*)(
+ bool, webview2_runtime_type, PCWSTR, IUnknown*,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
+ using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE*)();
+
+ static constexpr auto CreateWebViewEnvironmentWithOptionsInternal =
+ library_symbol<CreateWebViewEnvironmentWithOptionsInternal_t>(
+ "CreateWebViewEnvironmentWithOptionsInternal");
+ static constexpr auto DllCanUnloadNow =
+ library_symbol<DllCanUnloadNow_t>("DllCanUnloadNow");
+ } // namespace webview2_symbols
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ namespace webview2_symbols
+ {
+ using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE*)(
+ PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions*,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
+ using GetAvailableCoreWebView2BrowserVersionString_t =
+ HRESULT(STDMETHODCALLTYPE*)(PCWSTR, LPWSTR*);
+
+ static constexpr auto CreateCoreWebView2EnvironmentWithOptions =
+ library_symbol<CreateCoreWebView2EnvironmentWithOptions_t>(
+ "CreateCoreWebView2EnvironmentWithOptions");
+ static constexpr auto GetAvailableCoreWebView2BrowserVersionString =
+ library_symbol<GetAvailableCoreWebView2BrowserVersionString_t>(
+ "GetAvailableCoreWebView2BrowserVersionString");
+ } // namespace webview2_symbols
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+
+ class loader
+ {
+ public:
+ HRESULT create_environment_with_options(
+ PCWSTR browser_dir, PCWSTR user_data_dir,
+ ICoreWebView2EnvironmentOptions* env_options,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
+ const
+ {
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ if (m_lib.is_loaded())
+ {
+ if (auto fn = m_lib.get(
+ webview2_symbols::CreateCoreWebView2EnvironmentWithOptions))
+ {
+ return fn(browser_dir, user_data_dir, env_options, created_handler);
+ }
+ }
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ return create_environment_with_options_impl(browser_dir, user_data_dir,
+ env_options, created_handler);
+#else
+ return S_FALSE;
+#endif
+#else
+ return ::CreateCoreWebView2EnvironmentWithOptions(browser_dir, user_data_dir,
+ env_options, created_handler);
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+ }
+
+ HRESULT
+ get_available_browser_version_string(PCWSTR browser_dir, LPWSTR* version) const
+ {
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ if (m_lib.is_loaded())
+ {
+ if (auto fn = m_lib.get(
+ webview2_symbols::GetAvailableCoreWebView2BrowserVersionString))
+ {
+ return fn(browser_dir, version);
+ }
+ }
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ return get_available_browser_version_string_impl(browser_dir, version);
+#else
+ return S_FALSE;
+#endif
+#else
+ return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version);
+#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
+ }
+
+ private:
+#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
+ struct client_info_t
+ {
+ bool found = false;
+ std::wstring dll_path;
+ std::wstring version;
+ webview2_runtime_type runtime_type;
+ };
+
+ HRESULT create_environment_with_options_impl(
+ PCWSTR browser_dir, PCWSTR user_data_dir,
+ ICoreWebView2EnvironmentOptions* env_options,
+ ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler)
+ const
+ {
+ auto found_client = find_available_client(browser_dir);
+ if (!found_client.found)
+ {
+ return -1;
+ }
+ auto client_dll = native_library(found_client.dll_path.c_str());
+ if (auto fn = client_dll.get(
+ webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal))
+ {
+ return fn(true, found_client.runtime_type, user_data_dir, env_options,
+ created_handler);
+ }
+ if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow))
+ {
+ if (!fn())
+ {
+ client_dll.detach();
+ }
+ }
+ return ERROR_SUCCESS;
+ }
+
+ HRESULT
+ get_available_browser_version_string_impl(PCWSTR browser_dir, LPWSTR* version) const
+ {
+ if (!version)
+ {
+ return -1;
+ }
+ auto found_client = find_available_client(browser_dir);
+ if (!found_client.found)
+ {
+ return -1;
+ }
+ auto info_length_bytes =
+ found_client.version.size() * sizeof(found_client.version[0]);
+ auto info = static_cast<LPWSTR>(CoTaskMemAlloc(info_length_bytes));
+ if (!info)
+ {
+ return -1;
+ }
+ CopyMemory(info, found_client.version.c_str(), info_length_bytes);
+ *version = info;
+ return 0;
+ }
+
+ client_info_t find_available_client(PCWSTR browser_dir) const
+ {
+ if (browser_dir)
+ {
+ return find_embedded_client(api_version, browser_dir);
+ }
+ auto found_client =
+ find_installed_client(api_version, true, default_release_channel_guid);
+ if (!found_client.found)
+ {
+ found_client =
+ find_installed_client(api_version, false, default_release_channel_guid);
+ }
+ return found_client;
+ }
+
+ std::wstring make_client_dll_path(const std::wstring& dir) const
+ {
+ auto dll_path = dir;
+ if (!dll_path.empty())
+ {
+ auto last_char = dir[dir.size() - 1];
+ if (last_char != L'\\' && last_char != L'/')
+ {
+ dll_path += L'\\';
+ }
+ }
+ dll_path += L"EBWebView\\";
+#if defined(_M_X64) || defined(__x86_64__)
+ dll_path += L"x64";
+#elif defined(_M_IX86) || defined(__i386__)
+ dll_path += L"x86";
+#elif defined(_M_ARM64) || defined(__aarch64__)
+ dll_path += L"arm64";
+#else
+#error WebView2 integration for this platform is not yet supported.
+#endif
+ dll_path += L"\\EmbeddedBrowserWebView.dll";
+ return dll_path;
+ }
+
+ client_info_t find_installed_client(unsigned int min_api_version, bool system,
+ const std::wstring& release_channel) const
+ {
+ std::wstring sub_key = client_state_reg_sub_key;
+ sub_key += release_channel;
+ auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+ reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY);
+ if (!key.is_open())
+ {
+ return {};
+ }
+ auto ebwebview_value = key.query_string(L"EBWebView");
+
+ auto client_version_string = get_last_native_path_component(ebwebview_value);
+ auto client_version = parse_version(client_version_string);
+ if (client_version[2] < min_api_version)
+ {
+ // Our API version is greater than the runtime API version.
+ return {};
+ }
+
+ auto client_dll_path = make_client_dll_path(ebwebview_value);
+ return { true, client_dll_path, client_version_string,
+ webview2_runtime_type::installed };
+ }
+
+ client_info_t find_embedded_client(unsigned int min_api_version,
+ const std::wstring& dir) const
+ {
+ auto client_dll_path = make_client_dll_path(dir);
+
+ auto client_version_string = get_file_version_string(client_dll_path);
+ auto client_version = parse_version(client_version_string);
+ if (client_version[2] < min_api_version)
+ {
+ // Our API version is greater than the runtime API version.
+ return {};
+ }
+
+ return { true, client_dll_path, client_version_string,
+ webview2_runtime_type::embedded };
+ }
+
+ // The minimum WebView2 API version we need regardless of the SDK release
+ // actually used. The number comes from the SDK release version,
+ // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater
+ // than or equal to this number. The Edge browser webview client must
+ // have a number greater than or equal to this number.
+ static constexpr unsigned int api_version = 1150;
+
+ static constexpr auto client_state_reg_sub_key =
+ L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\";
+
+ // GUID for the stable release channel.
+ static constexpr auto stable_release_guid =
+ L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
+
+ static constexpr auto default_release_channel_guid = stable_release_guid;
+#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
+
+#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
+ native_library m_lib{ L"WebView2Loader.dll" };
+#endif
+ };
+
+ namespace cast_info
+ {
+ static constexpr auto controller_completed =
+ cast_info_t<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>{
+ IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler
+ };
+
+ static constexpr auto environment_completed =
+ cast_info_t<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>{
+ IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
+ };
+
+ static constexpr auto message_received =
+ cast_info_t<ICoreWebView2WebMessageReceivedEventHandler>{
+ IID_ICoreWebView2WebMessageReceivedEventHandler
+ };
+
+ static constexpr auto permission_requested =
+ cast_info_t<ICoreWebView2PermissionRequestedEventHandler>{
+ IID_ICoreWebView2PermissionRequestedEventHandler
+ };
+ } // namespace cast_info
+ } // namespace mswebview2
+
+ class webview2_com_handler
+ : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
+ public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
+ public ICoreWebView2WebMessageReceivedEventHandler,
+ public ICoreWebView2PermissionRequestedEventHandler,
+ public ICoreWebView2NavigationCompletedEventHandler
+ {
+ using webview2_com_handler_cb_t =
+ std::function<void(ICoreWebView2Controller*, ICoreWebView2* webview)>;
+
+ public:
+ webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
+ : m_window(hwnd), m_msgCb(msgCb), m_cb(cb)
+ {
+ }
+
+ virtual ~webview2_com_handler() = default;
+ webview2_com_handler(const webview2_com_handler& other) = delete;
+ webview2_com_handler& operator=(const webview2_com_handler& other) = delete;
+ webview2_com_handler(webview2_com_handler&& other) = delete;
+ webview2_com_handler& operator=(webview2_com_handler&& other) = delete;
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return ++m_ref_count;
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ if (m_ref_count > 1)
+ {
+ return --m_ref_count;
+ }
+ delete this;
+ return 0;
+ }
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv)
+ {
+ using namespace mswebview2::cast_info;
+
+ if (!ppv)
+ {
+ return E_POINTER;
+ }
+
+ // All of the COM interfaces we implement should be added here regardless
+ // of whether they are required.
+ // This is just to be on the safe side in case the WebView2 Runtime ever
+ // requests a pointer to an interface we implement.
+ // The WebView2 Runtime must at the very least be able to get a pointer to
+ // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
+ // our custom WebView2 loader implementation, and observations have shown
+ // that it is the only interface requested in this case. None have been
+ // observed to be requested when using the official WebView2 loader.
+
+ if (cast_if_equal_iid(riid, controller_completed, ppv) ||
+ cast_if_equal_iid(riid, environment_completed, ppv) ||
+ cast_if_equal_iid(riid, message_received, ppv) ||
+ cast_if_equal_iid(riid, permission_requested, ppv))
+ {
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env)
+ {
+ if (SUCCEEDED(res))
+ {
+ res = env->CreateCoreWebView2Controller(m_window, this);
+ if (SUCCEEDED(res))
+ {
+ return S_OK;
+ }
+ }
+ try_create_environment();
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller)
+ {
+ if (FAILED(res))
+ {
+ // See try_create_environment() regarding
+ // HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
+ // The result is E_ABORT if the parent window has been destroyed already.
+ switch (res)
+ {
+ case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
+ case E_ABORT:
+ return S_OK;
+ }
+ try_create_environment();
+ return S_OK;
+ }
+
+ ICoreWebView2* webview;
+ ::EventRegistrationToken token;
+ controller->get_CoreWebView2(&webview);
+ webview->add_WebMessageReceived(this, &token);
+ webview->add_PermissionRequested(this, &token);
+ webview->add_NavigationCompleted(this, &token);
+
+ m_cb(controller, webview);
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2WebMessageReceivedEventArgs* args)
+ {
+ LPWSTR message;
+ args->TryGetWebMessageAsString(&message);
+ m_msgCb(narrow_string(message));
+ sender->PostWebMessageAsString(message);
+
+ CoTaskMemFree(message);
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2PermissionRequestedEventArgs* args)
+ {
+ COREWEBVIEW2_PERMISSION_KIND kind;
+ args->get_PermissionKind(&kind);
+ if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ)
+ {
+ args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
+ }
+ return S_OK;
+ }
+ HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender,
+ ICoreWebView2NavigationCompletedEventArgs* args)
+ {
+ PWSTR uri = nullptr;
+ auto hr = sender->get_Source(&uri);
+ if (SUCCEEDED(hr))
+ {
+ auto curi = std::wstring_convert<std::codecvt_utf8<wchar_t> >().to_bytes(uri);
+ if (navigateCallback)
+ navigateCallback(curi, navigateCallbackArg);
+ }
+ CoTaskMemFree(uri);
+ return hr;
+ }
+
+ // Checks whether the specified IID equals the IID of the specified type and
+ // if so casts the "this" pointer to T and returns it. Returns nullptr on
+ // mismatching IIDs.
+ // If ppv is specified then the pointer will also be assigned to *ppv.
+ template <typename T>
+ T* cast_if_equal_iid(REFIID riid, const cast_info_t<T>& info,
+ LPVOID* ppv = nullptr) noexcept
+ {
+ T* ptr = nullptr;
+ if (IsEqualIID(riid, info.iid))
+ {
+ ptr = static_cast<T*>(this);
+ ptr->AddRef();
+ }
+ if (ppv)
+ {
+ *ppv = ptr;
+ }
+ return ptr;
+ }
+
+ // Set the function that will perform the initiating logic for creating
+ // the WebView2 environment.
+ void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept
+ {
+ m_attempt_handler = attempt_handler;
+ }
+
+ // Retry creating a WebView2 environment.
+ // The initiating logic for creating the environment is defined by the
+ // caller of set_attempt_handler().
+ void try_create_environment() noexcept
+ {
+ // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
+ // a running instance using the same user data folder exists, and the
+ // Environment objects have different EnvironmentOptions.
+ // Source:
+ // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
+ if (m_attempts < m_max_attempts)
+ {
+ ++m_attempts;
+ auto res = m_attempt_handler();
+ if (SUCCEEDED(res))
+ {
+ return;
+ }
+ // Not entirely sure if this error code only applies to
+ // CreateCoreWebView2Controller so we check here as well.
+ if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE))
+ {
+ return;
+ }
+ try_create_environment();
+ return;
+ }
+ // Give up.
+ m_cb(nullptr, nullptr);
+ }
+
+ void STDMETHODCALLTYPE add_navigate_listener(
+ std::function<void(const std::string&, void*)> callback, void* arg)
+ {
+ navigateCallback = std::move(callback);
+ navigateCallbackArg = arg;
+ }
+
+ private:
+ HWND m_window;
+ msg_cb_t m_msgCb;
+ webview2_com_handler_cb_t m_cb;
+ std::atomic<ULONG> m_ref_count{ 1 };
+ std::function<HRESULT()> m_attempt_handler;
+ unsigned int m_max_attempts = 5;
+ unsigned int m_attempts = 0;
+ void* navigateCallbackArg = nullptr;
+ std::function<void(const std::string&, void*)> navigateCallback = 0;
+ };
+
+ class win32_edge_engine
+ {
+ public:
+ win32_edge_engine(bool debug, void* window)
+ {
+ if (!is_webview2_available())
+ {
+ return;
+ }
+ if (!m_com_init.is_initialized())
+ {
+ return;
+ }
+ enable_dpi_awareness();
+ if (window == nullptr)
+ {
+ HINSTANCE hInstance = GetModuleHandle(nullptr);
+ HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON,
+ GetSystemMetrics(SM_CXICON),
+ GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
+
+ WNDCLASSEXW wc;
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.hInstance = hInstance;
+ wc.lpszClassName = L"webview";
+ wc.hIcon = icon;
+ wc.lpfnWndProc =
+ (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
+ auto w = (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ switch (msg)
+ {
+ case WM_SIZE:
+ w->resize(hwnd);
+ break;
+ case WM_CLOSE:
+ DestroyWindow(hwnd);
+ break;
+ case WM_DESTROY:
+ w->terminate();
+ break;
+ case WM_GETMINMAXINFO:
+ {
+ auto lpmmi = (LPMINMAXINFO)lp;
+ if (w == nullptr)
+ {
+ return 0;
+ }
+ if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0)
+ {
+ lpmmi->ptMaxSize = w->m_maxsz;
+ lpmmi->ptMaxTrackSize = w->m_maxsz;
+ }
+ if (w->m_minsz.x > 0 && w->m_minsz.y > 0)
+ {
+ lpmmi->ptMinTrackSize = w->m_minsz;
+ }
+ }
+ break;
+ default:
+ return DefWindowProcW(hwnd, msg, wp, lp);
+ }
+ return 0;
+ });
+ RegisterClassExW(&wc);
+ m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
+ CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance,
+ nullptr);
+ if (m_window == nullptr)
+ {
+ return;
+ }
+ SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
+ }
+ else
+ {
+ m_window = *(static_cast<HWND*>(window));
+ }
+
+ ShowWindow(m_window, SW_SHOW);
+ UpdateWindow(m_window);
+ SetFocus(m_window);
+
+ auto cb = std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
+
+ embed(m_window, debug, cb);
+ resize(m_window);
+ m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
+ }
+
+ virtual ~win32_edge_engine()
+ {
+ if (m_com_handler)
+ {
+ m_com_handler->Release();
+ m_com_handler = nullptr;
+ }
+ if (m_webview)
+ {
+ m_webview->Release();
+ m_webview = nullptr;
+ }
+ if (m_controller)
+ {
+ m_controller->Release();
+ m_controller = nullptr;
+ }
+ }
+
+ win32_edge_engine(const win32_edge_engine& other) = delete;
+ win32_edge_engine& operator=(const win32_edge_engine& other) = delete;
+ win32_edge_engine(win32_edge_engine&& other) = delete;
+ win32_edge_engine& operator=(win32_edge_engine&& other) = delete;
+
+ void run()
+ {
+ MSG msg;
+ BOOL res;
+ while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1)
+ {
+ if (msg.hwnd)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ continue;
+ }
+ if (msg.message == WM_APP)
+ {
+ auto f = (dispatch_fn_t*)(msg.lParam);
+ (*f)();
+ delete f;
+ }
+ else if (msg.message == WM_QUIT)
+ {
+ return;
+ }
+ }
+ }
+ void* window()
+ {
+ return (void*)m_window;
+ }
+ void terminate()
+ {
+ PostQuitMessage(0);
+ }
+ void dispatch(dispatch_fn_t f)
+ {
+ PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
+ }
+
+ void set_title(const std::string& title)
+ {
+ SetWindowTextW(m_window, widen_string(title).c_str());
+ }
+
+ void set_size(int width, int height, int hints)
+ {
+ auto style = GetWindowLong(m_window, GWL_STYLE);
+ if (hints == WEBVIEW_HINT_FIXED)
+ {
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+ }
+ else
+ {
+ style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
+ }
+ SetWindowLong(m_window, GWL_STYLE, style);
+
+ if (hints == WEBVIEW_HINT_MAX)
+ {
+ m_maxsz.x = width;
+ m_maxsz.y = height;
+ }
+ else if (hints == WEBVIEW_HINT_MIN)
+ {
+ m_minsz.x = width;
+ m_minsz.y = height;
+ }
+ else
+ {
+ RECT r;
+ r.left = r.top = 0;
+ r.right = width;
+ r.bottom = height;
+ AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
+ SetWindowPos(m_window, nullptr, r.left, r.top, r.right - r.left,
+ r.bottom - r.top,
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
+ resize(m_window);
+ }
+ }
+
+ void navigate(const std::string& url)
+ {
+ auto wurl = widen_string(url);
+ m_webview->Navigate(wurl.c_str());
+ }
+
+ void init(const std::string& js)
+ {
+ auto wjs = widen_string(js);
+ m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr);
+ }
+
+ void eval(const std::string& js)
+ {
+ auto wjs = widen_string(js);
+ m_webview->ExecuteScript(wjs.c_str(), nullptr);
+ }
+
+ void add_navigate_listener(std::function<void(const std::string&, void*)> callback,
+ void* arg)
+ {
+ m_com_handler->add_navigate_listener(callback, arg);
+ }
+
+ void set_html(const std::string& html)
+ {
+ m_webview->NavigateToString(widen_string(html).c_str());
+ }
+
+ private:
+ bool embed(HWND wnd, bool debug, msg_cb_t cb)
+ {
+ std::atomic_flag flag = ATOMIC_FLAG_INIT;
+ flag.test_and_set();
+
+ wchar_t currentExePath[MAX_PATH];
+ GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
+ wchar_t* currentExeName = PathFindFileNameW(currentExePath);
+
+ wchar_t dataPath[MAX_PATH];
+ if (!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath)))
+ {
+ return false;
+ }
+ wchar_t userDataFolder[MAX_PATH];
+ PathCombineW(userDataFolder, dataPath, currentExeName);
+
+ m_com_handler = new webview2_com_handler(
+ wnd, cb, [&](ICoreWebView2Controller* controller, ICoreWebView2* webview) {
+ if (!controller || !webview)
+ {
+ flag.clear();
+ return;
+ }
+ controller->AddRef();
+ webview->AddRef();
+ m_controller = controller;
+ m_webview = webview;
+ flag.clear();
+ });
+
+ m_com_handler->set_attempt_handler([&] {
+ return m_webview2_loader.create_environment_with_options(
+ nullptr, userDataFolder, nullptr, m_com_handler);
+ });
+ m_com_handler->try_create_environment();
+
+ MSG msg = {};
+ while (flag.test_and_set() && GetMessage(&msg, nullptr, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ if (!m_controller || !m_webview)
+ {
+ return false;
+ }
+ ICoreWebView2Settings* settings = nullptr;
+ auto res = m_webview->get_Settings(&settings);
+ if (res != S_OK)
+ {
+ return false;
+ }
+ res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
+ if (res != S_OK)
+ {
+ return false;
+ }
+ init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
+ return true;
+ }
+
+ void resize(HWND wnd)
+ {
+ if (m_controller == nullptr)
+ {
+ return;
+ }
+ RECT bounds;
+ GetClientRect(wnd, &bounds);
+ m_controller->put_Bounds(bounds);
+ }
+
+ bool is_webview2_available() const noexcept
+ {
+ LPWSTR version_info = nullptr;
+ auto res =
+ m_webview2_loader.get_available_browser_version_string(nullptr, &version_info);
+ // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
+ // if the WebView2 runtime is not installed.
+ auto ok = SUCCEEDED(res) && version_info;
+ if (version_info)
+ {
+ CoTaskMemFree(version_info);
+ }
+ return ok;
+ }
+
+ virtual void on_message(const std::string& msg) = 0;
+
+ // The app is expected to call CoInitializeEx before
+ // CreateCoreWebView2EnvironmentWithOptions.
+ // Source:
+ // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
+ com_init_wrapper m_com_init{ COINIT_APARTMENTTHREADED };
+ HWND m_window = nullptr;
+ POINT m_minsz = POINT{ 0, 0 };
+ POINT m_maxsz = POINT{ 0, 0 };
+ DWORD m_main_thread = GetCurrentThreadId();
+ ICoreWebView2* m_webview = nullptr;
+ ICoreWebView2Controller* m_controller = nullptr;
+ webview2_com_handler* m_com_handler = nullptr;
+ mswebview2::loader m_webview2_loader;
+ };
+
+ } // namespace detail
+
+ using browser_engine = detail::win32_edge_engine;
+
+} // namespace webview
+
+#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
+
+namespace webview
+{
+
+ class webview : public browser_engine
+ {
+ public:
+ webview(bool debug = false, void* wnd = nullptr) : browser_engine(debug, wnd)
+ {
+ }
+
+ void navigate(const std::string& url)
+ {
+ if (url.empty())
+ {
+ browser_engine::navigate("about:blank");
+ return;
+ }
+ browser_engine::navigate(url);
+ }
+
+ using binding_t = std::function<void(std::string, std::string, void*)>;
+ class binding_ctx_t
+ {
+ public:
+ binding_ctx_t(binding_t callback, void* arg) : callback(callback), arg(arg)
+ {
+ }
+ // This function is called upon execution of the bound JS function
+ binding_t callback;
+ // This user-supplied argument is passed to the callback
+ void* arg;
+ };
+
+ using sync_binding_t = std::function<std::string(std::string)>;
+
+ // Synchronous bind
+ void bind(const std::string& name, sync_binding_t fn)
+ {
+ auto wrapper = [this, fn](const std::string& seq, const std::string& req,
+ void* /*arg*/) { resolve(seq, 0, fn(req)); };
+ bind(name, wrapper, nullptr);
+ }
+
+ // Asynchronous bind
+ void bind(const std::string& name, binding_t fn, void* arg)
+ {
+ if (bindings.count(name) > 0)
+ {
+ return;
+ }
+ bindings.emplace(name, binding_ctx_t(fn, arg));
+ auto js = "(function() { var name = '" + name + "';" + R""(
+ var RPC = window._rpc = (window._rpc || {nextSeq: 1});
+ window[name] = function() {
+ var seq = RPC.nextSeq++;
+ var promise = new Promise(function(resolve, reject) {
+ RPC[seq] = {
+ resolve: resolve,
+ reject: reject,
+ };
+ });
+ window.external.invoke(JSON.stringify({
+ id: seq,
+ method: name,
+ params: Array.prototype.slice.call(arguments),
+ }));
+ return promise;
+ }
+ })())"";
+ init(js);
+ eval(js);
+ }
+
+ void unbind(const std::string& name)
+ {
+ auto found = bindings.find(name);
+ if (found != bindings.end())
+ {
+ auto js = "delete window['" + name + "'];";
+ init(js);
+ eval(js);
+ bindings.erase(found);
+ }
+ }
+
+ void resolve(const std::string& seq, int status, const std::string& result)
+ {
+ dispatch([seq, status, result, this]() {
+ if (status == 0)
+ {
+ eval("window._rpc[" + seq + "].resolve(" + result + "); delete window._rpc[" +
+ seq + "]");
+ }
+ else
+ {
+ eval("window._rpc[" + seq + "].reject(" + result + "); delete window._rpc[" +
+ seq + "]");
+ }
+ });
+ }
+
+ private:
+ void on_message(const std::string& msg) override
+ {
+ auto seq = detail::json_parse(msg, "id", 0);
+ auto name = detail::json_parse(msg, "method", 0);
+ auto args = detail::json_parse(msg, "params", 0);
+ auto found = bindings.find(name);
+ if (found == bindings.end())
+ {
+ return;
+ }
+ const auto& context = found->second;
+ context.callback(seq, args, context.arg);
+ }
+
+ std::map<std::string, binding_ctx_t> bindings;
+ };
+} // namespace webview
+
+WEBVIEW_API webview_t webview_create(int debug, void* wnd)
+{
+ auto w = new webview::webview(debug, wnd);
+ if (!w->window())
+ {
+ delete w;
+ return nullptr;
+ }
+ return w;
+}
+
+WEBVIEW_API void webview_destroy(webview_t w)
+{
+ delete static_cast<webview::webview*>(w);
+}
+
+WEBVIEW_API void webview_run(webview_t w)
+{
+ static_cast<webview::webview*>(w)->run();
+}
+
+WEBVIEW_API void webview_terminate(webview_t w)
+{
+ static_cast<webview::webview*>(w)->terminate();
+}
+
+WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void*), void* arg)
+{
+ static_cast<webview::webview*>(w)->dispatch([=]() { fn(w, arg); });
+}
+
+WEBVIEW_API void* webview_get_window(webview_t w)
+{
+ return static_cast<webview::webview*>(w)->window();
+}
+
+WEBVIEW_API void webview_set_title(webview_t w, const char* title)
+{
+ static_cast<webview::webview*>(w)->set_title(title);
+}
+
+WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints)
+{
+ static_cast<webview::webview*>(w)->set_size(width, height, hints);
+}
+
+WEBVIEW_API void webview_navigate(webview_t w, const char* url)
+{
+ static_cast<webview::webview*>(w)->navigate(url);
+}
+
+WEBVIEW_API void webview_set_html(webview_t w, const char* html)
+{
+ static_cast<webview::webview*>(w)->set_html(html);
+}
+
+WEBVIEW_API void webview_init(webview_t w, const char* js)
+{
+ static_cast<webview::webview*>(w)->init(js);
+}
+
+WEBVIEW_API void webview_eval(webview_t w, const char* js)
+{
+ static_cast<webview::webview*>(w)->eval(js);
+}
+
+WEBVIEW_API void webview_bind(webview_t w, const char* name,
+ void (*fn)(const char* seq, const char* req, void* arg), void* arg)
+{
+ static_cast<webview::webview*>(w)->bind(
+ name,
+ [=](const std::string& seq, const std::string& req, void* arg) {
+ fn(seq.c_str(), req.c_str(), arg);
+ },
+ arg);
+}
+
+WEBVIEW_API void webview_unbind(webview_t w, const char* name)
+{
+ static_cast<webview::webview*>(w)->unbind(name);
+}
+
+WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result)
+{
+ static_cast<webview::webview*>(w)->resolve(seq, status, result);
+}
+
+WEBVIEW_API const webview_version_info_t* webview_version()
+{
+ return &webview::detail::library_version_info;
+}
+
+#endif /* WEBVIEW_HEADER */
+#endif /* __cplusplus */
+#endif /* WEBVIEW_H */
diff --git a/client/SDL/aad/wrapper/webview_impl.cpp b/client/SDL/aad/wrapper/webview_impl.cpp
new file mode 100644
index 0000000..5f4d3d5
--- /dev/null
+++ b/client/SDL/aad/wrapper/webview_impl.cpp
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Popup browser for AAD authentication
+ *
+ * Copyright 2023 Isaac Klein <fifthdegree@protonmail.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 "webview.h"
+
+#include <assert.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <regex>
+#include <sstream>
+#include "../webview_impl.hpp"
+
+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 std::map<std::string, std::string> urlsplit(const std::string& url)
+{
+ auto pos = url.find("?");
+ if (pos == std::string::npos)
+ return {};
+ auto surl = url.substr(pos);
+ auto args = split(surl, "&");
+
+ std::map<std::string, std::string> argmap;
+ for (const auto& arg : args)
+ {
+ auto kv = split(arg, "=");
+ if (kv.size() == 2)
+ argmap.insert({ kv[0], kv[1] });
+ }
+ return argmap;
+}
+
+static void fkt(const std::string& url, void* arg)
+{
+ auto args = urlsplit(url);
+ auto val = args.find("code");
+ if (val == args.end())
+ return;
+
+ assert(arg);
+ auto rcode = static_cast<std::string*>(arg);
+ *rcode = val->second;
+}
+
+bool webview_impl_run(const std::string& title, const std::string& url, std::string& code)
+{
+ webview::webview w(false, nullptr);
+
+ w.set_title(title);
+ w.set_size(640, 480, WEBVIEW_HINT_NONE);
+
+ std::string scheme;
+ w.add_scheme_handler("ms-appx-web", fkt, &scheme);
+ w.add_navigate_listener(fkt, &code);
+ w.navigate(url);
+ w.run();
+ return !code.empty();
+}
diff --git a/client/SDL/dialogs/CMakeLists.txt b/client/SDL/dialogs/CMakeLists.txt
new file mode 100644
index 0000000..4cf2a16
--- /dev/null
+++ b/client/SDL/dialogs/CMakeLists.txt
@@ -0,0 +1,75 @@
+set(SRCS
+ sdl_button.hpp
+ sdl_button.cpp
+ sdl_buttons.hpp
+ sdl_buttons.cpp
+ sdl_dialogs.cpp
+ sdl_dialogs.hpp
+ sdl_widget.hpp
+ sdl_widget.cpp
+ sdl_input.hpp
+ sdl_input.cpp
+ sdl_input_widgets.cpp
+ sdl_input_widgets.hpp
+ sdl_select.hpp
+ sdl_select.cpp
+ sdl_selectlist.hpp
+ sdl_selectlist.cpp
+ sdl_connection_dialog.cpp
+ sdl_connection_dialog.hpp
+)
+
+list(APPEND LIBS
+ sdl_client_res
+ winpr
+)
+
+if (NOT WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${SDL2_STATIC_LIBRARIES})
+else()
+ list(APPEND LIBS ${SDL2_LIBRARIES})
+endif()
+
+macro(find_sdl_component name)
+ find_package(${name})
+ if (NOT ${name}_FOUND)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(${name} REQUIRED ${name})
+
+ if (BUILD_SHARED_LIBS)
+ list(APPEND LIBS ${${name}_LIBRARIES})
+ link_directories(${${name}_LIBRARY_DIRS})
+ include_directories(${${name}_INCLUDE_DIRS})
+ else()
+ list(APPEND LIBS ${${name}_STATIC_LIBRARIES})
+ link_directories(${${name}_STATIC_LIBRARY_DIRS})
+ include_directories(${${name}_STATIC_INCLUDE_DIRS})
+ endif()
+ else()
+ if (WITH_SDL_LINK_SHARED)
+ list(APPEND LIBS ${name}::${name})
+ else()
+ list(APPEND LIBS ${name}::${name}-static)
+ endif()
+ endif()
+endmacro()
+
+find_sdl_component(SDL2_ttf)
+
+option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF)
+if (WITH_SDL_IMAGE_DIALOGS)
+ find_sdl_component(SDL2_image)
+ add_definitions(-DWITH_SDL_IMAGE_DIALOGS)
+endif()
+
+add_subdirectory(res)
+
+add_library(dialogs STATIC
+ ${SRCS}
+)
+
+target_link_libraries(dialogs PRIVATE ${LIBS})
+
+if(BUILD_TESTING)
+# add_subdirectory(test)
+endif()
diff --git a/client/SDL/dialogs/font/OFL.txt b/client/SDL/dialogs/font/OFL.txt
new file mode 100644
index 0000000..9b448d4
--- /dev/null
+++ b/client/SDL/dialogs/font/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..5bda9cc
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..e4142bf
--- /dev/null
+++ b/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
Binary files differ
diff --git a/client/SDL/dialogs/font/README.txt b/client/SDL/dialogs/font/README.txt
new file mode 100644
index 0000000..2548322
--- /dev/null
+++ b/client/SDL/dialogs/font/README.txt
@@ -0,0 +1,100 @@
+Open Sans Variable Font
+=======================
+
+This download contains Open Sans as both variable fonts and static fonts.
+
+Open Sans is a variable font with these axes:
+ wdth
+ wght
+
+This means all the styles are contained in these files:
+ OpenSans-VariableFont_wdth,wght.ttf
+ OpenSans-Italic-VariableFont_wdth,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Open Sans:
+ static/OpenSans_Condensed-Light.ttf
+ static/OpenSans_Condensed-Regular.ttf
+ static/OpenSans_Condensed-Medium.ttf
+ static/OpenSans_Condensed-SemiBold.ttf
+ static/OpenSans_Condensed-Bold.ttf
+ static/OpenSans_Condensed-ExtraBold.ttf
+ static/OpenSans_SemiCondensed-Light.ttf
+ static/OpenSans_SemiCondensed-Regular.ttf
+ static/OpenSans_SemiCondensed-Medium.ttf
+ static/OpenSans_SemiCondensed-SemiBold.ttf
+ static/OpenSans_SemiCondensed-Bold.ttf
+ static/OpenSans_SemiCondensed-ExtraBold.ttf
+ static/OpenSans-Light.ttf
+ static/OpenSans-Regular.ttf
+ static/OpenSans-Medium.ttf
+ static/OpenSans-SemiBold.ttf
+ static/OpenSans-Bold.ttf
+ static/OpenSans-ExtraBold.ttf
+ static/OpenSans_Condensed-LightItalic.ttf
+ static/OpenSans_Condensed-Italic.ttf
+ static/OpenSans_Condensed-MediumItalic.ttf
+ static/OpenSans_Condensed-SemiBoldItalic.ttf
+ static/OpenSans_Condensed-BoldItalic.ttf
+ static/OpenSans_Condensed-ExtraBoldItalic.ttf
+ static/OpenSans_SemiCondensed-LightItalic.ttf
+ static/OpenSans_SemiCondensed-Italic.ttf
+ static/OpenSans_SemiCondensed-MediumItalic.ttf
+ static/OpenSans_SemiCondensed-SemiBoldItalic.ttf
+ static/OpenSans_SemiCondensed-BoldItalic.ttf
+ static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf
+ static/OpenSans-LightItalic.ttf
+ static/OpenSans-Italic.ttf
+ static/OpenSans-MediumItalic.ttf
+ static/OpenSans-SemiBoldItalic.ttf
+ static/OpenSans-BoldItalic.ttf
+ static/OpenSans-ExtraBoldItalic.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/client/SDL/dialogs/res/CMakeLists.txt b/client/SDL/dialogs/res/CMakeLists.txt
new file mode 100644
index 0000000..5591e4a
--- /dev/null
+++ b/client/SDL/dialogs/res/CMakeLists.txt
@@ -0,0 +1,89 @@
+
+add_executable(freerdp-res2bin
+ convert_res_to_c.cpp
+)
+
+set(SRCS
+ sdl_resource_manager.cpp
+ sdl_resource_manager.hpp
+)
+
+set(RES_SVG_FILES
+ ${CMAKE_SOURCE_DIR}/resources/FreeRDP_Icon.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_info.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_warning.svg
+ ${CMAKE_SOURCE_DIR}/resources/icon_error.svg
+)
+
+set(RES_FONT_FILES
+ ${CMAKE_SOURCE_DIR}/client/SDL/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf
+)
+
+macro(convert_to_bin FILE FILE_TYPE)
+ get_filename_component(FILE_NAME ${FILE} NAME)
+ string(REGEX REPLACE "[^a-zA-Z0-9]" "_" TARGET_NAME ${FILE_NAME})
+
+ set(FILE_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin)
+ set(FILE_BYPRODUCTS ${FILE_BIN_DIR}/${TARGET_NAME}.hpp ${FILE_BIN_DIR}/${TARGET_NAME}.cpp)
+
+ list(APPEND FACTORY_SRCS
+ ${FILE_BYPRODUCTS}
+ )
+
+ add_custom_command(
+ OUTPUT ${FILE_BYPRODUCTS}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${FILE_BIN_DIR}
+ COMMAND $<TARGET_FILE:freerdp-res2bin> ${FILE} ${FILE_TYPE} ${TARGET_NAME} ${FILE_BIN_DIR}
+ COMMENT "create image resources"
+ DEPENDS freerdp-res2bin
+ DEPENDS ${FILE}
+ )
+endmacro()
+
+option(SDL_USE_COMPILED_RESOURCES "Compile in images/fonts" ON)
+
+if (SDL_USE_COMPILED_RESOURCES)
+ list(APPEND SRCS
+ sdl_resource_file.cpp
+ sdl_resource_file.hpp
+ )
+
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ foreach(FILE ${RES_SVG_FILES})
+ convert_to_bin("${FILE}" "images")
+ endforeach()
+ endif()
+
+ foreach(FILE ${RES_FONT_FILES})
+ convert_to_bin("${FILE}" "fonts")
+ endforeach()
+ add_definitions(-DSDL_USE_COMPILED_RESOURCES)
+else()
+ set(SDL_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP)
+ if (WITH_BINARY_VERSIONING)
+ string(APPEND SDL_RESOURCE_ROOT "${PROJECT_VERSION_MAJOR}")
+ endif()
+
+ add_definitions(-DSDL_RESOURCE_ROOT="${SDL_RESOURCE_ROOT}")
+
+ if (WITH_SDL_IMAGE_DIALOGS)
+ install(
+ FILES ${RES_SVG_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/images
+ )
+ endif()
+
+ install(
+ FILES ${RES_FONT_FILES}
+ DESTINATION ${SDL_RESOURCE_ROOT}/fonts
+ )
+endif()
+
+add_library(sdl_client_res OBJECT
+ ${RES_FILES}
+ ${SRCS}
+ ${FACTORY_SRCS}
+)
+set_property(TARGET sdl_client_res PROPERTY POSITION_INDEPENDENT_CODE ON)
diff --git a/client/SDL/dialogs/res/convert_res_to_c.cpp b/client/SDL/dialogs/res/convert_res_to_c.cpp
new file mode 100644
index 0000000..07309d5
--- /dev/null
+++ b/client/SDL/dialogs/res/convert_res_to_c.cpp
@@ -0,0 +1,184 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <string>
+#include <fstream>
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#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
+
+static void usage(const char* prg)
+{
+ std::cerr << prg << " <file> <type> <class name> <dst path>" << std::endl;
+}
+
+static int write_comment_header(std::ostream& out, const fs::path& prg, const std::string& fname)
+{
+ out << "/* AUTOGENERATED file, do not edit" << std::endl
+ << " *" << std::endl
+ << " * generated by '" << prg.filename() << "'" << std::endl
+ << " *" << std::endl
+ << " * contains the converted file '" << fname << "'" << std::endl
+ << " */" << std::endl
+ << std::endl;
+ return 0;
+}
+
+static int write_cpp_header(std::ostream& out, const fs::path& prg, const fs::path& file,
+ const std::string& name, const std::string& type)
+{
+ auto fname = file.filename().string();
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#include <vector>" << std::endl
+ << "#include \"" << name << ".hpp\"" << std::endl
+ << std::endl
+ << "std::string " << name << "::name() {" << std::endl
+ << "return \"" << fname << "\";" << std::endl
+ << "}" << std::endl
+ << "std::string " << name << "::type() {" << std::endl
+ << "return \"" << type << "\";" << std::endl
+ << "}" << std::endl
+ << std::endl
+ << "const SDLResourceFile " << name << "::_initializer(type(), name(), init());"
+ << std::endl
+ << std::endl
+ << "std::vector<unsigned char> " << name << "::init() {" << std::endl
+ << "static const unsigned char data[] = {" << std::endl;
+
+ return 0;
+}
+
+static int readwrite(std::ofstream& out, std::ifstream& ifs)
+{
+ size_t pos = 0;
+ char c = 0;
+ while (ifs.read(&c, 1) && ifs.good())
+ {
+ unsigned val = c & 0xff;
+ out << "0x" << std::hex << std::setfill('0') << std::setw(2) << val;
+ if (ifs.peek() != EOF)
+ out << ",";
+ if ((pos++ % 16) == 15)
+ out << std::endl;
+ }
+
+ return 0;
+}
+
+static int write_cpp_trailer(std::ostream& out)
+{
+ out << std::endl;
+ out << "};" << std::endl;
+ out << std::endl;
+ out << "return std::vector<unsigned char>(data, data + sizeof(data));" << std::endl;
+ out << "}" << std::endl;
+ return 0;
+}
+
+static int write_hpp_header(const fs::path& prg, const fs::path& file, const std::string& name,
+ const std::string& fname)
+{
+ std::ofstream out(file, std::ios::out);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << file << "'" << std::endl;
+ return -1;
+ }
+ auto rc = write_comment_header(out, prg, fname);
+ if (rc != 0)
+ return rc;
+
+ out << "#pragma once" << std::endl
+ << std::endl
+ << "#include <vector>" << std::endl
+ << "#include <string>" << std::endl
+ << "#include \"sdl_resource_file.hpp\"" << std::endl
+ << std::endl
+ << "class " << name << " {" << std::endl
+ << "public:" << std::endl
+ << name << "() = delete;" << std::endl
+ << std::endl
+ << "static std::string name();" << std::endl
+ << "static std::string type();" << std::endl
+ << std::endl
+ << "private:" << std::endl
+ << "static std::vector<unsigned char> init();" << std::endl
+ << "static const SDLResourceFile _initializer;" << std::endl
+ << std::endl
+ << "};" << std::endl;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ fs::path prg(argv[0]);
+ if (argc != 5)
+ {
+ usage(argv[0]);
+ return -1;
+ }
+
+ fs::path file(argv[1]);
+ std::string etype = argv[2];
+ std::string cname = argv[3];
+ fs::path dst(argv[4]);
+ fs::path hdr(argv[4]);
+
+ dst /= cname + ".cpp";
+ hdr /= cname + ".hpp";
+
+ std::ofstream out;
+ out.open(dst);
+ if (!out.is_open())
+ {
+ std::cerr << "Failed to open output file '" << dst << "'" << std::endl;
+ return -2;
+ }
+
+ std::ifstream ifs(file, std::ios::in | std::ios::binary);
+ if (!ifs.is_open())
+ {
+ std::cerr << "Failed to open input file '" << file << "'" << std::endl;
+ return -3;
+ }
+
+ auto rc = write_cpp_header(out, prg, file, cname, etype);
+ if (rc != 0)
+ return -1;
+
+ rc = readwrite(out, ifs);
+ if (rc != 0)
+ return rc;
+
+ rc = write_cpp_trailer(out);
+ if (rc != 0)
+ return rc;
+ return write_hpp_header(prg, hdr, cname, file.filename().string());
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.cpp b/client/SDL/dialogs/res/sdl_resource_file.cpp
new file mode 100644
index 0000000..c48612d
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.cpp
@@ -0,0 +1,25 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 "sdl_resource_file.hpp"
+#include "sdl_resource_manager.hpp"
+
+SDLResourceFile::SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ SDLResourceManager::insert(type, id, data);
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_file.hpp b/client/SDL/dialogs/res/sdl_resource_file.hpp
new file mode 100644
index 0000000..5846921
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_file.hpp
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+class SDLResourceFile
+{
+ public:
+ SDLResourceFile(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+ virtual ~SDLResourceFile() = default;
+
+ private:
+ SDLResourceFile(const SDLResourceFile& other) = delete;
+ SDLResourceFile(const SDLResourceFile&& other) = delete;
+};
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.cpp b/client/SDL/dialogs/res/sdl_resource_manager.cpp
new file mode 100644
index 0000000..90ccf31
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.cpp
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 "sdl_resource_manager.hpp"
+#include <iostream>
+#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
+
+SDL_RWops* SDLResourceManager::get(const std::string& type, const std::string& id)
+{
+ std::string uuid = type + "/" + id;
+
+#if defined(SDL_USE_COMPILED_RESOURCES)
+ auto val = resources().find(uuid);
+ if (val == resources().end())
+ return nullptr;
+
+ const auto& v = val->second;
+ return SDL_RWFromConstMem(v.data(), v.size());
+#else
+ fs::path path(SDL_RESOURCE_ROOT);
+ path /= type;
+ path /= id;
+
+ if (!fs::exists(path))
+ {
+ std::cerr << "sdl-freerdp expects resource '" << uuid << "' at location "
+ << fs::absolute(path) << std::endl;
+ std::cerr << "file not found, application will fail" << std::endl;
+ }
+ return SDL_RWFromFile(path.native().c_str(), "rb");
+#endif
+}
+
+const std::string SDLResourceManager::typeFonts()
+{
+ return "fonts";
+}
+
+const std::string SDLResourceManager::typeImages()
+{
+ return "images";
+}
+
+void SDLResourceManager::insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data)
+{
+ std::string uuid = type + "/" + id;
+ resources().emplace(uuid, data);
+}
+
+std::map<std::string, std::vector<unsigned char>>& SDLResourceManager::resources()
+{
+
+ static std::map<std::string, std::vector<unsigned char>> resources = {};
+ return resources;
+}
diff --git a/client/SDL/dialogs/res/sdl_resource_manager.hpp b/client/SDL/dialogs/res/sdl_resource_manager.hpp
new file mode 100644
index 0000000..b4f463c
--- /dev/null
+++ b/client/SDL/dialogs/res/sdl_resource_manager.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+#pragma once
+
+#include <string>
+#include <map>
+#include <vector>
+#include <SDL.h>
+
+class SDLResourceManager
+{
+ friend class SDLResourceFile;
+
+ public:
+ static SDL_RWops* get(const std::string& type, const std::string& id);
+
+ static const std::string typeFonts();
+ static const std::string typeImages();
+
+ protected:
+ static void insert(const std::string& type, const std::string& id,
+ const std::vector<unsigned char>& data);
+
+ private:
+ SDLResourceManager() = delete;
+ SDLResourceManager(const SDLResourceManager& other) = delete;
+ SDLResourceManager(const SDLResourceManager&& other) = delete;
+ ~SDLResourceManager() = delete;
+
+ static std::map<std::string, std::vector<unsigned char>>& resources();
+};
diff --git a/client/SDL/dialogs/sdl_button.cpp b/client/SDL/dialogs/sdl_button.cpp
new file mode 100644
index 0000000..cfa2107
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.cpp
@@ -0,0 +1,71 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <cassert>
+
+#include "sdl_button.hpp"
+
+static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 };
+static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlButton::SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, false), _name(label), _id(id)
+{
+ assert(renderer);
+
+ update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+SdlButton::SdlButton(SdlButton&& other) noexcept
+ : SdlWidget(std::move(other)), _name(std::move(other._name)), _id(std::move(other._id))
+{
+}
+
+bool SdlButton::highlight(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonhighlightcolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::mouseover(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { buttonbackgroundcolor, buttonmouseovercolor };
+ if (!fill(renderer, colors))
+ return false;
+ return update_text(renderer, _name, buttonfontcolor);
+}
+
+bool SdlButton::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor);
+}
+
+int SdlButton::id() const
+{
+ return _id;
+}
diff --git a/client/SDL/dialogs/sdl_button.hpp b/client/SDL/dialogs/sdl_button.hpp
new file mode 100644
index 0000000..350e7db
--- /dev/null
+++ b/client/SDL/dialogs/sdl_button.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <string>
+
+#include "sdl_widget.hpp"
+
+class SdlButton : public SdlWidget
+{
+ public:
+ SdlButton(SDL_Renderer* renderer, const std::string& label, int id, const SDL_Rect& rect);
+ SdlButton(SdlButton&& other) noexcept;
+ ~SdlButton() override = default;
+
+ bool highlight(SDL_Renderer* renderer);
+ bool mouseover(SDL_Renderer* renderer);
+ bool update(SDL_Renderer* renderer);
+
+ [[nodiscard]] int id() const;
+
+ private:
+ SdlButton(const SdlButton& other) = delete;
+
+ private:
+ std::string _name;
+ int _id;
+};
diff --git a/client/SDL/dialogs/sdl_buttons.cpp b/client/SDL/dialogs/sdl_buttons.cpp
new file mode 100644
index 0000000..8190cbe
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.cpp
@@ -0,0 +1,105 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_buttons.hpp"
+
+static const Uint32 hpadding = 10;
+
+bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY,
+ Sint32 width, Sint32 height)
+{
+ assert(renderer);
+ assert(width >= 0);
+ assert(height >= 0);
+ assert(labels.size() == ids.size());
+
+ _list.clear();
+ size_t button_width = ids.size() * (width + hpadding) + hpadding;
+ size_t offsetX = total_width - std::min<size_t>(total_width, button_width);
+ for (size_t x = 0; x < ids.size(); x++)
+ {
+ const size_t curOffsetX = offsetX + x * (static_cast<size_t>(width) + hpadding);
+ const SDL_Rect rect = { static_cast<int>(curOffsetX), offsetY, width, height };
+ _list.emplace_back(renderer, labels[x], ids[x], rect);
+ }
+ return true;
+}
+
+SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+
+ return get_selected(x, y);
+}
+
+SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y)
+{
+ for (auto& btn : _list)
+ {
+ auto r = btn.rect();
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return &btn;
+ }
+ return nullptr;
+}
+
+bool SdlButtonList::set_highlight_next(bool reset)
+{
+ if (reset)
+ _highlighted = nullptr;
+ else
+ {
+ auto next = _highlight_index++;
+ _highlight_index %= _list.size();
+ auto& element = _list[next];
+ _highlighted = &element;
+ }
+ return true;
+}
+
+bool SdlButtonList::set_highlight(size_t index)
+{
+ if (index >= _list.size())
+ {
+ _highlighted = nullptr;
+ return false;
+ }
+ auto& element = _list[index];
+ _highlighted = &element;
+ _highlight_index = ++index % _list.size();
+ return true;
+}
+
+bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y)
+{
+ _mouseover = get_selected(x, y);
+ return _mouseover != nullptr;
+}
+
+void SdlButtonList::clear()
+{
+ _list.clear();
+ _mouseover = nullptr;
+ _highlighted = nullptr;
+ _highlight_index = 0;
+}
+
+bool SdlButtonList::update(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ for (auto& btn : _list)
+ {
+ if (!btn.update(renderer))
+ return false;
+ }
+
+ if (_highlighted)
+ _highlighted->highlight(renderer);
+
+ if (_mouseover)
+ _mouseover->mouseover(renderer);
+ return true;
+}
diff --git a/client/SDL/dialogs/sdl_buttons.hpp b/client/SDL/dialogs/sdl_buttons.hpp
new file mode 100644
index 0000000..7f82903
--- /dev/null
+++ b/client/SDL/dialogs/sdl_buttons.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <vector>
+#include <cstdint>
+
+#include "sdl_button.hpp"
+
+class SdlButtonList
+{
+ public:
+ SdlButtonList() = default;
+ virtual ~SdlButtonList() = default;
+
+ bool populate(SDL_Renderer* renderer, const std::vector<std::string>& labels,
+ const std::vector<int>& ids, Sint32 total_width, Sint32 offsetY, Sint32 width,
+ Sint32 heigth);
+
+ bool update(SDL_Renderer* renderer);
+ SdlButton* get_selected(const SDL_MouseButtonEvent& button);
+ SdlButton* get_selected(Sint32 x, Sint32 y);
+
+ bool set_highlight_next(bool reset = false);
+ bool set_highlight(size_t index);
+ bool set_mouseover(Sint32 x, Sint32 y);
+
+ void clear();
+
+ private:
+ SdlButtonList(const SdlButtonList& other) = delete;
+ SdlButtonList(SdlButtonList&& other) = delete;
+
+ private:
+ std::vector<SdlButton> _list;
+ SdlButton* _highlighted = nullptr;
+ size_t _highlight_index = 0;
+ SdlButton* _mouseover = nullptr;
+};
diff --git a/client/SDL/dialogs/sdl_connection_dialog.cpp b/client/SDL/dialogs/sdl_connection_dialog.cpp
new file mode 100644
index 0000000..cbb6349
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.cpp
@@ -0,0 +1,536 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <cassert>
+#include <thread>
+
+#include "sdl_connection_dialog.hpp"
+#include "../sdl_utils.hpp"
+#include "../sdl_freerdp.hpp"
+#include "res/sdl_resource_manager.hpp"
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
+static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };
+
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 5;
+
+SDLConnectionDialog::SDLConnectionDialog(rdpContext* context)
+ : _context(context), _window(nullptr), _renderer(nullptr)
+{
+ SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
+ hide();
+}
+
+SDLConnectionDialog::~SDLConnectionDialog()
+{
+ resetTimer();
+ destroyWindow();
+ SDL_Quit();
+}
+
+bool SDLConnectionDialog::visible() const
+{
+ return _window && _renderer;
+}
+
+bool SDLConnectionDialog::setTitle(const char* fmt, ...)
+{
+ std::lock_guard lock(_mux);
+ va_list ap;
+ va_start(ap, fmt);
+ _title = print(fmt, ap);
+ va_end(ap);
+
+ return show(MSG_NONE);
+}
+
+bool SDLConnectionDialog::showInfo(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_INFO, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showWarn(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_WARN, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool SDLConnectionDialog::showError(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ auto rc = show(MSG_ERROR, fmt, ap);
+ va_end(ap);
+ return setTimer();
+}
+
+bool SDLConnectionDialog::show()
+{
+ std::lock_guard lock(_mux);
+ return show(_type_active);
+}
+
+bool SDLConnectionDialog::hide()
+{
+ std::lock_guard lock(_mux);
+ return show(MSG_DISCARD);
+}
+
+bool SDLConnectionDialog::running() const
+{
+ std::lock_guard lock(_mux);
+ return _running;
+}
+
+bool SDLConnectionDialog::update()
+{
+ std::lock_guard lock(_mux);
+ switch (_type)
+ {
+ case MSG_INFO:
+ case MSG_WARN:
+ case MSG_ERROR:
+ _type_active = _type;
+ createWindow();
+ break;
+ case MSG_DISCARD:
+ resetTimer();
+ destroyWindow();
+ break;
+ default:
+ if (_window)
+ {
+ SDL_SetWindowTitle(_window, _title.c_str());
+ }
+ break;
+ }
+ _type = MSG_NONE;
+ return true;
+}
+
+bool SDLConnectionDialog::setModal()
+{
+ if (_window)
+ {
+ auto sdl = get_context(_context);
+ if (sdl->windows.empty())
+ return true;
+
+ auto parent = sdl->windows.begin()->second.window();
+ SDL_SetWindowModalFor(_window, parent);
+ SDL_RaiseWindow(_window);
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
+
+bool SDLConnectionDialog::update(SDL_Renderer* renderer)
+{
+ if (!renderer)
+ return false;
+
+ if (!clearWindow(renderer))
+ return false;
+
+ for (auto& btn : _list)
+ {
+ if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
+ return false;
+ }
+
+ if (!_buttons.update(renderer))
+ return false;
+
+ SDL_RenderPresent(renderer);
+ return true;
+}
+
+bool SDLConnectionDialog::wait(bool ignoreRdpContext)
+{
+ while (running())
+ {
+ if (!ignoreRdpContext)
+ {
+ if (freerdp_shall_disconnect_context(_context))
+ return false;
+ }
+ std::this_thread::yield();
+ }
+ return true;
+}
+
+bool SDLConnectionDialog::handle(const SDL_Event& event)
+{
+ Uint32 windowID = 0;
+ if (_window)
+ {
+ windowID = SDL_GetWindowID(_window);
+ }
+
+ switch (event.type)
+ {
+ case SDL_USEREVENT_RETRY_DIALOG:
+ return update();
+ case SDL_QUIT:
+ resetTimer();
+ destroyWindow();
+ return false;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
+ update(_renderer);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_ESCAPE:
+ case SDLK_KP_ENTER:
+ if (event.type == SDL_KEYUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ case SDLK_TAB:
+ _buttons.set_highlight_next();
+ break;
+ default:
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEMOTION:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
+ update(_renderer);
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ if (event.type == SDL_MOUSEBUTTONUP)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ }
+
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_MOUSEWHEEL:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
+ update(_renderer);
+ return windowID == ev.windowID;
+ }
+ return false;
+ case SDL_FINGERUP:
+ case SDL_FINGERDOWN:
+ if (visible())
+ {
+ auto ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
+ update(_renderer);
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ return windowID == ev.windowID;
+#else
+ return false;
+#endif
+ }
+ return false;
+ case SDL_WINDOWEVENT:
+ {
+ auto ev = reinterpret_cast<const SDL_WindowEvent&>(event);
+ switch (ev.event)
+ {
+ case SDL_WINDOWEVENT_CLOSE:
+ if (windowID == ev.windowID)
+ {
+ freerdp_abort_event(_context);
+ sdl_push_quit();
+ }
+ break;
+ default:
+ update(_renderer);
+ setModal();
+ break;
+ }
+
+ return windowID == ev.windowID;
+ }
+ default:
+ return false;
+ }
+}
+
+bool SDLConnectionDialog::createWindow()
+{
+ destroyWindow();
+
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+ const size_t total_height = 300;
+
+ _window = SDL_CreateWindow(
+ _title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, widget_width,
+ total_height, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ return false;
+ }
+ setModal();
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ return false;
+ }
+
+ SDL_Color res_bgcolor;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_bgcolor = infocolor;
+ break;
+ case MSG_WARN:
+ res_bgcolor = warncolor;
+ break;
+ case MSG_ERROR:
+ res_bgcolor = errorcolor;
+ break;
+ case MSG_DISCARD:
+ default:
+ res_bgcolor = backgroundcolor;
+ break;
+ }
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+ std::string res_name;
+ switch (_type_active)
+ {
+ case MSG_INFO:
+ res_name = "icon_info.svg";
+ break;
+ case MSG_WARN:
+ res_name = "icon_warning.svg";
+ break;
+ case MSG_ERROR:
+ res_name = "icon_error.svg";
+ break;
+ case MSG_DISCARD:
+ default:
+ res_name = "";
+ break;
+ }
+
+ int height = (total_height - 3ul * vpadding) / 2ul;
+ SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
+ widget_cfg_t icon{ textcolor,
+ res_bgcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
+ _list.emplace_back(std::move(icon));
+
+ iconRect.y += height;
+
+ widget_cfg_t logo{ textcolor,
+ backgroundcolor,
+ { _renderer, iconRect,
+ SDLResourceManager::get(SDLResourceManager::typeImages(),
+ "FreeRDP_Icon.svg") } };
+ _list.emplace_back(std::move(logo));
+
+ SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
+ total_height - 3ul * vpadding - widget_height };
+#else
+ SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
+ total_height - 2ul * vpadding };
+#endif
+
+ widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } };
+ w.widget.set_wrap(true, widget_width);
+ _list.emplace_back(std::move(w));
+ rect.y += widget_height + vpadding;
+
+ const std::vector<int> buttonids = { 1 };
+ const std::vector<std::string> buttonlabels = { "cancel" };
+ _buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
+ total_height - widget_height - vpadding,
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+
+ SDL_ShowWindow(_window);
+ SDL_RaiseWindow(_window);
+
+ return true;
+}
+
+void SDLConnectionDialog::destroyWindow()
+{
+ _buttons.clear();
+ _list.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+ _renderer = nullptr;
+ _window = nullptr;
+}
+
+bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap)
+{
+ std::lock_guard lock(_mux);
+ _msg = print(fmt, ap);
+ return show(type);
+}
+
+bool SDLConnectionDialog::show(MsgType type)
+{
+ _type = type;
+ return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG);
+}
+
+std::string SDLConnectionDialog::print(const char* fmt, va_list ap)
+{
+ int size = -1;
+ std::string res;
+
+ do
+ {
+ res.resize(128);
+ if (size > 0)
+ res.resize(size);
+
+ va_list copy;
+ va_copy(copy, ap);
+ size = vsnprintf(res.data(), res.size(), fmt, copy);
+ va_end(copy);
+
+ } while ((size > 0) && (size > res.size()));
+
+ return res;
+}
+
+bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
+{
+ std::lock_guard lock(_mux);
+ resetTimer();
+
+ _timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this);
+ _running = true;
+ return true;
+}
+
+void SDLConnectionDialog::resetTimer()
+{
+ if (_running)
+ SDL_RemoveTimer(_timer);
+ _running = false;
+}
+
+Uint32 SDLConnectionDialog::timeout(Uint32 intervalMS, void* pvthis)
+{
+ auto ths = static_cast<SDLConnectionDialog*>(pvthis);
+ ths->hide();
+ ths->_running = false;
+ return 0;
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
+ : SDLConnectionDialogHider(get(instance))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
+ : SDLConnectionDialogHider(get(context))
+{
+}
+
+SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
+{
+ if (_dialog)
+ {
+ _visible = _dialog->visible();
+ if (_visible)
+ {
+ _dialog->hide();
+ }
+ }
+}
+
+SDLConnectionDialogHider::~SDLConnectionDialogHider()
+{
+ if (_dialog && _visible)
+ {
+ _dialog->show();
+ }
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
+{
+ if (!instance)
+ return nullptr;
+ return get(instance->context);
+}
+
+SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ if (!sdl)
+ return nullptr;
+ return sdl->connection_dialog.get();
+}
diff --git a/client/SDL/dialogs/sdl_connection_dialog.hpp b/client/SDL/dialogs/sdl_connection_dialog.hpp
new file mode 100644
index 0000000..f21f538
--- /dev/null
+++ b/client/SDL/dialogs/sdl_connection_dialog.hpp
@@ -0,0 +1,129 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include <freerdp/freerdp.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_buttons.hpp"
+
+class SDLConnectionDialog
+{
+ public:
+ explicit SDLConnectionDialog(rdpContext* context);
+ SDLConnectionDialog(const SDLConnectionDialog& other) = delete;
+ SDLConnectionDialog(const SDLConnectionDialog&& other) = delete;
+ virtual ~SDLConnectionDialog();
+
+ bool visible() const;
+
+ bool setTitle(const char* fmt, ...);
+ bool showInfo(const char* fmt, ...);
+ bool showWarn(const char* fmt, ...);
+ bool showError(const char* fmt, ...);
+
+ bool show();
+ bool hide();
+
+ bool running() const;
+ bool wait(bool ignoreRdpContextQuit = false);
+
+ bool handle(const SDL_Event& event);
+
+ private:
+ enum MsgType
+ {
+ MSG_NONE,
+ MSG_INFO,
+ MSG_WARN,
+ MSG_ERROR,
+ MSG_DISCARD
+ };
+
+ private:
+ bool createWindow();
+ void destroyWindow();
+
+ bool update();
+
+ bool setModal();
+
+ bool clearWindow(SDL_Renderer* renderer);
+
+ bool update(SDL_Renderer* renderer);
+
+ bool show(MsgType type, const char* fmt, va_list ap);
+ bool show(MsgType type);
+
+ std::string print(const char* fmt, va_list ap);
+ bool setTimer(Uint32 timeoutMS = 15000);
+ void resetTimer();
+
+ private:
+ static Uint32 timeout(Uint32 intervalMS, void* _this);
+
+ private:
+ struct widget_cfg_t
+ {
+ SDL_Color fgcolor;
+ SDL_Color bgcolor;
+ SdlWidget widget;
+ };
+
+ private:
+ rdpContext* _context;
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ mutable std::mutex _mux;
+ std::string _title;
+ std::string _msg;
+ MsgType _type = MSG_NONE;
+ MsgType _type_active = MSG_NONE;
+ SDL_TimerID _timer = -1;
+ bool _running = false;
+ std::vector<widget_cfg_t> _list;
+ SdlButtonList _buttons;
+};
+
+class SDLConnectionDialogHider
+{
+ public:
+ explicit SDLConnectionDialogHider(freerdp* instance);
+ explicit SDLConnectionDialogHider(rdpContext* context);
+
+ explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog);
+
+ ~SDLConnectionDialogHider();
+
+ private:
+ SDLConnectionDialog* get(freerdp* instance);
+ SDLConnectionDialog* get(rdpContext* context);
+
+ private:
+ SDLConnectionDialog* _dialog;
+ bool _visible;
+};
diff --git a/client/SDL/dialogs/sdl_dialogs.cpp b/client/SDL/dialogs/sdl_dialogs.cpp
new file mode 100644
index 0000000..32f8457
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.cpp
@@ -0,0 +1,621 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <vector>
+#include <string>
+#include <cassert>
+
+#include <freerdp/log.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#include <SDL.h>
+
+#include "../sdl_freerdp.hpp"
+#include "sdl_dialogs.hpp"
+#include "sdl_input.hpp"
+#include "sdl_input_widgets.hpp"
+#include "sdl_select.hpp"
+#include "sdl_selectlist.hpp"
+
+#define TAG CLIENT_TAG("SDL.dialogs")
+
+enum
+{
+ SHOW_DIALOG_ACCEPT_REJECT = 1,
+ SHOW_DIALOG_TIMED_ACCEPT = 2
+};
+
+static const char* type_str_for_flags(UINT32 flags)
+{
+ const char* type = "RDP-Server";
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+ return type;
+}
+
+static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result)
+{
+ const SDL_Event empty = { 0 };
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(result);
+
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ *result = empty;
+ const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type);
+ if (rc > 0)
+ return TRUE;
+ Sleep(1);
+ }
+ return FALSE;
+}
+
+static int sdl_show_dialog(rdpContext* context, const char* title, const char* message,
+ Sint32 flags)
+{
+ SDL_Event event = { 0 };
+
+ if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags))
+ return 0;
+
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event))
+ return 0;
+
+ return event.user.code;
+}
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ SDL_Event event = { 0 };
+ BOOL res = FALSE;
+
+ SDLConnectionDialogHider hider(instance);
+
+ const char* target = freerdp_settings_get_server_name(instance->context->settings);
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ target =
+ freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
+ break;
+ default:
+ break;
+ }
+
+ char* title = nullptr;
+ size_t titlesize = 0;
+ winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
+
+ char* u = nullptr;
+ char* d = nullptr;
+ char* p = nullptr;
+
+ assert(username);
+ assert(domain);
+ assert(password);
+
+ u = *username;
+ d = *domain;
+ p = *password;
+
+ if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event))
+ goto fail;
+ else
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
+
+ res = arg->result > 0 ? TRUE : FALSE;
+
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = arg->user;
+ *domain = arg->domain;
+ *password = arg->password;
+ }
+
+fail:
+ free(title);
+ return res;
+}
+
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ BOOL res = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(cert_list);
+ WINPR_ASSERT(choice);
+
+ SDLConnectionDialogHider hider(instance);
+ std::vector<std::string> strlist;
+ std::vector<const char*> list;
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
+
+ char* msg = nullptr;
+ size_t len = 0;
+
+ winpr_asprintf(&msg, &len,
+ "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
+ container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ strlist.emplace_back(msg);
+ free(msg);
+ free(reader);
+ free(container_name);
+
+ auto& m = strlist.back();
+ list.push_back(m.c_str());
+ }
+
+ SDL_Event event = { 0 };
+ const char* title = "Select a logon smartcard certificate";
+ if (gateway)
+ title = "Select a gateway logon smartcard certificate";
+ if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count))
+ goto fail;
+
+ if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event))
+ goto fail;
+
+ res = (event.user.code >= 0) ? TRUE : FALSE;
+ *choice = static_cast<DWORD>(event.user.code);
+
+fail:
+ return res;
+}
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(what);
+
+ auto sdl = get_context(instance->context);
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ WINPR_ASSERT(sdl->connection_dialog);
+
+ sdl->connection_dialog->setTitle("Retry connection to %s",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ sdl->connection_dialog->showError("Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes",
+ what);
+ }
+
+ auto settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+
+ if (!enabled)
+ {
+ sdl->connection_dialog->showError(
+ "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ sdl->connection_dialog->showError(
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
+ "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* wmessage)
+{
+ if (!isDisplayMandatory)
+ return TRUE;
+
+ char* title = nullptr;
+ size_t len = 0;
+ winpr_asprintf(&title, &len, "[gateway]");
+
+ Sint32 flags = 0;
+ if (isConsentMandatory)
+ flags = SHOW_DIALOG_ACCEPT_REJECT;
+ else if (isDisplayMandatory)
+ flags = SHOW_DIALOG_TIMED_ACCEPT;
+ char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
+
+ SDLConnectionDialogHider hider(instance);
+ const int rc = sdl_show_dialog(instance->context, title, message, flags);
+ free(title);
+ free(message);
+ return rc > 0 ? TRUE : FALSE;
+}
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ int rc = -1;
+ 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;
+
+ /* ignore LOGON_MSG_SESSION_CONTINUE message */
+ if (type == LOGON_MSG_SESSION_CONTINUE)
+ return 0;
+
+ SDLConnectionDialogHider hider(instance);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "[%s] info",
+ freerdp_settings_get_server_name(instance->context->settings));
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
+
+ rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
+ free(title);
+ free(message);
+ return rc;
+}
+
+static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
+ const char* message)
+{
+ SDLConnectionDialogHider hider(context);
+ if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message))
+ return 0;
+
+ SDL_Event event = { 0 };
+ if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event))
+ return 0;
+ return static_cast<DWORD>(event.user.code);
+}
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = type_str_for_flags(flags);
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ SDLConnectionDialogHider hider(instance);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* new_fp_str = nullptr;
+ size_t len = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&new_fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ new_fingerprint);
+ }
+ else
+ winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
+
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* old_fp_str = nullptr;
+ size_t olen = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&old_fp_str, &olen,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ old_fingerprint);
+ }
+ else
+ winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
+
+ const char* collission_str = "";
+ if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
+ {
+ collission_str =
+ "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
+ "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
+ "The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
+ "All manually accepted certificates must be reconfirmed!\n"
+ "\n";
+ }
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
+ type);
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(&message, &mlen,
+ "New Certificate details:\n"
+ "Common Name: %s\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "Old Certificate details:\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "%s\n"
+ "The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n",
+ common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
+ collission_str);
+
+ const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
+ free(title);
+ free(message);
+ free(new_fp_str);
+ free(old_fp_str);
+
+ return rc;
+}
+
+DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags)
+{
+ const char* type = type_str_for_flags(flags);
+
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ char* fp_str = nullptr;
+ size_t len = 0;
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ winpr_asprintf(&fp_str, &len,
+ "----------- Certificate --------------\n"
+ "%s\n"
+ "--------------------------------------\n",
+ fingerprint);
+ }
+ else
+ winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
+
+ char* title = nullptr;
+ size_t tlen = 0;
+ winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
+
+ char* message = nullptr;
+ size_t mlen = 0;
+ winpr_asprintf(
+ &message, &mlen,
+ "Common Name: %s\n"
+ "Subject: %s\n"
+ "Issuer: %s\n"
+ "%s\n"
+ "The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
+ common_name, subject, issuer, fp_str);
+
+ SDLConnectionDialogHider hider(instance);
+ const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
+ free(fp_str);
+ free(title);
+ free(message);
+ return rc;
+}
+
+BOOL sdl_cert_dialog_show(const char* title, const char* message)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_CERT_ACCEPT_PERMANENT = 23,
+ BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
+ BUTTONID_CERT_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
+ };
+
+ const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
+ ARRAYSIZE(buttons), buttons, nullptr };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_CERT_ACCEPT_PERMANENT:
+ value = 1;
+ break;
+ case BUTTONID_CERT_ACCEPT_TEMPORARY:
+ value = 2;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value);
+}
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
+{
+ int buttonid = -1;
+ enum
+ {
+ BUTTONID_SHOW_ACCEPT = 24,
+ BUTTONID_SHOW_DENY = 25
+ };
+ const SDL_MessageBoxButtonData buttons[] = {
+ { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
+ { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
+ };
+
+ const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
+ const SDL_MessageBoxData data = {
+ SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
+ };
+ const int rc = SDL_ShowMessageBox(&data, &buttonid);
+
+ Sint32 value = -1;
+ if (rc < 0)
+ value = 0;
+ else
+ {
+ switch (buttonid)
+ {
+ case BUTTONID_SHOW_ACCEPT:
+ value = 1;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ }
+
+ return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value);
+}
+
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
+{
+ const std::vector<std::string> auth = { "Username: ", "Domain: ",
+ "Password: " };
+ const std::vector<std::string> authPin = { "Device: ", "PIN: " };
+ const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
+ "GatewayPassword: " };
+ std::vector<std::string> prompt;
+ Sint32 rc = -1;
+
+ switch (args->result)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ break;
+ }
+
+ std::vector<std::string> result;
+
+ if (!prompt.empty())
+ {
+ std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
+ std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
+ SdlInputWidget::SDL_INPUT_MASK };
+ if (args->result != AUTH_SMARTCARD_PIN)
+ {
+ initial = { args->user ? args->user : "", args->domain ? args->domain : "",
+ args->password ? args->password : "" };
+ flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
+ }
+ SdlInputWidgetList ilist(args->title, prompt, initial, flags);
+ rc = ilist.run(result);
+ }
+
+ if ((result.size() < prompt.size()))
+ rc = -1;
+
+ char* user = nullptr;
+ char* domain = nullptr;
+ char* pwd = nullptr;
+ if (rc > 0)
+ {
+ user = _strdup(result[0].c_str());
+ if (args->result == AUTH_SMARTCARD_PIN)
+ pwd = _strdup(result[1].c_str());
+ else
+ {
+ domain = _strdup(result[1].c_str());
+ pwd = _strdup(result[2].c_str());
+ }
+ }
+ return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc);
+}
+
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
+{
+ std::vector<std::string> vlist;
+ vlist.reserve(count);
+ for (Sint32 x = 0; x < count; x++)
+ vlist.emplace_back(list[x]);
+ SdlSelectList slist(title, vlist);
+ Sint32 value = slist.run();
+ return sdl_push_user_event(SDL_USEREVENT_SCARD_RESULT, value);
+}
diff --git a/client/SDL/dialogs/sdl_dialogs.hpp b/client/SDL/dialogs/sdl_dialogs.hpp
new file mode 100644
index 0000000..ae9bbe6
--- /dev/null
+++ b/client/SDL/dialogs/sdl_dialogs.hpp
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <winpr/wtypes.h>
+#include <freerdp/freerdp.h>
+
+#include "../sdl_types.hpp"
+#include "../sdl_utils.hpp"
+
+BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason);
+BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway);
+
+SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg);
+
+DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject, const char* issuer,
+ const char* fingerprint, DWORD flags);
+
+DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* new_fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags);
+
+int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
+
+BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length, const WCHAR* message);
+
+BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags);
+BOOL sdl_cert_dialog_show(const char* title, const char* message);
+BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list);
+BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args);
diff --git a/client/SDL/dialogs/sdl_input.cpp b/client/SDL/dialogs/sdl_input.cpp
new file mode 100644
index 0000000..6e7bf12
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.cpp
@@ -0,0 +1,177 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 "sdl_input.hpp"
+
+#include <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 };
+static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+static const Uint32 vpadding = 5;
+static const Uint32 hpadding = 10;
+
+SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, const std::string& label,
+ const std::string& initial, Uint32 flags, size_t offset,
+ size_t width, size_t height)
+ : _flags(flags), _text(initial), _text_label(label),
+ _label(renderer,
+ { 0, static_cast<int>(offset * (height + vpadding)), static_cast<int>(width),
+ static_cast<int>(height) },
+ false),
+ _input(renderer,
+ { static_cast<int>(width + hpadding), static_cast<int>(offset * (height + vpadding)),
+ static_cast<int>(width), static_cast<int>(height) },
+ true),
+ _highlight(false), _mouseover(false)
+{
+}
+
+SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept
+ : _flags(std::move(other._flags)), _text(std::move(other._text)),
+ _text_label(std::move(other._text_label)), _label(std::move(other._label)),
+ _input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover)
+{
+}
+
+bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color)
+{
+ if (!_label.fill(renderer, color))
+ return false;
+ return _label.update_text(renderer, _text_label, labelfontcolor);
+}
+
+bool SdlInputWidget::update_label(SDL_Renderer* renderer)
+{
+ return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor);
+}
+
+bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ if (readonly())
+ return true;
+ _mouseover = mouseOver;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ if (readonly())
+ return true;
+ _highlight = highlight;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer)
+{
+ std::vector<SDL_Color> colors = { inputbackgroundcolor };
+ if (_highlight)
+ colors.push_back(inputhighlightcolor);
+ if (_mouseover)
+ colors.push_back(inputmouseovercolor);
+
+ if (!_input.fill(renderer, colors))
+ return false;
+ return update_input(renderer, inputfontcolor);
+}
+
+bool SdlInputWidget::resize_input(size_t size)
+{
+ _text.resize(size);
+
+ return true;
+}
+
+bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text)
+{
+ if (readonly())
+ return true;
+ _text = text;
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count)
+{
+ if (readonly())
+ return true;
+
+ assert(renderer);
+ if (_text.empty())
+ return true;
+
+ if (!resize_input(_text.size() - count))
+ return false;
+ return update_input(renderer);
+}
+
+bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& str)
+{
+ assert(renderer);
+ if (readonly())
+ return true;
+
+ _text.append(str);
+ if (!resize_input(_text.size()))
+ return false;
+ return update_input(renderer);
+}
+
+const SDL_Rect& SdlInputWidget::input_rect() const
+{
+ return _input.rect();
+}
+
+std::string SdlInputWidget::value() const
+{
+ return _text;
+}
+
+bool SdlInputWidget::readonly() const
+{
+ return (_flags & SDL_INPUT_READONLY) != 0;
+}
+
+bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor)
+{
+ std::string text = _text;
+ if (!text.empty())
+ {
+ if (_flags & SDL_INPUT_MASK)
+ {
+ for (char& x : text)
+ x = '*';
+ }
+ }
+
+ return _input.update_text(renderer, text, fgcolor);
+}
diff --git a/client/SDL/dialogs/sdl_input.hpp b/client/SDL/dialogs/sdl_input.hpp
new file mode 100644
index 0000000..11492d1
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input.hpp
@@ -0,0 +1,73 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlInputWidget
+{
+ public:
+ enum
+ {
+ SDL_INPUT_MASK = 1,
+ SDL_INPUT_READONLY = 2
+ };
+
+ public:
+ SdlInputWidget(SDL_Renderer* renderer, const std::string& label, const std::string& initial,
+ Uint32 flags, size_t offset, size_t width, size_t height);
+ SdlInputWidget(SdlInputWidget&& other) noexcept;
+
+ bool fill_label(SDL_Renderer* renderer, SDL_Color color);
+ bool update_label(SDL_Renderer* renderer);
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool hightlight);
+ bool update_input(SDL_Renderer* renderer);
+ bool resize_input(size_t size);
+
+ bool set_str(SDL_Renderer* renderer, const std::string& text);
+ bool remove_str(SDL_Renderer* renderer, size_t count);
+ bool append_str(SDL_Renderer* renderer, const std::string& text);
+
+ [[nodiscard]] const SDL_Rect& input_rect() const;
+ [[nodiscard]] std::string value() const;
+
+ [[nodiscard]] bool readonly() const;
+
+ protected:
+ bool update_input(SDL_Renderer* renderer, SDL_Color fgclor);
+
+ private:
+ SdlInputWidget(const SdlInputWidget& other) = delete;
+
+ private:
+ Uint32 _flags;
+ std::string _text;
+ std::string _text_label;
+ SdlWidget _label;
+ SdlWidget _input;
+ bool _highlight;
+ bool _mouseover;
+};
diff --git a/client/SDL/dialogs/sdl_input_widgets.cpp b/client/SDL/dialogs/sdl_input_widgets.cpp
new file mode 100644
index 0000000..5846308
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.cpp
@@ -0,0 +1,299 @@
+#include <cassert>
+#include <algorithm>
+
+#include "sdl_input_widgets.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlInputWidgetList::SdlInputWidgetList(const std::string& title,
+ const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial,
+ const std::vector<Uint32>& flags)
+ : _window(nullptr), _renderer(nullptr)
+{
+ assert(labels.size() == initial.size());
+ assert(labels.size() == flags.size());
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+
+ const size_t widget_width = 300;
+ const size_t widget_heigth = 50;
+
+ const size_t total_width = widget_width + widget_width;
+ const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding;
+ const size_t total_height = input_height + widget_heigth;
+ _window = SDL_CreateWindow(
+ title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, total_width, total_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ for (size_t x = 0; x < labels.size(); x++)
+ _list.emplace_back(_renderer, labels[x], initial[x], flags[x], x, widget_width,
+ widget_heigth);
+
+ _buttons.populate(_renderer, buttonlabels, buttonids, total_width,
+ static_cast<Sint32>(input_height), static_cast<Sint32>(widget_width),
+ static_cast<Sint32>(widget_heigth));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+ssize_t SdlInputWidgetList::next(ssize_t current)
+{
+ size_t iteration = 0;
+ auto val = static_cast<size_t>(current);
+
+ do
+ {
+ if (iteration >= _list.size())
+ return -1;
+
+ if (iteration == 0)
+ {
+ if (current < 0)
+ val = 0;
+ else
+ val++;
+ }
+ else
+ val++;
+ iteration++;
+ val %= _list.size();
+ } while (!valid(static_cast<ssize_t>(val)));
+ return static_cast<ssize_t>(val);
+}
+
+bool SdlInputWidgetList::valid(ssize_t current) const
+{
+ if (current < 0)
+ return false;
+ auto s = static_cast<size_t>(current);
+ if (s >= _list.size())
+ return false;
+ return !_list[s].readonly();
+}
+
+SdlInputWidget* SdlInputWidgetList::get(ssize_t index)
+{
+ if (index < 0)
+ return nullptr;
+ auto s = static_cast<size_t>(index);
+ if (s >= _list.size())
+ return nullptr;
+ return &_list[s];
+}
+
+SdlInputWidgetList::~SdlInputWidgetList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+bool SdlInputWidgetList::update(SDL_Renderer* renderer)
+{
+ for (auto& btn : _list)
+ {
+ if (!btn.update_label(renderer))
+ return false;
+ if (!btn.update_input(renderer))
+ return false;
+ }
+
+ return _buttons.update(renderer);
+}
+
+ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.input_rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+int SdlInputWidgetList::run(std::vector<std::string>& result)
+{
+ int res = -1;
+ ssize_t LastActiveTextInput = -1;
+ ssize_t CurrentActiveTextInput = next(-1);
+
+ if (!_window || !_renderer)
+ return -2;
+
+ try
+ {
+ bool running = true;
+ std::vector<SDL_Keycode> pressed;
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update(_renderer))
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = {};
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYUP:
+ {
+ auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym);
+ pressed.erase(it, pressed.end());
+ }
+ break;
+ case SDL_KEYDOWN:
+ pressed.push_back(event.key.keysym.sym);
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_BACKSPACE:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->remove_str(_renderer, 1))
+ throw;
+ }
+ }
+ break;
+ case SDLK_TAB:
+ CurrentActiveTextInput = next(CurrentActiveTextInput);
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = INPUT_BUTTON_ACCEPT;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ case SDLK_v:
+ if (pressed.size() == 2)
+ {
+ if ((pressed[0] == SDLK_LCTRL) || (pressed[0] == SDLK_RCTRL))
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ auto text = SDL_GetClipboardText();
+ cur->set_str(_renderer, text);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_TEXTINPUT:
+ {
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->append_str(_renderer, event.text.text))
+ throw;
+ }
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ auto TextInputIndex = get_index(event.button);
+ for (auto& cur : _list)
+ {
+ if (!cur.set_mouseover(_renderer, false))
+ throw;
+ }
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[static_cast<size_t>(TextInputIndex)];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto val = get_index(event.button);
+ if (valid(val))
+ CurrentActiveTextInput = val;
+
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = INPUT_BUTTON_ACCEPT;
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ if (LastActiveTextInput != CurrentActiveTextInput)
+ {
+ if (CurrentActiveTextInput < 0)
+ SDL_StopTextInput();
+ else
+ SDL_StartTextInput();
+ LastActiveTextInput = CurrentActiveTextInput;
+ }
+
+ for (auto& cur : _list)
+ {
+ if (!cur.set_highlight(_renderer, false))
+ throw;
+ }
+ auto cur = get(CurrentActiveTextInput);
+ if (cur)
+ {
+ if (!cur->set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+
+ for (auto& cur : _list)
+ result.push_back(cur.value());
+ }
+ catch (...)
+ {
+ }
+
+ return res;
+}
diff --git a/client/SDL/dialogs/sdl_input_widgets.hpp b/client/SDL/dialogs/sdl_input_widgets.hpp
new file mode 100644
index 0000000..83568ba
--- /dev/null
+++ b/client/SDL/dialogs/sdl_input_widgets.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <SDL.h>
+
+#include "sdl_input.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlInputWidgetList
+{
+ public:
+ SdlInputWidgetList(const std::string& title, const std::vector<std::string>& labels,
+ const std::vector<std::string>& initial, const std::vector<Uint32>& flags);
+ virtual ~SdlInputWidgetList();
+
+ int run(std::vector<std::string>& result);
+
+ protected:
+ bool update(SDL_Renderer* renderer);
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+
+ private:
+ SdlInputWidgetList(const SdlInputWidgetList& other) = delete;
+ SdlInputWidgetList(SdlInputWidgetList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 1,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t next(ssize_t current);
+ [[nodiscard]] bool valid(ssize_t current) const;
+ SdlInputWidget* get(ssize_t index);
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlInputWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_select.cpp b/client/SDL/dialogs/sdl_select.cpp
new file mode 100644
index 0000000..f0e0327
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.cpp
@@ -0,0 +1,74 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <cassert>
+
+#include <string>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_select.hpp"
+#include "sdl_widget.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+#include "sdl_input_widgets.hpp"
+
+static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 };
+static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff };
+static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 };
+static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff };
+
+SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, const std::string& label,
+ const SDL_Rect& rect)
+ : SdlWidget(renderer, rect, true), _text(label), _mouseover(false), _highlight(false)
+{
+ update_text(renderer);
+}
+
+SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept
+ : SdlWidget(std::move(other)), _text(std::move(other._text)), _mouseover(other._mouseover),
+ _highlight(other._highlight)
+{
+}
+
+bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver)
+{
+ _mouseover = mouseOver;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight)
+{
+ _highlight = highlight;
+ return update_text(renderer);
+}
+
+bool SdlSelectWidget::update_text(SDL_Renderer* renderer)
+{
+ assert(renderer);
+ std::vector<SDL_Color> colors = { labelbackgroundcolor };
+ if (_highlight)
+ colors.push_back(labelhighlightcolor);
+ if (_mouseover)
+ colors.push_back(labelmouseovercolor);
+ if (!fill(renderer, colors))
+ return false;
+ return SdlWidget::update_text(renderer, _text, labelfontcolor);
+}
diff --git a/client/SDL/dialogs/sdl_select.hpp b/client/SDL/dialogs/sdl_select.hpp
new file mode 100644
index 0000000..af67b74
--- /dev/null
+++ b/client/SDL/dialogs/sdl_select.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+#include "sdl_widget.hpp"
+
+class SdlSelectWidget : public SdlWidget
+{
+ public:
+ SdlSelectWidget(SDL_Renderer* renderer, const std::string& label, const SDL_Rect& rect);
+ SdlSelectWidget(SdlSelectWidget&& other) noexcept;
+ ~SdlSelectWidget() override = default;
+
+ bool set_mouseover(SDL_Renderer* renderer, bool mouseOver);
+ bool set_highlight(SDL_Renderer* renderer, bool highlight);
+ bool update_text(SDL_Renderer* renderer);
+
+ private:
+ SdlSelectWidget(const SdlSelectWidget& other) = delete;
+
+ private:
+ std::string _text;
+ bool _mouseover;
+ bool _highlight;
+};
diff --git a/client/SDL/dialogs/sdl_selectlist.cpp b/client/SDL/dialogs/sdl_selectlist.cpp
new file mode 100644
index 0000000..20437cc
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.cpp
@@ -0,0 +1,208 @@
+#include "sdl_selectlist.hpp"
+
+static const Uint32 vpadding = 5;
+
+SdlSelectList::SdlSelectList(const std::string& title, const std::vector<std::string>& labels)
+ : _window(nullptr), _renderer(nullptr)
+{
+ const size_t widget_height = 50;
+ const size_t widget_width = 600;
+
+ const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding;
+ _window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ widget_width, total_height + widget_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS |
+ SDL_WINDOW_INPUT_FOCUS);
+ if (_window == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateWindow");
+ }
+ else
+ {
+ _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
+ if (_renderer == nullptr)
+ {
+ widget_log_error(-1, "SDL_CreateRenderer");
+ }
+ else
+ {
+ SDL_Rect rect = { 0, 0, widget_width, widget_height };
+ for (auto& label : labels)
+ {
+ _list.emplace_back(_renderer, label, rect);
+ rect.y += widget_height + vpadding;
+ }
+
+ const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
+ const std::vector<std::string> buttonlabels = { "accept", "cancel" };
+ _buttons.populate(
+ _renderer, buttonlabels, buttonids, widget_width, static_cast<Sint32>(total_height),
+ static_cast<Sint32>(widget_width / 2), static_cast<Sint32>(widget_height));
+ _buttons.set_highlight(0);
+ }
+ }
+}
+
+SdlSelectList::~SdlSelectList()
+{
+ _list.clear();
+ _buttons.clear();
+ SDL_DestroyRenderer(_renderer);
+ SDL_DestroyWindow(_window);
+}
+
+int SdlSelectList::run()
+{
+ int res = -2;
+ ssize_t CurrentActiveTextInput = 0;
+ bool running = true;
+
+ if (!_window || !_renderer)
+ return -2;
+ try
+ {
+ while (running)
+ {
+ if (!clear_window(_renderer))
+ throw;
+
+ if (!update_text())
+ throw;
+
+ if (!_buttons.update(_renderer))
+ throw;
+
+ SDL_Event event = { 0 };
+ SDL_WaitEvent(&event);
+ switch (event.type)
+ {
+ case SDL_KEYDOWN:
+ switch (event.key.keysym.sym)
+ {
+ case SDLK_UP:
+ case SDLK_BACKSPACE:
+ if (CurrentActiveTextInput > 0)
+ CurrentActiveTextInput--;
+ else
+ CurrentActiveTextInput = _list.size() - 1;
+ break;
+ case SDLK_DOWN:
+ case SDLK_TAB:
+ if (CurrentActiveTextInput < 0)
+ CurrentActiveTextInput = 0;
+ else
+ CurrentActiveTextInput++;
+ CurrentActiveTextInput = CurrentActiveTextInput % _list.size();
+ break;
+ case SDLK_RETURN:
+ case SDLK_RETURN2:
+ case SDLK_KP_ENTER:
+ running = false;
+ res = CurrentActiveTextInput;
+ break;
+ case SDLK_ESCAPE:
+ running = false;
+ res = INPUT_BUTTON_CANCEL;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ {
+ ssize_t TextInputIndex = get_index(event.button);
+ reset_mouseover();
+ if (TextInputIndex >= 0)
+ {
+ auto& cur = _list[TextInputIndex];
+ if (!cur.set_mouseover(_renderer, true))
+ throw;
+ }
+
+ _buttons.set_mouseover(event.button.x, event.button.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ auto button = _buttons.get_selected(event.button);
+ if (button)
+ {
+ running = false;
+ if (button->id() == INPUT_BUTTON_CANCEL)
+ res = INPUT_BUTTON_CANCEL;
+ else
+ res = CurrentActiveTextInput;
+ }
+ else
+ {
+ CurrentActiveTextInput = get_index(event.button);
+ }
+ }
+ break;
+ case SDL_QUIT:
+ res = INPUT_BUTTON_CANCEL;
+ running = false;
+ break;
+ default:
+ break;
+ }
+
+ reset_highlight();
+ if (CurrentActiveTextInput >= 0)
+ {
+ auto& cur = _list[CurrentActiveTextInput];
+ if (!cur.set_highlight(_renderer, true))
+ throw;
+ }
+
+ SDL_RenderPresent(_renderer);
+ }
+ }
+ catch (...)
+ {
+ return -1;
+ }
+ return res;
+}
+
+ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button)
+{
+ const Sint32 x = button.x;
+ const Sint32 y = button.y;
+ for (size_t i = 0; i < _list.size(); i++)
+ {
+ auto& cur = _list[i];
+ auto r = cur.rect();
+
+ if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
+ return i;
+ }
+ return -1;
+}
+
+bool SdlSelectList::update_text()
+{
+ for (auto& cur : _list)
+ {
+ if (!cur.update_text(_renderer))
+ return false;
+ }
+
+ return true;
+}
+
+void SdlSelectList::reset_mouseover()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_mouseover(_renderer, false);
+ }
+}
+
+void SdlSelectList::reset_highlight()
+{
+ for (auto& cur : _list)
+ {
+ cur.set_highlight(_renderer, false);
+ }
+}
diff --git a/client/SDL/dialogs/sdl_selectlist.hpp b/client/SDL/dialogs/sdl_selectlist.hpp
new file mode 100644
index 0000000..3da0e14
--- /dev/null
+++ b/client/SDL/dialogs/sdl_selectlist.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <SDL.h>
+
+#include "sdl_select.hpp"
+#include "sdl_button.hpp"
+#include "sdl_buttons.hpp"
+
+class SdlSelectList
+{
+ public:
+ SdlSelectList(const std::string& title, const std::vector<std::string>& labels);
+ virtual ~SdlSelectList();
+
+ int run();
+
+ private:
+ SdlSelectList(const SdlSelectList& other) = delete;
+ SdlSelectList(SdlSelectList&& other) = delete;
+
+ private:
+ enum
+ {
+ INPUT_BUTTON_ACCEPT = 0,
+ INPUT_BUTTON_CANCEL = -2
+ };
+
+ private:
+ ssize_t get_index(const SDL_MouseButtonEvent& button);
+ bool update_text();
+ void reset_mouseover();
+ void reset_highlight();
+
+ private:
+ SDL_Window* _window;
+ SDL_Renderer* _renderer;
+ std::vector<SdlSelectWidget> _list;
+ SdlButtonList _buttons;
+};
diff --git a/client/SDL/dialogs/sdl_widget.cpp b/client/SDL/dialogs/sdl_widget.cpp
new file mode 100644
index 0000000..6e11b5a
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.cpp
@@ -0,0 +1,280 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "sdl_widget.hpp"
+#include "../sdl_utils.hpp"
+
+#include "res/sdl_resource_manager.hpp"
+
+#include <freerdp/log.h>
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+#include <SDL_image.h>
+#endif
+
+#define TAG CLIENT_TAG("SDL.widget")
+
+static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
+
+static const Uint32 hpadding = 10;
+
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input)
+ : _rect(rect), _input(input)
+{
+ assert(renderer);
+
+ auto ops = SDLResourceManager::get(SDLResourceManager::typeFonts(),
+ "OpenSans-VariableFont_wdth,wght.ttf");
+ if (!ops)
+ widget_log_error(-1, "SDLResourceManager::get");
+ else
+ {
+ _font = TTF_OpenFontRW(ops, 1, 64);
+ if (!_font)
+ widget_log_error(-1, "TTF_OpenFontRW");
+ }
+}
+
+#if defined(WITH_SDL_IMAGE_DIALOGS)
+SdlWidget::SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops) : _rect(rect)
+{
+ if (ops)
+ {
+ _image = IMG_LoadTexture_RW(renderer, ops, 1);
+ if (!_image)
+ widget_log_error(-1, "IMG_LoadTextureTyped_RW");
+ }
+}
+#endif
+
+SdlWidget::SdlWidget(SdlWidget&& other) noexcept
+ : _font(std::move(other._font)), _image(other._image), _rect(std::move(other._rect)),
+ _input(other._input), _wrap(other._wrap), _text_width(other._text_width)
+{
+ other._font = nullptr;
+ other._image = nullptr;
+}
+
+SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h);
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(dst.h) / static_cast<float>(src.h);
+ const float sws = static_cast<float>(src.w) * scale;
+ const float dws = static_cast<float>(dst.w) / scale;
+ if (static_cast<float>(dst.w) > sws)
+ dst.w = static_cast<int>(sws);
+ if (static_cast<float>(src.w) > dws)
+ {
+ src.x = src.w - static_cast<int>(dws);
+ src.w = static_cast<int>(dws);
+ }
+ return texture;
+}
+
+SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst)
+{
+ Sint32 w = 0;
+ Sint32 h = 0;
+ TTF_SizeUTF8(_font, " ", &w, &h);
+ auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor, _text_width);
+ if (!surface)
+ {
+ widget_log_error(-1, "TTF_RenderText_Blended");
+ return nullptr;
+ }
+
+ src.w = surface->w;
+ src.h = surface->h;
+
+ auto texture = SDL_CreateTextureFromSurface(renderer, surface);
+ SDL_FreeSurface(surface);
+ if (!texture)
+ {
+ widget_log_error(-1, "SDL_CreateTextureFromSurface");
+ return nullptr;
+ }
+
+ /* Do some magic:
+ * - Add padding before and after text
+ * - if text is too long only show the last elements
+ * - if text is too short only update used space
+ */
+ dst = _rect;
+ dst.x += hpadding;
+ dst.w -= 2 * hpadding;
+ const float scale = static_cast<float>(src.h) / static_cast<float>(src.w);
+ auto dh = src.h * scale;
+ if (dh < dst.h)
+ dst.h = dh;
+
+ return texture;
+}
+
+SdlWidget::~SdlWidget()
+{
+ TTF_CloseFont(_font);
+ if (_image)
+ SDL_DestroyTexture(_image);
+}
+
+bool SdlWidget::error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ static wLog* log = nullptr;
+ if (!log)
+ log = WLog_Get(TAG);
+ return sdl_log_error_ex(res, log, what, file, line, fkt);
+}
+
+static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color)
+{
+ const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rc = SDL_RenderFillRect(renderer, rect);
+ return !widget_log_error(rc, "SDL_RenderFillRect");
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color)
+{
+ std::vector<SDL_Color> colors = { color };
+ return fill(renderer, colors);
+}
+
+bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors)
+{
+ assert(renderer);
+ SDL_BlendMode mode = SDL_BLENDMODE_INVALID;
+ SDL_GetRenderDrawBlendMode(renderer, &mode);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
+ for (auto color : colors)
+ {
+ draw_rect(renderer, &_rect, color);
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
+ }
+ SDL_SetRenderDrawBlendMode(renderer, mode);
+ return true;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor)
+{
+ assert(renderer);
+
+ if (!fill(renderer, bgcolor))
+ return false;
+ return update_text(renderer, text, fgcolor);
+}
+
+bool SdlWidget::wrap() const
+{
+ return _wrap;
+}
+
+bool SdlWidget::set_wrap(bool wrap, size_t width)
+{
+ _wrap = wrap;
+ _text_width = width;
+ return _wrap;
+}
+
+const SDL_Rect& SdlWidget::rect() const
+{
+ return _rect;
+}
+
+bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor)
+{
+
+ if (text.empty())
+ return true;
+
+ SDL_Rect src{};
+ SDL_Rect dst{};
+
+ SDL_Texture* texture = nullptr;
+ if (_image)
+ {
+ texture = _image;
+ dst = _rect;
+ auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h);
+ if (rc < 0)
+ widget_log_error(rc, "SDL_QueryTexture");
+ }
+ else if (_wrap)
+ texture = render_text_wrapped(renderer, text, fgcolor, src, dst);
+ else
+ texture = render_text(renderer, text, fgcolor, src, dst);
+ if (!texture)
+ return false;
+
+ const int rc = SDL_RenderCopy(renderer, texture, &src, &dst);
+ if (!_image)
+ SDL_DestroyTexture(texture);
+ if (rc < 0)
+ return !widget_log_error(rc, "SDL_RenderCopy");
+ return true;
+}
+
+bool clear_window(SDL_Renderer* renderer)
+{
+ assert(renderer);
+
+ const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
+ backgroundcolor.b, backgroundcolor.a);
+ if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
+ return false;
+
+ const int rcls = SDL_RenderClear(renderer);
+ return !widget_log_error(rcls, "SDL_RenderClear");
+}
diff --git a/client/SDL/dialogs/sdl_widget.hpp b/client/SDL/dialogs/sdl_widget.hpp
new file mode 100644
index 0000000..ebc7dbd
--- /dev/null
+++ b/client/SDL/dialogs/sdl_widget.hpp
@@ -0,0 +1,88 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client helper dialogs
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <vector>
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#if defined(_MSC_VER)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+#if !defined(HAS_NOEXCEPT)
+#if defined(__clang__)
+#if __has_feature(cxx_noexcept)
+#define HAS_NOEXCEPT
+#endif
+#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \
+ defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
+#define HAS_NOEXCEPT
+#endif
+#endif
+
+#ifndef HAS_NOEXCEPT
+#define noexcept
+#endif
+
+class SdlWidget
+{
+ public:
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, bool input);
+ SdlWidget(SDL_Renderer* renderer, const SDL_Rect& rect, SDL_RWops* ops);
+ SdlWidget(SdlWidget&& other) noexcept;
+ virtual ~SdlWidget();
+
+ bool fill(SDL_Renderer* renderer, SDL_Color color);
+ bool fill(SDL_Renderer* renderer, const std::vector<SDL_Color>& colors);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor);
+ bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Color bgcolor);
+
+ bool wrap() const;
+ bool set_wrap(bool wrap = true, size_t width = 0);
+ const SDL_Rect& rect() const;
+
+ public:
+#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__)
+ static bool error_ex(Uint32 res, const char* what, const char* file, size_t line,
+ const char* fkt);
+
+ private:
+ SdlWidget(const SdlWidget& other) = delete;
+
+ SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor,
+ SDL_Rect& src, SDL_Rect& dst);
+ SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text,
+ SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst);
+
+ private:
+ TTF_Font* _font = nullptr;
+ SDL_Texture* _image = nullptr;
+ SDL_Rect _rect;
+ bool _input = false;
+ bool _wrap = false;
+ size_t _text_width = 0;
+};
+
+bool clear_window(SDL_Renderer* renderer);
diff --git a/client/SDL/dialogs/test/CMakeLists.txt b/client/SDL/dialogs/test/CMakeLists.txt
new file mode 100644
index 0000000..c1003d4
--- /dev/null
+++ b/client/SDL/dialogs/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+set(MODULE_NAME "TestSDL")
+set(MODULE_PREFIX "TEST_SDL")
+
+set(DRIVER ${MODULE_NAME}.cpp)
+
+set(TEST_SRCS
+ TestSDLDialogs.cpp
+)
+
+create_test_sourcelist(SRCS
+ ${DRIVER}
+ ${TEST_SRCS})
+
+add_library(${MODULE_NAME} ${SRCS})
+
+list(APPEND LIBS
+ dialogs
+)
+
+target_link_libraries(${MODULE_NAME} ${LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/SDL/dialogs/test/TestSDLDialogs.cpp b/client/SDL/dialogs/test/TestSDLDialogs.cpp
new file mode 100644
index 0000000..558fb4c
--- /dev/null
+++ b/client/SDL/dialogs/test/TestSDLDialogs.cpp
@@ -0,0 +1,99 @@
+#include "../sdl_selectlist.hpp"
+#include "../sdl_input_widgets.hpp"
+
+#include <freerdp/api.h>
+#include <winpr/wlog.h>
+
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ return FALSE;
+}
+
+static bool test_input_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ std::vector<std::string> initial;
+ std::vector<Uint32> flags;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ initial.push_back(std::to_string(x));
+
+ Uint32 flag = 0;
+ if ((x % 2) != 0)
+ flag |= SdlInputWidget::SDL_INPUT_MASK;
+ if ((x % 3) == 0)
+ flag |= SdlInputWidget::SDL_INPUT_READONLY;
+
+ flags.push_back(flag);
+ }
+ SdlInputWidgetList list{ title, labels, initial, flags };
+ std::vector<std::string> result;
+ auto rc = list.run(result);
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (result.size() != labels.size())
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool test_select_dialog()
+{
+ const auto title = "sometitle";
+ std::vector<std::string> labels;
+ for (size_t x = 0; x < 12; x++)
+ {
+ labels.push_back("label" + std::to_string(x));
+ }
+ SdlSelectList list{ title, labels };
+ auto rc = list.run();
+ if (rc < 0)
+ {
+ return false;
+ }
+ if (static_cast<size_t>(rc) >= labels.size())
+ return false;
+
+ return true;
+}
+
+extern "C"
+{
+ FREERDP_API int TestSDLDialogs(int argc, char* argv[]);
+}
+
+int TestSDLDialogs(int argc, char* argv[])
+{
+ int rc = 0;
+
+ (void)argc;
+ (void)argv;
+
+#if 0
+ SDL_Init(SDL_INIT_VIDEO);
+ try
+ {
+#if 1
+ if (!test_input_dialog())
+ throw -1;
+#endif
+#if 1
+ if (!test_select_dialog())
+ throw -2;
+#endif
+ }
+ catch (int e)
+ {
+ rc = e;
+ }
+ SDL_Quit();
+
+#endif
+ return rc;
+}
diff --git a/client/SDL/man/CMakeLists.txt b/client/SDL/man/CMakeLists.txt
new file mode 100644
index 0000000..1fb2adc
--- /dev/null
+++ b/client/SDL/man/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(DEPS
+ sdl-freerdp-channels.1.xml
+ sdl-freerdp-config.1.xml
+ sdl-freerdp-examples.1.xml
+ sdl-freerdp-envvar.1.xml
+ )
+
+set(MANPAGE_NAME ${PROJECT_NAME})
+if (WITH_BINARY_VERSIONING)
+ set(MANPAGE_NAME ${PROJECT_NAME}${PROJECT_VERSION_MAJOR})
+endif()
+generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 "${DEPS}")
diff --git a/client/SDL/man/sdl-freerdp-config.1.xml.in b/client/SDL/man/sdl-freerdp-config.1.xml.in
new file mode 100644
index 0000000..3bace73
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-config.1.xml.in
@@ -0,0 +1,81 @@
+<refsect1>
+ <title>Configuration file</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>Format and Location:</term>
+ <listitem>
+ <para>The configuration file is stored per user.<sbr/>
+ The <replaceable>XDG_CONFIG_HOME</replaceable> environment variable can be used to override the base directory.<sbr/>
+ This defaults to <replaceable>~/.config</replaceable>
+ The location relative to <replaceable>XDG_CONFIG_HOME</replaceable> is <replaceable>$XDG_CONFIG_HOME/@VENDOR@/@PRODUCT@/@PROJECT_NAME@.json</replaceable><sbr/>
+ The configuration is stored in JSON format</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Supported options:</term>
+ <listitem>
+ <varlistentry>
+ <term><replaceable>SDL_KeyModMask</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Defines the key combination required for SDL client shortcuts.<sbr/>
+ Default <replaceable>KMOD_RSHIFT</replaceable><sbr/>
+ An array of <replaceable>SDL_Keymod</replaceable> strings as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDL_Keymod</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Fullscreen</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles client fullscreen state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_RETURN</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Resizeable</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles local window resizeable state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_R</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Grab</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Toggles keyboard and mouse grab state.<sbr/>
+ Default <replaceable>SDL_SCANCODE_G</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>SDL_Disconnect</replaceable></term>
+ <listitem>
+ <varlistentry>
+ <listitem>
+ <para>Disconnects from the RDP session.<sbr/>
+ Default <replaceable>SDL_SCANCODE_D</replaceable>.<sbr/>
+ A string as defined at <replaceable>https://wiki.libsdl.org/SDL2/SDLScancodeLookup</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp-envvar.1.xml.in b/client/SDL/man/sdl-freerdp-envvar.1.xml.in
new file mode 100644
index 0000000..ab6c8c5
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-envvar.1.xml.in
@@ -0,0 +1,15 @@
+<refsect1>
+ <title>Environment variables</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>wlog environment variable</term>
+ <listitem>
+ <para>sdl-freerdp uses wLog as its log facility, you can refer to the
+ corresponding man page (wlog(7)) for more informations. Arguments passed
+ via the <replaceable>/log-level</replaceable> or <replaceable>/log-filters</replaceable>
+ have precedence over the environment variables.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp-examples.1.xml.in b/client/SDL/man/sdl-freerdp-examples.1.xml.in
new file mode 100644
index 0000000..7b0f873
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp-examples.1.xml.in
@@ -0,0 +1,95 @@
+<refsect1>
+ <title>Examples</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>sdl-freerdp connection.rdp /p:Pwd123! /f</command></term>
+ <listitem>
+ <para>Connect in fullscreen mode using a stored configuration <replaceable>connection.rdp</replaceable> and the password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:USER /size:50%h /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>USER</replaceable> and a size of <replaceable>50 percent of the height</replaceable>. If width (w) is set instead of height (h) like /size:50%w. 50 percent of the width is used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>CONTOSO\\JohnDoe</replaceable> and password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>192.168.1.100</replaceable> on port <replaceable>4489</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable>. The screen width is set to <replaceable>1366</replaceable> and the height to <replaceable>768</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>sdl-freerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100</command></term>
+ <listitem>
+ <para>Establish a connection to host <replaceable>192.168.1.100</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable> and connect to Hyper-V console (use port 2179, disable negotiation) with VMID <replaceable>C824F53E-95D2-46C6-9A18-23A5BB403532</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>+clipboard</command></term>
+ <listitem>
+ <para>Activate clipboard redirection</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/drive:home,/home/user</command></term>
+ <listitem>
+ <para>Activate drive redirection of <replaceable>/home/user</replaceable> as home drive</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/smartcard:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate smartcard redirection for device <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/printer:&lt;device&gt;,&lt;driver&gt;</command></term>
+ <listitem>
+ <para>Activate printer redirection for printer <replaceable>device</replaceable> using driver <replaceable>driver</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/serial:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate serial port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/parallel:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate parallel port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/sound:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio output redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/microphone:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio input redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/multimedia:sys:alsa</command></term>
+ <listitem>
+ <para>Activate multimedia redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/usb:id,dev:054c:0268</command></term>
+ <listitem>
+ <para>Activate USB device redirection for the device identified by <replaceable>054c:0268</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/SDL/man/sdl-freerdp.1.xml.in b/client/SDL/man/sdl-freerdp.1.xml.in
new file mode 100644
index 0000000..c4b9918
--- /dev/null
+++ b/client/SDL/man/sdl-freerdp.1.xml.in
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE refentry
+PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY syntax SYSTEM "freerdp-argument.1.xml">
+ <!ENTITY channels SYSTEM "sdl-freerdp-channels.1.xml">
+ <!ENTITY config SYSTEM "sdl-freerdp-config.1.xml">
+ <!ENTITY envvar SYSTEM "sdl-freerdp-envvar.1.xml">
+ <!ENTITY examples SYSTEM "sdl-freerdp-examples.1.xml">
+ ]
+>
+
+<refentry>
+ <refentryinfo>
+ <date>@MAN_TODAY@</date>
+ <author>
+ <authorblurb><para>The FreeRDP Team</para></authorblurb>
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>@MANPAGE_NAME@</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class="source">freerdp</refmiscinfo>
+ <refmiscinfo class="manual">@MANPAGE_NAME@</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname><application>@MANPAGE_NAME@</application></refname>
+ <refpurpose>FreeRDP SDL client</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <refsynopsisdivinfo>
+ <date>@MAN_TODAY@</date>
+ </refsynopsisdivinfo>
+ <para>
+ <command>@MANPAGE_NAME@</command> [file] [options] [/v:server[:port]]
+ </para>
+ </refsynopsisdiv>
+ <refsect1>
+ <refsect1info>
+ <date>@MAN_TODAY@</date>
+ </refsect1info>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>@MANPAGE_NAME@</command> is an SDL Remote Desktop Protocol (RDP)
+ client which is part of the FreeRDP project. An RDP server is built-in
+ to many editions of Windows. Alternative servers included ogon, gnome-remote-desktop,
+ xrdp and VRDP (VirtualBox).
+ </para>
+ </refsect1>
+
+ &syntax;
+
+ &channels;
+
+ &config;
+
+ &envvar;
+
+ &examples;
+
+ <refsect1>
+ <title>LINKS</title>
+ <para>
+ <ulink url="http://www.freerdp.com/">http://www.freerdp.com/</ulink>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/client/SDL/sdl_channels.cpp b/client/SDL/sdl_channels.cpp
new file mode 100644
index 0000000..958c5e7
--- /dev/null
+++ b/client/SDL/sdl_channels.cpp
@@ -0,0 +1,83 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/disp.h>
+
+#include "sdl_channels.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_disp.hpp"
+
+void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
+ WINPR_ASSERT(clip);
+ clip->custom = context;
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
+ WINPR_ASSERT(disp);
+ sdl->disp.init(disp);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(e);
+
+ // TODO: Set resizeable depending on disp channel and /dynamic-resolution
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ auto clip = reinterpret_cast<CliprdrClientContext*>(e->pInterface);
+ WINPR_ASSERT(clip);
+ clip->custom = nullptr;
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ auto disp = reinterpret_cast<DispClientContext*>(e->pInterface);
+ WINPR_ASSERT(disp);
+ sdl->disp.uninit(disp);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/SDL/sdl_channels.hpp b/client/SDL/sdl_channels.hpp
new file mode 100644
index 0000000..a5c9f7d
--- /dev/null
+++ b/client/SDL/sdl_channels.hpp
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client Channels
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+
+int sdl_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int sdl_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
diff --git a/client/SDL/sdl_disp.cpp b/client/SDL/sdl_disp.cpp
new file mode 100644
index 0000000..ffd13c8
--- /dev/null
+++ b/client/SDL/sdl_disp.cpp
@@ -0,0 +1,471 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Display Control Channel
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 <vector>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+
+#include <freerdp/gdi/gdi.h>
+
+#include <SDL.h>
+
+#include "sdl_disp.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_freerdp.hpp"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("sdl.disp")
+
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+#define MAX_RETRIES 5
+
+BOOL sdlDispContext::settings_changed()
+{
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ if (_lastSentWidth != _targetWidth)
+ return TRUE;
+
+ if (_lastSentHeight != _targetHeight)
+ return TRUE;
+
+ if (_lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (_lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (_lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+ /* TODO
+ if (_fullscreen != _sdl->fullscreen)
+ return TRUE;
+ */
+ return FALSE;
+}
+
+BOOL sdlDispContext::update_last_sent()
+{
+ WINPR_ASSERT(_sdl);
+
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ _lastSentWidth = _targetWidth;
+ _lastSentHeight = _targetHeight;
+ _lastSentDesktopOrientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ _lastSentDesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ _lastSentDeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ // TODO _fullscreen = _sdl->fullscreen;
+ return TRUE;
+}
+
+BOOL sdlDispContext::sendResize()
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout = {};
+ auto settings = _sdl->context()->settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!_activated || !_disp)
+ return TRUE;
+
+ if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ _lastSentDate = GetTickCount64();
+
+ if (!settings_changed())
+ return TRUE;
+
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ if (_sdl->fullscreen && (mcount > 0))
+ {
+ auto monitors = static_cast<const rdpMonitor*>(
+ freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray));
+ if (sendLayout(monitors, mcount) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ else
+ {
+ _waitingResize = TRUE;
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = _targetWidth;
+ layout.Height = _targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = _targetWidth;
+ layout.PhysicalHeight = _targetHeight;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, 1, &layout) !=
+ CHANNEL_RC_OK)
+ return FALSE;
+ }
+ return update_last_sent();
+}
+
+BOOL sdlDispContext::set_window_resizable()
+{
+ _sdl->update_resizeable(TRUE);
+ return TRUE;
+}
+
+static BOOL sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp,
+ rdpSettings** ppSettings)
+{
+ if (!context)
+ return FALSE;
+
+ auto sdl = get_context(context);
+
+ if (!sdl->context()->settings)
+ return FALSE;
+
+ *ppsdl = sdl;
+ *ppsdlDisp = &sdl->disp;
+ *ppSettings = sdl->context()->settings;
+ return TRUE;
+}
+
+void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ SdlContext* sdl = nullptr;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
+ return;
+
+ sdlDisp->_waitingResize = FALSE;
+
+ if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ sdlDisp->set_window_resizable();
+
+ if (e->firstActivation)
+ return;
+
+ sdlDisp->addTimer();
+ }
+}
+
+void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ SdlContext* sdl = nullptr;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ WINPR_UNUSED(e);
+ if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings))
+ return;
+
+ sdlDisp->_waitingResize = FALSE;
+
+ if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ sdlDisp->set_window_resizable();
+ sdlDisp->addTimer();
+ }
+}
+
+Uint32 sdlDispContext::OnTimer(Uint32 interval, void* param)
+{
+ auto ctx = static_cast<sdlDispContext*>(param);
+ if (!ctx)
+ return 0;
+
+ SdlContext* sdl = ctx->_sdl;
+ sdlDispContext* sdlDisp = nullptr;
+ rdpSettings* settings = nullptr;
+
+ if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings))
+ return 0;
+
+ WLog_Print(sdl->log, WLOG_TRACE, "checking for display changes...");
+ if (!sdlDisp->_activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return 0;
+ else
+ {
+ auto rc = sdlDisp->sendResize();
+ if (!rc)
+ WLog_Print(sdl->log, WLOG_TRACE, "sent new display layout, result %d", rc);
+ }
+ if (sdlDisp->_timer_retries++ >= MAX_RETRIES)
+ {
+ WLog_Print(sdl->log, WLOG_TRACE, "deactivate timer, retries exceeded");
+ return 0;
+ }
+
+ WLog_Print(sdl->log, WLOG_TRACE, "fire timer one more time");
+ return interval;
+}
+
+UINT sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ std::vector<DISPLAY_CONTROL_MONITOR_LAYOUT> layouts;
+ layouts.resize(nmonitors);
+
+ for (size_t i = 0; i < nmonitors; i++)
+ {
+ auto monitor = &monitors[i];
+ auto layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ WINPR_ASSERT(_disp);
+ ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, layouts.size(),
+ layouts.data());
+ return ret;
+}
+
+BOOL sdlDispContext::addTimer()
+{
+ if (SDL_WasInit(SDL_INIT_TIMER) == 0)
+ return FALSE;
+
+ SDL_RemoveTimer(_timer);
+ WLog_Print(_sdl->log, WLOG_TRACE, "adding new display check timer");
+
+ _timer_retries = 0;
+ sendResize();
+ _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this);
+ return TRUE;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+BOOL sdlDispContext::handle_display_event(const SDL_DisplayEvent* ev)
+{
+ WINPR_ASSERT(ev);
+
+ switch (ev->event)
+ {
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ case SDL_DISPLAYEVENT_CONNECTED:
+ SDL_Log("A new display with id %d was connected", ev->display);
+ return TRUE;
+ case SDL_DISPLAYEVENT_DISCONNECTED:
+ SDL_Log("The display with id %d was disconnected", ev->display);
+ return TRUE;
+#endif
+ case SDL_DISPLAYEVENT_ORIENTATION:
+ SDL_Log("The orientation of display with id %d was changed", ev->display);
+ return TRUE;
+ default:
+ return TRUE;
+ }
+}
+#endif
+
+BOOL sdlDispContext::handle_window_event(const SDL_WindowEvent* ev)
+{
+ WINPR_ASSERT(ev);
+
+ auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations)
+ ? SDL_TRUE
+ : SDL_FALSE;
+
+ auto it = _sdl->windows.find(ev->windowID);
+ if (it != _sdl->windows.end())
+ it->second.setBordered(bordered);
+
+ switch (ev->event)
+ {
+ case SDL_WINDOWEVENT_HIDDEN:
+ case SDL_WINDOWEVENT_MINIMIZED:
+ gdi_send_suppress_output(_sdl->context()->gdi, TRUE);
+
+ return TRUE;
+
+ case SDL_WINDOWEVENT_EXPOSED:
+ case SDL_WINDOWEVENT_SHOWN:
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ case SDL_WINDOWEVENT_RESTORED:
+ gdi_send_suppress_output(_sdl->context()->gdi, FALSE);
+ return TRUE;
+
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ _targetWidth = ev->data1;
+ _targetHeight = ev->data2;
+ return addTimer();
+
+ case SDL_WINDOWEVENT_LEAVE:
+ WINPR_ASSERT(_sdl);
+ _sdl->input.keyboard_grab(ev->windowID, SDL_FALSE);
+ return TRUE;
+ case SDL_WINDOWEVENT_ENTER:
+ WINPR_ASSERT(_sdl);
+ _sdl->input.keyboard_grab(ev->windowID, SDL_TRUE);
+ return _sdl->input.keyboard_focus_in();
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ case SDL_WINDOWEVENT_TAKE_FOCUS:
+ return _sdl->input.keyboard_focus_in();
+
+ default:
+ return TRUE;
+ }
+}
+
+UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ WINPR_ASSERT(disp);
+
+ auto sdlDisp = reinterpret_cast<sdlDispContext*>(disp->custom);
+ return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA,
+ maxMonitorAreaFactorB);
+}
+
+UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
+ UINT32 maxMonitorAreaFactorB)
+{
+ auto settings = _sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ _activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return set_window_resizable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL sdlDispContext::init(DispClientContext* disp)
+{
+ if (!disp)
+ return FALSE;
+
+ auto settings = _sdl->context()->settings;
+
+ if (!settings)
+ return FALSE;
+
+ _disp = disp;
+ disp->custom = this;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps;
+ }
+
+ _sdl->update_resizeable(TRUE);
+ return TRUE;
+}
+
+BOOL sdlDispContext::uninit(DispClientContext* disp)
+{
+ if (!disp)
+ return FALSE;
+
+ _disp = nullptr;
+ _sdl->update_resizeable(FALSE);
+ return TRUE;
+}
+
+sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl), _timer(0)
+{
+ SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
+
+ WINPR_ASSERT(_sdl);
+ WINPR_ASSERT(_sdl->context()->settings);
+ WINPR_ASSERT(_sdl->context()->pubSub);
+
+ auto settings = _sdl->context()->settings;
+ auto pubSub = _sdl->context()->pubSub;
+
+ _lastSentWidth = _targetWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ _lastSentHeight = _targetHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
+ addTimer();
+}
+
+sdlDispContext::~sdlDispContext()
+{
+ wPubSub* pubSub = _sdl->context()->pubSub;
+ WINPR_ASSERT(pubSub);
+
+ PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset);
+ SDL_RemoveTimer(_timer);
+ SDL_Quit();
+}
diff --git a/client/SDL/sdl_disp.hpp b/client/SDL/sdl_disp.hpp
new file mode 100644
index 0000000..fbe6362
--- /dev/null
+++ b/client/SDL/sdl_disp.hpp
@@ -0,0 +1,82 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Display Control Channel
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+#pragma once
+
+#include <freerdp/types.h>
+#include <freerdp/event.h>
+#include <freerdp/client/disp.h>
+
+#include "sdl_types.hpp"
+
+#include <SDL.h>
+
+class sdlDispContext
+{
+
+ public:
+ explicit sdlDispContext(SdlContext* sdl);
+ ~sdlDispContext();
+
+ BOOL init(DispClientContext* disp);
+ BOOL uninit(DispClientContext* disp);
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ BOOL handle_display_event(const SDL_DisplayEvent* ev);
+#endif
+
+ BOOL handle_window_event(const SDL_WindowEvent* ev);
+
+ private:
+ UINT DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA,
+ UINT32 maxMonitorAreaFactorB);
+ BOOL set_window_resizable();
+
+ BOOL sendResize();
+ BOOL settings_changed();
+ BOOL update_last_sent();
+ UINT sendLayout(const rdpMonitor* monitors, size_t nmonitors);
+
+ BOOL addTimer();
+
+ private:
+ static UINT DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB);
+ static void OnActivated(void* context, const ActivatedEventArgs* e);
+ static void OnGraphicsReset(void* context, const GraphicsResetEventArgs* e);
+ static Uint32 SDLCALL OnTimer(Uint32 interval, void* param);
+
+ private:
+ SdlContext* _sdl = nullptr;
+ DispClientContext* _disp = nullptr;
+ int _eventBase = -1;
+ int _errorBase = -1;
+ int _lastSentWidth = -1;
+ int _lastSentHeight = -1;
+ UINT64 _lastSentDate = 0;
+ int _targetWidth = -1;
+ int _targetHeight = -1;
+ BOOL _activated = FALSE;
+ BOOL _waitingResize = FALSE;
+ BOOL _fullscreen = FALSE;
+ UINT16 _lastSentDesktopOrientation = 0;
+ UINT32 _lastSentDesktopScaleFactor = 0;
+ UINT32 _lastSentDeviceScaleFactor = 0;
+ SDL_TimerID _timer = 0;
+ unsigned _timer_retries = 0;
+};
diff --git a/client/SDL/sdl_freerdp.cpp b/client/SDL/sdl_freerdp.cpp
new file mode 100644
index 0000000..890bf77
--- /dev/null
+++ b/client/SDL/sdl_freerdp.cpp
@@ -0,0 +1,1704 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL UI
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 <memory>
+#include <mutex>
+#include <iostream>
+
+#include <freerdp/config.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/utils/signal.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/channels.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include <SDL.h>
+#include <SDL_hints.h>
+#include <SDL_video.h>
+
+#include "sdl_channels.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_monitor.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_touch.hpp"
+#include "sdl_pointer.hpp"
+#include "dialogs/sdl_dialogs.hpp"
+
+#include "aad/sdl_webview.hpp"
+
+#define SDL_TAG CLIENT_TAG("SDL")
+
+enum SDL_EXIT_CODE
+{
+ /* section 0-15: protocol-independent codes */
+ SDL_EXIT_SUCCESS = 0,
+ SDL_EXIT_DISCONNECT = 1,
+ SDL_EXIT_LOGOFF = 2,
+ SDL_EXIT_IDLE_TIMEOUT = 3,
+ SDL_EXIT_LOGON_TIMEOUT = 4,
+ SDL_EXIT_CONN_REPLACED = 5,
+ SDL_EXIT_OUT_OF_MEMORY = 6,
+ SDL_EXIT_CONN_DENIED = 7,
+ SDL_EXIT_CONN_DENIED_FIPS = 8,
+ SDL_EXIT_USER_PRIVILEGES = 9,
+ SDL_EXIT_FRESH_CREDENTIALS_REQUIRED = 10,
+ SDL_EXIT_DISCONNECT_BY_USER = 11,
+
+ /* section 16-31: license error set */
+ SDL_EXIT_LICENSE_INTERNAL = 16,
+ SDL_EXIT_LICENSE_NO_LICENSE_SERVER = 17,
+ SDL_EXIT_LICENSE_NO_LICENSE = 18,
+ SDL_EXIT_LICENSE_BAD_CLIENT_MSG = 19,
+ SDL_EXIT_LICENSE_HWID_DOESNT_MATCH = 20,
+ SDL_EXIT_LICENSE_BAD_CLIENT = 21,
+ SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22,
+ SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23,
+ SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24,
+ SDL_EXIT_LICENSE_CANT_UPGRADE = 25,
+ SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26,
+
+ /* section 32-127: RDP protocol error set */
+ SDL_EXIT_RDP = 32,
+
+ /* section 128-254: xfreerdp specific exit codes */
+ SDL_EXIT_PARSE_ARGUMENTS = 128,
+ SDL_EXIT_MEMORY = 129,
+ SDL_EXIT_PROTOCOL = 130,
+ SDL_EXIT_CONN_FAILED = 131,
+ SDL_EXIT_AUTH_FAILURE = 132,
+ SDL_EXIT_NEGO_FAILURE = 133,
+ SDL_EXIT_LOGON_FAILURE = 134,
+ SDL_EXIT_ACCOUNT_LOCKED_OUT = 135,
+ SDL_EXIT_PRE_CONNECT_FAILED = 136,
+ SDL_EXIT_CONNECT_UNDEFINED = 137,
+ SDL_EXIT_POST_CONNECT_FAILED = 138,
+ SDL_EXIT_DNS_ERROR = 139,
+ SDL_EXIT_DNS_NAME_NOT_FOUND = 140,
+ SDL_EXIT_CONNECT_FAILED = 141,
+ SDL_EXIT_MCS_CONNECT_INITIAL_ERROR = 142,
+ SDL_EXIT_TLS_CONNECT_FAILED = 143,
+ SDL_EXIT_INSUFFICIENT_PRIVILEGES = 144,
+ SDL_EXIT_CONNECT_CANCELLED = 145,
+
+ SDL_EXIT_CONNECT_TRANSPORT_FAILED = 147,
+ SDL_EXIT_CONNECT_PASSWORD_EXPIRED = 148,
+ SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149,
+ SDL_EXIT_CONNECT_KDC_UNREACHABLE = 150,
+ SDL_EXIT_CONNECT_ACCOUNT_DISABLED = 151,
+ SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
+ SDL_EXIT_CONNECT_CLIENT_REVOKED = 153,
+ SDL_EXIT_CONNECT_WRONG_PASSWORD = 154,
+ SDL_EXIT_CONNECT_ACCESS_DENIED = 155,
+ SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156,
+ SDL_EXIT_CONNECT_ACCOUNT_EXPIRED = 157,
+ SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
+ SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
+
+ SDL_EXIT_UNKNOWN = 255,
+};
+
+struct sdl_exit_code_map_t
+{
+ DWORD error;
+ int code;
+ const char* code_tag;
+};
+
+#define ENTRY(x, y) \
+ { \
+ x, y, #y \
+ }
+static const struct sdl_exit_code_map_t sdl_exit_code_map[] = {
+ ENTRY(FREERDP_ERROR_SUCCESS, SDL_EXIT_SUCCESS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGOFF), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_IDLE_TIMEOUT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGON_TIMEOUT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_REPLACED),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_OUT_OF_MEMORY),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED_FIPS),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_USER_PRIVILEGES),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_FRESH_CREDENTIALS_REQUIRED),
+ ENTRY(ERRINFO_LOGOFF_BY_USER, SDL_EXIT_DISCONNECT_BY_USER),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_UNKNOWN),
+
+ /* section 16-31: license error set */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_INTERNAL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE_SERVER),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_MSG),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_HWID_DOESNT_MATCH),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE),
+
+ /* section 32-127: RDP protocol error set */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_RDP),
+
+ /* section 128-254: xfreerdp specific exit codes */
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_MEMORY),
+ ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PROTOCOL), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_FAILED),
+
+ ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, SDL_EXIT_AUTH_FAILURE),
+ ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, SDL_EXIT_NEGO_FAILURE),
+ ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, SDL_EXIT_LOGON_FAILURE),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, SDL_EXIT_ACCOUNT_LOCKED_OUT),
+ ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, SDL_EXIT_PRE_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, SDL_EXIT_CONNECT_UNDEFINED),
+ ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, SDL_EXIT_POST_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_DNS_ERROR, SDL_EXIT_DNS_ERROR),
+ ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, SDL_EXIT_DNS_NAME_NOT_FOUND),
+ ENTRY(FREERDP_ERROR_CONNECT_FAILED, SDL_EXIT_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, SDL_EXIT_MCS_CONNECT_INITIAL_ERROR),
+ ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, SDL_EXIT_TLS_CONNECT_FAILED),
+ ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, SDL_EXIT_INSUFFICIENT_PRIVILEGES),
+ ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, SDL_EXIT_CONNECT_CANCELLED),
+ ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, SDL_EXIT_CONNECT_TRANSPORT_FAILED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, SDL_EXIT_CONNECT_PASSWORD_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE),
+ ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, SDL_EXIT_CONNECT_KDC_UNREACHABLE),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, SDL_EXIT_CONNECT_ACCOUNT_DISABLED),
+ ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED,
+ SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, SDL_EXIT_CONNECT_CLIENT_REVOKED),
+ ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, SDL_EXIT_CONNECT_WRONG_PASSWORD),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, SDL_EXIT_CONNECT_ACCESS_DENIED),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION),
+ ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, SDL_EXIT_CONNECT_ACCOUNT_EXPIRED),
+ ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED),
+ ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS,
+ SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS)
+};
+
+static const struct sdl_exit_code_map_t* sdl_map_entry_by_code(int exit_code)
+{
+ for (size_t x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++)
+ {
+ const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x];
+ if (cur->code == exit_code)
+ return cur;
+ }
+ return nullptr;
+}
+
+static const struct sdl_exit_code_map_t* sdl_map_entry_by_error(DWORD error)
+{
+ for (size_t x = 0; x < ARRAYSIZE(sdl_exit_code_map); x++)
+ {
+ const struct sdl_exit_code_map_t* cur = &sdl_exit_code_map[x];
+ if (cur->error == error)
+ return cur;
+ }
+ return nullptr;
+}
+
+static int sdl_map_error_to_exit_code(DWORD error)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
+ if (entry)
+ return entry->code;
+
+ return SDL_EXIT_CONN_FAILED;
+}
+
+static const char* sdl_map_error_to_code_tag(DWORD error)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error);
+ if (entry)
+ return entry->code_tag;
+ return nullptr;
+}
+
+static const char* sdl_map_to_code_tag(int code)
+{
+ const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_code(code);
+ if (entry)
+ return entry->code_tag;
+ return nullptr;
+}
+
+static int error_info_to_error(freerdp* instance, DWORD* pcode, char** msg, size_t* len)
+{
+ const DWORD code = freerdp_error_info(instance);
+ const char* name = freerdp_get_error_info_name(code);
+ const char* str = freerdp_get_error_info_string(code);
+ const int exit_code = sdl_map_error_to_exit_code(code);
+
+ winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s",
+ sdl_map_error_to_code_tag(exit_code), name, code, str);
+ WLog_DBG(SDL_TAG, "%s", *msg);
+ if (pcode)
+ *pcode = code;
+ return exit_code;
+}
+
+/* This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas. */
+static BOOL sdl_begin_paint(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+
+ HANDLE handles[] = { sdl->update_complete.handle(), freerdp_abort_event(context) };
+ const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ return FALSE;
+ }
+ sdl->update_complete.clear();
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+
+ return TRUE;
+}
+
+static BOOL sdl_redraw(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ auto gdi = sdl->context()->gdi;
+ return gdi_send_suppress_output(gdi, FALSE);
+}
+
+class SdlEventUpdateTriggerGuard
+{
+ private:
+ SdlContext* _sdl;
+
+ public:
+ explicit SdlEventUpdateTriggerGuard(SdlContext* sdl) : _sdl(sdl)
+ {
+ }
+ ~SdlEventUpdateTriggerGuard()
+ {
+ _sdl->update_complete.set();
+ }
+};
+
+static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ SDL_Point offset, const SDL_Rect& srcRect)
+{
+ SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h };
+ return window.blit(surface, srcRect, dstRect);
+}
+
+static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ SDL_Point offset, const std::vector<SDL_Rect>& rects = {})
+{
+ if (rects.empty())
+ {
+ return sdl_draw_to_window_rect(sdl, window, surface, offset,
+ { 0, 0, surface->w, surface->h });
+ }
+ for (auto& srcRect : rects)
+ {
+ if (!sdl_draw_to_window_rect(sdl, window, surface, offset, srcRect))
+ return false;
+ }
+ return true;
+}
+
+static bool sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ const SDL_Rect& srcRect)
+{
+ SDL_Rect dstRect = srcRect;
+ sdl_scale_coordinates(sdl, window.id(), &dstRect.x, &dstRect.y, FALSE, TRUE);
+ sdl_scale_coordinates(sdl, window.id(), &dstRect.w, &dstRect.h, FALSE, TRUE);
+ return window.blit(surface, srcRect, dstRect);
+}
+
+static BOOL sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ if (rects.empty())
+ {
+ return sdl_draw_to_window_scaled_rect(sdl, window, surface,
+ { 0, 0, surface->w, surface->h });
+ }
+ for (const auto& srcRect : rects)
+ {
+ if (!sdl_draw_to_window_scaled_rect(sdl, window, surface, srcRect))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL sdl_draw_to_window(SdlContext* sdl, SdlWindow& window,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ WINPR_ASSERT(sdl);
+
+ auto context = sdl->context();
+ auto gdi = context->gdi;
+
+ auto size = window.rect();
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))
+ {
+ if (gdi->width < size.w)
+ {
+ window.setOffsetX((size.w - gdi->width) / 2);
+ }
+ if (gdi->height < size.h)
+ {
+ window.setOffsetY((size.h - gdi->height) / 2);
+ }
+
+ auto surface = sdl->primary.get();
+ if (!sdl_draw_to_window_rect(sdl, window, surface, { window.offsetX(), window.offsetY() },
+ rects))
+ return FALSE;
+ }
+ else
+ {
+ if (!sdl_draw_to_window_scaled_rect(sdl, window, sdl->primary.get(), rects))
+ return FALSE;
+ }
+ window.updateSurface();
+ return TRUE;
+}
+
+static BOOL sdl_draw_to_window(SdlContext* sdl, std::map<Uint32, SdlWindow>& windows,
+ const std::vector<SDL_Rect>& rects = {})
+{
+ for (auto& window : windows)
+ {
+ if (!sdl_draw_to_window(sdl, window.second, rects))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL sdl_end_paint_process(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(context);
+
+ SdlEventUpdateTriggerGuard guard(sdl);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ if (gdi->suppressOutput || gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ const INT32 ninvalid = gdi->primary->hdc->hwnd->ninvalid;
+ const GDI_RGN* cinvalid = gdi->primary->hdc->hwnd->cinvalid;
+
+ if (ninvalid < 1)
+ return TRUE;
+
+ std::vector<SDL_Rect> rects;
+ for (INT32 x = 0; x < ninvalid; x++)
+ {
+ auto& rgn = cinvalid[x];
+ rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h });
+ }
+
+ return sdl_draw_to_window(sdl, sdl->windows, rects);
+}
+
+/* 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 sdl_end_paint(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ const BOOL rc = sdl_push_user_event(SDL_USEREVENT_UPDATE, context);
+
+ return rc;
+}
+
+static void sdl_destroy_primary(SdlContext* sdl)
+{
+ if (!sdl)
+ return;
+ sdl->primary.reset();
+ sdl->primary_format.reset();
+}
+
+/* Create a SDL surface from the GDI buffer */
+static BOOL sdl_create_primary(SdlContext* sdl)
+{
+ rdpGdi* gdi = nullptr;
+
+ WINPR_ASSERT(sdl);
+
+ gdi = sdl->context()->gdi;
+ WINPR_ASSERT(gdi);
+
+ sdl_destroy_primary(sdl);
+ sdl->primary = SDLSurfacePtr(
+ SDL_CreateRGBSurfaceWithFormatFrom(gdi->primary_buffer, static_cast<int>(gdi->width),
+ static_cast<int>(gdi->height),
+ static_cast<int>(FreeRDPGetBitsPerPixel(gdi->dstFormat)),
+ static_cast<int>(gdi->stride), sdl->sdl_pixel_format),
+ SDL_FreeSurface);
+ sdl->primary_format = SDLPixelFormatPtr(SDL_AllocFormat(sdl->sdl_pixel_format), SDL_FreeFormat);
+
+ if (!sdl->primary || !sdl->primary_format)
+ return FALSE;
+
+ SDL_SetSurfaceBlendMode(sdl->primary.get(), SDL_BLENDMODE_NONE);
+ SDL_FillRect(sdl->primary.get(), nullptr,
+ SDL_MapRGBA(sdl->primary_format.get(), 0, 0, 0, 0xff));
+
+ return TRUE;
+}
+
+static BOOL sdl_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = nullptr;
+ rdpSettings* settings = nullptr;
+ auto sdl = get_context(context);
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ gdi = context->gdi;
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+ return sdl_create_primary(sdl);
+}
+
+/* This function is called to output a System BEEP */
+static BOOL sdl_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(play_sound);
+ return TRUE;
+}
+
+static BOOL sdl_wait_for_init(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+ sdl->initialize.set();
+
+ HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) };
+
+ const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (rc)
+ {
+ case WAIT_OBJECT_0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* Called before a connection is established.
+ * Set all configuration options to support and load channels here. */
+static BOOL sdl_pre_connect(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ auto sdl = get_context(instance->context);
+
+ auto settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Optional OS identifier sent to server */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL))
+ return FALSE;
+ /* OrderSupport is initialized at this point.
+ * Only override it if you plan to implement custom order
+ * callbacks or deactiveate certain features. */
+ /* Register the channel listeners.
+ * They are required to set up / tear down channels if they are loaded. */
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, sdl_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ sdl_OnChannelDisconnectedEventHandler);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ if (!sdl_wait_for_init(sdl))
+ return FALSE;
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (!freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks))
+ sdl->connection_dialog = std::make_unique<SDLConnectionDialog>(instance->context);
+ if (sdl->connection_dialog)
+ {
+ sdl->connection_dialog->setTitle("Connecting to '%s'",
+ freerdp_settings_get_server_name(settings));
+ sdl->connection_dialog->showInfo(
+ "The connection is being established\n\nPlease wait...");
+ }
+ if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight))
+ return FALSE;
+
+ if ((maxWidth != 0) && (maxHeight != 0) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect SDL.");
+ }
+
+ /* TODO: Any code your client requires */
+ return TRUE;
+}
+
+static const char* sdl_window_get_title(rdpSettings* settings)
+{
+ const char* windowTitle = nullptr;
+ UINT32 port = 0;
+ BOOL addPort = 0;
+ const char* name = nullptr;
+ const char* prefix = "FreeRDP:";
+
+ if (!settings)
+ return nullptr;
+
+ windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+ if (windowTitle)
+ return windowTitle;
+
+ name = freerdp_settings_get_server_name(settings);
+ port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
+
+ addPort = (port != 3389);
+
+ char buffer[MAX_PATH + 64] = { 0 };
+
+ if (!addPort)
+ sprintf_s(buffer, sizeof(buffer), "%s %s", prefix, name);
+ else
+ sprintf_s(buffer, sizeof(buffer), "%s %s:%" PRIu32, prefix, name, port);
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, buffer))
+ return nullptr;
+ return freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+}
+
+static void sdl_term_handler(int signum, const char* signame, void* context)
+{
+ sdl_push_quit();
+}
+
+static void sdl_cleanup_sdl(SdlContext* sdl)
+{
+ if (!sdl)
+ return;
+
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ sdl->windows.clear();
+ sdl->connection_dialog.reset();
+
+ sdl_destroy_primary(sdl);
+
+ freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler);
+ TTF_Quit();
+ SDL_Quit();
+}
+
+static BOOL sdl_create_windows(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ auto settings = sdl->context()->settings;
+ auto title = sdl_window_get_title(settings);
+ BOOL rc = FALSE;
+
+ UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+
+ for (UINT32 x = 0; x < windowCount; x++)
+ {
+ auto monitor = static_cast<rdpMonitor*>(
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
+
+ Uint32 w = monitor->width;
+ Uint32 h = monitor->height;
+ if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) ||
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)))
+ {
+ w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ Uint32 flags = SDL_WINDOW_SHOWN;
+ Uint32 startupX = SDL_WINDOWPOS_CENTERED_DISPLAY(x);
+ Uint32 startupY = SDL_WINDOWPOS_CENTERED_DISPLAY(x);
+
+ if (monitor->attributes.desktopScaleFactor > 100)
+ {
+#if SDL_VERSION_ATLEAST(2, 0, 1)
+ flags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ flags |= SDL_WINDOW_FULLSCREEN;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ flags |= SDL_WINDOW_BORDERLESS;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
+ flags |= SDL_WINDOW_BORDERLESS;
+
+ SdlWindow window{ title,
+ static_cast<int>(startupX),
+ static_cast<int>(startupY),
+ static_cast<int>(w),
+ static_cast<int>(h),
+ flags };
+ if (!window.window())
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ auto r = window.rect();
+ window.setOffsetX(0 - r.x);
+ window.setOffsetY(0 - r.y);
+ }
+
+ sdl->windows.insert({ window.id(), std::move(window) });
+ }
+
+ rc = TRUE;
+fail:
+
+ sdl->windows_created.set();
+ return rc;
+}
+
+static BOOL sdl_wait_create_windows(SdlContext* sdl)
+{
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ sdl->windows_created.clear();
+ if (!sdl_push_user_event(SDL_USEREVENT_CREATE_WINDOWS, sdl))
+ return FALSE;
+
+ HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) };
+
+ const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (rc)
+ {
+ case WAIT_OBJECT_0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static bool shall_abort(SdlContext* sdl)
+{
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (freerdp_shall_disconnect_context(sdl->context()))
+ {
+ if (!sdl->connection_dialog)
+ return true;
+ return !sdl->connection_dialog->running();
+ }
+ return false;
+}
+
+static int sdl_run(SdlContext* sdl)
+{
+ int rc = -1;
+ WINPR_ASSERT(sdl);
+
+ HANDLE handles[] = { sdl->initialize.handle(), freerdp_abort_event(sdl->context()) };
+ const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ return -1;
+ }
+
+ SDL_Init(SDL_INIT_VIDEO);
+ TTF_Init();
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
+#endif
+#if SDL_VERSION_ATLEAST(2, 0, 8)
+ SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
+#endif
+
+ freerdp_add_signal_cleanup_handler(sdl->context(), sdl_term_handler);
+
+ sdl->initialized.set();
+
+ while (!shall_abort(sdl))
+ {
+ SDL_Event windowEvent = { 0 };
+ while (!shall_abort(sdl) && SDL_WaitEventTimeout(nullptr, 1000))
+ {
+ /* Only poll standard SDL events and SDL_USEREVENTS meant to create dialogs.
+ * do not process the dialog return value events here.
+ */
+ const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_FIRSTEVENT,
+ SDL_USEREVENT_RETRY_DIALOG);
+ if (prc < 0)
+ {
+ if (sdl_log_error(prc, sdl->log, "SDL_PeepEvents"))
+ continue;
+ }
+
+#if defined(WITH_DEBUG_SDL_EVENTS)
+ SDL_Log("got event %s [0x%08" PRIx32 "]", sdl_event_type_str(windowEvent.type),
+ windowEvent.type);
+#endif
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ /* The session might have been disconnected while we were waiting for a new SDL event.
+ * In that case ignore the SDL event and terminate. */
+ if (freerdp_shall_disconnect_context(sdl->context()))
+ continue;
+
+ if (sdl->connection_dialog)
+ {
+ if (sdl->connection_dialog->handle(windowEvent))
+ {
+ continue;
+ }
+ }
+
+ switch (windowEvent.type)
+ {
+ case SDL_QUIT:
+ freerdp_abort_connect_context(sdl->context());
+ break;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ const SDL_KeyboardEvent* ev = &windowEvent.key;
+ sdl->input.keyboard_handle_event(ev);
+ }
+ break;
+ case SDL_KEYMAPCHANGED:
+ {
+ }
+ break; // TODO: Switch keyboard layout
+ case SDL_MOUSEMOTION:
+ {
+ const SDL_MouseMotionEvent* ev = &windowEvent.motion;
+ sdl_handle_mouse_motion(sdl, ev);
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ {
+ const SDL_MouseButtonEvent* ev = &windowEvent.button;
+ sdl_handle_mouse_button(sdl, ev);
+ }
+ break;
+ case SDL_MOUSEWHEEL:
+ {
+ const SDL_MouseWheelEvent* ev = &windowEvent.wheel;
+ sdl_handle_mouse_wheel(sdl, ev);
+ }
+ break;
+ case SDL_FINGERDOWN:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_down(sdl, ev);
+ }
+ break;
+ case SDL_FINGERUP:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_up(sdl, ev);
+ }
+ break;
+ case SDL_FINGERMOTION:
+ {
+ const SDL_TouchFingerEvent* ev = &windowEvent.tfinger;
+ sdl_handle_touch_motion(sdl, ev);
+ }
+ break;
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ case SDL_DISPLAYEVENT:
+ {
+ const SDL_DisplayEvent* ev = &windowEvent.display;
+ sdl->disp.handle_display_event(ev);
+ }
+ break;
+#endif
+ case SDL_WINDOWEVENT:
+ {
+ const SDL_WindowEvent* ev = &windowEvent.window;
+ sdl->disp.handle_window_event(ev);
+
+ switch (ev->event)
+ {
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ {
+ auto window = sdl->windows.find(ev->windowID);
+ if (window != sdl->windows.end())
+ {
+ window->second.fill();
+ window->second.updateSurface();
+ }
+ }
+ break;
+ case SDL_WINDOWEVENT_MOVED:
+ {
+ auto window = sdl->windows.find(ev->windowID);
+ if (window != sdl->windows.end())
+ {
+ auto r = window->second.rect();
+ auto id = window->second.id();
+ WLog_DBG(SDL_TAG, "%lu: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case SDL_RENDER_TARGETS_RESET:
+ sdl_redraw(sdl);
+ break;
+ case SDL_RENDER_DEVICE_RESET:
+ sdl_redraw(sdl);
+ break;
+ case SDL_APP_WILLENTERFOREGROUND:
+ sdl_redraw(sdl);
+ break;
+ case SDL_USEREVENT_CERT_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char*>(windowEvent.user.data2);
+ sdl_cert_dialog_show(title, msg);
+ }
+ break;
+ case SDL_USEREVENT_SHOW_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char*>(windowEvent.user.data2);
+ sdl_message_dialog_show(title, msg, windowEvent.user.code);
+ }
+ break;
+ case SDL_USEREVENT_SCARD_DIALOG:
+ {
+ auto title = static_cast<const char*>(windowEvent.user.data1);
+ auto msg = static_cast<const char**>(windowEvent.user.data2);
+ sdl_scard_dialog_show(title, windowEvent.user.code, msg);
+ }
+ break;
+ case SDL_USEREVENT_AUTH_DIALOG:
+ sdl_auth_dialog_show(
+ reinterpret_cast<const SDL_UserAuthArg*>(windowEvent.padding));
+ break;
+ case SDL_USEREVENT_UPDATE:
+ {
+ auto context = static_cast<rdpContext*>(windowEvent.user.data1);
+ sdl_end_paint_process(context);
+ }
+ break;
+ case SDL_USEREVENT_CREATE_WINDOWS:
+ {
+ auto ctx = static_cast<SdlContext*>(windowEvent.user.data1);
+ sdl_create_windows(ctx);
+ }
+ break;
+ case SDL_USEREVENT_WINDOW_RESIZEABLE:
+ {
+ auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
+ const SDL_bool use = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE;
+ if (window)
+ window->resizeable(use);
+ }
+ break;
+ case SDL_USEREVENT_WINDOW_FULLSCREEN:
+ {
+ auto window = static_cast<SdlWindow*>(windowEvent.user.data1);
+ const SDL_bool enter = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE;
+ if (window)
+ window->fullscreen(enter);
+ }
+ break;
+ case SDL_USEREVENT_POINTER_NULL:
+ SDL_ShowCursor(SDL_DISABLE);
+ break;
+ case SDL_USEREVENT_POINTER_DEFAULT:
+ {
+ SDL_Cursor* def = SDL_GetDefaultCursor();
+ SDL_SetCursor(def);
+ SDL_ShowCursor(SDL_ENABLE);
+ }
+ break;
+ case SDL_USEREVENT_POINTER_POSITION:
+ {
+ const auto x =
+ static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data1));
+ const auto y =
+ static_cast<INT32>(reinterpret_cast<uintptr_t>(windowEvent.user.data2));
+
+ SDL_Window* window = SDL_GetMouseFocus();
+ if (window)
+ {
+ const Uint32 id = SDL_GetWindowID(window);
+
+ INT32 sx = x;
+ INT32 sy = y;
+ if (sdl_scale_coordinates(sdl, id, &sx, &sy, FALSE, FALSE))
+ SDL_WarpMouseInWindow(window, sx, sy);
+ }
+ }
+ break;
+ case SDL_USEREVENT_POINTER_SET:
+ sdl_Pointer_Set_Process(&windowEvent.user);
+ break;
+ case SDL_USEREVENT_QUIT:
+ default:
+ break;
+ }
+ }
+ }
+
+ rc = 1;
+
+ sdl_cleanup_sdl(sdl);
+ return rc;
+}
+
+/* Called after a RDP connection was successfully established.
+ * Settings might have changed during negociation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and paing callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL sdl_post_connect(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+
+ auto context = instance->context;
+ WINPR_ASSERT(context);
+
+ auto sdl = get_context(context);
+
+ // Retry was successful, discard dialog
+ {
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (sdl->connection_dialog)
+ sdl->connection_dialog->hide();
+ }
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly))
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(context->settings, FreeRDP_Password))
+ {
+ WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect to X.");
+ return TRUE;
+ }
+
+ if (!sdl_wait_create_windows(sdl))
+ return FALSE;
+
+ sdl->sdl_pixel_format = SDL_PIXELFORMAT_BGRA32;
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ if (!sdl_create_primary(sdl))
+ return FALSE;
+
+ if (!sdl_register_pointer(instance->context->graphics))
+ return FALSE;
+
+ WINPR_ASSERT(context->update);
+
+ context->update->BeginPaint = sdl_begin_paint;
+ context->update->EndPaint = sdl_end_paint;
+ context->update->PlaySound = sdl_play_sound;
+ context->update->DesktopResize = sdl_desktop_resize;
+ context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators;
+ context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status;
+
+ sdl->update_resizeable(FALSE);
+ sdl->update_fullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) ||
+ freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon));
+ return TRUE;
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void sdl_post_disconnect(freerdp* instance)
+{
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ sdl_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ sdl_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+}
+
+static void sdl_post_final_disconnect(freerdp* instance)
+{
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ auto context = get_context(instance->context);
+}
+
+/* RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends. */
+static DWORD WINAPI sdl_client_thread_proc(SdlContext* sdl)
+{
+ DWORD nCount = 0;
+ DWORD status = 0;
+ int exit_code = SDL_EXIT_SUCCESS;
+ char* error_msg = nullptr;
+ size_t error_msg_len = 0;
+
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {};
+
+ WINPR_ASSERT(sdl);
+
+ auto instance = sdl->context()->instance;
+ WINPR_ASSERT(instance);
+
+ BOOL rc = freerdp_connect(instance);
+
+ rdpContext* context = sdl->context();
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!rc)
+ {
+ UINT32 error = freerdp_get_last_error(context);
+ exit_code = sdl_map_error_to_exit_code(error);
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ DWORD code = freerdp_get_last_error(context);
+ freerdp_abort_connect_context(context);
+ WLog_Print(sdl->log, WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s",
+ freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code));
+ goto terminate;
+ }
+
+ if (!rc)
+ {
+ DWORD code = freerdp_error_info(instance);
+ if (exit_code == SDL_EXIT_SUCCESS)
+ exit_code = error_info_to_error(instance, &code, &error_msg, &error_msg_len);
+
+ auto last = freerdp_get_last_error(context);
+ if (!error_msg)
+ {
+ winpr_asprintf(&error_msg, &error_msg_len, "%s [0x%08" PRIx32 "]\n%s",
+ freerdp_get_last_error_name(last), last,
+ freerdp_get_last_error_string(last));
+ }
+
+ if (last == FREERDP_ERROR_AUTHENTICATION_FAILED)
+ exit_code = SDL_EXIT_AUTH_FAILURE;
+ else if (code == ERRINFO_SUCCESS)
+ exit_code = SDL_EXIT_CONN_FAILED;
+
+ goto terminate;
+ }
+
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ /*
+ * win8 and server 2k12 seem to have some timing issue/race condition
+ * when a initial sync request is send to sync the keyboard indicators
+ * sending the sync event twice fixed this problem
+ */
+ if (freerdp_focus_required(instance))
+ {
+ auto ctx = get_context(context);
+ WINPR_ASSERT(ctx);
+ if (!ctx->input.keyboard_focus_in())
+ break;
+ if (!ctx->input.keyboard_focus_in())
+ break;
+ }
+
+ nCount = freerdp_get_event_handles(context, handles, ARRAYSIZE(handles));
+
+ if (nCount == 0)
+ {
+ WLog_Print(sdl->log, WLOG_ERROR, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, handles, FALSE, 100);
+
+ if (status == WAIT_FAILED)
+ {
+ if (client_auto_reconnect(instance))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ if (freerdp_error_info(instance) == 0)
+ exit_code = SDL_EXIT_CONN_FAILED;
+ }
+
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(sdl->log, WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "",
+ status);
+ break;
+ }
+
+ if (!freerdp_check_event_handles(context))
+ {
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(sdl->log, WLOG_ERROR, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ }
+
+ if (exit_code == SDL_EXIT_SUCCESS)
+ {
+ DWORD code = 0;
+ exit_code = error_info_to_error(instance, &code, &error_msg, &error_msg_len);
+
+ if ((code == ERRINFO_LOGOFF_BY_USER) &&
+ (freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested))
+ {
+ const char* msg = "Error info says user did not initiate but disconnect ultimatum says "
+ "they did; treat this as a user logoff";
+ free(error_msg);
+ error_msg = nullptr;
+ error_msg_len = 0;
+ winpr_asprintf(&error_msg, &error_msg_len, "%s", msg);
+
+ /* This situation might be limited to Windows XP. */
+ WLog_Print(sdl->log, WLOG_INFO, "%s", msg);
+ exit_code = SDL_EXIT_LOGOFF;
+ }
+ }
+
+ freerdp_disconnect(instance);
+
+terminate:
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ WLog_Print(sdl->log, WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]",
+ sdl_map_to_code_tag(exit_code), exit_code);
+ else
+ {
+ switch (exit_code)
+ {
+ case SDL_EXIT_SUCCESS:
+ case SDL_EXIT_DISCONNECT:
+ case SDL_EXIT_LOGOFF:
+ case SDL_EXIT_DISCONNECT_BY_USER:
+ break;
+ default:
+ {
+ std::lock_guard<CriticalSection> lock(sdl->critical);
+ if (sdl->connection_dialog)
+ sdl->connection_dialog->showError(error_msg);
+ }
+ break;
+ }
+ }
+ free(error_msg);
+ sdl->exit_code = exit_code;
+ sdl_push_user_event(SDL_USEREVENT_QUIT);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_TLSCleanup();
+#endif
+ return 0;
+}
+
+/* Optional global initializer.
+ * Here we just register a signal handler to print out stack traces
+ * if available. */
+static BOOL sdl_client_global_init(void)
+{
+#if defined(_WIN32)
+ WSADATA wsaData = { 0 };
+ const DWORD wVersionRequested = MAKEWORD(1, 1);
+ const int rc = WSAStartup(wVersionRequested, &wsaData);
+ if (rc != 0)
+ {
+ WLog_ERR(SDL_TAG, "WSAStartup failed with %s [%d]", gai_strerrorA(rc), rc);
+ return FALSE;
+ }
+#endif
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Optional global tear down */
+static void sdl_client_global_uninit(void)
+{
+#if defined(_WIN32)
+ WSACleanup();
+#endif
+}
+
+static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
+{
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
+
+ if (!instance || !context)
+ return FALSE;
+
+ sdl->sdl = new SdlContext(context);
+ if (!sdl->sdl)
+ return FALSE;
+
+ instance->PreConnect = sdl_pre_connect;
+ instance->PostConnect = sdl_post_connect;
+ instance->PostDisconnect = sdl_post_disconnect;
+ instance->PostFinalDisconnect = sdl_post_final_disconnect;
+ instance->AuthenticateEx = sdl_authenticate_ex;
+ instance->VerifyCertificateEx = sdl_verify_certificate_ex;
+ instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex;
+ instance->LogonErrorInfo = sdl_logon_error_info;
+ instance->PresentGatewayMessage = sdl_present_gateway_message;
+ instance->ChooseSmartcard = sdl_choose_smartcard;
+ instance->RetryDialog = sdl_retry_dialog;
+
+#ifdef WITH_WEBVIEW
+ instance->GetAccessToken = sdl_webview_get_access_token;
+#else
+ instance->GetAccessToken = client_cli_get_access_token;
+#endif
+ /* TODO: Client display set up */
+
+ return TRUE;
+}
+
+static void sdl_client_free(freerdp* instance, rdpContext* context)
+{
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(context);
+
+ if (!context)
+ return;
+
+ delete sdl->sdl;
+}
+
+static int sdl_client_start(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ sdl->thread = std::thread(sdl_client_thread_proc, sdl);
+ return 0;
+}
+
+static int sdl_client_stop(rdpContext* context)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ /* We do not want to use freerdp_abort_connect_context here.
+ * It would change the exit code and we do not want that. */
+ HANDLE event = freerdp_abort_event(context);
+ if (!SetEvent(event))
+ return -1;
+
+ sdl->thread.join();
+ return 0;
+}
+
+static 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->GlobalInit = sdl_client_global_init;
+ pEntryPoints->GlobalUninit = sdl_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(sdl_rdp_context);
+ pEntryPoints->ClientNew = sdl_client_new;
+ pEntryPoints->ClientFree = sdl_client_free;
+ pEntryPoints->ClientStart = sdl_client_start;
+ pEntryPoints->ClientStop = sdl_client_stop;
+ return 0;
+}
+
+static void context_free(sdl_rdp_context* sdl)
+{
+ if (sdl)
+ freerdp_client_context_free(&sdl->common.context);
+}
+
+static const char* category2str(int category)
+{
+ switch (category)
+ {
+ case SDL_LOG_CATEGORY_APPLICATION:
+ return "SDL_LOG_CATEGORY_APPLICATION";
+ case SDL_LOG_CATEGORY_ERROR:
+ return "SDL_LOG_CATEGORY_ERROR";
+ case SDL_LOG_CATEGORY_ASSERT:
+ return "SDL_LOG_CATEGORY_ASSERT";
+ case SDL_LOG_CATEGORY_SYSTEM:
+ return "SDL_LOG_CATEGORY_SYSTEM";
+ case SDL_LOG_CATEGORY_AUDIO:
+ return "SDL_LOG_CATEGORY_AUDIO";
+ case SDL_LOG_CATEGORY_VIDEO:
+ return "SDL_LOG_CATEGORY_VIDEO";
+ case SDL_LOG_CATEGORY_RENDER:
+ return "SDL_LOG_CATEGORY_RENDER";
+ case SDL_LOG_CATEGORY_INPUT:
+ return "SDL_LOG_CATEGORY_INPUT";
+ case SDL_LOG_CATEGORY_TEST:
+ return "SDL_LOG_CATEGORY_TEST";
+ case SDL_LOG_CATEGORY_RESERVED1:
+ return "SDL_LOG_CATEGORY_RESERVED1";
+ case SDL_LOG_CATEGORY_RESERVED2:
+ return "SDL_LOG_CATEGORY_RESERVED2";
+ case SDL_LOG_CATEGORY_RESERVED3:
+ return "SDL_LOG_CATEGORY_RESERVED3";
+ case SDL_LOG_CATEGORY_RESERVED4:
+ return "SDL_LOG_CATEGORY_RESERVED4";
+ case SDL_LOG_CATEGORY_RESERVED5:
+ return "SDL_LOG_CATEGORY_RESERVED5";
+ case SDL_LOG_CATEGORY_RESERVED6:
+ return "SDL_LOG_CATEGORY_RESERVED6";
+ case SDL_LOG_CATEGORY_RESERVED7:
+ return "SDL_LOG_CATEGORY_RESERVED7";
+ case SDL_LOG_CATEGORY_RESERVED8:
+ return "SDL_LOG_CATEGORY_RESERVED8";
+ case SDL_LOG_CATEGORY_RESERVED9:
+ return "SDL_LOG_CATEGORY_RESERVED9";
+ case SDL_LOG_CATEGORY_RESERVED10:
+ return "SDL_LOG_CATEGORY_RESERVED10";
+ case SDL_LOG_CATEGORY_CUSTOM:
+ default:
+ return "SDL_LOG_CATEGORY_CUSTOM";
+ }
+}
+
+static SDL_LogPriority wloglevel2dl(DWORD level)
+{
+ switch (level)
+ {
+ case WLOG_TRACE:
+ return SDL_LOG_PRIORITY_VERBOSE;
+ case WLOG_DEBUG:
+ return SDL_LOG_PRIORITY_DEBUG;
+ case WLOG_INFO:
+ return SDL_LOG_PRIORITY_INFO;
+ case WLOG_WARN:
+ return SDL_LOG_PRIORITY_WARN;
+ case WLOG_ERROR:
+ return SDL_LOG_PRIORITY_ERROR;
+ case WLOG_FATAL:
+ return SDL_LOG_PRIORITY_CRITICAL;
+ case WLOG_OFF:
+ default:
+ return SDL_LOG_PRIORITY_VERBOSE;
+ }
+}
+
+static DWORD sdlpriority2wlog(SDL_LogPriority priority)
+{
+ DWORD level = WLOG_OFF;
+ switch (priority)
+ {
+ case SDL_LOG_PRIORITY_VERBOSE:
+ level = WLOG_TRACE;
+ break;
+ case SDL_LOG_PRIORITY_DEBUG:
+ level = WLOG_DEBUG;
+ break;
+ case SDL_LOG_PRIORITY_INFO:
+ level = WLOG_INFO;
+ break;
+ case SDL_LOG_PRIORITY_WARN:
+ level = WLOG_WARN;
+ break;
+ case SDL_LOG_PRIORITY_ERROR:
+ level = WLOG_ERROR;
+ break;
+ case SDL_LOG_PRIORITY_CRITICAL:
+ level = WLOG_FATAL;
+ break;
+ default:
+ break;
+ }
+
+ return level;
+}
+
+static void SDLCALL winpr_LogOutputFunction(void* userdata, int category, SDL_LogPriority priority,
+ const char* message)
+{
+ auto sdl = static_cast<SdlContext*>(userdata);
+ WINPR_ASSERT(sdl);
+
+ const DWORD level = sdlpriority2wlog(priority);
+ auto log = sdl->log;
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, __LINE__, __FILE__, __func__, "[%s] %s",
+ category2str(category), message);
+}
+
+static void print_config_file_help()
+{
+#if defined(CJSON_FOUND)
+ std::cout << "CONFIGURATION FILE" << std::endl;
+ std::cout << std::endl;
+ std::cout << " The SDL client supports some user defined configuration options." << std::endl;
+ std::cout << " Settings are stored in JSON format" << std::endl;
+ std::cout << " The location is a per user file. Location for current user is "
+ << sdl_get_pref_file() << std::endl;
+ std::cout
+ << " The XDG_CONFIG_HOME environment variable can be used to override the base directory."
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " The following configuration options are supported:" << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_KeyModMask" << std::endl;
+ std::cout << " Defines the key combination required for SDL client shortcuts."
+ << std::endl;
+ std::cout << " Default KMOD_RSHIFT" << std::endl;
+ std::cout << " An array of SDL_Keymod strings as defined at "
+ "https://wiki.libsdl.org/SDL2/SDL_Keymod"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Fullscreen" << std::endl;
+ std::cout << " Toggles client fullscreen state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_RETURN." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Resizeable" << std::endl;
+ std::cout << " Toggles local window resizeable state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_R." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Grab" << std::endl;
+ std::cout << " Toggles keyboard and mouse grab state." << std::endl;
+ std::cout << " Default SDL_SCANCODE_G." << std::endl;
+ std::cout << " A string as "
+ "defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+ std::cout << std::endl;
+ std::cout << " SDL_Disconnect" << std::endl;
+ std::cout << " Disconnects from the RDP session." << std::endl;
+ std::cout << " Default SDL_SCANCODE_D." << std::endl;
+ std::cout << " A string as defined at https://wiki.libsdl.org/SDL2/SDLScancodeLookup"
+ << std::endl;
+#endif
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ int status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {};
+
+ freerdp_client_warn_experimental(argc, argv);
+
+ RdpClientEntry(&clientEntryPoints);
+ std::unique_ptr<sdl_rdp_context, void (*)(sdl_rdp_context*)> sdl_rdp(
+ reinterpret_cast<sdl_rdp_context*>(freerdp_client_context_new(&clientEntryPoints)),
+ context_free);
+
+ if (!sdl_rdp)
+ return -1;
+ auto sdl = sdl_rdp->sdl;
+
+ auto settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+ print_config_file_help();
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ sdl_list_monitors(sdl);
+ return rc;
+ }
+
+ SDL_LogSetOutputFunction(winpr_LogOutputFunction, sdl);
+ auto level = WLog_GetLogLevel(sdl->log);
+ SDL_LogSetAllPriority(wloglevel2dl(level));
+
+ auto context = sdl->context();
+ WINPR_ASSERT(context);
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ return -1;
+
+ if (freerdp_client_start(context) != 0)
+ return -1;
+
+ rc = sdl_run(sdl);
+
+ if (freerdp_client_stop(context) != 0)
+ return -1;
+
+ rc = sdl->exit_code;
+
+ return rc;
+}
+
+BOOL SdlContext::update_fullscreen(BOOL enter)
+{
+ std::lock_guard<CriticalSection> lock(critical);
+ for (const auto& window : windows)
+ {
+ if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_FULLSCREEN, &window.second, enter))
+ return FALSE;
+ }
+ fullscreen = enter;
+ return TRUE;
+}
+
+BOOL SdlContext::update_resizeable(BOOL enable)
+{
+ std::lock_guard<CriticalSection> lock(critical);
+
+ const auto settings = context()->settings;
+ const BOOL dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
+ const BOOL smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing);
+ BOOL use = (dyn && enable) || smart;
+
+ for (const auto& window : windows)
+ {
+ if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_RESIZEABLE, &window.second, use))
+ return FALSE;
+ }
+ resizeable = use;
+
+ return TRUE;
+}
+
+SdlContext::SdlContext(rdpContext* context)
+ : _context(context), log(WLog_Get(SDL_TAG)), update_complete(true), disp(this), input(this),
+ primary(nullptr, SDL_FreeSurface), primary_format(nullptr, SDL_FreeFormat)
+{
+}
+
+rdpContext* SdlContext::context() const
+{
+ return _context;
+}
+
+rdpClientContext* SdlContext::common() const
+{
+ return reinterpret_cast<rdpClientContext*>(_context);
+}
diff --git a/client/SDL/sdl_freerdp.hpp b/client/SDL/sdl_freerdp.hpp
new file mode 100644
index 0000000..79ed890
--- /dev/null
+++ b/client/SDL/sdl_freerdp.hpp
@@ -0,0 +1,88 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+#include <map>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+
+#include <SDL.h>
+#include <SDL_video.h>
+
+#include "sdl_types.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_kbd.hpp"
+#include "sdl_utils.hpp"
+#include "sdl_window.hpp"
+#include "dialogs/sdl_connection_dialog.hpp"
+
+using SDLSurfacePtr = std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)>;
+using SDLPixelFormatPtr = std::unique_ptr<SDL_PixelFormat, decltype(&SDL_FreeFormat)>;
+
+class SdlContext
+{
+ public:
+ explicit SdlContext(rdpContext* context);
+
+ private:
+ rdpContext* _context;
+
+ public:
+ wLog* log;
+
+ /* SDL */
+ bool fullscreen = false;
+ bool resizeable = false;
+ bool grab_mouse = false;
+ bool grab_kbd = false;
+
+ std::map<Uint32, SdlWindow> windows;
+
+ CriticalSection critical;
+ std::thread thread;
+ WinPREvent initialize;
+ WinPREvent initialized;
+ WinPREvent update_complete;
+ WinPREvent windows_created;
+ int exit_code = -1;
+
+ sdlDispContext disp;
+ sdlInput input;
+
+ SDLSurfacePtr primary;
+ SDLPixelFormatPtr primary_format;
+
+ Uint32 sdl_pixel_format = 0;
+
+ std::unique_ptr<SDLConnectionDialog> connection_dialog;
+
+ public:
+ BOOL update_resizeable(BOOL enable);
+ BOOL update_fullscreen(BOOL enter);
+
+ [[nodiscard]] rdpContext* context() const;
+ [[nodiscard]] rdpClientContext* common() const;
+};
diff --git a/client/SDL/sdl_kbd.cpp b/client/SDL/sdl_kbd.cpp
new file mode 100644
index 0000000..4d62389
--- /dev/null
+++ b/client/SDL/sdl_kbd.cpp
@@ -0,0 +1,568 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL keyboard helper
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 "sdl_kbd.hpp"
+#include "sdl_disp.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_utils.hpp"
+
+#include <map>
+
+#include <freerdp/scancode.h>
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("SDL.kbd")
+
+typedef struct
+{
+ Uint32 sdl;
+ const char* sdl_name;
+ UINT32 rdp;
+ const char* rdp_name;
+} scancode_entry_t;
+
+#define STR(x) #x
+#define ENTRY(x, y) \
+ { \
+ x, STR(x), y, #y \
+ }
+static const scancode_entry_t map[] = {
+ ENTRY(SDL_SCANCODE_UNKNOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_A, RDP_SCANCODE_KEY_A),
+ ENTRY(SDL_SCANCODE_B, RDP_SCANCODE_KEY_B),
+ ENTRY(SDL_SCANCODE_C, RDP_SCANCODE_KEY_C),
+ ENTRY(SDL_SCANCODE_D, RDP_SCANCODE_KEY_D),
+ ENTRY(SDL_SCANCODE_E, RDP_SCANCODE_KEY_E),
+ ENTRY(SDL_SCANCODE_F, RDP_SCANCODE_KEY_F),
+ ENTRY(SDL_SCANCODE_G, RDP_SCANCODE_KEY_G),
+ ENTRY(SDL_SCANCODE_H, RDP_SCANCODE_KEY_H),
+ ENTRY(SDL_SCANCODE_I, RDP_SCANCODE_KEY_I),
+ ENTRY(SDL_SCANCODE_J, RDP_SCANCODE_KEY_J),
+ ENTRY(SDL_SCANCODE_K, RDP_SCANCODE_KEY_K),
+ ENTRY(SDL_SCANCODE_L, RDP_SCANCODE_KEY_L),
+ ENTRY(SDL_SCANCODE_M, RDP_SCANCODE_KEY_M),
+ ENTRY(SDL_SCANCODE_N, RDP_SCANCODE_KEY_N),
+ ENTRY(SDL_SCANCODE_O, RDP_SCANCODE_KEY_O),
+ ENTRY(SDL_SCANCODE_P, RDP_SCANCODE_KEY_P),
+ ENTRY(SDL_SCANCODE_Q, RDP_SCANCODE_KEY_Q),
+ ENTRY(SDL_SCANCODE_R, RDP_SCANCODE_KEY_R),
+ ENTRY(SDL_SCANCODE_S, RDP_SCANCODE_KEY_S),
+ ENTRY(SDL_SCANCODE_T, RDP_SCANCODE_KEY_T),
+ ENTRY(SDL_SCANCODE_U, RDP_SCANCODE_KEY_U),
+ ENTRY(SDL_SCANCODE_V, RDP_SCANCODE_KEY_V),
+ ENTRY(SDL_SCANCODE_W, RDP_SCANCODE_KEY_W),
+ ENTRY(SDL_SCANCODE_X, RDP_SCANCODE_KEY_X),
+ ENTRY(SDL_SCANCODE_Y, RDP_SCANCODE_KEY_Y),
+ ENTRY(SDL_SCANCODE_Z, RDP_SCANCODE_KEY_Z),
+ ENTRY(SDL_SCANCODE_1, RDP_SCANCODE_KEY_1),
+ ENTRY(SDL_SCANCODE_2, RDP_SCANCODE_KEY_2),
+ ENTRY(SDL_SCANCODE_3, RDP_SCANCODE_KEY_3),
+ ENTRY(SDL_SCANCODE_4, RDP_SCANCODE_KEY_4),
+ ENTRY(SDL_SCANCODE_5, RDP_SCANCODE_KEY_5),
+ ENTRY(SDL_SCANCODE_6, RDP_SCANCODE_KEY_6),
+ ENTRY(SDL_SCANCODE_7, RDP_SCANCODE_KEY_7),
+ ENTRY(SDL_SCANCODE_8, RDP_SCANCODE_KEY_8),
+ ENTRY(SDL_SCANCODE_9, RDP_SCANCODE_KEY_9),
+ ENTRY(SDL_SCANCODE_0, RDP_SCANCODE_KEY_0),
+ ENTRY(SDL_SCANCODE_RETURN, RDP_SCANCODE_RETURN),
+ ENTRY(SDL_SCANCODE_ESCAPE, RDP_SCANCODE_ESCAPE),
+ ENTRY(SDL_SCANCODE_BACKSPACE, RDP_SCANCODE_BACKSPACE),
+ ENTRY(SDL_SCANCODE_TAB, RDP_SCANCODE_TAB),
+ ENTRY(SDL_SCANCODE_SPACE, RDP_SCANCODE_SPACE),
+ ENTRY(SDL_SCANCODE_MINUS, RDP_SCANCODE_OEM_MINUS),
+ ENTRY(SDL_SCANCODE_CAPSLOCK, RDP_SCANCODE_CAPSLOCK),
+ ENTRY(SDL_SCANCODE_F1, RDP_SCANCODE_F1),
+ ENTRY(SDL_SCANCODE_F2, RDP_SCANCODE_F2),
+ ENTRY(SDL_SCANCODE_F3, RDP_SCANCODE_F3),
+ ENTRY(SDL_SCANCODE_F4, RDP_SCANCODE_F4),
+ ENTRY(SDL_SCANCODE_F5, RDP_SCANCODE_F5),
+ ENTRY(SDL_SCANCODE_F6, RDP_SCANCODE_F6),
+ ENTRY(SDL_SCANCODE_F7, RDP_SCANCODE_F7),
+ ENTRY(SDL_SCANCODE_F8, RDP_SCANCODE_F8),
+ ENTRY(SDL_SCANCODE_F9, RDP_SCANCODE_F9),
+ ENTRY(SDL_SCANCODE_F10, RDP_SCANCODE_F10),
+ ENTRY(SDL_SCANCODE_F11, RDP_SCANCODE_F11),
+ ENTRY(SDL_SCANCODE_F12, RDP_SCANCODE_F12),
+ ENTRY(SDL_SCANCODE_F13, RDP_SCANCODE_F13),
+ ENTRY(SDL_SCANCODE_F14, RDP_SCANCODE_F14),
+ ENTRY(SDL_SCANCODE_F15, RDP_SCANCODE_F15),
+ ENTRY(SDL_SCANCODE_F16, RDP_SCANCODE_F16),
+ ENTRY(SDL_SCANCODE_F17, RDP_SCANCODE_F17),
+ ENTRY(SDL_SCANCODE_F18, RDP_SCANCODE_F18),
+ ENTRY(SDL_SCANCODE_F19, RDP_SCANCODE_F19),
+ ENTRY(SDL_SCANCODE_F20, RDP_SCANCODE_F20),
+ ENTRY(SDL_SCANCODE_F21, RDP_SCANCODE_F21),
+ ENTRY(SDL_SCANCODE_F22, RDP_SCANCODE_F22),
+ ENTRY(SDL_SCANCODE_F23, RDP_SCANCODE_F23),
+ ENTRY(SDL_SCANCODE_F24, RDP_SCANCODE_F24),
+ ENTRY(SDL_SCANCODE_NUMLOCKCLEAR, RDP_SCANCODE_NUMLOCK),
+ ENTRY(SDL_SCANCODE_KP_DIVIDE, RDP_SCANCODE_DIVIDE),
+ ENTRY(SDL_SCANCODE_KP_MULTIPLY, RDP_SCANCODE_MULTIPLY),
+ ENTRY(SDL_SCANCODE_KP_MINUS, RDP_SCANCODE_SUBTRACT),
+ ENTRY(SDL_SCANCODE_KP_PLUS, RDP_SCANCODE_ADD),
+ ENTRY(SDL_SCANCODE_KP_ENTER, RDP_SCANCODE_RETURN_KP),
+ ENTRY(SDL_SCANCODE_KP_1, RDP_SCANCODE_NUMPAD1),
+ ENTRY(SDL_SCANCODE_KP_2, RDP_SCANCODE_NUMPAD2),
+ ENTRY(SDL_SCANCODE_KP_3, RDP_SCANCODE_NUMPAD3),
+ ENTRY(SDL_SCANCODE_KP_4, RDP_SCANCODE_NUMPAD4),
+ ENTRY(SDL_SCANCODE_KP_5, RDP_SCANCODE_NUMPAD5),
+ ENTRY(SDL_SCANCODE_KP_6, RDP_SCANCODE_NUMPAD6),
+ ENTRY(SDL_SCANCODE_KP_7, RDP_SCANCODE_NUMPAD7),
+ ENTRY(SDL_SCANCODE_KP_8, RDP_SCANCODE_NUMPAD8),
+ ENTRY(SDL_SCANCODE_KP_9, RDP_SCANCODE_NUMPAD9),
+ ENTRY(SDL_SCANCODE_KP_0, RDP_SCANCODE_NUMPAD0),
+ ENTRY(SDL_SCANCODE_KP_PERIOD, RDP_SCANCODE_OEM_PERIOD),
+ ENTRY(SDL_SCANCODE_LCTRL, RDP_SCANCODE_LCONTROL),
+ ENTRY(SDL_SCANCODE_LSHIFT, RDP_SCANCODE_LSHIFT),
+ ENTRY(SDL_SCANCODE_LALT, RDP_SCANCODE_LMENU),
+ ENTRY(SDL_SCANCODE_LGUI, RDP_SCANCODE_LWIN),
+ ENTRY(SDL_SCANCODE_RCTRL, RDP_SCANCODE_RCONTROL),
+ ENTRY(SDL_SCANCODE_RSHIFT, RDP_SCANCODE_RSHIFT),
+ ENTRY(SDL_SCANCODE_RALT, RDP_SCANCODE_RMENU),
+ ENTRY(SDL_SCANCODE_RGUI, RDP_SCANCODE_RWIN),
+ ENTRY(SDL_SCANCODE_MODE, RDP_SCANCODE_APPS),
+ ENTRY(SDL_SCANCODE_MUTE, RDP_SCANCODE_VOLUME_MUTE),
+ ENTRY(SDL_SCANCODE_VOLUMEUP, RDP_SCANCODE_VOLUME_UP),
+ ENTRY(SDL_SCANCODE_VOLUMEDOWN, RDP_SCANCODE_VOLUME_DOWN),
+ ENTRY(SDL_SCANCODE_GRAVE, RDP_SCANCODE_OEM_3),
+ ENTRY(SDL_SCANCODE_COMMA, RDP_SCANCODE_OEM_COMMA),
+ ENTRY(SDL_SCANCODE_PERIOD, RDP_SCANCODE_OEM_PERIOD),
+ ENTRY(SDL_SCANCODE_SLASH, RDP_SCANCODE_OEM_2),
+ ENTRY(SDL_SCANCODE_BACKSLASH, RDP_SCANCODE_OEM_5),
+ ENTRY(SDL_SCANCODE_SCROLLLOCK, RDP_SCANCODE_SCROLLLOCK),
+ ENTRY(SDL_SCANCODE_INSERT, RDP_SCANCODE_INSERT),
+ ENTRY(SDL_SCANCODE_PRINTSCREEN, RDP_SCANCODE_PRINTSCREEN),
+ ENTRY(SDL_SCANCODE_HOME, RDP_SCANCODE_HOME),
+ ENTRY(SDL_SCANCODE_DELETE, RDP_SCANCODE_DELETE),
+ ENTRY(SDL_SCANCODE_RIGHT, RDP_SCANCODE_RIGHT),
+ ENTRY(SDL_SCANCODE_LEFT, RDP_SCANCODE_LEFT),
+ ENTRY(SDL_SCANCODE_DOWN, RDP_SCANCODE_DOWN),
+ ENTRY(SDL_SCANCODE_UP, RDP_SCANCODE_UP),
+ ENTRY(SDL_SCANCODE_SEMICOLON, RDP_SCANCODE_OEM_1),
+ ENTRY(SDL_SCANCODE_PAUSE, RDP_SCANCODE_PAUSE),
+ ENTRY(SDL_SCANCODE_PAGEUP, RDP_SCANCODE_PRIOR),
+ ENTRY(SDL_SCANCODE_END, RDP_SCANCODE_END),
+ ENTRY(SDL_SCANCODE_PAGEDOWN, RDP_SCANCODE_NEXT),
+ ENTRY(SDL_SCANCODE_AUDIONEXT, RDP_SCANCODE_MEDIA_NEXT_TRACK),
+ ENTRY(SDL_SCANCODE_AUDIOPREV, RDP_SCANCODE_MEDIA_PREV_TRACK),
+ ENTRY(SDL_SCANCODE_AUDIOSTOP, RDP_SCANCODE_MEDIA_STOP),
+ ENTRY(SDL_SCANCODE_AUDIOPLAY, RDP_SCANCODE_MEDIA_PLAY_PAUSE),
+ ENTRY(SDL_SCANCODE_AUDIOMUTE, RDP_SCANCODE_VOLUME_MUTE),
+ ENTRY(SDL_SCANCODE_MEDIASELECT, RDP_SCANCODE_LAUNCH_MEDIA_SELECT),
+ ENTRY(SDL_SCANCODE_MAIL, RDP_SCANCODE_LAUNCH_MAIL),
+ ENTRY(SDL_SCANCODE_APP1, RDP_SCANCODE_LAUNCH_APP1),
+ ENTRY(SDL_SCANCODE_APP2, RDP_SCANCODE_LAUNCH_APP2),
+ ENTRY(SDL_SCANCODE_SYSREQ, RDP_SCANCODE_SYSREQ),
+ ENTRY(SDL_SCANCODE_WWW, RDP_SCANCODE_BROWSER_HOME),
+ ENTRY(SDL_SCANCODE_LEFTBRACKET, RDP_SCANCODE_OEM_4),
+ ENTRY(SDL_SCANCODE_RIGHTBRACKET, RDP_SCANCODE_OEM_6),
+ ENTRY(SDL_SCANCODE_APOSTROPHE, RDP_SCANCODE_OEM_7),
+ ENTRY(SDL_SCANCODE_NONUSBACKSLASH, RDP_SCANCODE_OEM_102),
+ ENTRY(SDL_SCANCODE_SLEEP, RDP_SCANCODE_SLEEP),
+ ENTRY(SDL_SCANCODE_EQUALS, RDP_SCANCODE_OEM_PLUS),
+ ENTRY(SDL_SCANCODE_KP_COMMA, RDP_SCANCODE_DECIMAL),
+ ENTRY(SDL_SCANCODE_FIND, RDP_SCANCODE_BROWSER_SEARCH),
+ ENTRY(SDL_SCANCODE_RETURN2, RDP_SCANCODE_RETURN_KP),
+ ENTRY(SDL_SCANCODE_AC_SEARCH, RDP_SCANCODE_BROWSER_SEARCH),
+ ENTRY(SDL_SCANCODE_AC_HOME, RDP_SCANCODE_BROWSER_HOME),
+ ENTRY(SDL_SCANCODE_AC_BACK, RDP_SCANCODE_BROWSER_BACK),
+ ENTRY(SDL_SCANCODE_AC_FORWARD, RDP_SCANCODE_BROWSER_FORWARD),
+ ENTRY(SDL_SCANCODE_AC_STOP, RDP_SCANCODE_BROWSER_STOP),
+
+#if 1 // TODO: unmapped
+ ENTRY(SDL_SCANCODE_NONUSHASH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_APPLICATION, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_POWER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EQUALS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EXECUTE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_HELP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_MENU, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_SELECT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_STOP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AGAIN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_UNDO, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CUT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_COPY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_PASTE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EQUALSAS400, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL1, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL2, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL3, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL4, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL5, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL6, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL7, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL8, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_INTERNATIONAL9, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG1, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG2, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG3, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG4, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG5, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG6, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG7, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG8, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_LANG9, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_ALTERASE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CANCEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_PRIOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_SEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_OUT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_OPER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CLEARAGAIN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CRSEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EXSEL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_00, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_000, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_THOUSANDSSEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_DECIMALSEPARATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CURRENCYUNIT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CURRENCYSUBUNIT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LEFTPAREN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_RIGHTPAREN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LEFTBRACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_RIGHTBRACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_TAB, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_BACKSPACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_A, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_B, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_C, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_D, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_E, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_F, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_XOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_POWER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_PERCENT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_LESS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_GREATER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_AMPERSAND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DBLAMPERSAND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_VERTICALBAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DBLVERTICALBAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_COLON, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_HASH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_SPACE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_AT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_EXCLAM, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMSTORE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMRECALL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMCLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMADD, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMSUBTRACT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMMULTIPLY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_MEMDIVIDE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_PLUSMINUS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_CLEAR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_CLEARENTRY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_BINARY, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_OCTAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_DECIMAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KP_HEXADECIMAL, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_CALCULATOR, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_COMPUTER, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AC_REFRESH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AC_BOOKMARKS, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_BRIGHTNESSDOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_BRIGHTNESSUP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_DISPLAYSWITCH, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMTOGGLE, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMDOWN, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_KBDILLUMUP, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_EJECT, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AUDIOREWIND, RDP_SCANCODE_UNKNOWN),
+ ENTRY(SDL_SCANCODE_AUDIOFASTFORWARD, RDP_SCANCODE_UNKNOWN)
+#endif
+};
+
+static UINT32 sdl_get_kbd_flags(void)
+{
+ UINT32 flags = 0;
+
+ SDL_Keymod mod = SDL_GetModState();
+ if ((mod & KMOD_NUM) != 0)
+ flags |= KBD_SYNC_NUM_LOCK;
+ if ((mod & KMOD_CAPS) != 0)
+ flags |= KBD_SYNC_CAPS_LOCK;
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ if ((mod & KMOD_SCROLL) != 0)
+ flags |= KBD_SYNC_SCROLL_LOCK;
+#endif
+
+ // TODO: KBD_SYNC_KANA_LOCK
+
+ return flags;
+}
+
+BOOL sdlInput::keyboard_sync_state()
+{
+ const UINT32 syncFlags = sdl_get_kbd_flags();
+ return freerdp_input_send_synchronize_event(_sdl->context()->input, syncFlags);
+}
+
+BOOL sdlInput::keyboard_focus_in()
+{
+ auto input = _sdl->context()->input;
+ WINPR_ASSERT(input);
+
+ auto syncFlags = sdl_get_kbd_flags();
+ freerdp_input_send_focus_in_event(input, syncFlags);
+
+ /* finish with a mouse pointer position like mstsc.exe if required */
+#if 0
+ if (xfc->remote_app)
+ return;
+
+ if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state))
+ {
+ if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height))
+ {
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+ }
+ }
+#endif
+ return TRUE;
+}
+
+/* This function is called to update the keyboard indicator LED */
+BOOL sdlInput::keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ WINPR_UNUSED(context);
+
+ int state = KMOD_NONE;
+
+ if ((led_flags & KBD_SYNC_NUM_LOCK) != 0)
+ state |= KMOD_NUM;
+ if ((led_flags & KBD_SYNC_CAPS_LOCK) != 0)
+ state |= KMOD_CAPS;
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ if ((led_flags & KBD_SYNC_SCROLL_LOCK) != 0)
+ state |= KMOD_SCROLL;
+#endif
+
+ // TODO: KBD_SYNC_KANA_LOCK
+
+ SDL_SetModState(static_cast<SDL_Keymod>(state));
+
+ return TRUE;
+}
+
+/* This function is called to set the IME state */
+BOOL sdlInput::keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+uint32_t sdlInput::prefToMask()
+{
+ const std::map<std::string, SDL_Keymod> mapping = {
+ { "KMOD_LSHIFT", KMOD_LSHIFT },
+ { "KMOD_RSHIFT", KMOD_RSHIFT },
+ { "KMOD_LCTRL", KMOD_LCTRL },
+ { "KMOD_RCTRL", KMOD_RCTRL },
+ { "KMOD_LALT", KMOD_LALT },
+ { "KMOD_RALT", KMOD_RALT },
+ { "KMOD_LGUI", KMOD_LGUI },
+ { "KMOD_RGUI", KMOD_RGUI },
+ { "KMOD_NUM", KMOD_NUM },
+ { "KMOD_CAPS", KMOD_CAPS },
+ { "KMOD_MODE", KMOD_MODE },
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ { "KMOD_SCROLL", KMOD_SCROLL },
+#endif
+ { "KMOD_CTRL", KMOD_CTRL },
+ { "KMOD_SHIFT", KMOD_SHIFT },
+ { "KMOD_ALT", KMOD_ALT },
+ { "KMOD_GUI", KMOD_GUI }
+ };
+ uint32_t mod = KMOD_NONE;
+ for (const auto& val : sdl_get_pref_array("SDL_KeyModMask", { "KMOD_RSHIFT" }))
+ {
+ auto it = mapping.find(val);
+ if (it != mapping.end())
+ {
+ mod |= it->second;
+ }
+ }
+ return mod;
+}
+
+static const char* sdl_scancode_name(Uint32 scancode)
+{
+ for (const auto& cur : map)
+ {
+ if (cur.sdl == scancode)
+ return cur.sdl_name;
+ }
+
+ return "SDL_SCANCODE_UNKNOWN";
+}
+
+static Uint32 sdl_scancode_val(const char* scancodeName)
+{
+ for (const auto& cur : map)
+ {
+ if (strcmp(cur.sdl_name, scancodeName) == 0)
+ return cur.sdl;
+ }
+
+ return SDL_SCANCODE_UNKNOWN;
+}
+
+static const char* sdl_rdp_scancode_name(UINT32 scancode)
+{
+ for (const auto& cur : map)
+ {
+ if (cur.rdp == scancode)
+ return cur.rdp_name;
+ }
+
+ return "RDP_SCANCODE_UNKNOWN";
+}
+
+static UINT32 sdl_rdp_scancode_val(const char* scancodeName)
+{
+ for (const auto& cur : map)
+ {
+ if (strcmp(cur.rdp_name, scancodeName) == 0)
+ return cur.rdp;
+ }
+
+ return RDP_SCANCODE_UNKNOWN;
+}
+
+static UINT32 sdl_scancode_to_rdp(Uint32 scancode)
+{
+ UINT32 rdp = RDP_SCANCODE_UNKNOWN;
+
+ for (const auto& cur : map)
+ {
+ if (cur.sdl == scancode)
+ {
+ rdp = cur.rdp;
+ break;
+ }
+ }
+
+#if defined(WITH_DEBUG_SDL_KBD_EVENTS)
+ auto code = static_cast<SDL_Scancode>(scancode);
+ WLog_DBG(TAG, "got %s [%s] -> [%s]", SDL_GetScancodeName(code), sdl_scancode_name(scancode),
+ sdl_rdp_scancode_name(rdp));
+#endif
+ return rdp;
+}
+
+uint32_t sdlInput::prefKeyValue(const std::string& key, uint32_t fallback)
+{
+ auto item = sdl_get_pref_string(key);
+ if (item.empty())
+ return fallback;
+ auto val = sdl_scancode_val(item.c_str());
+ if (val == SDL_SCANCODE_UNKNOWN)
+ return fallback;
+ return val;
+}
+
+BOOL sdlInput::keyboard_handle_event(const SDL_KeyboardEvent* ev)
+{
+ WINPR_ASSERT(ev);
+ const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode);
+ const SDL_Keymod mods = SDL_GetModState();
+ const auto mask = prefToMask();
+ const auto valFullscreen = prefKeyValue("SDL_Fullscreen", SDL_SCANCODE_RETURN);
+ const auto valResizeable = prefKeyValue("SDL_Resizeable", SDL_SCANCODE_R);
+ const auto valGrab = prefKeyValue("SDL_Grab", SDL_SCANCODE_G);
+ const auto valDisconnect = prefKeyValue("SDL_Disconnect", SDL_SCANCODE_D);
+
+ if ((mods & mask) == mask)
+ {
+ if (ev->type == SDL_KEYDOWN)
+ {
+ if (ev->keysym.scancode == valFullscreen)
+ {
+ _sdl->update_fullscreen(!_sdl->fullscreen);
+ return TRUE;
+ }
+ if (ev->keysym.scancode == valResizeable)
+ {
+ _sdl->update_resizeable(!_sdl->resizeable);
+ return TRUE;
+ }
+
+ if (ev->keysym.scancode == valGrab)
+ {
+ keyboard_grab(ev->windowID, _sdl->grab_kbd ? SDL_FALSE : SDL_TRUE);
+ return TRUE;
+ }
+ if (ev->keysym.scancode == valDisconnect)
+ {
+ freerdp_abort_connect_context(_sdl->context());
+ return TRUE;
+ }
+ }
+ }
+ return freerdp_input_send_keyboard_event_ex(_sdl->context()->input, ev->type == SDL_KEYDOWN,
+ ev->repeat, rdp_scancode);
+}
+
+BOOL sdlInput::keyboard_grab(Uint32 windowID, SDL_bool enable)
+{
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+ _sdl->grab_kbd = enable;
+ return it->second.grabKeyboard(enable);
+}
+
+BOOL sdlInput::mouse_focus(Uint32 windowID)
+{
+ if (_lastWindowID != windowID)
+ {
+ _lastWindowID = windowID;
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+
+ it->second.raise();
+ }
+ return TRUE;
+}
+
+BOOL sdlInput::mouse_grab(Uint32 windowID, SDL_bool enable)
+{
+ auto it = _sdl->windows.find(windowID);
+ if (it == _sdl->windows.end())
+ return FALSE;
+ _sdl->grab_mouse = enable;
+ return it->second.grabMouse(enable);
+}
+
+sdlInput::sdlInput(SdlContext* sdl) : _sdl(sdl), _lastWindowID(UINT32_MAX)
+{
+ WINPR_ASSERT(_sdl);
+}
diff --git a/client/SDL/sdl_kbd.hpp b/client/SDL/sdl_kbd.hpp
new file mode 100644
index 0000000..2a6c7fa
--- /dev/null
+++ b/client/SDL/sdl_kbd.hpp
@@ -0,0 +1,56 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client keyboard helper
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <winpr/wtypes.h>
+#include <freerdp/freerdp.h>
+#include <SDL.h>
+
+#include "sdl_types.hpp"
+
+class sdlInput
+{
+ public:
+ explicit sdlInput(SdlContext* sdl);
+ ~sdlInput() = default;
+
+ BOOL keyboard_sync_state();
+ BOOL keyboard_focus_in();
+
+ BOOL keyboard_handle_event(const SDL_KeyboardEvent* ev);
+
+ BOOL keyboard_grab(Uint32 windowID, SDL_bool enable);
+ BOOL mouse_focus(Uint32 windowID);
+ BOOL mouse_grab(Uint32 windowID, SDL_bool enable);
+
+ public:
+ static BOOL keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
+ static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode);
+
+ static uint32_t prefToMask();
+ static uint32_t prefKeyValue(const std::string& key, uint32_t fallback = SDL_SCANCODE_UNKNOWN);
+
+ private:
+ SdlContext* _sdl;
+ Uint32 _lastWindowID;
+};
diff --git a/client/SDL/sdl_monitor.cpp b/client/SDL/sdl_monitor.cpp
new file mode 100644
index 0000000..e637b48
--- /dev/null
+++ b/client/SDL/sdl_monitor.cpp
@@ -0,0 +1,331 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ * Copyright 2018 Kai Harms <kharms@rangee.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 <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include <SDL.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+
+#define TAG CLIENT_TAG("sdl")
+
+#include "sdl_monitor.hpp"
+#include "sdl_freerdp.hpp"
+
+typedef struct
+{
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ BOOL primary;
+} MONITOR_INFO;
+
+typedef struct
+{
+ int nmonitors;
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ MONITOR_INFO* monitors;
+} VIRTUAL_SCREEN;
+
+/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
+ */
+
+int sdl_list_monitors(SdlContext* sdl)
+{
+ SDL_Init(SDL_INIT_VIDEO);
+ const int nmonitors = SDL_GetNumVideoDisplays();
+
+ printf("listing %d monitors:\n", nmonitors);
+ for (int i = 0; i < nmonitors; i++)
+ {
+ SDL_Rect rect = {};
+ const int brc = SDL_GetDisplayBounds(i, &rect);
+ const char* name = SDL_GetDisplayName(i);
+
+ if (brc != 0)
+ continue;
+ printf(" %s [%d] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, name, rect.w, rect.h,
+ rect.x, rect.y);
+ }
+
+ SDL_Quit();
+ return 0;
+}
+
+static BOOL sdl_is_monitor_id_active(SdlContext* sdl, UINT32 id)
+{
+ const rdpSettings* settings = nullptr;
+
+ WINPR_ASSERT(sdl);
+
+ settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 NumMonitorIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (!NumMonitorIds)
+ return TRUE;
+
+ for (UINT32 index = 0; index < NumMonitorIds; index++)
+ {
+ auto cur = static_cast<const UINT32*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index));
+ if (cur && (*cur == id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL sdl_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ auto settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ *pMaxWidth = 0;
+ *pMaxHeight = 0;
+
+ for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
+ {
+ auto monitor = static_cast<const rdpMonitor*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ *pMaxWidth = monitor->width;
+ *pMaxHeight = monitor->height;
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ SDL_Rect rect = {};
+ SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
+ *pMaxWidth = rect.w;
+ *pMaxHeight = rect.h;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
+ {
+ SDL_Rect rect = {};
+ SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
+
+ *pMaxWidth = rect.w;
+ *pMaxHeight = rect.h;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth =
+ (rect.w * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight =
+ (rect.h * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
+ {
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+ }
+ return TRUE;
+}
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation)
+{
+ switch (orientation)
+ {
+ case SDL_ORIENTATION_LANDSCAPE:
+ return ORIENTATION_LANDSCAPE;
+ case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
+ return ORIENTATION_LANDSCAPE_FLIPPED;
+ case SDL_ORIENTATION_PORTRAIT_FLIPPED:
+ return ORIENTATION_PORTRAIT_FLIPPED;
+ case SDL_ORIENTATION_PORTRAIT:
+ default:
+ return ORIENTATION_PORTRAIT;
+ }
+}
+#endif
+
+static BOOL sdl_apply_display_properties(SdlContext* sdl)
+{
+ WINPR_ASSERT(sdl);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorDefArray, nullptr, numIds))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, numIds))
+ return FALSE;
+
+ for (UINT32 x = 0; x < numIds; x++)
+ {
+ auto id = static_cast<const UINT32*>(
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
+ WINPR_ASSERT(id);
+
+ float ddpi = 1.0f;
+ float hdpi = 1.0f;
+ float vdpi = 1.0f;
+ SDL_Rect rect = {};
+
+ SDL_GetDisplayBounds(*id, &rect);
+ SDL_GetDisplayDPI(*id, &ddpi, &hdpi, &vdpi);
+
+ bool highDpi = hdpi > 100;
+
+ if (highDpi)
+ {
+ // HighDPI is problematic with SDL: We can only get native resolution by creating a
+ // window. Work around this by checking the supported resolutions (and keep maximum)
+ // Also scale the DPI
+ const SDL_Rect scaleRect = rect;
+ for (int i = 0; i < SDL_GetNumDisplayModes(*id); i++)
+ {
+ SDL_DisplayMode mode = {};
+ SDL_GetDisplayMode(x, i, &mode);
+
+ if (mode.w > rect.w)
+ {
+ rect.w = mode.w;
+ rect.h = mode.h;
+ }
+ else if (mode.w == rect.w)
+ {
+ if (mode.h > rect.h)
+ {
+ rect.w = mode.w;
+ rect.h = mode.h;
+ }
+ }
+ }
+
+ const float dw = 1.0f * rect.w / scaleRect.w;
+ const float dh = 1.0f * rect.h / scaleRect.h;
+ hdpi /= dw;
+ vdpi /= dh;
+ }
+
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ const SDL_DisplayOrientation orientation = SDL_GetDisplayOrientation(*id);
+ const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation);
+#else
+ const UINT32 rdp_orientation = ORIENTATION_LANDSCAPE;
+#endif
+
+ auto monitor = static_cast<rdpMonitor*>(
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
+ WINPR_ASSERT(monitor);
+
+ /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
+ const auto factor = ddpi / 96.0f * 100.0f;
+ monitor->orig_screen = x;
+ monitor->x = rect.x;
+ monitor->y = rect.y;
+ monitor->width = rect.w;
+ monitor->height = rect.h;
+ monitor->is_primary = x == 0;
+ monitor->attributes.desktopScaleFactor = factor;
+ monitor->attributes.deviceScaleFactor = 100;
+ monitor->attributes.orientation = rdp_orientation;
+ monitor->attributes.physicalWidth = rect.w / hdpi;
+ monitor->attributes.physicalHeight = rect.h / vdpi;
+ }
+ return TRUE;
+}
+
+static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
+ (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
+ {
+ /* If no monitors were specified on the command-line then set the current monitor as active
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
+ {
+ const size_t id =
+ (sdl->windows.size() > 0) ? sdl->windows.begin()->second.displayIndex() : 0;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1))
+ return FALSE;
+ }
+ else
+ {
+
+ /* Always sets number of monitors from command-line to just 1.
+ * If the monitor is invalid then we will default back to current monitor
+ * later as a fallback. So, there is no need to validate command-line entry here.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
+ return FALSE;
+ }
+
+ // TODO: Fill monitor struct
+ if (!sdl_apply_display_properties(sdl))
+ return FALSE;
+ return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight);
+ }
+ return TRUE;
+}
+
+BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(pMaxWidth);
+ WINPR_ASSERT(pMaxHeight);
+
+ rdpSettings* settings = sdl->context()->settings;
+ WINPR_ASSERT(settings);
+
+ const int numDisplays = SDL_GetNumVideoDisplays();
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr, numDisplays))
+ return FALSE;
+
+ for (size_t x = 0; x < numDisplays; x++)
+ {
+ if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x))
+ return FALSE;
+ }
+
+ if (!sdl_apply_display_properties(sdl))
+ return FALSE;
+
+ return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
+}
diff --git a/client/SDL/sdl_monitor.hpp b/client/SDL/sdl_monitor.hpp
new file mode 100644
index 0000000..64f9f56
--- /dev/null
+++ b/client/SDL/sdl_monitor.hpp
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Monitor Handling
+ *
+ * Copyright 2023 Armin Novak <anovak@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.
+ */
+
+#pragma once
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+#include "sdl_types.hpp"
+
+int sdl_list_monitors(SdlContext* sdl);
+BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pWidth, UINT32* pHeight);
diff --git a/client/SDL/sdl_pointer.cpp b/client/SDL/sdl_pointer.cpp
new file mode 100644
index 0000000..ad8a4f3
--- /dev/null
+++ b/client/SDL/sdl_pointer.cpp
@@ -0,0 +1,197 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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/gdi/gdi.h>
+
+#include "sdl_pointer.hpp"
+#include "sdl_freerdp.hpp"
+#include "sdl_touch.hpp"
+#include "sdl_utils.hpp"
+
+#include <SDL_mouse.h>
+
+#define TAG CLIENT_TAG("SDL.pointer")
+
+typedef struct
+{
+ rdpPointer pointer;
+ SDL_Cursor* cursor;
+ SDL_Surface* image;
+ size_t size;
+ void* data;
+} sdlPointer;
+
+static BOOL sdl_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ auto ptr = reinterpret_cast<sdlPointer*>(pointer);
+
+ WINPR_ASSERT(context);
+ if (!ptr)
+ return FALSE;
+
+ rdpGdi* gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ ptr->size = 4ull * pointer->width * pointer->height;
+ ptr->data = winpr_aligned_malloc(ptr->size, 16);
+
+ if (!ptr->data)
+ return FALSE;
+
+ auto data = static_cast<BYTE*>(ptr->data);
+ if (!freerdp_image_copy_from_pointer_data(
+ data, gdi->dstFormat, 0, 0, 0, pointer->width, pointer->height, pointer->xorMaskData,
+ pointer->lengthXorMask, pointer->andMaskData, pointer->lengthAndMask, pointer->xorBpp,
+ &context->gdi->palette))
+ {
+ winpr_aligned_free(ptr->data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void sdl_Pointer_Clear(sdlPointer* ptr)
+{
+ WINPR_ASSERT(ptr);
+ SDL_FreeCursor(ptr->cursor);
+ SDL_FreeSurface(ptr->image);
+ ptr->cursor = nullptr;
+ ptr->image = nullptr;
+}
+
+static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ auto ptr = reinterpret_cast<sdlPointer*>(pointer);
+ WINPR_UNUSED(context);
+
+ if (ptr)
+ {
+ sdl_Pointer_Clear(ptr);
+ winpr_aligned_free(ptr->data);
+ ptr->data = nullptr;
+ }
+}
+
+static BOOL sdl_Pointer_SetDefault(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_DEFAULT);
+}
+
+static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ auto sdl = get_context(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_SET, pointer, sdl);
+}
+
+BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr)
+{
+ INT32 w = 0;
+ INT32 h = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 sw = 0;
+ INT32 sh = 0;
+
+ WINPR_ASSERT(uptr);
+
+ auto sdl = static_cast<SdlContext*>(uptr->data2);
+ WINPR_ASSERT(sdl);
+
+ auto context = sdl->context();
+ auto ptr = static_cast<sdlPointer*>(uptr->data1);
+ WINPR_ASSERT(ptr);
+
+ rdpPointer* pointer = &ptr->pointer;
+
+ rdpGdi* gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+
+ x = static_cast<INT32>(pointer->xPos);
+ y = static_cast<INT32>(pointer->yPos);
+ sw = w = static_cast<INT32>(pointer->width);
+ sh = h = static_cast<INT32>(pointer->height);
+
+ SDL_Window* window = SDL_GetMouseFocus();
+ if (!window)
+ return sdl_Pointer_SetDefault(context);
+
+ const Uint32 id = SDL_GetWindowID(window);
+
+ if (!sdl_scale_coordinates(sdl, id, &x, &y, FALSE, FALSE) ||
+ !sdl_scale_coordinates(sdl, id, &sw, &sh, FALSE, FALSE))
+ return FALSE;
+
+ sdl_Pointer_Clear(ptr);
+
+ const DWORD bpp = FreeRDPGetBitsPerPixel(gdi->dstFormat);
+ ptr->image =
+ SDL_CreateRGBSurfaceWithFormat(0, sw, sh, static_cast<int>(bpp), sdl->sdl_pixel_format);
+ if (!ptr->image)
+ return FALSE;
+
+ SDL_LockSurface(ptr->image);
+ auto pixels = static_cast<BYTE*>(ptr->image->pixels);
+ auto data = static_cast<const BYTE*>(ptr->data);
+ const BOOL rc = freerdp_image_scale(
+ pixels, gdi->dstFormat, static_cast<UINT32>(ptr->image->pitch), 0, 0,
+ static_cast<UINT32>(ptr->image->w), static_cast<UINT32>(ptr->image->h), data,
+ gdi->dstFormat, 0, 0, 0, static_cast<UINT32>(w), static_cast<UINT32>(h));
+ SDL_UnlockSurface(ptr->image);
+ if (!rc)
+ return FALSE;
+
+ ptr->cursor = SDL_CreateColorCursor(ptr->image, x, y);
+ if (!ptr->cursor)
+ return FALSE;
+
+ SDL_SetCursor(ptr->cursor);
+ SDL_ShowCursor(SDL_ENABLE);
+ return TRUE;
+}
+
+static BOOL sdl_Pointer_SetNull(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_NULL);
+}
+
+static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ auto sdl = get_context(context);
+ WINPR_ASSERT(sdl);
+
+ return sdl_push_user_event(SDL_USEREVENT_POINTER_POSITION, x, y);
+}
+
+BOOL sdl_register_pointer(rdpGraphics* graphics)
+{
+ const rdpPointer pointer = { sizeof(sdlPointer), sdl_Pointer_New,
+ sdl_Pointer_Free, sdl_Pointer_Set,
+ sdl_Pointer_SetNull, sdl_Pointer_SetDefault,
+ sdl_Pointer_SetPosition, 0 };
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
diff --git a/client/SDL/sdl_pointer.hpp b/client/SDL/sdl_pointer.hpp
new file mode 100644
index 0000000..006e962
--- /dev/null
+++ b/client/SDL/sdl_pointer.hpp
@@ -0,0 +1,27 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Mouse Pointer
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <SDL.h>
+#include <freerdp/graphics.h>
+
+BOOL sdl_register_pointer(rdpGraphics* graphics);
+
+BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr);
diff --git a/client/SDL/sdl_touch.cpp b/client/SDL/sdl_touch.cpp
new file mode 100644
index 0000000..81fcbfb
--- /dev/null
+++ b/client/SDL/sdl_touch.cpp
@@ -0,0 +1,285 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL touch/mouse input
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 "sdl_touch.hpp"
+#include "sdl_freerdp.hpp"
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <SDL.h>
+
+#define TAG CLIENT_TAG("SDL.touch")
+
+BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py,
+ BOOL fromLocalToRDP, BOOL applyOffset)
+{
+ rdpGdi* gdi = nullptr;
+ double sx = 1.0;
+ double sy = 1.0;
+
+ if (!sdl || !px || !py || !sdl->context()->gdi)
+ return FALSE;
+
+ WINPR_ASSERT(sdl->context()->gdi);
+ WINPR_ASSERT(sdl->context()->settings);
+
+ gdi = sdl->context()->gdi;
+
+ // TODO: Make this multimonitor ready!
+ // TODO: Need to find the primary monitor, get the scale
+ // TODO: Need to find the destination monitor, get the scale
+ // TODO: All intermediate monitors, get the scale
+
+ int offset_x = 0;
+ int offset_y = 0;
+ for (const auto& it : sdl->windows)
+ {
+ auto& window = it.second;
+ const auto id = window.id();
+ if (id != windowId)
+ {
+ continue;
+ }
+
+ auto size = window.rect();
+
+ sx = size.w / static_cast<double>(gdi->width);
+ sy = size.h / static_cast<double>(gdi->height);
+ offset_x = window.offsetX();
+ offset_y = window.offsetY();
+ break;
+ }
+
+ if (freerdp_settings_get_bool(sdl->context()->settings, FreeRDP_SmartSizing))
+ {
+ if (!fromLocalToRDP)
+ {
+ *px = static_cast<INT32>(*px * sx);
+ *py = static_cast<INT32>(*py * sy);
+ }
+ else
+ {
+ *px = static_cast<INT32>(*px / sx);
+ *py = static_cast<INT32>(*py / sy);
+ }
+ }
+ else if (applyOffset)
+ {
+ *px -= offset_x;
+ *py -= offset_y;
+ }
+
+ return TRUE;
+}
+
+static BOOL sdl_get_touch_scaled(SdlContext* sdl, const SDL_TouchFingerEvent* ev, INT32* px,
+ INT32* py, BOOL local)
+{
+ Uint32 windowID = 0;
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+ WINPR_ASSERT(px);
+ WINPR_ASSERT(py);
+
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+ SDL_Window* window = SDL_GetWindowFromID(ev->windowID);
+#else
+ SDL_Window* window = SDL_GetMouseFocus();
+#endif
+
+ if (!window)
+ return FALSE;
+
+ windowID = SDL_GetWindowID(window);
+ SDL_Surface* surface = SDL_GetWindowSurface(window);
+ if (!surface)
+ return FALSE;
+
+ // TODO: Add the offset of the surface in the global coordinates
+ *px = static_cast<INT32>(ev->x * static_cast<float>(surface->w));
+ *py = static_cast<INT32>(ev->y * static_cast<float>(surface->h));
+ return sdl_scale_coordinates(sdl, windowID, px, py, local, TRUE);
+}
+
+static BOOL send_mouse_wheel(SdlContext* sdl, UINT16 flags, INT32 avalue)
+{
+ WINPR_ASSERT(sdl);
+ if (avalue < 0)
+ {
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ avalue = -avalue;
+ }
+
+ while (avalue > 0)
+ {
+ const UINT16 cval = (avalue > 0xFF) ? 0xFF : static_cast<UINT16>(avalue);
+ UINT16 cflags = flags | cval;
+ /* Convert negative values to 9bit twos complement */
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ cflags = (flags & 0xFF00) | (0x100 - cval);
+ if (!freerdp_client_send_wheel_event(sdl->common(), cflags))
+ return FALSE;
+
+ avalue -= cval;
+ }
+ return TRUE;
+}
+
+static UINT32 sdl_scale_pressure(const float pressure)
+{
+ const float val = pressure * 0x400; /* [MS-RDPEI] 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT */
+ if (val < 0.0f)
+ return 0;
+ if (val > 0x400)
+ return 0x400;
+ return static_cast<UINT32>(val);
+}
+
+BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(sdl->common(), FREERDP_TOUCH_UP | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId),
+ sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(
+ sdl->common(), FREERDP_TOUCH_DOWN | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ INT32 x = 0;
+ INT32 y = 0;
+ if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE))
+ return FALSE;
+ return freerdp_client_handle_touch(
+ sdl->common(), FREERDP_TOUCH_MOTION | FREERDP_TOUCH_HAS_PRESSURE,
+ static_cast<INT32>(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y);
+}
+
+BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ sdl->input.mouse_focus(ev->windowID);
+ const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
+ INT32 x = relative ? ev->xrel : ev->x;
+ INT32 y = relative ? ev->yrel : ev->y;
+ sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE);
+ return freerdp_client_send_button_event(sdl->common(), relative, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev)
+{
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ const BOOL flipped = (ev->direction == SDL_MOUSEWHEEL_FLIPPED);
+ const INT32 x = ev->x * (flipped ? -1 : 1) * 0x78;
+ const INT32 y = ev->y * (flipped ? -1 : 1) * 0x78;
+ UINT16 flags = 0;
+
+ if (y != 0)
+ {
+ flags |= PTR_FLAGS_WHEEL;
+ send_mouse_wheel(sdl, flags, y);
+ }
+
+ if (x != 0)
+ {
+ flags |= PTR_FLAGS_HWHEEL;
+ send_mouse_wheel(sdl, flags, x);
+ }
+ return TRUE;
+}
+
+BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev)
+{
+ UINT16 flags = 0;
+ UINT16 xflags = 0;
+
+ WINPR_ASSERT(sdl);
+ WINPR_ASSERT(ev);
+
+ if (ev->state == SDL_PRESSED)
+ {
+ flags |= PTR_FLAGS_DOWN;
+ xflags |= PTR_XFLAGS_DOWN;
+ }
+
+ switch (ev->button)
+ {
+ case 1:
+ flags |= PTR_FLAGS_BUTTON1;
+ break;
+ case 2:
+ flags |= PTR_FLAGS_BUTTON3;
+ break;
+ case 3:
+ flags |= PTR_FLAGS_BUTTON2;
+ break;
+ case 4:
+ xflags |= PTR_XFLAGS_BUTTON1;
+ break;
+ case 5:
+ xflags |= PTR_XFLAGS_BUTTON2;
+ break;
+ default:
+ break;
+ }
+
+ const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common());
+ INT32 x = relative ? 0 : ev->x;
+ INT32 y = relative ? 0 : ev->y;
+ sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE);
+ if ((flags & (~PTR_FLAGS_DOWN)) != 0)
+ return freerdp_client_send_button_event(sdl->common(), relative, flags, x, y);
+ else if ((xflags & (~PTR_XFLAGS_DOWN)) != 0)
+ return freerdp_client_send_extended_button_event(sdl->common(), relative, xflags, x, y);
+ else
+ return FALSE;
+}
diff --git a/client/SDL/sdl_touch.hpp b/client/SDL/sdl_touch.hpp
new file mode 100644
index 0000000..395fddb
--- /dev/null
+++ b/client/SDL/sdl_touch.hpp
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP SDL touch/mouse input
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <winpr/wtypes.h>
+
+#include <SDL.h>
+#include "sdl_types.hpp"
+
+BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py,
+ BOOL fromLocalToRDP, BOOL applyOffset);
+
+BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev);
+BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev);
+BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev);
+
+BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
+BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
+BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev);
diff --git a/client/SDL/sdl_types.hpp b/client/SDL/sdl_types.hpp
new file mode 100644
index 0000000..831472c
--- /dev/null
+++ b/client/SDL/sdl_types.hpp
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <freerdp/freerdp.h>
+
+class SdlContext;
+
+typedef struct
+{
+ rdpClientContext common;
+ SdlContext* sdl;
+} sdl_rdp_context;
+
+static inline SdlContext* get_context(void* ctx)
+{
+ if (!ctx)
+ return nullptr;
+ auto sdl = static_cast<sdl_rdp_context*>(ctx);
+ return sdl->sdl;
+}
+
+static inline SdlContext* get_context(rdpContext* ctx)
+{
+ if (!ctx)
+ return nullptr;
+ auto sdl = reinterpret_cast<sdl_rdp_context*>(ctx);
+ return sdl->sdl;
+}
diff --git a/client/SDL/sdl_utils.cpp b/client/SDL/sdl_utils.cpp
new file mode 100644
index 0000000..c3bd2cf
--- /dev/null
+++ b/client/SDL/sdl_utils.cpp
@@ -0,0 +1,465 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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 <fstream>
+#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 <cassert>
+#include "sdl_utils.hpp"
+
+#include "sdl_freerdp.hpp"
+
+#include <SDL.h>
+
+#include <winpr/path.h>
+#include <freerdp/version.h>
+#if defined(CJSON_FOUND)
+#include <cjson/cJSON.h>
+#endif
+
+const char* sdl_event_type_str(Uint32 type)
+{
+#define STR(x) #x
+#define EV_CASE_STR(x) \
+ case x: \
+ return STR(x)
+
+ switch (type)
+ {
+ EV_CASE_STR(SDL_FIRSTEVENT);
+ EV_CASE_STR(SDL_QUIT);
+ EV_CASE_STR(SDL_APP_TERMINATING);
+ EV_CASE_STR(SDL_APP_LOWMEMORY);
+ EV_CASE_STR(SDL_APP_WILLENTERBACKGROUND);
+ EV_CASE_STR(SDL_APP_DIDENTERBACKGROUND);
+ EV_CASE_STR(SDL_APP_WILLENTERFOREGROUND);
+ EV_CASE_STR(SDL_APP_DIDENTERFOREGROUND);
+#if SDL_VERSION_ATLEAST(2, 0, 10)
+ EV_CASE_STR(SDL_DISPLAYEVENT);
+#endif
+ EV_CASE_STR(SDL_WINDOWEVENT);
+ EV_CASE_STR(SDL_SYSWMEVENT);
+ EV_CASE_STR(SDL_KEYDOWN);
+ EV_CASE_STR(SDL_KEYUP);
+ EV_CASE_STR(SDL_TEXTEDITING);
+ EV_CASE_STR(SDL_TEXTINPUT);
+ EV_CASE_STR(SDL_KEYMAPCHANGED);
+ EV_CASE_STR(SDL_MOUSEMOTION);
+ EV_CASE_STR(SDL_MOUSEBUTTONDOWN);
+ EV_CASE_STR(SDL_MOUSEBUTTONUP);
+ EV_CASE_STR(SDL_MOUSEWHEEL);
+ EV_CASE_STR(SDL_JOYAXISMOTION);
+ EV_CASE_STR(SDL_JOYBALLMOTION);
+ EV_CASE_STR(SDL_JOYHATMOTION);
+ EV_CASE_STR(SDL_JOYBUTTONDOWN);
+ EV_CASE_STR(SDL_JOYBUTTONUP);
+ EV_CASE_STR(SDL_JOYDEVICEADDED);
+ EV_CASE_STR(SDL_JOYDEVICEREMOVED);
+ EV_CASE_STR(SDL_CONTROLLERAXISMOTION);
+ EV_CASE_STR(SDL_CONTROLLERBUTTONDOWN);
+ EV_CASE_STR(SDL_CONTROLLERBUTTONUP);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEADDED);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEREMOVED);
+ EV_CASE_STR(SDL_CONTROLLERDEVICEREMAPPED);
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ EV_CASE_STR(SDL_LOCALECHANGED);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADDOWN);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADMOTION);
+ EV_CASE_STR(SDL_CONTROLLERTOUCHPADUP);
+ EV_CASE_STR(SDL_CONTROLLERSENSORUPDATE);
+#endif
+ EV_CASE_STR(SDL_FINGERDOWN);
+ EV_CASE_STR(SDL_FINGERUP);
+ EV_CASE_STR(SDL_FINGERMOTION);
+ EV_CASE_STR(SDL_DOLLARGESTURE);
+ EV_CASE_STR(SDL_DOLLARRECORD);
+ EV_CASE_STR(SDL_MULTIGESTURE);
+ EV_CASE_STR(SDL_CLIPBOARDUPDATE);
+ EV_CASE_STR(SDL_DROPFILE);
+ EV_CASE_STR(SDL_DROPTEXT);
+ EV_CASE_STR(SDL_DROPBEGIN);
+ EV_CASE_STR(SDL_DROPCOMPLETE);
+ EV_CASE_STR(SDL_AUDIODEVICEADDED);
+ EV_CASE_STR(SDL_AUDIODEVICEREMOVED);
+#if SDL_VERSION_ATLEAST(2, 0, 9)
+ EV_CASE_STR(SDL_SENSORUPDATE);
+#endif
+ EV_CASE_STR(SDL_RENDER_TARGETS_RESET);
+ EV_CASE_STR(SDL_RENDER_DEVICE_RESET);
+ EV_CASE_STR(SDL_USEREVENT);
+
+ EV_CASE_STR(SDL_USEREVENT_CERT_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_CERT_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_SHOW_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_SHOW_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_AUTH_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_AUTH_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_SCARD_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_RETRY_DIALOG);
+ EV_CASE_STR(SDL_USEREVENT_SCARD_RESULT);
+ EV_CASE_STR(SDL_USEREVENT_UPDATE);
+ EV_CASE_STR(SDL_USEREVENT_CREATE_WINDOWS);
+ EV_CASE_STR(SDL_USEREVENT_WINDOW_RESIZEABLE);
+ EV_CASE_STR(SDL_USEREVENT_WINDOW_FULLSCREEN);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_NULL);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_DEFAULT);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_POSITION);
+ EV_CASE_STR(SDL_USEREVENT_POINTER_SET);
+ EV_CASE_STR(SDL_USEREVENT_QUIT);
+
+ EV_CASE_STR(SDL_LASTEVENT);
+ default:
+ return "SDL_UNKNOWNEVENT";
+ }
+#undef EV_CASE_STR
+#undef STR
+}
+
+const char* sdl_error_string(Uint32 res)
+{
+ if (res == 0)
+ return nullptr;
+
+ return SDL_GetError();
+}
+
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt)
+{
+ const char* msg = sdl_error_string(res);
+
+ WINPR_UNUSED(file);
+
+ if (!msg)
+ return FALSE;
+
+ WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, msg);
+ return TRUE;
+}
+
+BOOL sdl_push_user_event(Uint32 type, ...)
+{
+ SDL_Event ev = {};
+ SDL_UserEvent* event = &ev.user;
+
+ va_list ap;
+ va_start(ap, type);
+ event->type = type;
+ switch (type)
+ {
+ case SDL_USEREVENT_AUTH_RESULT:
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
+ arg->user = va_arg(ap, char*);
+ arg->domain = va_arg(ap, char*);
+ arg->password = va_arg(ap, char*);
+ arg->result = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_AUTH_DIALOG:
+ {
+ auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
+
+ arg->title = va_arg(ap, char*);
+ arg->user = va_arg(ap, char*);
+ arg->domain = va_arg(ap, char*);
+ arg->password = va_arg(ap, char*);
+ arg->result = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_SCARD_DIALOG:
+ {
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char**);
+ event->code = va_arg(ap, Sint32);
+ }
+ break;
+ case SDL_USEREVENT_RETRY_DIALOG:
+ break;
+ case SDL_USEREVENT_SCARD_RESULT:
+ case SDL_USEREVENT_SHOW_RESULT:
+ case SDL_USEREVENT_CERT_RESULT:
+ event->code = va_arg(ap, Sint32);
+ break;
+
+ case SDL_USEREVENT_SHOW_DIALOG:
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char*);
+ event->code = va_arg(ap, Sint32);
+ break;
+ case SDL_USEREVENT_CERT_DIALOG:
+ event->data1 = va_arg(ap, char*);
+ event->data2 = va_arg(ap, char*);
+ break;
+ case SDL_USEREVENT_UPDATE:
+ event->data1 = va_arg(ap, void*);
+ break;
+ case SDL_USEREVENT_POINTER_POSITION:
+ event->data1 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
+ event->data2 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
+ break;
+ case SDL_USEREVENT_POINTER_SET:
+ event->data1 = va_arg(ap, void*);
+ event->data2 = va_arg(ap, void*);
+ break;
+ case SDL_USEREVENT_CREATE_WINDOWS:
+ event->data1 = reinterpret_cast<void*>(va_arg(ap, void*));
+ break;
+ case SDL_USEREVENT_WINDOW_FULLSCREEN:
+ case SDL_USEREVENT_WINDOW_RESIZEABLE:
+ event->data1 = va_arg(ap, void*);
+ event->code = va_arg(ap, int);
+ break;
+ case SDL_USEREVENT_QUIT:
+ case SDL_USEREVENT_POINTER_NULL:
+ case SDL_USEREVENT_POINTER_DEFAULT:
+ break;
+ default:
+ va_end(ap);
+ return FALSE;
+ }
+ va_end(ap);
+ return SDL_PushEvent(&ev) == 1;
+}
+
+CriticalSection::CriticalSection()
+{
+ InitializeCriticalSection(&_section);
+}
+
+CriticalSection::~CriticalSection()
+{
+ DeleteCriticalSection(&_section);
+}
+
+void CriticalSection::lock()
+{
+ EnterCriticalSection(&_section);
+}
+
+void CriticalSection::unlock()
+{
+ LeaveCriticalSection(&_section);
+}
+
+WinPREvent::WinPREvent(bool initial)
+ : _handle(CreateEventA(nullptr, TRUE, initial ? TRUE : FALSE, nullptr))
+{
+}
+
+WinPREvent::~WinPREvent()
+{
+ CloseHandle(_handle);
+}
+
+void WinPREvent::set()
+{
+ SetEvent(_handle);
+}
+
+void WinPREvent::clear()
+{
+ ResetEvent(_handle);
+}
+
+bool WinPREvent::isSet() const
+{
+ return WaitForSingleObject(_handle, 0) == WAIT_OBJECT_0;
+}
+
+HANDLE WinPREvent::handle() const
+{
+ return _handle;
+}
+
+bool sdl_push_quit()
+{
+ SDL_Event ev = { 0 };
+ ev.type = SDL_QUIT;
+ SDL_PushEvent(&ev);
+ return true;
+}
+
+std::string sdl_window_event_str(Uint8 ev)
+{
+ switch (ev)
+ {
+ case SDL_WINDOWEVENT_NONE:
+ return "SDL_WINDOWEVENT_NONE";
+ case SDL_WINDOWEVENT_SHOWN:
+ return "SDL_WINDOWEVENT_SHOWN";
+ case SDL_WINDOWEVENT_HIDDEN:
+ return "SDL_WINDOWEVENT_HIDDEN";
+ case SDL_WINDOWEVENT_EXPOSED:
+ return "SDL_WINDOWEVENT_EXPOSED";
+ case SDL_WINDOWEVENT_MOVED:
+ return "SDL_WINDOWEVENT_MOVED";
+ case SDL_WINDOWEVENT_RESIZED:
+ return "SDL_WINDOWEVENT_RESIZED";
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ return "SDL_WINDOWEVENT_SIZE_CHANGED";
+ case SDL_WINDOWEVENT_MINIMIZED:
+ return "SDL_WINDOWEVENT_MINIMIZED";
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ return "SDL_WINDOWEVENT_MAXIMIZED";
+ case SDL_WINDOWEVENT_RESTORED:
+ return "SDL_WINDOWEVENT_RESTORED";
+ case SDL_WINDOWEVENT_ENTER:
+ return "SDL_WINDOWEVENT_ENTER";
+ case SDL_WINDOWEVENT_LEAVE:
+ return "SDL_WINDOWEVENT_LEAVE";
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ return "SDL_WINDOWEVENT_FOCUS_GAINED";
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ return "SDL_WINDOWEVENT_FOCUS_LOST";
+ case SDL_WINDOWEVENT_CLOSE:
+ return "SDL_WINDOWEVENT_CLOSE";
+#if SDL_VERSION_ATLEAST(2, 0, 5)
+ case SDL_WINDOWEVENT_TAKE_FOCUS:
+ return "SDL_WINDOWEVENT_TAKE_FOCUS";
+ case SDL_WINDOWEVENT_HIT_TEST:
+ return "SDL_WINDOWEVENT_HIT_TEST";
+#endif
+#if SDL_VERSION_ATLEAST(2, 0, 18)
+ case SDL_WINDOWEVENT_ICCPROF_CHANGED:
+ return "SDL_WINDOWEVENT_ICCPROF_CHANGED";
+ case SDL_WINDOWEVENT_DISPLAY_CHANGED:
+ return "SDL_WINDOWEVENT_DISPLAY_CHANGED";
+#endif
+ default:
+ return "SDL_WINDOWEVENT_UNKNOWN";
+ }
+}
+
+#if defined(CJSON_FOUND)
+using cJSONPtr = std::unique_ptr<cJSON, decltype(&cJSON_Delete)>;
+
+static cJSONPtr get()
+{
+ auto config = sdl_get_pref_file();
+
+ std::ifstream ifs(config);
+ std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
+ return { cJSON_ParseWithLength(content.c_str(), content.size()), cJSON_Delete };
+}
+
+static cJSON* get_item(const std::string& key)
+{
+ static cJSONPtr config{ nullptr, cJSON_Delete };
+ if (!config)
+ config = get();
+ if (!config)
+ return nullptr;
+ return cJSON_GetObjectItem(config.get(), key.c_str());
+}
+
+static std::string item_to_str(cJSON* item, const std::string& fallback = "")
+{
+ if (!item || !cJSON_IsString(item))
+ return fallback;
+ auto str = cJSON_GetStringValue(item);
+ if (!str)
+ return {};
+ return str;
+}
+#endif
+
+std::string sdl_get_pref_string(const std::string& key, const std::string& fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ return item_to_str(item, fallback);
+#else
+ return fallback;
+#endif
+}
+
+bool sdl_get_pref_bool(const std::string& key, bool fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsBool(item))
+ return fallback;
+ return cJSON_IsTrue(item);
+#else
+ return fallback;
+#endif
+}
+
+int64_t sdl_get_pref_int(const std::string& key, int64_t fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsNumber(item))
+ return fallback;
+ auto val = cJSON_GetNumberValue(item);
+ return static_cast<int64_t>(val);
+#else
+ return fallback;
+#endif
+}
+
+std::vector<std::string> sdl_get_pref_array(const std::string& key,
+ const std::vector<std::string>& fallback)
+{
+#if defined(CJSON_FOUND)
+ auto item = get_item(key);
+ if (!item || !cJSON_IsArray(item))
+ return fallback;
+
+ std::vector<std::string> values;
+ for (int x = 0; x < cJSON_GetArraySize(item); x++)
+ {
+ auto cur = cJSON_GetArrayItem(item, x);
+ values.push_back(item_to_str(cur));
+ }
+
+ return values;
+#else
+ return fallback;
+#endif
+}
+
+std::string sdl_get_pref_dir()
+{
+ using CStringPtr = std::unique_ptr<char, decltype(&free)>;
+ CStringPtr path(GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME), free);
+ if (!path)
+ return {};
+
+ fs::path config{ path.get() };
+ config /= FREERDP_VENDOR;
+ config /= FREERDP_PRODUCT;
+ return config.string();
+}
+
+std::string sdl_get_pref_file()
+{
+ fs::path config{ sdl_get_pref_dir() };
+ config /= "sdl-freerdp.json";
+ return config.string();
+}
diff --git a/client/SDL/sdl_utils.hpp b/client/SDL/sdl_utils.hpp
new file mode 100644
index 0000000..75cb461
--- /dev/null
+++ b/client/SDL/sdl_utils.hpp
@@ -0,0 +1,113 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2022 Armin Novak <armin.novak@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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <winpr/synch.h>
+#include <winpr/wlog.h>
+
+#include <SDL.h>
+#include <string>
+#include <vector>
+
+class CriticalSection
+{
+ public:
+ CriticalSection();
+ ~CriticalSection();
+
+ void lock();
+ void unlock();
+
+ private:
+ CRITICAL_SECTION _section;
+};
+
+class WinPREvent
+{
+ public:
+ explicit WinPREvent(bool initial = false);
+ ~WinPREvent();
+
+ void set();
+ void clear();
+ [[nodiscard]] bool isSet() const;
+
+ [[nodiscard]] HANDLE handle() const;
+
+ private:
+ HANDLE _handle;
+};
+
+enum
+{
+ SDL_USEREVENT_UPDATE = SDL_USEREVENT + 1,
+ SDL_USEREVENT_CREATE_WINDOWS,
+ SDL_USEREVENT_WINDOW_RESIZEABLE,
+ SDL_USEREVENT_WINDOW_FULLSCREEN,
+ SDL_USEREVENT_POINTER_NULL,
+ SDL_USEREVENT_POINTER_DEFAULT,
+ SDL_USEREVENT_POINTER_POSITION,
+ SDL_USEREVENT_POINTER_SET,
+ SDL_USEREVENT_QUIT,
+ SDL_USEREVENT_CERT_DIALOG,
+ SDL_USEREVENT_SHOW_DIALOG,
+ SDL_USEREVENT_AUTH_DIALOG,
+ SDL_USEREVENT_SCARD_DIALOG,
+ SDL_USEREVENT_RETRY_DIALOG,
+
+ SDL_USEREVENT_CERT_RESULT,
+ SDL_USEREVENT_SHOW_RESULT,
+ SDL_USEREVENT_AUTH_RESULT,
+ SDL_USEREVENT_SCARD_RESULT
+};
+
+typedef struct
+{
+ Uint32 type;
+ Uint32 timestamp;
+ char* title;
+ char* user;
+ char* domain;
+ char* password;
+ Sint32 result;
+} SDL_UserAuthArg;
+
+BOOL sdl_push_user_event(Uint32 type, ...);
+
+bool sdl_push_quit();
+
+std::string sdl_window_event_str(Uint8 ev);
+const char* sdl_event_type_str(Uint32 type);
+const char* sdl_error_string(Uint32 res);
+
+#define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__)
+BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line,
+ const char* fkt);
+
+std::string sdl_get_pref_dir();
+std::string sdl_get_pref_file();
+
+std::string sdl_get_pref_string(const std::string& key, const std::string& fallback = "");
+int64_t sdl_get_pref_int(const std::string& key, int64_t fallback = 0);
+bool sdl_get_pref_bool(const std::string& key, bool fallback = false);
+std::vector<std::string> sdl_get_pref_array(const std::string& key,
+ const std::vector<std::string>& fallback = {});
diff --git a/client/SDL/sdl_window.cpp b/client/SDL/sdl_window.cpp
new file mode 100644
index 0000000..c5437bc
--- /dev/null
+++ b/client/SDL/sdl_window.cpp
@@ -0,0 +1,203 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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 "sdl_window.hpp"
+#include "sdl_utils.hpp"
+
+SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
+ Sint32 height, Uint32 flags)
+ : _window(SDL_CreateWindow(title.c_str(), startupX, startupY, width, height, flags)),
+ _offset_x(0), _offset_y(0)
+{
+}
+
+SdlWindow::SdlWindow(SdlWindow&& other)
+ : _window(other._window), _offset_x(other._offset_x), _offset_y(other._offset_y)
+{
+ other._window = nullptr;
+}
+
+SdlWindow::~SdlWindow()
+{
+ SDL_DestroyWindow(_window);
+}
+
+Uint32 SdlWindow::id() const
+{
+ if (!_window)
+ return 0;
+ return SDL_GetWindowID(_window);
+}
+
+int SdlWindow::displayIndex() const
+{
+ if (!_window)
+ return 0;
+ return SDL_GetWindowDisplayIndex(_window);
+}
+
+SDL_Rect SdlWindow::rect() const
+{
+ SDL_Rect rect = {};
+ if (_window)
+ {
+ SDL_GetWindowPosition(_window, &rect.x, &rect.y);
+ SDL_GetWindowSize(_window, &rect.w, &rect.h);
+ }
+ return rect;
+}
+
+SDL_Window* SdlWindow::window() const
+{
+ return _window;
+}
+
+Sint32 SdlWindow::offsetX() const
+{
+ return _offset_x;
+}
+
+void SdlWindow::setOffsetX(Sint32 x)
+{
+ _offset_x = x;
+}
+
+void SdlWindow::setOffsetY(Sint32 y)
+{
+ _offset_y = y;
+}
+
+Sint32 SdlWindow::offsetY() const
+{
+ return _offset_y;
+}
+
+bool SdlWindow::grabKeyboard(bool enable)
+{
+ if (!_window)
+ return false;
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowKeyboardGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+ return true;
+#else
+ SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Keyboard grabbing not supported by SDL2 < 2.0.16");
+ return false;
+#endif
+}
+
+bool SdlWindow::grabMouse(bool enable)
+{
+ if (!_window)
+ return false;
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowMouseGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+#else
+ SDL_SetWindowGrab(_window, enable ? SDL_TRUE : SDL_FALSE);
+#endif
+ return true;
+}
+
+void SdlWindow::setBordered(bool bordered)
+{
+ if (_window)
+ SDL_SetWindowBordered(_window, bordered ? SDL_TRUE : SDL_FALSE);
+}
+
+void SdlWindow::raise()
+{
+ SDL_RaiseWindow(_window);
+}
+
+void SdlWindow::resizeable(bool use)
+{
+ SDL_SetWindowResizable(_window, use ? SDL_TRUE : SDL_FALSE);
+}
+
+void SdlWindow::fullscreen(bool enter)
+{
+ auto curFlags = SDL_GetWindowFlags(_window);
+
+ if (enter)
+ {
+ if (!(curFlags & SDL_WINDOW_BORDERLESS))
+ {
+ auto idx = SDL_GetWindowDisplayIndex(_window);
+ SDL_DisplayMode mode = {};
+ SDL_GetCurrentDisplayMode(idx, &mode);
+
+ SDL_RestoreWindow(_window); // Maximize so we can see the caption and
+ // bits
+ SDL_SetWindowBordered(_window, SDL_FALSE);
+ SDL_SetWindowPosition(_window, 0, 0);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowAlwaysOnTop(_window, SDL_TRUE);
+#endif
+ SDL_RaiseWindow(_window);
+ SDL_SetWindowSize(_window, mode.w, mode.h);
+ }
+ }
+ else
+ {
+ if (curFlags & SDL_WINDOW_BORDERLESS)
+ {
+
+ SDL_SetWindowBordered(_window, SDL_TRUE);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ SDL_SetWindowAlwaysOnTop(_window, SDL_FALSE);
+#endif
+ SDL_RaiseWindow(_window);
+ SDL_MinimizeWindow(_window); // Maximize so we can see the caption and bits
+ SDL_MaximizeWindow(_window); // Maximize so we can see the caption and bits
+ }
+ }
+}
+
+bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
+{
+ auto surface = SDL_GetWindowSurface(_window);
+ if (!surface)
+ return false;
+ SDL_Rect rect = { 0, 0, surface->w, surface->h };
+ auto color = SDL_MapRGBA(surface->format, r, g, b, a);
+
+ SDL_FillRect(surface, &rect, color);
+ return true;
+}
+
+bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect)
+{
+ auto screen = SDL_GetWindowSurface(_window);
+ if (!screen || !surface)
+ return false;
+ if (!SDL_SetClipRect(surface, &srcRect))
+ return false;
+ if (!SDL_SetClipRect(screen, &dstRect))
+ return false;
+ auto rc = SDL_BlitScaled(surface, &srcRect, screen, &dstRect);
+ if (rc != 0)
+ {
+ SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s [%d]", sdl_error_string(rc), rc);
+ }
+ return rc == 0;
+}
+
+void SdlWindow::updateSurface()
+{
+ SDL_UpdateWindowSurface(_window);
+}
diff --git a/client/SDL/sdl_window.hpp b/client/SDL/sdl_window.hpp
new file mode 100644
index 0000000..4f84e1b
--- /dev/null
+++ b/client/SDL/sdl_window.hpp
@@ -0,0 +1,62 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * SDL Client
+ *
+ * Copyright 2023 Armin Novak <armin.novak@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.
+ */
+#pragma once
+
+#include <string>
+#include <SDL.h>
+
+class SdlWindow
+{
+ public:
+ SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width,
+ Sint32 height, Uint32 flags);
+ SdlWindow(SdlWindow&& other);
+ ~SdlWindow();
+
+ [[nodiscard]] Uint32 id() const;
+ [[nodiscard]] int displayIndex() const;
+ [[nodiscard]] SDL_Rect rect() const;
+ [[nodiscard]] SDL_Window* window() const;
+
+ [[nodiscard]] Sint32 offsetX() const;
+ void setOffsetX(Sint32 x);
+
+ void setOffsetY(Sint32 y);
+ [[nodiscard]] Sint32 offsetY() const;
+
+ bool grabKeyboard(bool enable);
+ bool grabMouse(bool enable);
+ void setBordered(bool bordered);
+ void raise();
+ void resizeable(bool use);
+ void fullscreen(bool use);
+
+ bool fill(Uint8 r = 0x00, Uint8 g = 0x00, Uint8 b = 0x00, Uint8 a = 0xff);
+ bool blit(SDL_Surface* surface, const SDL_Rect& src, SDL_Rect& dst);
+ void updateSurface();
+
+ private:
+ SDL_Window* _window = nullptr;
+ Sint32 _offset_x = 0;
+ Sint32 _offset_y = 0;
+
+ private:
+ SdlWindow(const SdlWindow& other) = delete;
+};
diff --git a/client/Sample/CMakeLists.txt b/client/Sample/CMakeLists.txt
new file mode 100644
index 0000000..db4e947
--- /dev/null
+++ b/client/Sample/CMakeLists.txt
@@ -0,0 +1,77 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Sample UI 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.
+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(sfreerdp
+ LANGUAGES C
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+set(SRCS
+ tf_channels.c
+ tf_channels.h
+ tf_freerdp.h
+ tf_freerdp.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 "${PROJECT_NAME}${CMAKE_EXECUTABLE_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_executable(${PROJECT_NAME} ${SRCS})
+
+set(LIBS
+ freerdp-client
+ freerdp
+ winpr
+)
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+endif()
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/Sample")
+install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
diff --git a/client/Sample/ModuleOptions.cmake b/client/Sample/ModuleOptions.cmake
new file mode 100644
index 0000000..d4d5a9e
--- /dev/null
+++ b/client/Sample/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_CLIENT_NAME "sfreerdp")
+set(FREERDP_CLIENT_PLATFORM "Sample")
+set(FREERDP_CLIENT_VENDOR "FreeRDP")
diff --git a/client/Sample/tf_channels.c b/client/Sample/tf_channels.c
new file mode 100644
index 0000000..d1bfdb8
--- /dev/null
+++ b/client/Sample/tf_channels.c
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client Channels
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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 <freerdp/gdi/gfx.h>
+
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+
+#include "tf_channels.h"
+#include "tf_freerdp.h"
+
+static UINT tf_update_surfaces(RdpgfxClientContext* context)
+{
+ WINPR_UNUSED(context);
+ return CHANNEL_RC_OK;
+}
+
+void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ tfContext* tf = (tfContext*)context;
+
+ WINPR_ASSERT(tf);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface;
+ WINPR_ASSERT(clip);
+ clip->custom = context;
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ tfContext* tf = (tfContext*)context;
+
+ WINPR_ASSERT(tf);
+ WINPR_ASSERT(e);
+
+ if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface;
+ WINPR_ASSERT(clip);
+ clip->custom = NULL;
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/Sample/tf_channels.h b/client/Sample/tf_channels.h
new file mode 100644
index 0000000..d00a5c2
--- /dev/null
+++ b/client/Sample/tf_channels.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client Channels
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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_CLIENT_SAMPLE_CHANNELS_H
+#define FREERDP_CLIENT_SAMPLE_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+
+int tf_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int tf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_SAMPLE_CHANNELS_H */
diff --git a/client/Sample/tf_freerdp.c b/client/Sample/tf_freerdp.c
new file mode 100644
index 0000000..2a799fc
--- /dev/null
+++ b/client/Sample/tf_freerdp.c
@@ -0,0 +1,411 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Test UI
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016,2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016,2018 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 <stdio.h>
+#include <string.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/utils/signal.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/channels.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include "tf_channels.h"
+#include "tf_freerdp.h"
+
+#define TAG CLIENT_TAG("sample")
+
+/* This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas. */
+static BOOL tf_begin_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ return TRUE;
+}
+
+/* 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 tf_end_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(context);
+
+ gdi = context->gdi;
+ WINPR_ASSERT(gdi);
+ WINPR_ASSERT(gdi->primary);
+ WINPR_ASSERT(gdi->primary->hdc);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd);
+ WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid);
+
+ if (gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ return TRUE;
+}
+
+static BOOL tf_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ gdi = context->gdi;
+ return gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+
+/* This function is called to output a System BEEP */
+static BOOL tf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(play_sound);
+ return TRUE;
+}
+
+/* This function is called to update the keyboard indocator LED */
+static BOOL tf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ /* TODO: Set local keyboard indicator LED status */
+ WINPR_UNUSED(context);
+ WINPR_UNUSED(led_flags);
+ return TRUE;
+}
+
+/* This function is called to set the IME state */
+static BOOL tf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+/* Called before a connection is established.
+ * Set all configuration options to support and load channels here. */
+static BOOL tf_pre_connect(freerdp* instance)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /* Optional OS identifier sent to server */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER))
+ return FALSE;
+ /* OrderSupport is initialized at this point.
+ * Only override it if you plan to implement custom order
+ * callbacks or deactiveate certain features. */
+ /* Register the channel listeners.
+ * They are required to set up / tear down channels if they are loaded. */
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, tf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ tf_OnChannelDisconnectedEventHandler);
+
+ /* TODO: Any code your client requires */
+ return TRUE;
+}
+
+/* Called after a RDP connection was successfully established.
+ * Settings might have changed during negociation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and paing callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL tf_post_connect(freerdp* instance)
+{
+ rdpContext* context = NULL;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_XRGB32))
+ return FALSE;
+
+ context = instance->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->update);
+
+ /* With this setting we disable all graphics processing in the library.
+ *
+ * This allows low resource (client) protocol parsing.
+ */
+ if (!freerdp_settings_set_bool(context->settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+
+ context->update->BeginPaint = tf_begin_paint;
+ context->update->EndPaint = tf_end_paint;
+ context->update->PlaySound = tf_play_sound;
+ context->update->DesktopResize = tf_desktop_resize;
+ context->update->SetKeyboardIndicators = tf_keyboard_set_indicators;
+ context->update->SetKeyboardImeStatus = tf_keyboard_set_ime_status;
+ return TRUE;
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void tf_post_disconnect(freerdp* instance)
+{
+ tfContext* context = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ context = (tfContext*)instance->context;
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ tf_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ tf_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+ /* TODO : Clean up custom stuff */
+ WINPR_UNUSED(context);
+}
+
+/* RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends. */
+static DWORD WINAPI tf_client_thread_proc(LPVOID arg)
+{
+ freerdp* instance = (freerdp*)arg;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ DWORD result = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ BOOL rc = freerdp_connect(instance);
+
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_AuthenticationOnly))
+ {
+ result = freerdp_get_last_error(instance->context);
+ freerdp_abort_connect_context(instance->context);
+ WLog_ERR(TAG, "Authentication only, exit status 0x%08" PRIx32 "", result);
+ goto disconnect;
+ }
+
+ if (!rc)
+ {
+ result = freerdp_get_last_error(instance->context);
+ WLog_ERR(TAG, "connection failure 0x%08" PRIx32, result);
+ return result;
+ }
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ nCount = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles));
+
+ if (nCount == 0)
+ {
+ WLog_ERR(TAG, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount, handles, FALSE, 100);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status);
+ 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;
+ }
+ }
+
+disconnect:
+ freerdp_disconnect(instance);
+ return result;
+}
+
+/* Optional global initializer.
+ * Here we just register a signal handler to print out stack traces
+ * if available. */
+static BOOL tf_client_global_init(void)
+{
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Optional global tear down */
+static void tf_client_global_uninit(void)
+{
+}
+
+static int tf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ tfContext* tf = NULL;
+ 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;
+
+ tf = (tfContext*)instance->context;
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ WINPR_UNUSED(tf);
+
+ return 1;
+}
+
+static BOOL tf_client_new(freerdp* instance, rdpContext* context)
+{
+ tfContext* tf = (tfContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->PreConnect = tf_pre_connect;
+ instance->PostConnect = tf_post_connect;
+ instance->PostDisconnect = tf_post_disconnect;
+ instance->LogonErrorInfo = tf_logon_error_info;
+ /* TODO: Client display set up */
+ WINPR_UNUSED(tf);
+ return TRUE;
+}
+
+static void tf_client_free(freerdp* instance, rdpContext* context)
+{
+ tfContext* tf = (tfContext*)instance->context;
+
+ if (!context)
+ return;
+
+ /* TODO: Client display tear down */
+ WINPR_UNUSED(tf);
+}
+
+static int tf_client_start(rdpContext* context)
+{
+ /* TODO: Start client related stuff */
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static int tf_client_stop(rdpContext* context)
+{
+ /* TODO: Stop client related stuff */
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static 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->GlobalInit = tf_client_global_init;
+ pEntryPoints->GlobalUninit = tf_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(tfContext);
+ pEntryPoints->ClientNew = tf_client_new;
+ pEntryPoints->ClientFree = tf_client_free;
+ pEntryPoints->ClientStart = tf_client_start;
+ pEntryPoints->ClientStop = tf_client_stop;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ DWORD status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ rdpContext* context = NULL;
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ goto fail;
+
+ status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(context->settings, status, argc,
+ argv);
+ goto fail;
+ }
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ goto fail;
+
+ if (freerdp_client_start(context) != 0)
+ goto fail;
+
+ rc = tf_client_thread_proc(context->instance);
+
+ if (freerdp_client_stop(context) != 0)
+ rc = -1;
+
+fail:
+ freerdp_client_context_free(context);
+ return rc;
+}
diff --git a/client/Sample/tf_freerdp.h b/client/Sample/tf_freerdp.h
new file mode 100644
index 0000000..c9b5295
--- /dev/null
+++ b/client/Sample/tf_freerdp.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Sample Client
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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_CLIENT_SAMPLE_H
+#define FREERDP_CLIENT_SAMPLE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+
+typedef struct
+{
+ rdpClientContext common;
+
+ /* Channels */
+} tfContext;
+
+#endif /* FREERDP_CLIENT_SAMPLE_H */
diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt
new file mode 100644
index 0000000..7076ff1
--- /dev/null
+++ b/client/Wayland/CMakeLists.txt
@@ -0,0 +1,62 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Wayland Client cmake build script
+#
+# Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+# Copyright 2015 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.
+
+set(MODULE_NAME "wlfreerdp")
+set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND")
+
+include_directories(${WAYLAND_INCLUDE_DIR})
+
+set(${MODULE_PREFIX}_SRCS
+ wlfreerdp.c
+ wlfreerdp.h
+ wlf_disp.c
+ wlf_disp.h
+ wlf_pointer.c
+ wlf_pointer.h
+ wlf_input.c
+ wlf_input.h
+ wlf_cliprdr.c
+ wlf_cliprdr.h
+ wlf_channels.c
+ wlf_channels.h
+ )
+
+if (FREERDP_UNIFIED_BUILD)
+ include_directories(${PROJECT_SOURCE_DIR}/uwac/include)
+ include_directories(${PROJECT_BINARY_DIR}/uwac/include)
+else()
+ find_package(uwac 0 REQUIRED)
+ include_directories(${UWAC_INCLUDE_DIR})
+endif()
+
+list (APPEND ${MODULE_PREFIX}_LIBS freerdp-client freerdp uwac)
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_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()
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland")
+configure_file(wlfreerdp.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1)
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1)
diff --git a/client/Wayland/wlf_channels.c b/client/Wayland/wlf_channels.c
new file mode 100644
index 0000000..3a11407
--- /dev/null
+++ b/client/Wayland/wlf_channels.c
@@ -0,0 +1,79 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * Copyright 2013 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 <freerdp/gdi/gfx.h>
+
+#include <freerdp/gdi/video.h>
+
+#include "wlf_channels.h"
+#include "wlf_cliprdr.h"
+#include "wlf_disp.h"
+#include "wlfreerdp.h"
+
+void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ WINPR_ASSERT(wlf);
+ WINPR_ASSERT(e);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ wlf_disp_init(wlf->disp, (DispClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ WINPR_ASSERT(wlf);
+ WINPR_ASSERT(e);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ wlf_disp_uninit(wlf->disp, (DispClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/Wayland/wlf_channels.h b/client/Wayland/wlf_channels.h
new file mode 100644
index 0000000..e876031
--- /dev/null
+++ b/client/Wayland/wlf_channels.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * Copyright 2013 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_CLIENT_WAYLAND_CHANNELS_H
+#define FREERDP_CLIENT_WAYLAND_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+
+int wlf_on_channel_connected(freerdp* instance, const char* name, void* pInterface);
+int wlf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface);
+
+void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_WAYLAND_CHANNELS_H */
diff --git a/client/Wayland/wlf_cliprdr.c b/client/Wayland/wlf_cliprdr.c
new file mode 100644
index 0000000..dc189d5
--- /dev/null
+++ b/client/Wayland/wlf_cliprdr.c
@@ -0,0 +1,1009 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Clipboard Redirection
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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 <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#include "wlf_cliprdr.h"
+
+#define TAG CLIENT_TAG("wayland.cliprdr")
+
+#define MAX_CLIPBOARD_FORMATS 255
+
+#define mime_text_plain "text/plain"
+#define mime_text_utf8 mime_text_plain ";charset=utf-8"
+
+static const char* mime_text[] = { mime_text_plain, mime_text_utf8, "UTF8_STRING",
+ "COMPOUND_TEXT", "TEXT", "STRING" };
+
+static const char mime_png[] = "image/png";
+static const char mime_webp[] = "image/webp";
+static const char mime_jpg[] = "image/jpeg";
+static const char mime_tiff[] = "image/tiff";
+static const char mime_uri_list[] = "text/uri-list";
+static const char mime_html[] = "text/html";
+
+#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
+static const char* mime_bitmap[] = { BMP_MIME_LIST };
+static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST };
+
+static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
+static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
+
+static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
+static const char type_HtmlFormat[] = "HTML Format";
+
+typedef struct
+{
+ FILE* responseFile;
+ UINT32 responseFormat;
+ char* responseMime;
+} wlf_request;
+
+struct wlf_clipboard
+{
+ wlfContext* wfc;
+ rdpChannels* channels;
+ CliprdrClientContext* context;
+ wLog* log;
+
+ UwacSeat* seat;
+ wClipboard* system;
+
+ size_t numClientFormats;
+ CLIPRDR_FORMAT* clientFormats;
+
+ size_t numServerFormats;
+ CLIPRDR_FORMAT* serverFormats;
+
+ BOOL sync;
+
+ CRITICAL_SECTION lock;
+ CliprdrFileContext* file;
+
+ wQueue* request_queue;
+};
+
+static void wlf_request_free(void* rq)
+{
+ wlf_request* request = rq;
+ if (request)
+ {
+ free(request->responseMime);
+ if (request->responseFile)
+ fclose(request->responseFile);
+ }
+ free(request);
+}
+
+static wlf_request* wlf_request_new(void)
+{
+ return calloc(1, sizeof(wlf_request));
+}
+
+static void* wlf_request_clone(const void* oth)
+{
+ const wlf_request* other = (const wlf_request*)oth;
+ wlf_request* copy = wlf_request_new();
+ if (!copy)
+ return NULL;
+ *copy = *other;
+ if (other->responseMime)
+ {
+ copy->responseMime = _strdup(other->responseMime);
+ if (!copy->responseMime)
+ goto fail;
+ }
+ return copy;
+fail:
+ wlf_request_free(copy);
+ return NULL;
+}
+
+static BOOL wlf_mime_is_file(const char* mime)
+{
+ if (strncmp(mime_uri_list, mime, sizeof(mime_uri_list)) == 0)
+ return TRUE;
+ if (strncmp(mime_gnome_copied_files, mime, sizeof(mime_gnome_copied_files)) == 0)
+ return TRUE;
+ if (strncmp(mime_mate_copied_files, mime, sizeof(mime_mate_copied_files)) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_text(const char* mime)
+{
+ for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
+ {
+ if (strcmp(mime, mime_text[x]) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_image(const char* mime)
+{
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ {
+ if (strcmp(mime, mime_image[x]) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL wlf_mime_is_html(const char* mime)
+{
+ if (strcmp(mime, mime_html) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard)
+{
+ if (clipboard && clipboard->serverFormats)
+ {
+ for (size_t j = 0; j < clipboard->numServerFormats; j++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[j];
+ free(format->formatName);
+ }
+
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ clipboard->numServerFormats = 0;
+ }
+
+ if (clipboard)
+ UwacClipboardOfferDestroy(clipboard->seat);
+}
+
+static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard)
+{
+ if (clipboard && clipboard->numClientFormats)
+ {
+ for (size_t j = 0; j < clipboard->numClientFormats; j++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->clientFormats[j];
+ free(format->formatName);
+ }
+
+ free(clipboard->clientFormats);
+ clipboard->clientFormats = NULL;
+ clipboard->numClientFormats = 0;
+ }
+
+ if (clipboard)
+ UwacClipboardOfferDestroy(clipboard->seat);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = CB_RESPONSE_OK,
+ .numFormats = (UINT32)clipboard->numClientFormats,
+ .formats = clipboard->clientFormats,
+ .common.msgType = CB_FORMAT_LIST };
+
+ cliprdr_file_context_clear(clipboard->file);
+
+ WLog_VRB(TAG, "-------------- client format list [%" PRIu32 "] ------------------",
+ formatList.numFormats);
+ for (UINT32 x = 0; x < formatList.numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList.formats[x];
+ WLog_VRB(TAG, "client announces %" PRIu32 " [%s][%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ }
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatList);
+ return clipboard->context->ClientFormatList(clipboard->context, &formatList);
+}
+
+static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId)
+{
+ CLIPRDR_FORMAT* format = NULL;
+ const char* name = ClipboardGetFormatName(clipboard->system, formatId);
+
+ for (size_t x = 0; x < clipboard->numClientFormats; x++)
+ {
+ format = &clipboard->clientFormats[x];
+
+ if (format->formatId == formatId)
+ return;
+ }
+
+ format = realloc(clipboard->clientFormats,
+ (clipboard->numClientFormats + 1) * sizeof(CLIPRDR_FORMAT));
+
+ if (!format)
+ return;
+
+ clipboard->clientFormats = format;
+ format = &clipboard->clientFormats[clipboard->numClientFormats++];
+ format->formatId = formatId;
+ format->formatName = NULL;
+
+ if (name && (formatId >= CF_MAX))
+ format->formatName = _strdup(name);
+}
+
+static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime)
+{
+ WINPR_ASSERT(mime);
+ ClipboardLock(clipboard->system);
+ if (wlf_mime_is_html(mime))
+ {
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ wfl_cliprdr_add_client_format_id(clipboard, formatId);
+ }
+ else if (wlf_mime_is_text(mime))
+ {
+ wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT);
+ }
+ else if (wlf_mime_is_image(mime))
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ {
+ const char* mime_bmp = mime_image[x];
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp);
+ if (formatId != 0)
+ wfl_cliprdr_add_client_format_id(clipboard, formatId);
+ }
+ wfl_cliprdr_add_client_format_id(clipboard, CF_DIB);
+ wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF);
+ }
+ else if (wlf_mime_is_file(mime))
+ {
+ const UINT32 fileFormatId =
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ wfl_cliprdr_add_client_format_id(clipboard, fileFormatId);
+ }
+
+ ClipboardUnlock(clipboard->system);
+ if (wlf_cliprdr_send_client_format_list(clipboard) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, const wlf_request* rq)
+{
+ WINPR_ASSERT(rq);
+
+ CLIPRDR_FORMAT_DATA_REQUEST request = { .requestedFormatId = rq->responseFormat };
+
+ if (!Queue_Enqueue(clipboard->request_queue, rq))
+ return ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
+ return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard, const BYTE* data, size_t size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
+
+ if (size > UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+
+ response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.common.dataLen = (UINT32)size;
+ response.requestedFormatData = data;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
+ return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
+}
+
+BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event)
+{
+ if (!clipboard || !event)
+ return FALSE;
+
+ if (!clipboard->context)
+ return TRUE;
+
+ switch (event->type)
+ {
+ case UWAC_EVENT_CLIPBOARD_AVAILABLE:
+ clipboard->seat = event->seat;
+ return TRUE;
+
+ case UWAC_EVENT_CLIPBOARD_OFFER:
+ WLog_Print(clipboard->log, WLOG_DEBUG, "client announces mime %s", event->mime);
+ return wlf_cliprdr_add_client_format(clipboard, event->mime);
+
+ case UWAC_EVENT_CLIPBOARD_SELECT:
+ WLog_Print(clipboard->log, WLOG_DEBUG, "client announces new data");
+ wlf_cliprdr_free_client_formats(clipboard);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = {
+ .capabilitySetType = CB_CAPSTYPE_GENERAL,
+ .capabilitySetLength = 12,
+ .version = CB_CAPS_VERSION_2,
+ .generalFlags =
+ CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file)
+ };
+ CLIPRDR_CAPABILITIES capabilities = { .cCapabilitiesSets = 1,
+ .capabilitySets =
+ (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet) };
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientCapabilities);
+ return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status)
+{
+ const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {
+ .common.msgType = CB_FORMAT_LIST_RESPONSE,
+ .common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL,
+ .common.dataLen = 0
+ };
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
+ return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ UINT ret = 0;
+
+ WINPR_UNUSED(monitorReady);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ clipboard->sync = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets;
+ WINPR_ASSERT(capsPtr);
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0))
+ return ERROR_INTERNAL_ERROR;
+
+ for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps =
+ (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
+
+ if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags))
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT32 wlf_get_server_format_id(const wfClipboard* clipboard, const char* name)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(name);
+
+ for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
+ if (!format->formatName)
+ continue;
+ if (strcmp(name, format->formatName) == 0)
+ return format->formatId;
+ }
+ return 0;
+}
+
+static const char* wlf_get_server_format_name(const wfClipboard* clipboard, UINT32 formatId)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x];
+ if (format->formatId == formatId)
+ return format->formatName;
+ }
+ return NULL;
+}
+
+static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd)
+{
+ wfClipboard* clipboard = (wfClipboard*)context;
+ WINPR_UNUSED(seat);
+
+ EnterCriticalSection(&clipboard->lock);
+
+ wlf_request request = { 0 };
+ if (wlf_mime_is_html(mime))
+ {
+ request.responseMime = mime_html;
+ request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat);
+ }
+ else if (wlf_mime_is_file(mime))
+ {
+ request.responseMime = mime;
+ request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW);
+ }
+ else if (wlf_mime_is_text(mime))
+ {
+ request.responseMime = mime_text_plain;
+ request.responseFormat = CF_UNICODETEXT;
+ }
+ else if (wlf_mime_is_image(mime))
+ {
+ request.responseMime = mime;
+ if (strcmp(mime, mime_tiff) == 0)
+ request.responseFormat = CF_TIFF;
+ else
+ request.responseFormat = CF_DIB;
+ }
+
+ if (request.responseMime != NULL)
+ {
+ request.responseFile = fdopen(fd, "w");
+
+ if (request.responseFile)
+ wlf_cliprdr_send_data_request(clipboard, &request);
+ else
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "failed to open clipboard file descriptor for MIME %s",
+ request.responseMime);
+ }
+
+ LeaveCriticalSection(&clipboard->lock);
+}
+
+static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context)
+{
+ wfClipboard* clipboard = (wfClipboard*)context;
+
+ WINPR_UNUSED(seat);
+ WINPR_ASSERT(clipboard);
+ cliprdr_file_context_clear(clipboard->file);
+}
+
+/**
+ * Called when the clipboard changes server side.
+ *
+ * Clear the local clipboard offer and replace it with a new one
+ * that announces the formats we get listed here.
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ BOOL html = FALSE;
+ BOOL text = FALSE;
+ BOOL image = FALSE;
+ BOOL file = FALSE;
+
+ if (!context || !context->custom)
+ return ERROR_INVALID_PARAMETER;
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ wlf_cliprdr_free_server_formats(clipboard);
+ cliprdr_file_context_clear(clipboard->file);
+
+ if (!(clipboard->serverFormats =
+ (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "failed to allocate %" PRIuz " CLIPRDR_FORMAT structs",
+ clipboard->numServerFormats);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ clipboard->numServerFormats = formatList->numFormats;
+
+ if (!clipboard->seat)
+ {
+ WLog_Print(clipboard->log, WLOG_ERROR,
+ "clipboard->seat=NULL, check your client implementation");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ for (UINT32 i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+ CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
+ srvFormat->formatId = format->formatId;
+
+ if (format->formatName)
+ {
+ srvFormat->formatName = _strdup(format->formatName);
+
+ if (!srvFormat->formatName)
+ {
+ wlf_cliprdr_free_server_formats(clipboard);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+
+ if (format->formatName)
+ {
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ text = TRUE;
+ html = TRUE;
+ }
+ else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ file = TRUE;
+ text = TRUE;
+ }
+ }
+ else
+ {
+ switch (format->formatId)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ text = TRUE;
+ break;
+
+ case CF_DIB:
+ image = TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (html)
+ {
+ UwacClipboardOfferCreate(clipboard->seat, mime_html);
+ }
+
+ if (file && cliprdr_file_context_has_local_support(clipboard->file))
+ {
+ UwacClipboardOfferCreate(clipboard->seat, mime_uri_list);
+ UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files);
+ UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files);
+ }
+
+ if (text)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_text); x++)
+ UwacClipboardOfferCreate(clipboard->seat, mime_text[x]);
+ }
+
+ if (image)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(mime_image); x++)
+ UwacClipboardOfferCreate(clipboard->seat, mime_image[x]);
+ }
+
+ UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data,
+ wlf_cliprdr_cancel_data);
+ return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
+ WLog_WARN(TAG, "format list update failed");
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ UINT rc = CHANNEL_RC_OK;
+ BYTE* data = NULL;
+ size_t size = 0;
+ const char* mime = NULL;
+ UINT32 formatId = 0;
+ UINT32 localFormatId = 0;
+ wfClipboard* clipboard = 0;
+
+ UINT32 dsize = 0;
+ BYTE* ddata = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ localFormatId = formatId = formatDataRequest->requestedFormatId;
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ ClipboardLock(clipboard->system);
+ const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+
+ switch (formatId)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain);
+ mime = mime_text_utf8;
+ break;
+
+ case CF_DIB:
+ case CF_DIBV5:
+ mime = mime_bitmap[0];
+ break;
+
+ case CF_TIFF:
+ mime = mime_tiff;
+ break;
+
+ default:
+ if (formatId == fileFormatId)
+ {
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ mime = mime_uri_list;
+ }
+ else if (formatId == htmlFormatId)
+ {
+ localFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ mime = mime_html;
+ }
+ else
+ goto fail;
+ break;
+ }
+
+ data = UwacClipboardDataGet(clipboard->seat, mime, &size);
+
+ if (!data)
+ goto fail;
+
+ if (fileFormatId == formatId)
+ {
+ if (!cliprdr_file_context_update_client_data(clipboard->file, data, size))
+ goto fail;
+ }
+
+ const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, size);
+ free(data);
+
+ UINT32 len = 0;
+ data = NULL;
+ if (res)
+ data = ClipboardGetData(clipboard->system, formatId, &len);
+
+ if (!res || !data)
+ goto fail;
+
+ if (fileFormatId == formatId)
+ {
+ const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
+ const UINT32 error = cliprdr_serialize_file_list_ex(
+ flags, (const FILEDESCRIPTORW*)data, len / sizeof(FILEDESCRIPTORW), &ddata, &dsize);
+ if (error)
+ goto fail;
+ }
+fail:
+ ClipboardUnlock(clipboard->system);
+ rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize);
+ free(data);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+wlf_cliprdr_server_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ const UINT32 size = formatDataResponse->common.dataLen;
+ const BYTE* data = formatDataResponse->requestedFormatData;
+
+ wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ wlf_request* request = Queue_Dequeue(clipboard->request_queue);
+ if (!request)
+ goto fail;
+
+ rc = CHANNEL_RC_OK;
+ if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL)
+ {
+ WLog_WARN(TAG, "clipboard data request for format %" PRIu32 " [%s], mime %s failed",
+ request->responseFormat, ClipboardGetFormatIdString(request->responseFormat),
+ request->responseMime);
+ goto fail;
+ }
+ rc = ERROR_INTERNAL_ERROR;
+
+ ClipboardLock(clipboard->system);
+ EnterCriticalSection(&clipboard->lock);
+
+ UINT32 srcFormatId = 0;
+ UINT32 dstFormatId = 0;
+ switch (request->responseFormat)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ srcFormatId = request->responseFormat;
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ break;
+
+ case CF_DIB:
+ case CF_DIBV5:
+ srcFormatId = request->responseFormat;
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ break;
+
+ default:
+ {
+ const char* name = wlf_get_server_format_name(clipboard, request->responseFormat);
+ if (name)
+ {
+ if (strcmp(type_FileGroupDescriptorW, name) == 0)
+ {
+ srcFormatId =
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+
+ if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system,
+ data, size))
+ goto unlock;
+ }
+ else if (strcmp(type_HtmlFormat, name) == 0)
+ {
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
+ }
+ }
+ }
+ break;
+ }
+
+ UINT32 len = 0;
+
+ const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size);
+ if (sres)
+ data = ClipboardGetData(clipboard->system, dstFormatId, &len);
+
+ if (!sres || !data)
+ goto unlock;
+
+ if (request->responseFile)
+ {
+ const size_t res = fwrite(data, 1, len, request->responseFile);
+ if (res == len)
+ rc = CHANNEL_RC_OK;
+ }
+ else
+ rc = CHANNEL_RC_OK;
+
+unlock:
+ ClipboardUnlock(clipboard->system);
+ LeaveCriticalSection(&clipboard->lock);
+fail:
+ wlf_request_free(request);
+ return rc;
+}
+
+wfClipboard* wlf_clipboard_new(wlfContext* wfc)
+{
+ rdpChannels* channels = NULL;
+ wfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(wfc);
+
+ clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard));
+
+ if (!clipboard)
+ goto fail;
+
+ InitializeCriticalSection(&clipboard->lock);
+ clipboard->wfc = wfc;
+ channels = wfc->common.context.channels;
+ clipboard->log = WLog_Get(TAG);
+ clipboard->channels = channels;
+ clipboard->system = ClipboardCreate();
+ if (!clipboard->system)
+ goto fail;
+
+ clipboard->file = cliprdr_file_context_new(clipboard);
+ if (!clipboard->file)
+ goto fail;
+
+ if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE))
+ goto fail;
+
+ clipboard->request_queue = Queue_New(TRUE, -1, -1);
+ if (!clipboard->request_queue)
+ goto fail;
+
+ wObject* obj = Queue_Object(clipboard->request_queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = wlf_request_free;
+ obj->fnObjectNew = wlf_request_clone;
+
+ return clipboard;
+
+fail:
+ wlf_clipboard_free(clipboard);
+ return NULL;
+}
+
+void wlf_clipboard_free(wfClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ cliprdr_file_context_free(clipboard->file);
+
+ wlf_cliprdr_free_server_formats(clipboard);
+ wlf_cliprdr_free_client_formats(clipboard);
+ ClipboardDestroy(clipboard->system);
+
+ EnterCriticalSection(&clipboard->lock);
+
+ Queue_Free(clipboard->request_queue);
+ LeaveCriticalSection(&clipboard->lock);
+ DeleteCriticalSection(&clipboard->lock);
+ free(clipboard);
+}
+
+BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(cliprdr);
+
+ clipboard->context = cliprdr;
+ cliprdr->MonitorReady = wlf_cliprdr_monitor_ready;
+ cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities;
+ cliprdr->ServerFormatList = wlf_cliprdr_server_format_list;
+ cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response;
+ cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request;
+ cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response;
+
+ return cliprdr_file_context_init(clipboard->file, cliprdr);
+}
+
+BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(clipboard);
+ if (!cliprdr_file_context_uninit(clipboard->file, cliprdr))
+ return FALSE;
+
+ if (cliprdr)
+ cliprdr->custom = NULL;
+
+ return TRUE;
+}
diff --git a/client/Wayland/wlf_cliprdr.h b/client/Wayland/wlf_cliprdr.h
new file mode 100644
index 0000000..a113140
--- /dev/null
+++ b/client/Wayland/wlf_cliprdr.h
@@ -0,0 +1,36 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Clipboard Redirection
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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_CLIENT_WAYLAND_CLIPRDR_H
+#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H
+
+#include "wlfreerdp.h"
+
+#include <freerdp/client/cliprdr.h>
+
+wfClipboard* wlf_clipboard_new(wlfContext* wlc);
+void wlf_clipboard_free(wfClipboard* clipboard);
+
+BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr);
+BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr);
+
+BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event);
+
+#endif /* FREERDP_CLIENT_WAYLAND_CLIPRDR_H */
diff --git a/client/Wayland/wlf_disp.c b/client/Wayland/wlf_disp.c
new file mode 100644
index 0000000..0d87675
--- /dev/null
+++ b/client/Wayland/wlf_disp.c
@@ -0,0 +1,455 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Display Control Channel
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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/sysinfo.h>
+
+#include "wlf_disp.h"
+
+#define TAG CLIENT_TAG("wayland.disp")
+
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+
+struct s_wlfDispContext
+{
+ wlfContext* wlc;
+ DispClientContext* disp;
+ BOOL haveXRandr;
+ int eventBase, errorBase;
+ int lastSentWidth, lastSentHeight;
+ UINT64 lastSentDate;
+ int targetWidth, targetHeight;
+ BOOL activated;
+ BOOL waitingResize;
+ BOOL fullscreen;
+ UINT16 lastSentDesktopOrientation;
+ UINT32 lastSentDesktopScaleFactor;
+ UINT32 lastSentDeviceScaleFactor;
+};
+
+static UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors,
+ size_t nmonitors);
+
+static BOOL wlf_disp_settings_changed(wlfDispContext* wlfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (wlfDisp->lastSentWidth != wlfDisp->targetWidth)
+ return TRUE;
+
+ if (wlfDisp->lastSentHeight != wlfDisp->targetHeight)
+ return TRUE;
+
+ if (wlfDisp->lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (wlfDisp->lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (wlfDisp->lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+
+ if (wlfDisp->fullscreen != wlfDisp->wlc->fullscreen)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ wlfDisp->lastSentWidth = wlfDisp->targetWidth;
+ wlfDisp->lastSentHeight = wlfDisp->targetHeight;
+ wlfDisp->lastSentDesktopOrientation =
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ wlfDisp->lastSentDesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ wlfDisp->lastSentDeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ wlfDisp->fullscreen = wlfDisp->wlc->fullscreen;
+ return TRUE;
+}
+
+static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp)
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout;
+ wlfContext* wlc = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlfDisp || !wlfDisp->wlc)
+ return FALSE;
+
+ wlc = wlfDisp->wlc;
+ settings = wlc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!wlfDisp->activated || !wlfDisp->disp)
+ return TRUE;
+
+ if (GetTickCount64() - wlfDisp->lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ wlfDisp->lastSentDate = GetTickCount64();
+
+ if (!wlf_disp_settings_changed(wlfDisp))
+ return TRUE;
+
+ /* TODO: Multimonitor support for wayland
+ if (wlc->fullscreen && (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount > 0))
+ {
+ if (wlf_disp_sendLayout(wlfDisp->disp, setings->MonitorDefArray,
+ freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) !=
+ CHANNEL_RC_OK) return FALSE;
+ }
+ else
+ */
+ {
+ wlfDisp->waitingResize = TRUE;
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = wlfDisp->targetWidth;
+ layout.Height = wlfDisp->targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = wlfDisp->targetWidth;
+ layout.PhysicalHeight = wlfDisp->targetHeight;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, wlfDisp->disp->SendMonitorLayout, wlfDisp->disp, 1,
+ &layout) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ return wlf_update_last_sent(wlfDisp);
+}
+
+static BOOL wlf_disp_set_window_resizable(wlfDispContext* wlfDisp)
+{
+#if 0 // TODO
+#endif
+ return TRUE;
+}
+
+static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp,
+ rdpSettings** ppSettings)
+{
+ wlfContext* wlc = NULL;
+
+ if (!context)
+ return FALSE;
+
+ wlc = (wlfContext*)context;
+
+ if (!(wlc->disp))
+ return FALSE;
+
+ if (!wlc->common.context.settings)
+ return FALSE;
+
+ *ppwlc = wlc;
+ *ppwlfDisp = wlc->disp;
+ *ppSettings = wlc->common.context.settings;
+ return TRUE;
+}
+
+static void wlf_disp_OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ wlfDisp->waitingResize = FALSE;
+
+ if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ wlf_disp_set_window_resizable(wlfDisp);
+
+ if (e->firstActivation)
+ return;
+
+ wlf_disp_sendResize(wlfDisp);
+ }
+}
+
+static void wlf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ wlfDisp->waitingResize = FALSE;
+
+ if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ wlf_disp_set_window_resizable(wlfDisp);
+ wlf_disp_sendResize(wlfDisp);
+ }
+}
+
+static void wlf_disp_OnTimer(void* context, const TimerEventArgs* e)
+{
+ wlfContext* wlc = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+ if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings))
+ return;
+
+ if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return;
+
+ wlf_disp_sendResize(wlfDisp);
+}
+
+wlfDispContext* wlf_disp_new(wlfContext* wlc)
+{
+ wlfDispContext* ret = NULL;
+ wPubSub* pubSub = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!wlc || !wlc->common.context.settings || !wlc->common.context.pubSub)
+ return NULL;
+
+ settings = wlc->common.context.settings;
+ pubSub = wlc->common.context.pubSub;
+ ret = calloc(1, sizeof(wlfDispContext));
+
+ if (!ret)
+ return NULL;
+
+ ret->wlc = wlc;
+ ret->lastSentWidth = ret->targetWidth =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ ret->lastSentHeight = ret->targetHeight =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, wlf_disp_OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset);
+ PubSub_SubscribeTimer(pubSub, wlf_disp_OnTimer);
+ return ret;
+}
+
+void wlf_disp_free(wlfDispContext* disp)
+{
+ if (!disp)
+ return;
+
+ if (disp->wlc)
+ {
+ wPubSub* pubSub = disp->wlc->common.context.pubSub;
+ PubSub_UnsubscribeActivated(pubSub, wlf_disp_OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset);
+ PubSub_UnsubscribeTimer(pubSub, wlf_disp_OnTimer);
+ }
+
+ free(disp);
+}
+
+UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, size_t nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = NULL;
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ wlfDisp = (wlfDispContext*)disp->custom;
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+
+ if (!layouts)
+ return CHANNEL_RC_NO_MEMORY;
+
+ for (size_t i = 0; i < nmonitors; i++)
+ {
+ const rdpMonitor* monitor = &monitors[i];
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts);
+ free(layouts);
+ return ret;
+}
+
+BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height)
+{
+ if (!disp)
+ return FALSE;
+
+ disp->targetWidth = width;
+ disp->targetHeight = height;
+ return wlf_disp_sendResize(disp);
+}
+
+static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ wlfDispContext* wlfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+
+ wlfDisp = (wlfDispContext*)disp->custom;
+ WINPR_ASSERT(wlfDisp);
+ WINPR_ASSERT(wlfDisp->wlc);
+
+ settings = wlfDisp->wlc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ wlfDisp->activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return wlf_disp_set_window_resizable(wlfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp)
+{
+ rdpSettings* settings = NULL;
+
+ if (!wlfDisp || !wlfDisp->wlc || !disp)
+ return FALSE;
+
+ settings = wlfDisp->wlc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ wlfDisp->disp = disp;
+ disp->custom = (void*)wlfDisp;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = wlf_DisplayControlCaps;
+ }
+
+ return TRUE;
+}
+
+BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp)
+{
+ if (!wlfDisp || !disp)
+ return FALSE;
+
+ wlfDisp->disp = NULL;
+ return TRUE;
+}
+
+int wlf_list_monitors(wlfContext* wlc)
+{
+ uint32_t nmonitors = UwacDisplayGetNbOutputs(wlc->display);
+
+ for (uint32_t i = 0; i < nmonitors; i++)
+ {
+ const UwacOutput* monitor = UwacDisplayGetOutput(wlc->display, i);
+ UwacSize resolution;
+ UwacPosition pos;
+
+ if (!monitor)
+ continue;
+ UwacOutputGetPosition(monitor, &pos);
+ UwacOutputGetResolution(monitor, &resolution);
+
+ printf(" %s [%d] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, resolution.width,
+ resolution.height, pos.x, pos.y);
+ }
+
+ return 0;
+}
diff --git a/client/Wayland/wlf_disp.h b/client/Wayland/wlf_disp.h
new file mode 100644
index 0000000..36fa27c
--- /dev/null
+++ b/client/Wayland/wlf_disp.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Display Control Channel
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 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_CLIENT_WAYLAND_DISP_H
+#define FREERDP_CLIENT_WAYLAND_DISP_H
+
+#include <freerdp/types.h>
+#include <freerdp/client/disp.h>
+
+#include "wlfreerdp.h"
+
+FREERDP_API BOOL wlf_disp_init(wlfDispContext* xfDisp, DispClientContext* disp);
+FREERDP_API BOOL wlf_disp_uninit(wlfDispContext* xfDisp, DispClientContext* disp);
+
+wlfDispContext* wlf_disp_new(wlfContext* wlc);
+void wlf_disp_free(wlfDispContext* disp);
+BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height);
+void wlf_disp_resized(wlfDispContext* disp);
+
+int wlf_list_monitors(wlfContext* wlc);
+
+#endif /* FREERDP_CLIENT_WAYLAND_DISP_H */
diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c
new file mode 100644
index 0000000..60603db
--- /dev/null
+++ b/client/Wayland/wlf_input.c
@@ -0,0 +1,458 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Input
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * Copyright 2015 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 <stdlib.h>
+#include <float.h>
+
+#include <linux/input.h>
+
+#include <winpr/assert.h>
+
+#include <freerdp/config.h>
+#include <freerdp/locale/keyboard.h>
+#if defined(CHANNEL_RDPEI_CLIENT)
+#include <freerdp/client/rdpei.h>
+#endif
+#include <uwac/uwac.h>
+
+#include "wlfreerdp.h"
+#include "wlf_input.h"
+
+#define TAG CLIENT_TAG("wayland.input")
+
+static BOOL scale_signed_coordinates(rdpContext* context, int32_t* x, int32_t* y,
+ BOOL fromLocalToRDP)
+{
+ BOOL rc = 0;
+ UINT32 ux = 0;
+ UINT32 uy = 0;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(x);
+ WINPR_ASSERT(y);
+ WINPR_ASSERT(*x >= 0);
+ WINPR_ASSERT(*y >= 0);
+
+ ux = (UINT32)*x;
+ uy = (UINT32)*y;
+ rc = wlf_scale_coordinates(context, &ux, &uy, fromLocalToRDP);
+ WINPR_ASSERT(ux < INT32_MAX);
+ WINPR_ASSERT(uy < INT32_MAX);
+ *x = (int32_t)ux;
+ *y = (int32_t)uy;
+ return rc;
+}
+
+BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev)
+{
+ uint32_t x = 0;
+ uint32_t y = 0;
+ rdpClientContext* cctx = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ cctx = (rdpClientContext*)instance->context;
+ return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev)
+{
+ uint32_t x = 0;
+ uint32_t y = 0;
+ rdpClientContext* cctx = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, x, y);
+}
+
+BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev)
+{
+ rdpClientContext* cctx = NULL;
+ UINT16 flags = 0;
+ UINT16 xflags = 0;
+ uint32_t x = 0;
+ uint32_t y = 0;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED)
+ {
+ flags |= PTR_FLAGS_DOWN;
+ xflags |= PTR_XFLAGS_DOWN;
+ }
+
+ switch (ev->button)
+ {
+ case BTN_LEFT:
+ flags |= PTR_FLAGS_BUTTON1;
+ break;
+
+ case BTN_RIGHT:
+ flags |= PTR_FLAGS_BUTTON2;
+ break;
+
+ case BTN_MIDDLE:
+ flags |= PTR_FLAGS_BUTTON3;
+ break;
+
+ case BTN_SIDE:
+ xflags |= PTR_XFLAGS_BUTTON1;
+ break;
+
+ case BTN_EXTRA:
+ xflags |= PTR_XFLAGS_BUTTON2;
+ break;
+
+ default:
+ return TRUE;
+ }
+
+ WINPR_ASSERT(x <= UINT16_MAX);
+ WINPR_ASSERT(y <= UINT16_MAX);
+
+ if ((flags & ~PTR_FLAGS_DOWN) != 0)
+ return freerdp_client_send_button_event(cctx, FALSE, flags, x, y);
+
+ if ((xflags & ~PTR_XFLAGS_DOWN) != 0)
+ return freerdp_client_send_extended_button_event(cctx, FALSE, xflags, x, y);
+
+ return FALSE;
+}
+
+BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+static BOOL wlf_handle_wheel(freerdp* instance, uint32_t x, uint32_t y, uint32_t axis,
+ int32_t value)
+{
+ rdpClientContext* cctx = NULL;
+ UINT16 flags = 0;
+ int32_t direction = 0;
+ uint32_t avalue = (uint32_t)abs(value);
+
+ WINPR_ASSERT(instance);
+
+ cctx = (rdpClientContext*)instance->context;
+ WINPR_ASSERT(cctx);
+
+ if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ direction = value;
+ switch (axis)
+ {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ flags |= PTR_FLAGS_WHEEL;
+ if (direction > 0)
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ break;
+
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ flags |= PTR_FLAGS_HWHEEL;
+ if (direction < 0)
+ flags |= PTR_FLAGS_WHEEL_NEGATIVE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ /* Wheel rotation steps:
+ *
+ * positive: 0 ... 0xFF -> slow ... fast
+ * negative: 0 ... 0xFF -> fast ... slow
+ */
+
+ while (avalue > 0)
+ {
+ const UINT16 cval = (avalue > 0xFF) ? 0xFF : (UINT16)avalue;
+ UINT16 cflags = flags | cval;
+ /* Convert negative values to 9bit twos complement */
+ if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
+ cflags = (flags & 0xFF00) | (0x100 - cval);
+ if (!freerdp_client_send_wheel_event(cctx, cflags))
+ return FALSE;
+
+ avalue -= cval;
+ }
+ return TRUE;
+}
+
+BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev)
+{
+ BOOL success = TRUE;
+ BOOL handle = FALSE;
+ wlfContext* context = NULL;
+ enum wl_pointer_axis_source source = WL_POINTER_AXIS_SOURCE_CONTINUOUS;
+
+ if (!instance || !ev || !instance->context)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ for (size_t x = 0; x < ArrayList_Count(context->events); x++)
+ {
+ UwacEvent* cev = ArrayList_GetItem(context->events, x);
+ if (!cev)
+ continue;
+ if (cev->type == UWAC_EVENT_POINTER_SOURCE)
+ {
+ handle = TRUE;
+ source = cev->mouse_source.axis_source;
+ }
+ }
+
+ /* We need source events to determine how to interpret the data */
+ if (handle)
+ {
+ for (size_t x = 0; x < ArrayList_Count(context->events); x++)
+ {
+ UwacEvent* cev = ArrayList_GetItem(context->events, x);
+ if (!cev)
+ continue;
+
+ switch (source)
+ {
+ /* If we have a mouse wheel, just use discrete data */
+ case WL_POINTER_AXIS_SOURCE_WHEEL:
+#if defined(WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION)
+ case WL_POINTER_AXIS_SOURCE_WHEEL_TILT:
+#endif
+ if (cev->type == UWAC_EVENT_POINTER_AXIS_DISCRETE)
+ {
+ /* Get the number of steps, multiply by default step width of 120 */
+ int32_t val = cev->mouse_axis.value * 0x78;
+ /* No wheel event received, success! */
+ if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y,
+ cev->mouse_axis.axis, val))
+ success = FALSE;
+ }
+ break;
+ /* If we have a touch pad we get actual data, scale */
+ case WL_POINTER_AXIS_SOURCE_FINGER:
+ case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
+ if (cev->type == UWAC_EVENT_POINTER_AXIS)
+ {
+ double dval = wl_fixed_to_double(cev->mouse_axis.value);
+ int32_t val = (int32_t)(dval * 0x78 / 10.0);
+ if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y,
+ cev->mouse_axis.axis, val))
+ success = FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ ArrayList_Clear(context->events);
+ return success;
+}
+
+BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ return ArrayList_Append(context->events, ev);
+}
+
+BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev)
+{
+ rdpInput* input = NULL;
+ DWORD rdp_scancode = 0;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard) &&
+ ev->raw_key == KEY_RIGHTCTRL)
+ wlf_handle_ungrab_key(instance, ev);
+
+ input = instance->context->input;
+ rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(ev->raw_key + 8);
+
+ if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ return TRUE;
+
+ return freerdp_input_send_keyboard_event_ex(input, ev->pressed, ev->repeated, rdp_scancode);
+}
+
+BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev)
+{
+ wlfContext* context = NULL;
+ if (!instance || !instance->context || !ev)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ return UwacSeatInhibitShortcuts(context->seat, false) == UWAC_SUCCESS;
+}
+
+BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev)
+{
+ if (!instance || !ev)
+ return FALSE;
+
+ ((wlfContext*)instance->context)->focusing = TRUE;
+ return TRUE;
+}
+
+BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev)
+{
+ rdpInput* input = NULL;
+ UINT16 syncFlags = 0;
+ wlfContext* wlf = NULL;
+
+ if (!instance || !ev)
+ return FALSE;
+
+ wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ input = instance->context->input;
+ WINPR_ASSERT(input);
+
+ syncFlags = 0;
+
+ if (ev->modifiers & UWAC_MOD_CAPS_MASK)
+ syncFlags |= KBD_SYNC_CAPS_LOCK;
+ if (ev->modifiers & UWAC_MOD_NUM_MASK)
+ syncFlags |= KBD_SYNC_NUM_LOCK;
+
+ if (!wlf->focusing)
+ return TRUE;
+
+ ((wlfContext*)instance->context)->focusing = FALSE;
+
+ return freerdp_input_send_focus_in_event(input, syncFlags) &&
+ freerdp_client_send_button_event(&wlf->common, FALSE, PTR_FLAGS_MOVE, 0, 0);
+}
+
+BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id, 0, x, y);
+}
+
+BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0, x, y);
+}
+
+BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(ev);
+
+ wlfContext* wlf = (wlfContext*)instance->context;
+ WINPR_ASSERT(wlf);
+
+ x = ev->x;
+ y = ev->y;
+
+ if (!scale_signed_coordinates(instance->context, &x, &y, TRUE))
+ return FALSE;
+
+ return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION, 0, ev->id, x, y);
+}
diff --git a/client/Wayland/wlf_input.h b/client/Wayland/wlf_input.h
new file mode 100644
index 0000000..0d4f5ef
--- /dev/null
+++ b/client/Wayland/wlf_input.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Input
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * Copyright 2015 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 FREERDP_CLIENT_WAYLAND_INPUT_H
+#define FREERDP_CLIENT_WAYLAND_INPUT_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/gfx.h>
+#include <uwac/uwac.h>
+
+BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev);
+BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev);
+BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev);
+BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev);
+BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev);
+BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev);
+BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev);
+BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev);
+BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev);
+BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev);
+
+BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev);
+BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev);
+BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev);
+BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev);
+
+#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */
diff --git a/client/Wayland/wlf_pointer.c b/client/Wayland/wlf_pointer.c
new file mode 100644
index 0000000..6fba40b
--- /dev/null
+++ b/client/Wayland/wlf_pointer.c
@@ -0,0 +1,167 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 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 "wlf_pointer.h"
+#include "wlfreerdp.h"
+
+#define TAG CLIENT_TAG("wayland.pointer")
+
+typedef struct
+{
+ rdpPointer pointer;
+ size_t size;
+ void* data;
+} wlfPointer;
+
+static BOOL wlf_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ wlfPointer* ptr = (wlfPointer*)pointer;
+
+ if (!ptr)
+ return FALSE;
+
+ ptr->size = pointer->width * pointer->height * 4ULL;
+ ptr->data = winpr_aligned_malloc(ptr->size, 16);
+
+ if (!ptr->data)
+ return FALSE;
+
+ if (!freerdp_image_copy_from_pointer_data(
+ ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height,
+ pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData,
+ pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette))
+ {
+ winpr_aligned_free(ptr->data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void wlf_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ wlfPointer* ptr = (wlfPointer*)pointer;
+ WINPR_UNUSED(context);
+
+ if (ptr)
+ winpr_aligned_free(ptr->data);
+}
+
+static BOOL wlf_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ wlfContext* wlf = (wlfContext*)context;
+ wlfPointer* ptr = (wlfPointer*)pointer;
+ void* data = NULL;
+ UINT32 w = 0;
+ UINT32 h = 0;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ size_t size = 0;
+ UwacReturnCode rc = UWAC_ERROR_INTERNAL;
+ BOOL res = FALSE;
+ RECTANGLE_16 area;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ x = pointer->xPos;
+ y = pointer->yPos;
+ w = pointer->width;
+ h = pointer->height;
+
+ if (!wlf_scale_coordinates(context, &x, &y, FALSE) ||
+ !wlf_scale_coordinates(context, &w, &h, FALSE))
+ return FALSE;
+
+ size = w * h * 4ULL;
+ data = malloc(size);
+
+ if (!data)
+ return FALSE;
+
+ area.top = 0;
+ area.left = 0;
+ area.right = (UINT16)pointer->width;
+ area.bottom = (UINT16)pointer->height;
+
+ if (!wlf_copy_image(ptr->data, pointer->width * 4, pointer->width, pointer->height, data, w * 4,
+ w, h, &area,
+ freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)))
+ goto fail;
+
+ rc = UwacSeatSetMouseCursor(wlf->seat, data, size, w, h, x, y);
+
+ if (rc == UWAC_SUCCESS)
+ res = TRUE;
+
+fail:
+ free(data);
+ return res;
+}
+
+static BOOL wlf_Pointer_SetNull(rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ if (UwacSeatSetMouseCursor(wlf->seat, NULL, 0, 0, 0, 0, 0) != UWAC_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wlf_Pointer_SetDefault(rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)context;
+
+ if (!wlf || !wlf->seat)
+ return FALSE;
+
+ if (UwacSeatSetMouseCursor(wlf->seat, NULL, 1, 0, 0, 0, 0) != UWAC_SUCCESS)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL wlf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ // TODO
+ WLog_WARN(TAG, "not implemented");
+ return TRUE;
+}
+
+BOOL wlf_register_pointer(rdpGraphics* graphics)
+{
+ rdpPointer pointer = { 0 };
+
+ pointer.size = sizeof(wlfPointer);
+ pointer.New = wlf_Pointer_New;
+ pointer.Free = wlf_Pointer_Free;
+ pointer.Set = wlf_Pointer_Set;
+ pointer.SetNull = wlf_Pointer_SetNull;
+ pointer.SetDefault = wlf_Pointer_SetDefault;
+ pointer.SetPosition = wlf_Pointer_SetPosition;
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
diff --git a/client/Wayland/wlf_pointer.h b/client/Wayland/wlf_pointer.h
new file mode 100644
index 0000000..8ae82e1
--- /dev/null
+++ b/client/Wayland/wlf_pointer.h
@@ -0,0 +1,28 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Mouse Pointer
+ *
+ * Copyright 2019 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2019 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_CLIENT_WAYLAND_POINTER_H
+#define FREERDP_CLIENT_WAYLAND_POINTER_H
+
+#include <freerdp/graphics.h>
+
+BOOL wlf_register_pointer(rdpGraphics* graphics);
+
+#endif /* FREERDP_CLIENT_WAYLAND_POINTER_H */
diff --git a/client/Wayland/wlfreerdp.1.in b/client/Wayland/wlfreerdp.1.in
new file mode 100644
index 0000000..ee40412
--- /dev/null
+++ b/client/Wayland/wlfreerdp.1.in
@@ -0,0 +1,38 @@
+.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@ \- FreeRDP wayland client
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[file]
+[\fIdefault_client_options\fP]
+[\fB/v\fP:<server>[:port]]
+[\fB/version\fP]
+[\fB/help\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+is a wayland Remote Desktop Protocol (RDP) client which is part of the FreeRDP project. A RDP server is built-in to many editions of Windows.. Alternative servers included ogon, gnome-remote-desktop, xrdp and VRDP (VirtualBox).
+.SH OPTIONS
+The wayland client also supports a lot of the \fIdefault client options\fP which are not described here. For details on those see the xfreerdp(1) man page.
+.IP \fB/v:\fP\fI<server>[:port]\fP
+The server hostname or IP, and optionally the port, to connect to.
+.IP /version
+Print the version and exit.
+.IP /help
+Print the help and exit.
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B not 0
+On failure.
+
+.SH SEE ALSO
+xfreerdp(1) wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c
new file mode 100644
index 0000000..037c999
--- /dev/null
+++ b/client/Wayland/wlfreerdp.c
@@ -0,0 +1,807 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Client
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@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 <math.h>
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+#include <float.h>
+
+#include <winpr/sysinfo.h>
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client.h>
+#include <freerdp/utils/signal.h>
+#include <freerdp/locale/keyboard.h>
+
+#include <linux/input.h>
+
+#include <uwac/uwac.h>
+
+#include "wlfreerdp.h"
+#include "wlf_input.h"
+#include "wlf_cliprdr.h"
+#include "wlf_disp.h"
+#include "wlf_channels.h"
+#include "wlf_pointer.h"
+
+#define TAG CLIENT_TAG("wayland")
+
+static BOOL wl_update_buffer(wlfContext* context_w, INT32 ix, INT32 iy, INT32 iw, INT32 ih)
+{
+ BOOL res = FALSE;
+ rdpGdi* gdi = NULL;
+ char* data = NULL;
+ UINT32 x = 0;
+ UINT32 y = 0;
+ UINT32 w = 0;
+ UINT32 h = 0;
+ UwacSize geometry;
+ size_t stride = 0;
+ UwacReturnCode rc = UWAC_ERROR_INTERNAL;
+ RECTANGLE_16 area;
+
+ if (!context_w)
+ return FALSE;
+
+ if ((ix < 0) || (iy < 0) || (iw < 0) || (ih < 0))
+ return FALSE;
+
+ EnterCriticalSection(&context_w->critical);
+ x = (UINT32)ix;
+ y = (UINT32)iy;
+ w = (UINT32)iw;
+ h = (UINT32)ih;
+ rc = UwacWindowGetDrawingBufferGeometry(context_w->window, &geometry, &stride);
+ data = UwacWindowGetDrawingBuffer(context_w->window);
+
+ if (!data || (rc != UWAC_SUCCESS))
+ goto fail;
+
+ gdi = context_w->common.context.gdi;
+
+ if (!gdi)
+ goto fail;
+
+ /* Ignore output if the surface size does not match. */
+ if (((INT64)x > geometry.width) || ((INT64)y > geometry.height))
+ {
+ res = TRUE;
+ goto fail;
+ }
+
+ area.left = x;
+ area.top = y;
+ area.right = x + w;
+ area.bottom = y + h;
+
+ if (!wlf_copy_image(
+ gdi->primary_buffer, gdi->stride, gdi->width, gdi->height, data, stride, geometry.width,
+ geometry.height, &area,
+ freerdp_settings_get_bool(context_w->common.context.settings, FreeRDP_SmartSizing)))
+ goto fail;
+
+ if (!wlf_scale_coordinates(&context_w->common.context, &x, &y, FALSE))
+ goto fail;
+
+ if (!wlf_scale_coordinates(&context_w->common.context, &w, &h, FALSE))
+ goto fail;
+
+ if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS)
+ goto fail;
+
+ if (UwacWindowSubmitBuffer(context_w->window, false) != UWAC_SUCCESS)
+ goto fail;
+
+ res = TRUE;
+fail:
+ LeaveCriticalSection(&context_w->critical);
+ return res;
+}
+
+static BOOL wl_end_paint(rdpContext* context)
+{
+ rdpGdi* gdi = NULL;
+ wlfContext* context_w = NULL;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 w = 0;
+ INT32 h = 0;
+
+ if (!context || !context->gdi || !context->gdi->primary)
+ return FALSE;
+
+ gdi = context->gdi;
+
+ if (gdi->primary->hdc->hwnd->invalid->null)
+ return TRUE;
+
+ x = gdi->primary->hdc->hwnd->invalid->x;
+ y = gdi->primary->hdc->hwnd->invalid->y;
+ w = gdi->primary->hdc->hwnd->invalid->w;
+ h = gdi->primary->hdc->hwnd->invalid->h;
+ context_w = (wlfContext*)context;
+ if (!wl_update_buffer(context_w, x, y, w, h))
+ {
+ return FALSE;
+ }
+
+ gdi->primary->hdc->hwnd->invalid->null = TRUE;
+ gdi->primary->hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL wl_refresh_display(wlfContext* context)
+{
+ rdpGdi* gdi = NULL;
+
+ if (!context || !context->common.context.gdi)
+ return FALSE;
+
+ gdi = context->common.context.gdi;
+ return wl_update_buffer(context, 0, 0, gdi->width, gdi->height);
+}
+
+static BOOL wl_resize_display(rdpContext* context)
+{
+ wlfContext* wlc = (wlfContext*)context;
+ rdpGdi* gdi = context->gdi;
+ rdpSettings* settings = context->settings;
+
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ return FALSE;
+
+ return wl_refresh_display(wlc);
+}
+
+static BOOL wl_pre_connect(freerdp* instance)
+{
+ rdpSettings* settings = NULL;
+ wlfContext* context = NULL;
+ const UwacOutput* output = NULL;
+ UwacSize resolution;
+
+ if (!instance)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_WAYLAND))
+ return FALSE;
+ PubSub_SubscribeChannelConnected(instance->context->pubSub, wlf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
+ wlf_OnChannelDisconnectedEventHandler);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ // Use the resolution of the first display output
+ output = UwacDisplayGetOutput(context->display, 0);
+
+ if ((output != NULL) && (UwacOutputGetResolution(output, &resolution) == UWAC_SUCCESS))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth,
+ (UINT32)resolution.width))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight,
+ (UINT32)resolution.height))
+ return FALSE;
+ }
+ else
+ {
+ WLog_WARN(TAG, "Failed to get output resolution! Check your display settings");
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL wl_post_connect(freerdp* instance)
+{
+ rdpGdi* gdi = NULL;
+ UwacWindow* window = NULL;
+ wlfContext* context = NULL;
+ rdpSettings* settings = NULL;
+ char* title = "FreeRDP";
+ char* app_id = "wlfreerdp";
+ UINT32 w = 0;
+ UINT32 h = 0;
+
+ if (!instance || !instance->context)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+ settings = instance->context->settings;
+
+ const char* wtitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+ if (wtitle)
+ title = wtitle;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ gdi = instance->context->gdi;
+
+ if (!gdi || (gdi->width < 0) || (gdi->height < 0))
+ return FALSE;
+
+ if (!wlf_register_pointer(instance->context->graphics))
+ return FALSE;
+
+ w = (UINT32)gdi->width;
+ h = (UINT32)gdi->height;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !context->fullscreen)
+ {
+ const UINT32 sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
+ if (sw > 0)
+ w = sw;
+
+ const UINT32 sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
+ if (sh > 0)
+ h = sh;
+ }
+
+ context->window = window = UwacCreateWindowShm(context->display, w, h, WL_SHM_FORMAT_XRGB8888);
+
+ if (!window)
+ return FALSE;
+
+ UwacWindowSetFullscreenState(
+ window, NULL, freerdp_settings_get_bool(instance->context->settings, FreeRDP_Fullscreen));
+ UwacWindowSetTitle(window, title);
+ UwacWindowSetAppId(window, app_id);
+ UwacWindowSetOpaqueRegion(context->window, 0, 0, w, h);
+ instance->context->update->EndPaint = wl_end_paint;
+ instance->context->update->DesktopResize = wl_resize_display;
+ UINT32 KeyboardLayout =
+ freerdp_settings_get_uint32(instance->context->settings, FreeRDP_KeyboardLayout);
+ const char* KeyboardRemappingList =
+ freerdp_settings_get_string(instance->context->settings, FreeRDP_KeyboardRemappingList);
+
+ freerdp_keyboard_init_ex(KeyboardLayout, KeyboardRemappingList);
+
+ if (!(context->disp = wlf_disp_new(context)))
+ return FALSE;
+
+ context->clipboard = wlf_clipboard_new(context);
+
+ if (!context->clipboard)
+ return FALSE;
+
+ return wl_refresh_display(context);
+}
+
+static void wl_post_disconnect(freerdp* instance)
+{
+ wlfContext* context = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ context = (wlfContext*)instance->context;
+ gdi_free(instance);
+ wlf_clipboard_free(context->clipboard);
+ wlf_disp_free(context->disp);
+
+ if (context->window)
+ UwacDestroyWindow(&context->window);
+}
+
+static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display)
+{
+ UwacEvent event;
+ wlfContext* context = NULL;
+
+ if (UwacDisplayDispatch(display, 1) < 0)
+ return FALSE;
+
+ context = (wlfContext*)instance->context;
+
+ while (UwacHasEvent(display))
+ {
+ if (UwacNextEvent(display, &event) != UWAC_SUCCESS)
+ return FALSE;
+
+ /*printf("UWAC event type %d\n", event.type);*/
+ switch (event.type)
+ {
+ case UWAC_EVENT_NEW_SEAT:
+ context->seat = event.seat_new.seat;
+ break;
+
+ case UWAC_EVENT_REMOVED_SEAT:
+ context->seat = NULL;
+ break;
+
+ case UWAC_EVENT_FRAME_DONE:
+ {
+ EnterCriticalSection(&context->critical);
+ UwacReturnCode r = UwacWindowSubmitBuffer(context->window, false);
+ LeaveCriticalSection(&context->critical);
+ if (r != UWAC_SUCCESS)
+ return FALSE;
+ }
+ break;
+
+ case UWAC_EVENT_POINTER_ENTER:
+ if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_MOTION:
+ if (!wlf_handle_pointer_motion(instance, &event.mouse_motion))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_BUTTONS:
+ if (!wlf_handle_pointer_buttons(instance, &event.mouse_button))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_POINTER_AXIS:
+ if (!wlf_handle_pointer_axis(instance, &event.mouse_axis))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_POINTER_AXIS_DISCRETE:
+ if (!wlf_handle_pointer_axis_discrete(instance, &event.mouse_axis))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_POINTER_FRAME:
+ if (!wlf_handle_pointer_frame(instance, &event.mouse_frame))
+ return FALSE;
+ break;
+ case UWAC_EVENT_POINTER_SOURCE:
+ if (!wlf_handle_pointer_source(instance, &event.mouse_source))
+ return FALSE;
+ break;
+
+ case UWAC_EVENT_KEY:
+ if (!wlf_handle_key(instance, &event.key))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_UP:
+ if (!wlf_handle_touch_up(instance, &event.touchUp))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_DOWN:
+ if (!wlf_handle_touch_down(instance, &event.touchDown))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_TOUCH_MOTION:
+ if (!wlf_handle_touch_motion(instance, &event.touchMotion))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_KEYBOARD_ENTER:
+ if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard))
+ UwacSeatInhibitShortcuts(event.keyboard_enter_leave.seat, true);
+
+ if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_KEYBOARD_MODIFIERS:
+ if (!wlf_keyboard_modifiers(instance, &event.keyboard_modifiers))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CONFIGURE:
+ if (!wlf_disp_handle_configure(context->disp, event.configure.width,
+ event.configure.height))
+ return FALSE;
+
+ if (!wl_refresh_display(context))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CLIPBOARD_AVAILABLE:
+ case UWAC_EVENT_CLIPBOARD_OFFER:
+ case UWAC_EVENT_CLIPBOARD_SELECT:
+ if (!wlf_cliprdr_handle_event(context->clipboard, &event.clipboard))
+ return FALSE;
+
+ break;
+
+ case UWAC_EVENT_CLOSE:
+ context->closed = TRUE;
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL handle_window_events(freerdp* instance)
+{
+ if (!instance)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int wlfreerdp_run(freerdp* instance)
+{
+ wlfContext* context = NULL;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD status = WAIT_ABANDONED;
+ HANDLE timer = NULL;
+ LARGE_INTEGER due;
+
+ TimerEventArgs timerEvent;
+ EventArgsInit(&timerEvent, "xfreerdp");
+
+ if (!instance)
+ return -1;
+
+ context = (wlfContext*)instance->context;
+
+ if (!context)
+ return -1;
+
+ if (!freerdp_connect(instance))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "Failed to connect");
+ return -1;
+ }
+
+ timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer");
+
+ if (!timer)
+ {
+ WLog_ERR(TAG, "failed to create timer");
+ goto disconnect;
+ }
+
+ due.QuadPart = 0;
+
+ if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE))
+ {
+ goto disconnect;
+ }
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ DWORD count = 0;
+ handles[count++] = timer;
+ handles[count++] = context->displayHandle;
+ count += freerdp_get_event_handles(instance->context, &handles[count],
+ ARRAYSIZE(handles) - count);
+
+ if (count <= 2)
+ {
+ WLog_Print(context->log, WLOG_ERROR, "Failed to get FreeRDP file descriptor");
+ break;
+ }
+
+ status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_Print(context->log, WLOG_ERROR, "WaitForMultipleObjects failed");
+ break;
+ }
+
+ if (!handle_uwac_events(instance, context->display))
+ {
+ WLog_Print(context->log, WLOG_ERROR, "error handling UWAC events");
+ break;
+ }
+
+ if (context->closed)
+ {
+ WLog_Print(context->log, WLOG_INFO, "Closed from Wayland");
+ break;
+ }
+
+ if (freerdp_check_event_handles(instance->context) != TRUE)
+ {
+ if (client_auto_reconnect_ex(instance, handle_window_events))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ if (freerdp_error_info(instance) == 0)
+ status = 42;
+ }
+
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_Print(context->log, WLOG_ERROR, "Failed to check FreeRDP file descriptor");
+
+ break;
+ }
+
+ if ((status != WAIT_TIMEOUT) && (status == WAIT_OBJECT_0))
+ {
+ timerEvent.now = GetTickCount64();
+ PubSub_OnTimer(context->common.context.pubSub, context, &timerEvent);
+ }
+ }
+
+disconnect:
+ if (timer)
+ CloseHandle(timer);
+ freerdp_disconnect(instance);
+ return status;
+}
+
+static BOOL wlf_client_global_init(void)
+{
+ setlocale(LC_ALL, "");
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void wlf_client_global_uninit(void)
+{
+}
+
+static int wlf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ wlfContext* wlf = NULL;
+ 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;
+
+ wlf = (wlfContext*)instance->context;
+ WLog_Print(wlf->log, WLOG_INFO, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static void wlf_client_free(freerdp* instance, rdpContext* context)
+{
+ wlfContext* wlf = (wlfContext*)instance->context;
+
+ if (!context)
+ return;
+
+ if (wlf->display)
+ UwacCloseDisplay(&wlf->display);
+
+ if (wlf->displayHandle)
+ CloseHandle(wlf->displayHandle);
+ ArrayList_Free(wlf->events);
+ DeleteCriticalSection(&wlf->critical);
+}
+
+static void* uwac_event_clone(const void* val)
+{
+ UwacEvent* copy = NULL;
+ const UwacEvent* ev = (const UwacEvent*)val;
+
+ copy = calloc(1, sizeof(UwacEvent));
+ if (!copy)
+ return NULL;
+ *copy = *ev;
+ return copy;
+}
+
+static BOOL wlf_client_new(freerdp* instance, rdpContext* context)
+{
+ wObject* obj = NULL;
+ UwacReturnCode status = UWAC_ERROR_INTERNAL;
+ wlfContext* wfl = (wlfContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->PreConnect = wl_pre_connect;
+ instance->PostConnect = wl_post_connect;
+ instance->PostDisconnect = wl_post_disconnect;
+ instance->LogonErrorInfo = wlf_logon_error_info;
+ wfl->log = WLog_Get(TAG);
+ wfl->display = UwacOpenDisplay(NULL, &status);
+
+ if (!wfl->display || (status != UWAC_SUCCESS) || !wfl->log)
+ return FALSE;
+
+ wfl->displayHandle = CreateFileDescriptorEvent(NULL, FALSE, FALSE,
+ UwacDisplayGetFd(wfl->display), WINPR_FD_READ);
+
+ if (!wfl->displayHandle)
+ return FALSE;
+
+ wfl->events = ArrayList_New(FALSE);
+ if (!wfl->events)
+ return FALSE;
+
+ obj = ArrayList_Object(wfl->events);
+ obj->fnObjectNew = uwac_event_clone;
+ obj->fnObjectFree = free;
+
+ InitializeCriticalSection(&wfl->critical);
+
+ return TRUE;
+}
+
+static int wfl_client_start(rdpContext* context)
+{
+ WINPR_UNUSED(context);
+ return 0;
+}
+
+static 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->GlobalInit = wlf_client_global_init;
+ pEntryPoints->GlobalUninit = wlf_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(wlfContext);
+ pEntryPoints->ClientNew = wlf_client_new;
+ pEntryPoints->ClientFree = wlf_client_free;
+ pEntryPoints->ClientStart = wfl_client_start;
+ pEntryPoints->ClientStop = freerdp_client_common_stop;
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = -1;
+ int status = 0;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ wlfContext* wlc = NULL;
+
+ freerdp_client_warn_deprecated(argc, argv);
+
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+ if (!context)
+ goto fail;
+ wlc = (wlfContext*)context;
+ settings = context->settings;
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ wlf_list_monitors(wlc);
+
+ goto fail;
+ }
+
+ if (freerdp_client_start(context) != 0)
+ goto fail;
+
+ rc = wlfreerdp_run(context->instance);
+
+ if (freerdp_client_stop(context) != 0)
+ rc = -1;
+
+fail:
+ freerdp_client_context_free(context);
+ return rc;
+}
+
+BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst,
+ size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area,
+ BOOL scale)
+{
+ BOOL rc = FALSE;
+
+ if (!src || !dst || !area)
+ return FALSE;
+
+ if (scale)
+ {
+ return freerdp_image_scale(dst, PIXEL_FORMAT_BGRA32, dstStride, 0, 0, dstWidth, dstHeight,
+ src, PIXEL_FORMAT_BGRA32, srcStride, 0, 0, srcWidth, srcHeight);
+ }
+ else
+ {
+ const size_t baseSrcOffset = area->top * srcStride + area->left * 4;
+ const size_t baseDstOffset = area->top * dstStride + area->left * 4;
+ const size_t width = MIN((size_t)area->right - area->left, dstWidth - area->left);
+ const size_t height = MIN((size_t)area->bottom - area->top, dstHeight - area->top);
+ const BYTE* psrc = (const BYTE*)src;
+ BYTE* pdst = (BYTE*)dst;
+
+ for (size_t i = 0; i < height; i++)
+ {
+ const size_t srcOffset = i * srcStride + baseSrcOffset;
+ const size_t dstOffset = i * dstStride + baseDstOffset;
+ memcpy(&pdst[dstOffset], &psrc[srcOffset], width * 4);
+ }
+
+ rc = TRUE;
+ }
+
+ return rc;
+}
+
+BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP)
+{
+ wlfContext* wlf = (wlfContext*)context;
+ rdpGdi* gdi = NULL;
+ UwacSize geometry;
+ double sx = NAN;
+ double sy = NAN;
+
+ if (!context || !px || !py || !context->gdi)
+ return FALSE;
+
+ if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))
+ return TRUE;
+
+ gdi = context->gdi;
+
+ if (UwacWindowGetDrawingBufferGeometry(wlf->window, &geometry, NULL) != UWAC_SUCCESS)
+ return FALSE;
+
+ sx = geometry.width / (double)gdi->width;
+ sy = geometry.height / (double)gdi->height;
+
+ if (!fromLocalToRDP)
+ {
+ *px *= sx;
+ *py *= sy;
+ }
+ else
+ {
+ *px /= sx;
+ *py /= sy;
+ }
+
+ return TRUE;
+}
diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h
new file mode 100644
index 0000000..e1a4650
--- /dev/null
+++ b/client/Wayland/wlfreerdp.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Wayland Client
+ *
+ * Copyright 2014 Manuel Bachmann <tarnyko@tarnyko.net>
+ *
+ * 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_CLIENT_WAYLAND_FREERDP_H
+#define FREERDP_CLIENT_WAYLAND_FREERDP_H
+
+#include <freerdp/client/rdpei.h>
+#include <freerdp/gdi/gfx.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/log.h>
+#include <winpr/wtypes.h>
+#include <uwac/uwac.h>
+
+typedef struct wlf_clipboard wfClipboard;
+typedef struct s_wlfDispContext wlfDispContext;
+
+typedef struct
+{
+ rdpClientContext common;
+
+ UwacDisplay* display;
+ HANDLE displayHandle;
+ UwacWindow* window;
+ UwacSeat* seat;
+
+ BOOL fullscreen;
+ BOOL closed;
+ BOOL focusing;
+
+ /* Channels */
+ wfClipboard* clipboard;
+ wlfDispContext* disp;
+ wLog* log;
+ CRITICAL_SECTION critical;
+ wArrayList* events;
+} wlfContext;
+
+BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP);
+BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst,
+ size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area,
+ BOOL scale);
+
+#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */
diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt
new file mode 100644
index 0000000..099d00d
--- /dev/null
+++ b/client/X11/CMakeLists.txt
@@ -0,0 +1,245 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP X11 Client
+#
+# 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.
+cmake_minimum_required(VERSION 3.13)
+
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(xfreerdp
+ LANGUAGES C
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/)
+include(CommonConfigOptions)
+
+include(ConfigureFreeRDP)
+
+find_package(X11 REQUIRED)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../resources)
+include_directories(${X11_INCLUDE_DIRS})
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+set(SRCS
+ xf_utils.h
+ xf_utils.c
+ xf_gfx.c
+ xf_gfx.h
+ xf_rail.c
+ xf_rail.h
+ xf_input.c
+ xf_input.h
+ xf_event.c
+ xf_event.h
+ xf_floatbar.c
+ xf_floatbar.h
+ xf_input.c
+ xf_input.h
+ xf_channels.c
+ xf_channels.h
+ xf_cliprdr.c
+ xf_cliprdr.h
+ xf_monitor.c
+ xf_monitor.h
+ xf_disp.c
+ xf_disp.h
+ xf_graphics.c
+ xf_graphics.h
+ xf_keyboard.c
+ xf_keyboard.h
+ xf_video.c
+ xf_video.h
+ xf_window.c
+ xf_window.h
+ xf_client.c
+ xf_client.h)
+
+if (CHANNEL_TSMF_CLIENT)
+ list(APPEND SRCS
+ xf_tsmf.c
+ xf_tsmf.h
+ )
+endif()
+
+if(WITH_CLIENT_INTERFACE)
+ if(CLIENT_INTERFACE_SHARED)
+ add_library(${PROJECT_NAME} SHARED ${SRCS})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ else()
+ add_library(${PROJECT_NAME} ${SRCS})
+ endif()
+ target_include_directories(${PROJECT_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+
+else()
+ list(APPEND SRCS
+ cli/xfreerdp.c xfreerdp.h
+ )
+ add_executable(${PROJECT_NAME} ${SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}")
+ endif()
+ include_directories(..)
+endif()
+
+set(LIBS
+ ${X11_LIBRARIES}
+)
+
+add_subdirectory(man)
+
+find_package(X11 REQUIRED)
+if(X11_XShm_FOUND)
+ add_definitions(-DWITH_XSHM)
+ include_directories(${X11_XShm_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xext_LIB}
+ )
+endif()
+
+
+option(WITH_XINERAMA "[X11] enable xinerama" ON)
+if (WITH_XINERAMA)
+ find_package(X11 REQUIRED)
+ if(X11_Xinerama_FOUND)
+ add_definitions(-DWITH_XINERAMA)
+ include_directories(${X11_Xinerama_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xinerama_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XEXT "[X11] enable Xext" ON)
+if (WITH_XEXT)
+ find_package(X11 REQUIRED)
+ if(X11_Xext_FOUND)
+ add_definitions(-DWITH_XEXT)
+ list(APPEND LIBS
+ ${X11_Xext_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XCURSOR "[X11] enalbe Xcursor" ON)
+if (WITH_XCURSOR)
+ find_package(X11 REQUIRED)
+ if(X11_Xcursor_FOUND)
+ add_definitions(-DWITH_XCURSOR)
+ include_directories(${X11_Xcursor_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xcursor_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XV "[X11] enable Xv" ON)
+if (WITH_XV)
+ find_package(X11 REQUIRED)
+ if(X11_Xv_FOUND)
+ add_definitions(-DWITH_XV)
+ include_directories(${X11_Xv_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xv_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XI "[X11] enalbe Xi" ON)
+if (WITH_XI)
+ find_package(X11 REQUIRED)
+ if(X11_Xi_FOUND)
+ add_definitions(-DWITH_XI)
+ include_directories(${X11_Xi_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xi_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XRENDER "[X11] enable XRender" ON)
+if(WITH_XRENDER)
+ find_package(X11 REQUIRED)
+ if(X11_Xrender_FOUND)
+ add_definitions(-DWITH_XRENDER)
+ include_directories(${X11_Xrender_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xrender_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XRANDR "[X11] enable XRandR" ON)
+if (WITH_XRANDR)
+ find_package(X11 REQUIRED)
+ if(X11_Xrandr_FOUND)
+ add_definitions(-DWITH_XRANDR)
+ include_directories(${X11_Xrandr_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xrandr_LIB}
+ )
+ endif()
+endif()
+
+option(WITH_XFIXES "[X11] enable Xfixes" ON)
+if (WITH_XFIXES)
+ find_package(X11 REQUIRED)
+ if(X11_Xfixes_FOUND)
+ add_definitions(-DWITH_XFIXES)
+ include_directories(${X11_Xfixes_INCLUDE_PATH})
+ list(APPEND LIBS
+ ${X11_Xfixes_LIB}
+ )
+ endif()
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR}/resources)
+
+list(APPEND LIBS
+ freerdp-client
+ freerdp
+ m
+)
+if (NOT APPLE)
+ list(APPEND LIBS rt)
+endif()
+target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
+
+if(WITH_IPP)
+ target_link_libraries(${PROJECT_NAME} PRIVATE ${IPP_LIBRARY_LIST})
+endif()
+
+option(WITH_CLIENT_INTERFACE "Build clients as a library with an interface" OFF)
+if(WITH_CLIENT_INTERFACE)
+ install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
+ add_subdirectory(cli)
+else()
+ install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+endif()
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/X11")
+
diff --git a/client/X11/ModuleOptions.cmake b/client/X11/ModuleOptions.cmake
new file mode 100644
index 0000000..4fef68a
--- /dev/null
+++ b/client/X11/ModuleOptions.cmake
@@ -0,0 +1,4 @@
+
+set(FREERDP_CLIENT_NAME "xfreerdp")
+set(FREERDP_CLIENT_PLATFORM "X11")
+set(FREERDP_CLIENT_VENDOR "FreeRDP")
diff --git a/client/X11/cli/CMakeLists.txt b/client/X11/cli/CMakeLists.txt
new file mode 100644
index 0000000..580337b
--- /dev/null
+++ b/client/X11/cli/CMakeLists.txt
@@ -0,0 +1,49 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP X11 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 "xfreerdp-cli")
+set(MODULE_PREFIX "FREERDP_CLIENT_X11")
+
+set(SRCS
+ xfreerdp.c
+)
+
+add_executable(${MODULE_NAME} ${SRCS})
+
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp${PROJECT_VERSION_MAJOR}")
+else()
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp")
+endif()
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "..")
+
+list(APPEND LIBS
+ xfreerdp-client freerdp-client
+)
+
+if(OPENBSD)
+ list(APPEND LIBS
+ ossaudio
+ )
+endif()
+
+target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
+
+install(TARGETS ${MODULE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client)
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/X11")
+
diff --git a/client/X11/cli/xfreerdp.c b/client/X11/cli/xfreerdp.c
new file mode 100644
index 0000000..33b2a96
--- /dev/null
+++ b/client/X11/cli/xfreerdp.c
@@ -0,0 +1,86 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 HP Development Company, LLC
+ *
+ * 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/synch.h>
+#include <winpr/thread.h>
+
+#include <freerdp/streamdump.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/cmdline.h>
+
+#include "../xf_client.h"
+#include "../xfreerdp.h"
+
+int main(int argc, char* argv[])
+{
+ int rc = 1;
+ int status = 0;
+ HANDLE thread = NULL;
+ xfContext* xfc = NULL;
+ DWORD dwExitCode = 0;
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 };
+
+ clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS);
+ clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION;
+
+ RdpClientEntry(&clientEntryPoints);
+
+ context = freerdp_client_context_new(&clientEntryPoints);
+ if (!context)
+ return 1;
+
+ settings = context->settings;
+ xfc = (xfContext*)context;
+
+ status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE);
+ if (status)
+ {
+ rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors))
+ xf_list_monitors(xfc);
+
+ goto out;
+ }
+
+ if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE))
+ goto out;
+
+ if (freerdp_client_start(context) != 0)
+ goto out;
+
+ thread = freerdp_client_get_thread(context);
+
+ WaitForSingleObject(thread, INFINITE);
+ GetExitCodeThread(thread, &dwExitCode);
+ rc = xf_exit_code_from_disconnect_reason(dwExitCode);
+
+ freerdp_client_stop(context);
+
+out:
+ freerdp_client_context_free(context);
+
+ return rc;
+}
diff --git a/client/X11/man/CMakeLists.txt b/client/X11/man/CMakeLists.txt
new file mode 100644
index 0000000..386f13d
--- /dev/null
+++ b/client/X11/man/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(DEPS
+ xfreerdp-channels.1.xml
+ xfreerdp-examples.1.xml
+ xfreerdp-envvar.1.xml
+ )
+
+set(MANPAGE_NAME ${PROJECT_NAME})
+if (WITH_BINARY_VERSIONING)
+ set(MANPAGE_NAME ${PROJECT_NAME}${PROJECT_VERSION_MAJOR})
+endif()
+generate_and_install_freerdp_man_from_xml(${PROJECT_NAME}.1 ${MANPAGE_NAME}.1 ${DEPS})
diff --git a/client/X11/man/xfreerdp-channels.1.xml b/client/X11/man/xfreerdp-channels.1.xml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/X11/man/xfreerdp-channels.1.xml
diff --git a/client/X11/man/xfreerdp-envvar.1.xml b/client/X11/man/xfreerdp-envvar.1.xml
new file mode 100644
index 0000000..955adf5
--- /dev/null
+++ b/client/X11/man/xfreerdp-envvar.1.xml
@@ -0,0 +1,15 @@
+<refsect1>
+ <title>Environment variables</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>wlog environment variable</term>
+ <listitem>
+ <para>xfreerdp uses wLog as its log facility, you can refer to the
+ corresponding man page (wlog(7)) for more informations. Arguments passed
+ via the <replaceable>/log-level</replaceable> or <replaceable>/log-filters</replaceable>
+ have precedence over the environment variables.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/X11/man/xfreerdp-examples.1.xml b/client/X11/man/xfreerdp-examples.1.xml
new file mode 100644
index 0000000..3418143
--- /dev/null
+++ b/client/X11/man/xfreerdp-examples.1.xml
@@ -0,0 +1,95 @@
+<refsect1>
+ <title>Examples</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>xfreerdp connection.rdp /p:Pwd123! /f</command></term>
+ <listitem>
+ <para>Connect in fullscreen mode using a stored configuration <replaceable>connection.rdp</replaceable> and the password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:USER /size:50%h /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>USER</replaceable> and a size of <replaceable>50 percent of the height</replaceable>. If width (w) is set instead of height (h) like /size:50%w. 50 percent of the width is used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>rdp.contoso.com</replaceable> with user <replaceable>CONTOSO\\JohnDoe</replaceable> and password <replaceable>Pwd123!</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489</command></term>
+ <listitem>
+ <para>Connect to host <replaceable>192.168.1.100</replaceable> on port <replaceable>4489</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable>. The screen width is set to <replaceable>1366</replaceable> and the height to <replaceable>768</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>xfreerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100</command></term>
+ <listitem>
+ <para>Establish a connection to host <replaceable>192.168.1.100</replaceable> with user <replaceable>JohnDoe</replaceable>, password <replaceable>Pwd123!</replaceable> and connect to Hyper-V console (use port 2179, disable negotiation) with VMID <replaceable>C824F53E-95D2-46C6-9A18-23A5BB403532</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>+clipboard</command></term>
+ <listitem>
+ <para>Activate clipboard redirection</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/drive:home,/home/user</command></term>
+ <listitem>
+ <para>Activate drive redirection of <replaceable>/home/user</replaceable> as home drive</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/smartcard:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate smartcard redirection for device <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/printer:&lt;device&gt;,&lt;driver&gt;</command></term>
+ <listitem>
+ <para>Activate printer redirection for printer <replaceable>device</replaceable> using driver <replaceable>driver</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/serial:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate serial port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/parallel:&lt;device&gt;</command></term>
+ <listitem>
+ <para>Activate parallel port redirection for port <replaceable>device</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/sound:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio output redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/microphone:sys:alsa</command></term>
+ <listitem>
+ <para>Activate audio input redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/multimedia:sys:alsa</command></term>
+ <listitem>
+ <para>Activate multimedia redirection using device <replaceable>sys:alsa</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>/usb:id,dev:054c:0268</command></term>
+ <listitem>
+ <para>Activate USB device redirection for the device identified by <replaceable>054c:0268</replaceable></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</refsect1>
diff --git a/client/X11/man/xfreerdp.1.xml.in b/client/X11/man/xfreerdp.1.xml.in
new file mode 100644
index 0000000..271e39d
--- /dev/null
+++ b/client/X11/man/xfreerdp.1.xml.in
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE refentry
+PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY syntax SYSTEM "freerdp-argument.1.xml">
+ <!ENTITY channels SYSTEM "xfreerdp-channels.1.xml">
+ <!ENTITY envvar SYSTEM "xfreerdp-envvar.1.xml">
+ <!ENTITY examples SYSTEM "xfreerdp-examples.1.xml">
+ ]
+>
+
+<refentry>
+ <refentryinfo>
+ <date>@MAN_TODAY@</date>
+ <author>
+ <authorblurb><para>The FreeRDP Team</para></authorblurb>
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>@MANPAGE_NAME@</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class="source">freerdp</refmiscinfo>
+ <refmiscinfo class="manual">@MANPAGE_NAME@</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname><application>@MANPAGE_NAME@</application></refname>
+ <refpurpose>FreeRDP X11 client</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <refsynopsisdivinfo>
+ <date>@MAN_TODAY@</date>
+ </refsynopsisdivinfo>
+ <para>
+ <command>@MANPAGE_NAME@</command> [file] [options] [/v:server[:port]]
+ </para>
+ </refsynopsisdiv>
+ <refsect1>
+ <refsect1info>
+ <date>@MAN_TODAY@</date>
+ </refsect1info>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>@MANPAGE_NAME@</command> is an X11 Remote Desktop Protocol (RDP)
+ client which is part of the FreeRDP project. An RDP server is built-in
+ to many editions of Windows. Alternative servers included ogon, gnome-remote-desktop,
+ xrdp and VRDP (VirtualBox).
+ </para>
+ </refsect1>
+
+ &syntax;
+
+ &channels;
+
+ &envvar;
+
+ &examples;
+
+ <refsect1>
+ <title>LINKS</title>
+ <para>
+ <ulink url="http://www.freerdp.com/">http://www.freerdp.com/</ulink>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/client/X11/resource/close.xbm b/client/X11/resource/close.xbm
new file mode 100644
index 0000000..45c60e3
--- /dev/null
+++ b/client/X11/resource/close.xbm
@@ -0,0 +1,11 @@
+#define close_width 24
+#define close_height 24
+static unsigned char close_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x7c, 0xfe, 0xff, 0x38, 0xfe, 0xff, 0x11, 0xff, 0xff, 0x83, 0xff,
+ 0xff, 0xc7, 0xff, 0xff, 0x83, 0xff, 0xff, 0x11, 0xff, 0xff, 0x38, 0xfe,
+ 0xff, 0x7c, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/lock.xbm b/client/X11/resource/lock.xbm
new file mode 100644
index 0000000..12340f5
--- /dev/null
+++ b/client/X11/resource/lock.xbm
@@ -0,0 +1,11 @@
+#define lock_width 24
+#define lock_height 24
+static unsigned char lock_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff,
+ 0xff, 0x83, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff,
+ 0xff, 0x00, 0xfe, 0xff, 0x00, 0xfe, 0xff, 0xef, 0xff, 0xff, 0xef, 0xff,
+ 0xff, 0xef, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/minimize.xbm b/client/X11/resource/minimize.xbm
new file mode 100644
index 0000000..c69d861
--- /dev/null
+++ b/client/X11/resource/minimize.xbm
@@ -0,0 +1,11 @@
+#define minimize_width 24
+#define minimize_height 24
+static unsigned char minimize_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc,
+ 0x3f, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/restore.xbm b/client/X11/resource/restore.xbm
new file mode 100644
index 0000000..e9909f5
--- /dev/null
+++ b/client/X11/resource/restore.xbm
@@ -0,0 +1,11 @@
+#define restore_width 24
+#define restore_height 24
+static unsigned char restore_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x3b, 0xff, 0x7f, 0x20, 0xff,
+ 0x7f, 0x20, 0xff, 0x7f, 0x07, 0xff, 0x7f, 0xe7, 0xff, 0x7f, 0xe7, 0xff,
+ 0x7f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/resource/unlock.xbm b/client/X11/resource/unlock.xbm
new file mode 100644
index 0000000..a809126
--- /dev/null
+++ b/client/X11/resource/unlock.xbm
@@ -0,0 +1,11 @@
+#define unlock_width 24
+#define unlock_height 24
+static unsigned char unlock_bits[] =
+{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x73, 0xfe, 0xff, 0x03, 0xfe,
+ 0x3f, 0x00, 0xfe, 0xff, 0x03, 0xfe, 0xff, 0x73, 0xfe, 0xff, 0xf3, 0xff,
+ 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/client/X11/xf_channels.c b/client/X11/xf_channels.c
new file mode 100644
index 0000000..7177622
--- /dev/null
+++ b/client/X11/xf_channels.c
@@ -0,0 +1,132 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * Copyright 2013 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 <freerdp/gdi/video.h>
+#include "xf_channels.h"
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include "xf_gfx.h"
+#if defined(CHANNEL_TSMF_CLIENT)
+#include "xf_tsmf.h"
+#endif
+#include "xf_rail.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_video.h"
+
+void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(e->name);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (FALSE)
+ {
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_tsmf_init(xfc, (TsmfClientContext*)e->pInterface);
+ }
+#endif
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_graphics_pipeline_init(xfc, (RdpgfxClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_rail_init(xfc, (RailClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_cliprdr_init(xfc, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_disp_init(xfc->xfDisp, (DispClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ gdi_video_control_init(xfc->common.context.gdi, (VideoClientContext*)e->pInterface);
+ else
+ xf_video_control_init(xfc, (VideoClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelConnectedEventHandler(context, e);
+}
+
+void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+ WINPR_ASSERT(e->name);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (FALSE)
+ {
+ }
+ else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_disp_uninit(xfc->xfDisp, (DispClientContext*)e->pInterface);
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_tsmf_uninit(xfc, (TsmfClientContext*)e->pInterface);
+ }
+#endif
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ xf_graphics_pipeline_uninit(xfc, (RdpgfxClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_rail_uninit(xfc, (RailClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ xf_cliprdr_uninit(xfc, (CliprdrClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ gdi_video_control_uninit(xfc->common.context.gdi, (VideoClientContext*)e->pInterface);
+ else
+ xf_video_control_uninit(xfc, (VideoClientContext*)e->pInterface);
+ }
+ else
+ freerdp_client_OnChannelDisconnectedEventHandler(context, e);
+}
diff --git a/client/X11/xf_channels.h b/client/X11/xf_channels.h
new file mode 100644
index 0000000..86b00b9
--- /dev/null
+++ b/client/X11/xf_channels.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Channels
+ *
+ * Copyright 2013 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_CLIENT_X11_CHANNELS_H
+#define FREERDP_CLIENT_X11_CHANNELS_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/rdpei.h>
+#include <freerdp/client/rail.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/client/encomsp.h>
+#include <freerdp/client/disp.h>
+#include <freerdp/client/geometry.h>
+#include <freerdp/client/video.h>
+
+void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e);
+void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e);
+
+#endif /* FREERDP_CLIENT_X11_CHANNELS_H */
diff --git a/client/X11/xf_client.c b/client/X11/xf_client.c
new file mode 100644
index 0000000..0f51745
--- /dev/null
+++ b/client/X11/xf_client.c
@@ -0,0 +1,2000 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Interface
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <math.h>
+#include <winpr/assert.h>
+#include <winpr/sspicli.h>
+
+#include <float.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#ifdef WITH_XRENDER
+#include <X11/extensions/Xrender.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#endif
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include <X11/XKBlib.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/constants.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/channels.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/file.h>
+#include <winpr/print.h>
+#include <winpr/sysinfo.h>
+
+#include "xf_rail.h"
+#if defined(CHANNEL_TSMF_CLIENT)
+#include "xf_tsmf.h"
+#endif
+#include "xf_event.h"
+#include "xf_input.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_video.h"
+#include "xf_monitor.h"
+#include "xf_graphics.h"
+#include "xf_keyboard.h"
+#include "xf_channels.h"
+#include "xfreerdp.h"
+#include "xf_utils.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#define MIN_PIXEL_DIFF 0.001
+
+struct xf_exit_code_map_t
+{
+ DWORD error;
+ int rc;
+};
+static const struct xf_exit_code_map_t xf_exit_code_map[] = {
+ { FREERDP_ERROR_AUTHENTICATION_FAILED, XF_EXIT_AUTH_FAILURE },
+ { FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, XF_EXIT_NEGO_FAILURE },
+ { FREERDP_ERROR_CONNECT_LOGON_FAILURE, XF_EXIT_LOGON_FAILURE },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, XF_EXIT_ACCOUNT_LOCKED_OUT },
+ { FREERDP_ERROR_PRE_CONNECT_FAILED, XF_EXIT_PRE_CONNECT_FAILED },
+ { FREERDP_ERROR_CONNECT_UNDEFINED, XF_EXIT_CONNECT_UNDEFINED },
+ { FREERDP_ERROR_POST_CONNECT_FAILED, XF_EXIT_POST_CONNECT_FAILED },
+ { FREERDP_ERROR_DNS_ERROR, XF_EXIT_DNS_ERROR },
+ { FREERDP_ERROR_DNS_NAME_NOT_FOUND, XF_EXIT_DNS_NAME_NOT_FOUND },
+ { FREERDP_ERROR_CONNECT_FAILED, XF_EXIT_CONNECT_FAILED },
+ { FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, XF_EXIT_MCS_CONNECT_INITIAL_ERROR },
+ { FREERDP_ERROR_TLS_CONNECT_FAILED, XF_EXIT_TLS_CONNECT_FAILED },
+ { FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, XF_EXIT_INSUFFICIENT_PRIVILEGES },
+ { FREERDP_ERROR_CONNECT_CANCELLED, XF_EXIT_CONNECT_CANCELLED },
+ { FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, XF_EXIT_CONNECT_TRANSPORT_FAILED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, XF_EXIT_CONNECT_PASSWORD_EXPIRED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE },
+ { FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, XF_EXIT_CONNECT_KDC_UNREACHABLE },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, XF_EXIT_CONNECT_ACCOUNT_DISABLED },
+ { FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED,
+ XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED },
+ { FREERDP_ERROR_CONNECT_CLIENT_REVOKED, XF_EXIT_CONNECT_CLIENT_REVOKED },
+ { FREERDP_ERROR_CONNECT_WRONG_PASSWORD, XF_EXIT_CONNECT_WRONG_PASSWORD },
+ { FREERDP_ERROR_CONNECT_ACCESS_DENIED, XF_EXIT_CONNECT_ACCESS_DENIED },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, XF_EXIT_CONNECT_ACCOUNT_RESTRICTION },
+ { FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, XF_EXIT_CONNECT_ACCOUNT_EXPIRED },
+ { FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED },
+ { FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS }
+};
+
+static BOOL xf_setup_x11(xfContext* xfc);
+static void xf_teardown_x11(xfContext* xfc);
+
+static int xf_map_error_to_exit_code(DWORD error)
+{
+ for (size_t x = 0; x < ARRAYSIZE(xf_exit_code_map); x++)
+ {
+ const struct xf_exit_code_map_t* cur = &xf_exit_code_map[x];
+ if (cur->error == error)
+ return cur->rc;
+ }
+
+ return XF_EXIT_CONN_FAILED;
+}
+static int (*def_error_handler)(Display*, XErrorEvent*);
+static int xf_error_handler_ex(Display* d, XErrorEvent* ev);
+static void xf_check_extensions(xfContext* context);
+static void xf_window_free(xfContext* xfc);
+static BOOL xf_get_pixmap_info(xfContext* xfc);
+
+#ifdef WITH_XRENDER
+static void xf_draw_screen_scaled(xfContext* xfc, int x, int y, int w, int h)
+{
+ XTransform transform;
+ Picture windowPicture = 0;
+ Picture primaryPicture = 0;
+ XRenderPictureAttributes pa;
+ XRenderPictFormat* picFormat = NULL;
+ double xScalingFactor = NAN;
+ double yScalingFactor = NAN;
+ int x2 = 0;
+ int y2 = 0;
+ const char* filter = NULL;
+ rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->scaledWidth <= 0 || xfc->scaledHeight <= 0)
+ {
+ WLog_ERR(TAG, "the current window dimensions are invalid");
+ return;
+ }
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= 0 ||
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= 0)
+ {
+ WLog_ERR(TAG, "the window dimensions are invalid");
+ return;
+ }
+
+ xScalingFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) / (double)xfc->scaledWidth;
+ yScalingFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) / (double)xfc->scaledHeight;
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, 0);
+ /* Black out possible space between desktop and window borders */
+ {
+ XRectangle box1 = { 0, 0, xfc->window->width, xfc->window->height };
+ XRectangle box2 = { xfc->offset_x, xfc->offset_y, xfc->scaledWidth, xfc->scaledHeight };
+ Region reg1 = XCreateRegion();
+ Region reg2 = XCreateRegion();
+ XUnionRectWithRegion(&box1, reg1, reg1);
+ XUnionRectWithRegion(&box2, reg2, reg2);
+
+ if (XSubtractRegion(reg1, reg2, reg1) && !XEmptyRegion(reg1))
+ {
+ XSetRegion(xfc->display, xfc->gc, reg1);
+ XFillRectangle(xfc->display, xfc->window->handle, xfc->gc, 0, 0, xfc->window->width,
+ xfc->window->height);
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+
+ XDestroyRegion(reg1);
+ XDestroyRegion(reg2);
+ }
+ picFormat = XRenderFindVisualFormat(xfc->display, xfc->visual);
+ pa.subwindow_mode = IncludeInferiors;
+ primaryPicture =
+ XRenderCreatePicture(xfc->display, xfc->primary, picFormat, CPSubwindowMode, &pa);
+ windowPicture =
+ XRenderCreatePicture(xfc->display, xfc->window->handle, picFormat, CPSubwindowMode, &pa);
+ /* avoid blurry filter when scaling factor is 2x, 3x, etc
+ * useful when the client has high-dpi monitor */
+ filter = FilterBilinear;
+ if (fabs(xScalingFactor - yScalingFactor) < MIN_PIXEL_DIFF)
+ {
+ const double inverseX = 1.0 / xScalingFactor;
+ const double inverseRoundedX = round(inverseX);
+ const double absInverse = fabs(inverseX - inverseRoundedX);
+
+ if (absInverse < MIN_PIXEL_DIFF)
+ filter = FilterNearest;
+ }
+ XRenderSetPictureFilter(xfc->display, primaryPicture, filter, 0, 0);
+ transform.matrix[0][0] = XDoubleToFixed(xScalingFactor);
+ transform.matrix[0][1] = XDoubleToFixed(0.0);
+ transform.matrix[0][2] = XDoubleToFixed(0.0);
+ transform.matrix[1][0] = XDoubleToFixed(0.0);
+ transform.matrix[1][1] = XDoubleToFixed(yScalingFactor);
+ transform.matrix[1][2] = XDoubleToFixed(0.0);
+ transform.matrix[2][0] = XDoubleToFixed(0.0);
+ transform.matrix[2][1] = XDoubleToFixed(0.0);
+ transform.matrix[2][2] = XDoubleToFixed(1.0);
+ /* calculate and fix up scaled coordinates */
+ x2 = x + w;
+ y2 = y + h;
+ x = ((int)floor(x / xScalingFactor)) - 1;
+ y = ((int)floor(y / yScalingFactor)) - 1;
+ w = ((int)ceil(x2 / xScalingFactor)) + 1 - x;
+ h = ((int)ceil(y2 / yScalingFactor)) + 1 - y;
+ XRenderSetPictureTransform(xfc->display, primaryPicture, &transform);
+ XRenderComposite(xfc->display, PictOpSrc, primaryPicture, 0, windowPicture, x, y, 0, 0,
+ xfc->offset_x + x, xfc->offset_y + y, w, h);
+ XRenderFreePicture(xfc->display, primaryPicture);
+ XRenderFreePicture(xfc->display, windowPicture);
+}
+
+BOOL xf_picture_transform_required(xfContext* xfc)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if ((xfc->offset_x != 0) || (xfc->offset_y != 0) ||
+ (xfc->scaledWidth != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) ||
+ (xfc->scaledHeight != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif /* WITH_XRENDER defined */
+
+void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file,
+ int line)
+{
+ if (!xfc)
+ {
+ WLog_DBG(TAG, "called from [%s] xfc=%p", fkt, xfc);
+ return;
+ }
+
+ if (w == 0 || h == 0)
+ {
+ WLog_WARN(TAG, "invalid width and/or height specified: w=%d h=%d", w, h);
+ return;
+ }
+
+ if (!xfc->window)
+ {
+ WLog_WARN(TAG, "invalid xfc->window=%p", xfc->window);
+ return;
+ }
+
+#ifdef WITH_XRENDER
+
+ if (xf_picture_transform_required(xfc))
+ {
+ xf_draw_screen_scaled(xfc, x, y, w, h);
+ return;
+ }
+
+#endif
+ XCopyArea(xfc->display, xfc->primary, xfc->window->handle, xfc->gc, x, y, w, h, x, y);
+}
+
+static BOOL xf_desktop_resize(rdpContext* context)
+{
+ rdpSettings* settings = NULL;
+ xfContext* xfc = (xfContext*)context;
+
+ WINPR_ASSERT(xfc);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->primary)
+ {
+ BOOL same = (xfc->primary == xfc->drawing) ? TRUE : FALSE;
+ XFreePixmap(xfc->display, xfc->primary);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!(xfc->primary = XCreatePixmap(
+ xfc->display, xfc->drawable,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), xfc->depth)))
+ return FALSE;
+
+ if (same)
+ xfc->drawing = xfc->primary;
+ }
+
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+#endif
+
+ if (!xfc->fullscreen)
+ {
+ xf_ResizeDesktopWindow(xfc, xfc->window,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+ else
+ {
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+#endif
+ {
+ /* Update the saved width and height values the window will be
+ * resized to when toggling out of fullscreen */
+ xfc->savedWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->savedHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, 0);
+ XFillRectangle(xfc->display, xfc->drawable, xfc->gc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_paint(xfContext* xfc, const GDI_RGN* region)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(region);
+
+ if (xfc->remote_app)
+ {
+ const RECTANGLE_16 rect = { .left = region->x,
+ .top = region->y,
+ .right = region->x + region->w,
+ .bottom = region->y + region->h };
+ xf_rail_paint(xfc, &rect);
+ }
+ else
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, xfc->image, region->x, region->y, region->x,
+ region->y, region->w, region->h);
+ xf_draw_screen(xfc, region->x, region->y, region->w, region->h);
+ }
+ return TRUE;
+}
+
+static BOOL xf_end_paint(rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpGdi* gdi = context->gdi;
+
+ if (gdi->suppressOutput)
+ return TRUE;
+
+ HGDI_DC hdc = gdi->primary->hdc;
+
+ if (!xfc->complex_regions)
+ {
+ const GDI_RGN* rgn = hdc->hwnd->invalid;
+ if (rgn->null)
+ return TRUE;
+ xf_lock_x11(xfc);
+ if (!xf_paint(xfc, rgn))
+ return FALSE;
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ const INT32 ninvalid = hdc->hwnd->ninvalid;
+ const GDI_RGN* cinvalid = hdc->hwnd->cinvalid;
+
+ if (hdc->hwnd->ninvalid < 1)
+ return TRUE;
+
+ xf_lock_x11(xfc);
+
+ for (INT32 i = 0; i < ninvalid; i++)
+ {
+ const GDI_RGN* rgn = &cinvalid[i];
+ if (!xf_paint(xfc, rgn))
+ return FALSE;
+ }
+
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+ }
+
+ hdc->hwnd->invalid->null = TRUE;
+ hdc->hwnd->ninvalid = 0;
+ return TRUE;
+}
+
+static BOOL xf_sw_desktop_resize(rdpContext* context)
+{
+ rdpGdi* gdi = context->gdi;
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = context->settings;
+ BOOL ret = FALSE;
+ xf_lock_x11(xfc);
+
+ if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)))
+ goto out;
+
+ if (xfc->image)
+ {
+ xfc->image->data = NULL;
+ XDestroyImage(xfc->image);
+ }
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!(xfc->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)gdi->primary_buffer, gdi->width, gdi->height,
+ xfc->scanline_pad, gdi->stride)))
+ {
+ goto out;
+ }
+
+ xfc->image->byte_order = LSBFirst;
+ xfc->image->bitmap_bit_order = LSBFirst;
+ ret = xf_desktop_resize(context);
+out:
+ xf_unlock_x11(xfc);
+ return ret;
+}
+
+static BOOL xf_process_x_events(freerdp* instance)
+{
+ BOOL status = TRUE;
+ int pending_status = 1;
+ xfContext* xfc = (xfContext*)instance->context;
+
+ while (pending_status)
+ {
+ xf_lock_x11(xfc);
+ pending_status = XPending(xfc->display);
+
+ if (pending_status)
+ {
+ XEvent xevent = { 0 };
+
+ XNextEvent(xfc->display, &xevent);
+ status = xf_event_process(instance, &xevent);
+ }
+ xf_unlock_x11(xfc);
+ if (!status)
+ break;
+ }
+
+ return status;
+}
+
+static char* xf_window_get_title(rdpSettings* settings)
+{
+ BOOL port = 0;
+ char* windowTitle = NULL;
+ size_t size = 0;
+ const char* prefix = "FreeRDP:";
+
+ if (!settings)
+ return NULL;
+
+ const char* name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
+ const char* title = freerdp_settings_get_string(settings, FreeRDP_WindowTitle);
+
+ if (title)
+ return _strdup(title);
+
+ port = (freerdp_settings_get_uint32(settings, FreeRDP_ServerPort) != 3389);
+ /* Just assume a window title is never longer than a filename... */
+ size = strnlen(name, MAX_PATH) + 16;
+ windowTitle = calloc(size, sizeof(char));
+
+ if (!windowTitle)
+ return NULL;
+
+ if (!port)
+ sprintf_s(windowTitle, size, "%s %s", prefix, name);
+ else
+ sprintf_s(windowTitle, size, "%s %s:%i", prefix, name,
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
+
+ return windowTitle;
+}
+
+BOOL xf_create_window(xfContext* xfc)
+{
+ XGCValues gcv = { 0 };
+ XEvent xevent = { 0 };
+ char* windowTitle = NULL;
+
+ WINPR_ASSERT(xfc);
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ int width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ int height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ const XSetWindowAttributes empty = { 0 };
+ xfc->attribs = empty;
+
+ if (xfc->remote_app)
+ xfc->depth = 32;
+ else
+ xfc->depth = DefaultDepthOfScreen(xfc->screen);
+
+ XVisualInfo vinfo = { 0 };
+ if (XMatchVisualInfo(xfc->display, xfc->screen_number, xfc->depth, TrueColor, &vinfo))
+ {
+ Window root = XDefaultRootWindow(xfc->display);
+ xfc->visual = vinfo.visual;
+ xfc->attribs.colormap = xfc->colormap =
+ XCreateColormap(xfc->display, root, vinfo.visual, AllocNone);
+ }
+ else
+ {
+ if (xfc->remote_app)
+ {
+ WLog_WARN(TAG, "running in remote app mode, but XServer does not support transparency");
+ WLog_WARN(TAG, "display of remote applications might be distorted (black frames, ...)");
+ }
+ xfc->depth = DefaultDepthOfScreen(xfc->screen);
+ xfc->visual = DefaultVisual(xfc->display, xfc->screen_number);
+ xfc->attribs.colormap = xfc->colormap = DefaultColormap(xfc->display, xfc->screen_number);
+ }
+
+ /*
+ * Detect if the server visual has an inverted colormap
+ * (BGR vs RGB, or red being the least significant byte)
+ */
+ if (vinfo.red_mask & 0xFF)
+ {
+ xfc->invert = FALSE;
+ }
+
+ if (!xfc->remote_app)
+ {
+ xfc->attribs.background_pixel = BlackPixelOfScreen(xfc->screen);
+ xfc->attribs.border_pixel = WhitePixelOfScreen(xfc->screen);
+ xfc->attribs.backing_store = xfc->primary ? NotUseful : Always;
+ xfc->attribs.override_redirect = False;
+
+ xfc->attribs.bit_gravity = NorthWestGravity;
+ xfc->attribs.win_gravity = NorthWestGravity;
+ xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap |
+ CWBorderPixel | CWWinGravity | CWBitGravity;
+
+#ifdef WITH_XRENDER
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+#endif
+ windowTitle = xf_window_get_title(settings);
+
+ if (!windowTitle)
+ return FALSE;
+
+#ifdef WITH_XRENDER
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !xfc->fullscreen)
+ {
+ if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0)
+ width = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth);
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0)
+ height = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight);
+
+ xfc->scaledWidth = width;
+ xfc->scaledHeight = height;
+ }
+
+#endif
+ xfc->window = xf_CreateDesktopWindow(xfc, windowTitle, width, height);
+ free(windowTitle);
+
+ if (xfc->fullscreen)
+ xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen);
+
+ xfc->unobscured = (xevent.xvisibility.state == VisibilityUnobscured);
+ XSetWMProtocols(xfc->display, xfc->window->handle, &(xfc->WM_DELETE_WINDOW), 1);
+ xfc->drawable = xfc->window->handle;
+ }
+ else
+ {
+ xfc->attribs.border_pixel = 0;
+ xfc->attribs.background_pixel = 0;
+ xfc->attribs.backing_store = xfc->primary ? NotUseful : Always;
+ xfc->attribs.override_redirect = False;
+
+ xfc->attribs.bit_gravity = NorthWestGravity;
+ xfc->attribs.win_gravity = NorthWestGravity;
+ xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap |
+ CWBorderPixel | CWWinGravity | CWBitGravity;
+
+ xfc->drawable = xf_CreateDummyWindow(xfc);
+ }
+
+ if (!xfc->gc)
+ xfc->gc = XCreateGC(xfc->display, xfc->drawable, GCGraphicsExposures, &gcv);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (!xfc->primary)
+ xfc->primary =
+ XCreatePixmap(xfc->display, xfc->drawable,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), xfc->depth);
+
+ xfc->drawing = xfc->primary;
+
+ if (!xfc->bitmap_mono)
+ xfc->bitmap_mono = XCreatePixmap(xfc->display, xfc->drawable, 8, 8, 1);
+
+ if (!xfc->gc_mono)
+ xfc->gc_mono = XCreateGC(xfc->display, xfc->bitmap_mono, GCGraphicsExposures, &gcv);
+
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, BlackPixelOfScreen(xfc->screen));
+ XFillRectangle(xfc->display, xfc->primary, xfc->gc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ XFlush(xfc->display);
+
+ return TRUE;
+}
+
+BOOL xf_create_image(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ if (!xfc->image)
+ {
+ const rdpSettings* settings = xfc->common.context.settings;
+ rdpGdi* cgdi = xfc->common.context.gdi;
+ WINPR_ASSERT(cgdi);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ xfc->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)cgdi->primary_buffer,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
+ xfc->scanline_pad, cgdi->stride);
+ xfc->image->byte_order = LSBFirst;
+ xfc->image->bitmap_bit_order = LSBFirst;
+ }
+ return TRUE;
+}
+
+static void xf_window_free(xfContext* xfc)
+{
+ if (xfc->window)
+ {
+ xf_DestroyDesktopWindow(xfc, xfc->window);
+ xfc->window = NULL;
+ }
+
+#if defined(CHANNEL_TSMF_CLIENT)
+ if (xfc->xv_context)
+ {
+ xf_tsmf_uninit(xfc, NULL);
+ xfc->xv_context = NULL;
+ }
+#endif
+
+ if (xfc->image)
+ {
+ xfc->image->data = NULL;
+ XDestroyImage(xfc->image);
+ xfc->image = NULL;
+ }
+
+ if (xfc->bitmap_mono)
+ {
+ XFreePixmap(xfc->display, xfc->bitmap_mono);
+ xfc->bitmap_mono = 0;
+ }
+
+ if (xfc->gc_mono)
+ {
+ XFreeGC(xfc->display, xfc->gc_mono);
+ xfc->gc_mono = 0;
+ }
+
+ if (xfc->primary)
+ {
+ XFreePixmap(xfc->display, xfc->primary);
+ xfc->primary = 0;
+ }
+
+ if (xfc->gc)
+ {
+ XFreeGC(xfc->display, xfc->gc);
+ xfc->gc = 0;
+ }
+}
+
+void xf_toggle_fullscreen(xfContext* xfc)
+{
+ WindowStateChangeEventArgs e;
+ rdpContext* context = (rdpContext*)xfc;
+ rdpSettings* settings = context->settings;
+
+ /*
+ when debugging, ungrab keyboard when toggling fullscreen
+ to allow keyboard usage on the debugger
+ */
+ if (xfc->debug)
+ xf_ungrab(xfc);
+
+ xfc->fullscreen = (xfc->fullscreen) ? FALSE : TRUE;
+ xfc->decorations =
+ (xfc->fullscreen) ? FALSE : freerdp_settings_get_bool(settings, FreeRDP_Decorations);
+ xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen);
+ EventArgsInit(&e, "xfreerdp");
+ e.state = xfc->fullscreen ? FREERDP_WINDOW_STATE_FULLSCREEN : 0;
+ PubSub_OnWindowStateChange(context->pubSub, context, &e);
+}
+
+void xf_lock_x11_(xfContext* xfc, const char* fkt)
+{
+
+ if (!xfc->UseXThreads)
+ WaitForSingleObject(xfc->mutex, INFINITE);
+ else
+ XLockDisplay(xfc->display);
+
+ xfc->locked++;
+ WLog_VRB(TAG, "[%" PRIu32 "] from %s", xfc->locked, fkt);
+}
+
+void xf_unlock_x11_(xfContext* xfc, const char* fkt)
+{
+ if (xfc->locked == 0)
+ WLog_WARN(TAG, "X11: trying to unlock although not locked!");
+
+ WLog_VRB(TAG, "[%" PRIu32 "] from %s", xfc->locked - 1, fkt);
+ if (!xfc->UseXThreads)
+ ReleaseMutex(xfc->mutex);
+ else
+ XUnlockDisplay(xfc->display);
+ xfc->locked--;
+}
+
+static BOOL xf_get_pixmap_info(xfContext* xfc)
+{
+ int pf_count = 0;
+ XPixmapFormatValues* pfs = NULL;
+
+ WINPR_ASSERT(xfc->display);
+ pfs = XListPixmapFormats(xfc->display, &pf_count);
+
+ if (!pfs)
+ {
+ WLog_ERR(TAG, "XListPixmapFormats failed");
+ return 1;
+ }
+
+ WINPR_ASSERT(xfc->depth != 0);
+ for (int i = 0; i < pf_count; i++)
+ {
+ const XPixmapFormatValues* pf = &pfs[i];
+
+ if (pf->depth == xfc->depth)
+ {
+ xfc->scanline_pad = pf->scanline_pad;
+ break;
+ }
+ }
+
+ XFree(pfs);
+ if ((xfc->visual == NULL) || (xfc->scanline_pad == 0))
+ return FALSE;
+
+ return TRUE;
+}
+
+static int xf_error_handler(Display* d, XErrorEvent* ev)
+{
+ char buf[256] = { 0 };
+ XGetErrorText(d, ev->error_code, buf, sizeof(buf));
+ WLog_ERR(TAG, "%s", buf);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+
+#if 0
+ const BOOL do_abort = TRUE;
+ if (do_abort)
+ abort();
+#endif
+
+ if (def_error_handler)
+ return def_error_handler(d, ev);
+
+ return 0;
+}
+
+static int xf_error_handler_ex(Display* d, XErrorEvent* ev)
+{
+ /*
+ * ungrab the keyboard, in case a debugger is running in
+ * another window. This make xf_error_handler() a potential
+ * debugger breakpoint.
+ */
+
+ XUngrabKeyboard(d, CurrentTime);
+ XUngrabPointer(d, CurrentTime);
+ return xf_error_handler(d, ev);
+}
+
+static BOOL xf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound)
+{
+ xfContext* xfc = (xfContext*)context;
+ WINPR_UNUSED(play_sound);
+ XkbBell(xfc->display, None, 100, 0);
+ return TRUE;
+}
+
+static void xf_check_extensions(xfContext* context)
+{
+ int xkb_opcode = 0;
+ int xkb_event = 0;
+ int xkb_error = 0;
+ int xkb_major = XkbMajorVersion;
+ int xkb_minor = XkbMinorVersion;
+
+ if (XkbLibraryVersion(&xkb_major, &xkb_minor) &&
+ XkbQueryExtension(context->display, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major,
+ &xkb_minor))
+ {
+ context->xkbAvailable = TRUE;
+ }
+
+#ifdef WITH_XRENDER
+ {
+ int xrender_event_base = 0;
+ int xrender_error_base = 0;
+
+ if (XRenderQueryExtension(context->display, &xrender_event_base, &xrender_error_base))
+ {
+ context->xrenderAvailable = TRUE;
+ }
+ }
+#endif
+}
+
+#ifdef WITH_XI
+/* Input device which does NOT have the correct mapping. We must disregard */
+/* this device when trying to find the input device which is the pointer. */
+static const char TEST_PTR_STR[] = "Virtual core XTEST pointer";
+static const size_t TEST_PTR_LEN = sizeof(TEST_PTR_STR) / sizeof(char);
+#endif /* WITH_XI */
+
+static void xf_get_x11_button_map(xfContext* xfc, unsigned char* x11_map)
+{
+#ifdef WITH_XI
+ int opcode = 0;
+ int event = 0;
+ int error = 0;
+ XDevice* ptr_dev = NULL;
+ XExtensionVersion* version = NULL;
+ XDeviceInfo* devices1 = NULL;
+ XIDeviceInfo* devices2 = NULL;
+ int num_devices = 0;
+
+ if (XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error))
+ {
+ WLog_DBG(TAG, "Searching for XInput pointer device");
+ ptr_dev = NULL;
+ /* loop through every device, looking for a pointer */
+ version = XGetExtensionVersion(xfc->display, INAME);
+
+ if (version->major_version >= 2)
+ {
+ /* XID of pointer device using XInput version 2 */
+ devices2 = XIQueryDevice(xfc->display, XIAllDevices, &num_devices);
+
+ if (devices2)
+ {
+ for (int i = 0; i < num_devices; ++i)
+ {
+ if ((devices2[i].use == XISlavePointer) &&
+ (strncmp(devices2[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0))
+ {
+ ptr_dev = XOpenDevice(xfc->display, devices2[i].deviceid);
+ if (ptr_dev)
+ break;
+ }
+ }
+
+ XIFreeDeviceInfo(devices2);
+ }
+ }
+ else
+ {
+ /* XID of pointer device using XInput version 1 */
+ devices1 = XListInputDevices(xfc->display, &num_devices);
+
+ if (devices1)
+ {
+ for (int i = 0; i < num_devices; ++i)
+ {
+ if ((devices1[i].use == IsXExtensionPointer) &&
+ (strncmp(devices1[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0))
+ {
+ ptr_dev = XOpenDevice(xfc->display, devices1[i].id);
+ if (ptr_dev)
+ break;
+ }
+ }
+
+ XFreeDeviceList(devices1);
+ }
+ }
+
+ XFree(version);
+
+ /* get button mapping from input extension if there is a pointer device; */
+ /* otherwise leave unchanged. */
+ if (ptr_dev)
+ {
+ WLog_DBG(TAG, "Pointer device: %d", ptr_dev->device_id);
+ XGetDeviceButtonMapping(xfc->display, ptr_dev, x11_map, NUM_BUTTONS_MAPPED);
+ XCloseDevice(xfc->display, ptr_dev);
+ }
+ else
+ {
+ WLog_DBG(TAG, "No pointer device found!");
+ }
+ }
+ else
+#endif /* WITH_XI */
+ {
+ WLog_DBG(TAG, "Get global pointer mapping (no XInput)");
+ XGetPointerMapping(xfc->display, x11_map, NUM_BUTTONS_MAPPED);
+ }
+}
+
+/* Assignment of physical (not logical) mouse buttons to wire flags. */
+/* Notice that the middle button is 2 in X11, but 3 in RDP. */
+static const button_map xf_button_flags[NUM_BUTTONS_MAPPED] = {
+ { Button1, PTR_FLAGS_BUTTON1 },
+ { Button2, PTR_FLAGS_BUTTON3 },
+ { Button3, PTR_FLAGS_BUTTON2 },
+ { Button4, PTR_FLAGS_WHEEL | 0x78 },
+ /* Negative value is 9bit twos complement */
+ { Button5, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) },
+ { 6, PTR_FLAGS_HWHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) },
+ { 7, PTR_FLAGS_HWHEEL | 0x78 },
+ { 8, PTR_XFLAGS_BUTTON1 },
+ { 9, PTR_XFLAGS_BUTTON2 },
+ { 97, PTR_XFLAGS_BUTTON1 },
+ { 112, PTR_XFLAGS_BUTTON2 }
+};
+
+static UINT16 get_flags_for_button(int button)
+{
+ for (size_t x = 0; x < ARRAYSIZE(xf_button_flags); x++)
+ {
+ const button_map* map = &xf_button_flags[x];
+
+ if (map->button == button)
+ return map->flags;
+ }
+
+ return 0;
+}
+
+static void xf_button_map_init(xfContext* xfc)
+{
+ size_t pos = 0;
+ /* loop counter for array initialization */
+
+ /* logical mouse button which is used for each physical mouse */
+ /* button (indexed from zero). This is the default map. */
+ unsigned char x11_map[112] = { 0 };
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ x11_map[0] = Button1;
+ x11_map[1] = Button2;
+ x11_map[2] = Button3;
+ x11_map[3] = Button4;
+ x11_map[4] = Button5;
+ x11_map[5] = 6;
+ x11_map[6] = 7;
+ x11_map[7] = 8;
+ x11_map[8] = 9;
+ x11_map[96] = 97;
+ x11_map[111] = 112;
+
+ /* query system for actual remapping */
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnmapButtons))
+ {
+ xf_get_x11_button_map(xfc, x11_map);
+ }
+
+ /* iterate over all (mapped) physical buttons; for each of them */
+ /* find the logical button in X11, and assign to this the */
+ /* appropriate value to send over the RDP wire. */
+ for (size_t physical = 0; physical < ARRAYSIZE(x11_map); ++physical)
+ {
+ const unsigned char logical = x11_map[physical];
+ const UINT16 flags = get_flags_for_button(logical);
+
+ if ((logical != 0) && (flags != 0))
+ {
+ if (pos >= NUM_BUTTONS_MAPPED)
+ {
+ WLog_ERR(TAG, "Failed to map mouse button to RDP button, no space");
+ }
+ else
+ {
+ button_map* map = &xfc->button_map[pos++];
+ map->button = logical;
+ map->flags = get_flags_for_button(physical + Button1);
+ }
+ }
+ }
+}
+
+/**
+ * Callback given to freerdp_connect() to process the pre-connect operations.
+ * It will fill the rdp_freerdp structure (instance) with the appropriate options to use for the
+ * connection.
+ *
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection's parameters,
+ * and will be filled with the appropriate informations.
+ *
+ * @return TRUE if successful. FALSE otherwise.
+ * Can exit with error code XF_EXIT_PARSE_ARGUMENTS if there is an error in the parameters.
+ */
+static BOOL xf_pre_connect(freerdp* instance)
+{
+ rdpChannels* channels = NULL;
+ rdpSettings* settings = NULL;
+ rdpContext* context = NULL;
+ xfContext* xfc = NULL;
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ WINPR_ASSERT(instance);
+
+ context = instance->context;
+ xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(context);
+
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ if (!xf_setup_x11(xfc))
+ return FALSE;
+ }
+
+ channels = context->channels;
+ WINPR_ASSERT(channels);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER))
+ return FALSE;
+ PubSub_SubscribeChannelConnected(context->pubSub, xf_OnChannelConnectedEventHandler);
+ PubSub_SubscribeChannelDisconnected(context->pubSub, xf_OnChannelDisconnectedEventHandler);
+
+ if (!freerdp_settings_get_string(settings, FreeRDP_Username) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
+ {
+ char login_name[MAX_PATH] = { 0 };
+ ULONG size = sizeof(login_name) - 1;
+
+ if (GetUserNameExA(NameSamCompatible, login_name, &size))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, login_name))
+ return FALSE;
+
+ WLog_INFO(TAG, "No user name set. - Using login name: %s",
+ freerdp_settings_get_string(settings, FreeRDP_Username));
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ /* Check +auth-only has a username and password. */
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ WLog_INFO(TAG, "auth-only, but no password set. Please provide one.");
+ return FALSE;
+ }
+
+ WLog_INFO(TAG, "Authentication only. Don't connect to X.");
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ if (!xf_keyboard_init(xfc))
+ return FALSE;
+ }
+
+ if (!xf_detect_monitors(xfc, &maxWidth, &maxHeight))
+ return FALSE;
+
+ if (maxWidth && maxHeight && !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight))
+ return FALSE;
+ }
+
+#ifdef WITH_XRENDER
+
+ /**
+ * If /f is specified in combination with /smart-sizing:widthxheight then
+ * we run the session in the /smart-sizing dimensions scaled to full screen
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
+ freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0))
+ {
+ if (!freerdp_settings_set_uint32(
+ settings, FreeRDP_DesktopWidth,
+ freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth)))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(
+ settings, FreeRDP_DesktopHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight)))
+ return FALSE;
+ }
+
+#endif
+ xfc->fullscreen = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen);
+ xfc->decorations = freerdp_settings_get_bool(settings, FreeRDP_Decorations);
+ xfc->grab_keyboard = freerdp_settings_get_bool(settings, FreeRDP_GrabKeyboard);
+ xfc->fullscreen_toggle = freerdp_settings_get_bool(settings, FreeRDP_ToggleFullscreen);
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ xf_button_map_init(xfc);
+ return TRUE;
+}
+
+static BOOL xf_inject_keypress(rdpContext* context, const char* buffer, size_t size)
+{
+ WCHAR wbuffer[64] = { 0 };
+ const SSIZE_T len = ConvertUtf8NToWChar(buffer, size, wbuffer, ARRAYSIZE(wbuffer));
+ if (len < 0)
+ return FALSE;
+
+ rdpInput* input = context->input;
+ WINPR_ASSERT(input);
+
+ for (SSIZE_T x = 0; x < len; x++)
+ {
+ const WCHAR code = wbuffer[x];
+ freerdp_input_send_unicode_keyboard_event(input, 0, code);
+ Sleep(5);
+ freerdp_input_send_unicode_keyboard_event(input, KBD_FLAGS_RELEASE, code);
+ Sleep(5);
+ }
+ return TRUE;
+}
+
+static BOOL xf_process_pipe(rdpContext* context, const char* pipe)
+{
+ int fd = open(pipe, O_NONBLOCK | O_RDONLY);
+ if (fd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pipe '%s' open returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return FALSE;
+ }
+ while (!freerdp_shall_disconnect_context(context))
+ {
+ char buffer[64] = { 0 };
+ ssize_t rd = read(fd, buffer, sizeof(buffer) - 1);
+ if (rd == 0)
+ {
+ char ebuffer[256] = { 0 };
+ if ((errno == EAGAIN) || (errno == 0))
+ {
+ Sleep(100);
+ continue;
+ }
+
+ // EOF, abort reading.
+ WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ break;
+ }
+ else if (rd < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ break;
+ }
+ else
+ {
+ if (!xf_inject_keypress(context, buffer, rd))
+ break;
+ }
+ }
+ close(fd);
+ return TRUE;
+}
+
+static void cleanup_pipe(int signum, const char* signame, void* context)
+{
+ const char* pipe = context;
+ if (!pipe)
+ return;
+ unlink(pipe);
+}
+
+static DWORD WINAPI xf_handle_pipe(void* arg)
+{
+ xfContext* xfc = arg;
+ WINPR_ASSERT(xfc);
+
+ rdpContext* context = &xfc->common.context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName);
+ WINPR_ASSERT(pipe);
+
+ const int rc = mkfifo(pipe, S_IWUSR | S_IRUSR);
+ if (rc != 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "Failed to create named pipe '%s': %s [%d]", pipe,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return 0;
+ }
+ freerdp_add_signal_cleanup_handler(pipe, cleanup_pipe);
+
+ xf_process_pipe(context, pipe);
+
+ freerdp_del_signal_cleanup_handler(pipe, cleanup_pipe);
+ unlink(pipe);
+ return 0;
+}
+
+/**
+ * Callback given to freerdp_connect() to perform post-connection operations.
+ * It will be called only if the connection was initialized properly, and will continue the
+ * initialization based on the newly created connection.
+ */
+static BOOL xf_post_connect(freerdp* instance)
+{
+ ResizeWindowEventArgs e = { 0 };
+
+ WINPR_ASSERT(instance);
+ xfContext* xfc = (xfContext*)instance->context;
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ rdpUpdate* update = context->update;
+ WINPR_ASSERT(update);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ xfc->remote_app = TRUE;
+
+ if (!xf_create_window(xfc))
+ return FALSE;
+
+ if (!xf_get_pixmap_info(xfc))
+ return FALSE;
+
+ if (!gdi_init(instance, xf_get_local_color_format(xfc, TRUE)))
+ return FALSE;
+
+ if (!xf_create_image(xfc))
+ return FALSE;
+
+ if (!xf_register_pointer(context->graphics))
+ return FALSE;
+
+#ifdef WITH_XRENDER
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+#endif
+
+ if (!xfc->xrenderAvailable)
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_ERR(TAG, "XRender not available: disabling smart-sizing");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, FALSE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ WLog_ERR(TAG, "XRender not available: disabling local multi-touch gestures");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, FALSE))
+ return FALSE;
+ }
+ }
+
+ update->DesktopResize = xf_sw_desktop_resize;
+ update->EndPaint = xf_end_paint;
+ update->PlaySound = xf_play_sound;
+ update->SetKeyboardIndicators = xf_keyboard_set_indicators;
+ update->SetKeyboardImeStatus = xf_keyboard_set_ime_status;
+
+ const BOOL serverIsWindowsPlatform =
+ (freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType) == OSMAJORTYPE_WINDOWS);
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) &&
+ !(xfc->clipboard = xf_clipboard_new(xfc, !serverIsWindowsPlatform)))
+ return FALSE;
+
+ if (!(xfc->xfDisp = xf_disp_new(xfc)))
+ return FALSE;
+
+ const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName);
+ if (pipe)
+ {
+ xfc->pipethread = CreateThread(NULL, 0, xf_handle_pipe, xfc, 0, NULL);
+ if (!xfc->pipethread)
+ return FALSE;
+ }
+
+ EventArgsInit(&e, "xfreerdp");
+ e.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ e.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_OnResizeWindow(context->pubSub, xfc, &e);
+ return TRUE;
+}
+
+static void xf_post_disconnect(freerdp* instance)
+{
+ xfContext* xfc = NULL;
+ rdpContext* context = NULL;
+
+ if (!instance || !instance->context)
+ return;
+
+ context = instance->context;
+ xfc = (xfContext*)context;
+ PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
+ xf_OnChannelConnectedEventHandler);
+ PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
+ xf_OnChannelDisconnectedEventHandler);
+ gdi_free(instance);
+
+ if (xfc->pipethread)
+ {
+ WaitForSingleObject(xfc->pipethread, INFINITE);
+ CloseHandle(xfc->pipethread);
+ xfc->pipethread = NULL;
+ }
+ if (xfc->clipboard)
+ {
+ xf_clipboard_free(xfc->clipboard);
+ xfc->clipboard = NULL;
+ }
+
+ if (xfc->xfDisp)
+ {
+ xf_disp_free(xfc->xfDisp);
+ xfc->xfDisp = NULL;
+ }
+
+ if ((xfc->window != NULL) && (xfc->drawable == xfc->window->handle))
+ xfc->drawable = 0;
+ else
+ xf_DestroyDummyWindow(xfc, xfc->drawable);
+
+ xf_window_free(xfc);
+}
+
+static void xf_post_final_disconnect(freerdp* instance)
+{
+ xfContext* xfc = NULL;
+ rdpContext* context = NULL;
+
+ if (!instance || !instance->context)
+ return;
+
+ context = instance->context;
+ xfc = (xfContext*)context;
+
+ xf_keyboard_free(xfc);
+ xf_teardown_x11(xfc);
+}
+
+static int xf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ xfContext* xfc = (xfContext*)instance->context;
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ if (type != LOGON_MSG_SESSION_CONTINUE)
+ {
+ xf_rail_disable_remoteapp_mode(xfc);
+ }
+ return 1;
+}
+
+static BOOL handle_window_events(freerdp* instance)
+{
+ if (!xf_process_x_events(instance))
+ {
+ WLog_DBG(TAG, "Closed from X11");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/** Main loop for the rdp connection.
+ * It will be run from the thread's entry point (thread_func()).
+ * It initiates the connection, and will continue to run until the session ends,
+ * processing events as they are received.
+ *
+ * @param param - pointer to the rdp_freerdp structure that contains the session's settings
+ * @return A code from the enum XF_EXIT_CODE (0 if successful)
+ */
+static DWORD WINAPI xf_client_thread(LPVOID param)
+{
+ DWORD exit_code = 0;
+ DWORD waitStatus = 0;
+ HANDLE inputEvent = NULL;
+ HANDLE timer = NULL;
+ LARGE_INTEGER due = { 0 };
+ TimerEventArgs timerEvent = { 0 };
+
+ EventArgsInit(&timerEvent, "xfreerdp");
+ freerdp* instance = (freerdp*)param;
+ WINPR_ASSERT(instance);
+
+ const BOOL status = freerdp_connect(instance);
+ rdpContext* context = instance->context;
+ WINPR_ASSERT(context);
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = context->settings;
+ WINPR_ASSERT(settings);
+
+ if (!status)
+ {
+ UINT32 error = freerdp_get_last_error(instance->context);
+ exit_code = xf_map_error_to_exit_code(error);
+ }
+ else
+ exit_code = XF_EXIT_SUCCESS;
+
+ if (!status)
+ goto end;
+
+ /* --authonly ? */
+ if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly))
+ {
+ WLog_ERR(TAG, "Authentication only, exit status %" PRId32 "", !status);
+ goto disconnect;
+ }
+
+ if (!status)
+ {
+ WLog_ERR(TAG, "Freerdp connect error exit status %" PRId32 "", !status);
+ exit_code = freerdp_error_info(instance);
+
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_AUTHENTICATION_FAILED)
+ exit_code = XF_EXIT_AUTH_FAILURE;
+ else if (exit_code == ERRINFO_SUCCESS)
+ exit_code = XF_EXIT_CONN_FAILED;
+
+ goto disconnect;
+ }
+
+ timer = CreateWaitableTimerA(NULL, FALSE, "mainloop-periodic-timer");
+
+ if (!timer)
+ {
+ WLog_ERR(TAG, "failed to create timer");
+ goto disconnect;
+ }
+
+ due.QuadPart = 0;
+
+ if (!SetWaitableTimer(timer, &due, 20, NULL, NULL, FALSE))
+ {
+ goto disconnect;
+ }
+ inputEvent = xfc->x11event;
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD nCount = 0;
+ handles[nCount++] = timer;
+ handles[nCount++] = inputEvent;
+
+ /*
+ * win8 and server 2k12 seem to have some timing issue/race condition
+ * when a initial sync request is send to sync the keyboard indicators
+ * sending the sync event twice fixed this problem
+ */
+ if (freerdp_focus_required(instance))
+ {
+ xf_keyboard_focus_in(xfc);
+ xf_keyboard_focus_in(xfc);
+ }
+
+ {
+ DWORD tmp =
+ freerdp_get_event_handles(context, &handles[nCount], ARRAYSIZE(handles) - nCount);
+
+ if (tmp == 0)
+ {
+ WLog_ERR(TAG, "freerdp_get_event_handles failed");
+ break;
+ }
+
+ nCount += tmp;
+ }
+
+ if (xfc->window)
+ xf_floatbar_hide_and_show(xfc->window->floatbar);
+
+ waitStatus = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
+
+ if (waitStatus == WAIT_FAILED)
+ break;
+
+ {
+ if (!freerdp_check_event_handles(context))
+ {
+ if (client_auto_reconnect_ex(instance, handle_window_events))
+ continue;
+ else
+ {
+ /*
+ * Indicate an unsuccessful connection attempt if reconnect
+ * did not succeed and no other error was specified.
+ */
+ const UINT32 error = freerdp_get_last_error(instance->context);
+
+ if (freerdp_error_info(instance) == 0)
+ exit_code = xf_map_error_to_exit_code(error);
+ }
+
+ if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
+
+ break;
+ }
+ }
+
+ if (!handle_window_events(instance))
+ break;
+
+ if ((waitStatus != WAIT_TIMEOUT) && (waitStatus == WAIT_OBJECT_0))
+ {
+ timerEvent.now = GetTickCount64();
+ PubSub_OnTimer(context->pubSub, context, &timerEvent);
+ }
+ }
+
+ if (!exit_code)
+ {
+ exit_code = freerdp_error_info(instance);
+
+ if (exit_code == XF_EXIT_DISCONNECT &&
+ freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested)
+ {
+ /* This situation might be limited to Windows XP. */
+ WLog_INFO(TAG, "Error info says user did not initiate but disconnect ultimatum says "
+ "they did; treat this as a user logoff");
+ exit_code = XF_EXIT_LOGOFF;
+ }
+ }
+
+disconnect:
+
+ if (timer)
+ CloseHandle(timer);
+
+ freerdp_disconnect(instance);
+end:
+ ExitThread(exit_code);
+ return exit_code;
+}
+
+DWORD xf_exit_code_from_disconnect_reason(DWORD reason)
+{
+ if (reason == 0 ||
+ (reason >= XF_EXIT_PARSE_ARGUMENTS && reason <= XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS))
+ return reason;
+ /* License error set */
+ else if (reason >= 0x100 && reason <= 0x10A)
+ reason -= 0x100 + XF_EXIT_LICENSE_INTERNAL;
+ /* RDP protocol error set */
+ else if (reason >= 0x10c9 && reason <= 0x1193)
+ reason = XF_EXIT_RDP;
+ /* There's no need to test protocol-independent codes: they match */
+ else if (!(reason <= 0xC))
+ reason = XF_EXIT_UNKNOWN;
+
+ return reason;
+}
+
+static void xf_TerminateEventHandler(void* context, const TerminateEventArgs* e)
+{
+ rdpContext* ctx = (rdpContext*)context;
+ WINPR_UNUSED(e);
+ freerdp_abort_connect_context(ctx);
+}
+
+#ifdef WITH_XRENDER
+static void xf_ZoomingChangeEventHandler(void* context, const ZoomingChangeEventArgs* e)
+{
+ int w = 0;
+ int h = 0;
+ rdpSettings* settings = NULL;
+ xfContext* xfc = (xfContext*)context;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ w = xfc->scaledWidth + e->dx;
+ h = xfc->scaledHeight + e->dy;
+
+ if (e->dx == 0 && e->dy == 0)
+ return;
+
+ if (w < 10)
+ w = 10;
+
+ if (h < 10)
+ h = 10;
+
+ if (w == xfc->scaledWidth && h == xfc->scaledHeight)
+ return;
+
+ xfc->scaledWidth = w;
+ xfc->scaledHeight = h;
+ xf_draw_screen(xfc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+
+static void xf_PanningChangeEventHandler(void* context, const PanningChangeEventArgs* e)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(e);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (e->dx == 0 && e->dy == 0)
+ return;
+
+ xfc->offset_x += e->dx;
+ xfc->offset_y += e->dy;
+ xf_draw_screen(xfc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+}
+#endif
+
+/**
+ * Client Interface
+ */
+
+static BOOL xfreerdp_client_global_init(void)
+{
+ setlocale(LC_ALL, "");
+
+ if (freerdp_handle_signals() != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void xfreerdp_client_global_uninit(void)
+{
+}
+
+static int xfreerdp_client_start(rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)context;
+ rdpSettings* settings = context->settings;
+
+ if (!freerdp_settings_get_string(settings, FreeRDP_ServerHostname))
+ {
+ WLog_ERR(TAG, "error: server hostname was not specified with /v:<server>[:port]");
+ return -1;
+ }
+
+ if (!(xfc->common.thread = CreateThread(NULL, 0, xf_client_thread, context->instance, 0, NULL)))
+ {
+ WLog_ERR(TAG, "failed to create client thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+static Atom get_supported_atom(xfContext* xfc, const char* atomName)
+{
+ const Atom atom = XInternAtom(xfc->display, atomName, False);
+
+ for (unsigned long i = 0; i < xfc->supportedAtomCount; i++)
+ {
+ if (xfc->supportedAtoms[i] == atom)
+ return atom;
+ }
+
+ return None;
+}
+
+void xf_teardown_x11(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (xfc->display)
+ {
+ XCloseDisplay(xfc->display);
+ xfc->display = NULL;
+ }
+
+ if (xfc->x11event)
+ {
+ CloseHandle(xfc->x11event);
+ xfc->x11event = NULL;
+ }
+
+ if (xfc->mutex)
+ {
+ CloseHandle(xfc->mutex);
+ xfc->mutex = NULL;
+ }
+
+ if (xfc->vscreen.monitors)
+ {
+ free(xfc->vscreen.monitors);
+ xfc->vscreen.monitors = NULL;
+ }
+ xfc->vscreen.nmonitors = 0;
+
+ free(xfc->supportedAtoms);
+ xfc->supportedAtoms = NULL;
+ xfc->supportedAtomCount = 0;
+}
+
+BOOL xf_setup_x11(xfContext* xfc)
+{
+
+ WINPR_ASSERT(xfc);
+ xfc->UseXThreads = TRUE;
+
+#if !defined(NDEBUG)
+ /* uncomment below if debugging to prevent keyboard grap */
+ xfc->debug = TRUE;
+#endif
+
+ if (xfc->UseXThreads)
+ {
+ if (!XInitThreads())
+ {
+ WLog_WARN(TAG, "XInitThreads() failure");
+ xfc->UseXThreads = FALSE;
+ }
+ }
+
+ xfc->display = XOpenDisplay(NULL);
+
+ if (!xfc->display)
+ {
+ WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL));
+ WLog_ERR(TAG, "Please check that the $DISPLAY environment variable is properly set.");
+ goto fail;
+ }
+ if (xfc->debug)
+ {
+ WLog_INFO(TAG, "Enabling X11 debug mode.");
+ XSynchronize(xfc->display, TRUE);
+ }
+ def_error_handler = XSetErrorHandler(xf_error_handler_ex);
+
+ xfc->mutex = CreateMutex(NULL, FALSE, NULL);
+
+ if (!xfc->mutex)
+ {
+ WLog_ERR(TAG, "Could not create mutex!");
+ goto fail;
+ }
+
+ xfc->xfds = ConnectionNumber(xfc->display);
+ xfc->screen_number = DefaultScreen(xfc->display);
+ xfc->screen = ScreenOfDisplay(xfc->display, xfc->screen_number);
+ xfc->big_endian = (ImageByteOrder(xfc->display) == MSBFirst);
+ xfc->invert = TRUE;
+ xfc->complex_regions = TRUE;
+ xfc->_NET_SUPPORTED = XInternAtom(xfc->display, "_NET_SUPPORTED", True);
+ xfc->_NET_SUPPORTING_WM_CHECK = XInternAtom(xfc->display, "_NET_SUPPORTING_WM_CHECK", True);
+
+ if ((xfc->_NET_SUPPORTED != None) && (xfc->_NET_SUPPORTING_WM_CHECK != None))
+ {
+ Atom actual_type = 0;
+ int actual_format = 0;
+ unsigned long nitems = 0;
+ unsigned long after = 0;
+ unsigned char* data = NULL;
+ int status = LogTagAndXGetWindowProperty(
+ TAG, xfc->display, RootWindowOfScreen(xfc->screen), xfc->_NET_SUPPORTED, 0, 1024, False,
+ XA_ATOM, &actual_type, &actual_format, &nitems, &after, &data);
+
+ if ((status == Success) && (actual_type == XA_ATOM) && (actual_format == 32))
+ {
+ xfc->supportedAtomCount = nitems;
+ xfc->supportedAtoms = calloc(xfc->supportedAtomCount, sizeof(Atom));
+ WINPR_ASSERT(xfc->supportedAtoms);
+ memcpy(xfc->supportedAtoms, data, nitems * sizeof(Atom));
+ }
+
+ if (data)
+ XFree(data);
+ }
+
+ xfc->_XWAYLAND_MAY_GRAB_KEYBOARD =
+ XInternAtom(xfc->display, "_XWAYLAND_MAY_GRAB_KEYBOARD", False);
+ xfc->_NET_WM_ICON = XInternAtom(xfc->display, "_NET_WM_ICON", False);
+ xfc->_MOTIF_WM_HINTS = XInternAtom(xfc->display, "_MOTIF_WM_HINTS", False);
+ xfc->_NET_CURRENT_DESKTOP = XInternAtom(xfc->display, "_NET_CURRENT_DESKTOP", False);
+ xfc->_NET_WORKAREA = XInternAtom(xfc->display, "_NET_WORKAREA", False);
+ xfc->_NET_WM_STATE = get_supported_atom(xfc, "_NET_WM_STATE");
+ xfc->_NET_WM_STATE_FULLSCREEN = get_supported_atom(xfc, "_NET_WM_STATE_FULLSCREEN");
+ xfc->_NET_WM_STATE_MAXIMIZED_HORZ =
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT =
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
+ xfc->_NET_WM_FULLSCREEN_MONITORS = get_supported_atom(xfc, "_NET_WM_FULLSCREEN_MONITORS");
+ xfc->_NET_WM_NAME = XInternAtom(xfc->display, "_NET_WM_NAME", False);
+ xfc->_NET_WM_PID = XInternAtom(xfc->display, "_NET_WM_PID", False);
+ xfc->_NET_WM_WINDOW_TYPE = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE", False);
+ xfc->_NET_WM_WINDOW_TYPE_NORMAL =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ xfc->_NET_WM_WINDOW_TYPE_DIALOG =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ xfc->_NET_WM_WINDOW_TYPE_POPUP = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP", False);
+ xfc->_NET_WM_WINDOW_TYPE_POPUP_MENU =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False);
+ xfc->_NET_WM_WINDOW_TYPE_UTILITY =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU =
+ XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False);
+ xfc->_NET_WM_STATE_SKIP_TASKBAR =
+ XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_TASKBAR", False);
+ xfc->_NET_WM_STATE_SKIP_PAGER = XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_PAGER", False);
+ xfc->_NET_WM_MOVERESIZE = XInternAtom(xfc->display, "_NET_WM_MOVERESIZE", False);
+ xfc->_NET_MOVERESIZE_WINDOW = XInternAtom(xfc->display, "_NET_MOVERESIZE_WINDOW", False);
+ xfc->UTF8_STRING = XInternAtom(xfc->display, "UTF8_STRING", FALSE);
+ xfc->WM_PROTOCOLS = XInternAtom(xfc->display, "WM_PROTOCOLS", False);
+ xfc->WM_DELETE_WINDOW = XInternAtom(xfc->display, "WM_DELETE_WINDOW", False);
+ xfc->WM_STATE = XInternAtom(xfc->display, "WM_STATE", False);
+ xfc->x11event = CreateFileDescriptorEvent(NULL, FALSE, FALSE, xfc->xfds, WINPR_FD_READ);
+
+ if (!xfc->x11event)
+ {
+ WLog_ERR(TAG, "Could not create xfds event");
+ goto fail;
+ }
+
+ xf_check_extensions(xfc);
+
+ xfc->vscreen.monitors = calloc(16, sizeof(MONITOR_INFO));
+
+ if (!xfc->vscreen.monitors)
+ goto fail;
+ return TRUE;
+
+fail:
+ xf_teardown_x11(xfc);
+ return FALSE;
+}
+
+static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context)
+{
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(!xfc->display);
+ WINPR_ASSERT(!xfc->mutex);
+ WINPR_ASSERT(!xfc->x11event);
+ instance->PreConnect = xf_pre_connect;
+ instance->PostConnect = xf_post_connect;
+ instance->PostDisconnect = xf_post_disconnect;
+ instance->PostFinalDisconnect = xf_post_final_disconnect;
+ instance->LogonErrorInfo = xf_logon_error_info;
+ instance->GetAccessToken = client_cli_get_access_token;
+ PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
+#ifdef WITH_XRENDER
+ PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);
+ PubSub_SubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler);
+#endif
+
+ return TRUE;
+}
+
+static void xfreerdp_client_free(freerdp* instance, rdpContext* context)
+{
+ if (!context)
+ return;
+
+ PubSub_UnsubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
+#ifdef WITH_XRENDER
+ PubSub_UnsubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);
+ PubSub_UnsubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler);
+#endif
+}
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ pEntryPoints->Version = 1;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->GlobalInit = xfreerdp_client_global_init;
+ pEntryPoints->GlobalUninit = xfreerdp_client_global_uninit;
+ pEntryPoints->ContextSize = sizeof(xfContext);
+ pEntryPoints->ClientNew = xfreerdp_client_new;
+ pEntryPoints->ClientFree = xfreerdp_client_free;
+ pEntryPoints->ClientStart = xfreerdp_client_start;
+ pEntryPoints->ClientStop = freerdp_client_common_stop;
+ return 0;
+}
diff --git a/client/X11/xf_client.h b/client/X11/xf_client.h
new file mode 100644
index 0000000..c9bc21b
--- /dev/null
+++ b/client/X11/xf_client.h
@@ -0,0 +1,53 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client Interface
+ *
+ * Copyright 2013 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_CLIENT_X11_CLIENT_H
+#define FREERDP_CLIENT_X11_CLIENT_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/collections.h>
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client.h>
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/dc.h>
+#include <freerdp/gdi/region.h>
+
+#include <freerdp/channels/channels.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /**
+ * Client Interface
+ */
+
+ FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_CLIENT_X11_CLIENT_H */
diff --git a/client/X11/xf_cliprdr.c b/client/X11/xf_cliprdr.c
new file mode 100644
index 0000000..a68dae9
--- /dev/null
+++ b/client/X11/xf_cliprdr.c
@@ -0,0 +1,2517 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ * 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 <stdlib.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#ifdef WITH_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+#include <winpr/path.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#include "xf_cliprdr.h"
+#include "xf_event.h"
+#include "xf_utils.h"
+
+#define TAG CLIENT_TAG("x11.cliprdr")
+
+#define MAX_CLIPBOARD_FORMATS 255
+#define WIN32_FILETIME_TO_UNIX_EPOCH_USEC UINT64_C(116444736000000000)
+
+#ifdef WITH_DEBUG_CLIPRDR
+#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_CLIPRDR(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+typedef struct
+{
+ Atom atom;
+ UINT32 formatToRequest;
+ UINT32 localFormat;
+ char* formatName;
+} xfCliprdrFormat;
+
+typedef struct
+{
+ BYTE* data;
+ UINT32 data_length;
+} xfCachedData;
+
+struct xf_clipboard
+{
+ xfContext* xfc;
+ rdpChannels* channels;
+ CliprdrClientContext* context;
+
+ wClipboard* system;
+
+ Window root_window;
+ Atom clipboard_atom;
+ Atom property_atom;
+
+ Atom timestamp_property_atom;
+ Time selection_ownership_timestamp;
+
+ Atom raw_transfer_atom;
+ Atom raw_format_list_atom;
+
+ UINT32 numClientFormats;
+ xfCliprdrFormat clientFormats[20];
+
+ UINT32 numServerFormats;
+ CLIPRDR_FORMAT* serverFormats;
+
+ size_t numTargets;
+ Atom targets[20];
+
+ int requestedFormatId;
+
+ wHashTable* cachedData;
+ wHashTable* cachedRawData;
+
+ BOOL data_raw_format;
+
+ const xfCliprdrFormat* requestedFormat;
+
+ XSelectionEvent* respond;
+
+ Window owner;
+ BOOL sync;
+
+ /* INCR mechanism */
+ Atom incr_atom;
+ BOOL incr_starts;
+ BYTE* incr_data;
+ int incr_data_length;
+
+ /* XFixes extension */
+ int xfixes_event_base;
+ int xfixes_error_base;
+ BOOL xfixes_supported;
+
+ /* last sent data */
+ CLIPRDR_FORMAT* lastSentFormats;
+ UINT32 lastSentNumFormats;
+ CliprdrFileContext* file;
+};
+
+static const char mime_text_plain[] = "text/plain";
+static const char mime_uri_list[] = "text/uri-list";
+static const char mime_html[] = "text/html";
+static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp",
+ "image/x-win-bitmap" };
+static const char mime_webp[] = "image/webp";
+static const char mime_png[] = "image/png";
+static const char mime_jpeg[] = "image/jpeg";
+static const char mime_tiff[] = "image/tiff";
+static const char* mime_images[] = { mime_webp, mime_png, mime_jpeg, mime_tiff };
+
+static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
+static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
+
+static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
+static const char type_HtmlFormat[] = "HTML Format";
+
+static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard);
+static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force);
+static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp);
+
+static void xf_cached_data_free(void* ptr)
+{
+ xfCachedData* cached_data = ptr;
+ if (!cached_data)
+ return;
+
+ free(cached_data->data);
+ free(cached_data);
+}
+
+static xfCachedData* xf_cached_data_new(BYTE* data, UINT32 data_length)
+{
+ xfCachedData* cached_data = NULL;
+
+ cached_data = calloc(1, sizeof(xfCachedData));
+ if (!cached_data)
+ return NULL;
+
+ cached_data->data = data;
+ cached_data->data_length = data_length;
+
+ return cached_data;
+}
+
+static xfCachedData* xf_cached_data_new_copy(BYTE* data, UINT32 data_length)
+{
+ BYTE* copy = NULL;
+ if (data_length > 0)
+ {
+ copy = malloc(data_length);
+ if (!copy)
+ return NULL;
+ memcpy(copy, data, data_length);
+ }
+
+ xfCachedData* cache = xf_cached_data_new(copy, data_length);
+ if (!cache)
+ free(copy);
+ return cache;
+}
+
+static void xf_clipboard_free_server_formats(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+ if (clipboard->serverFormats)
+ {
+ for (size_t i = 0; i < clipboard->numServerFormats; i++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
+ free(format->formatName);
+ }
+
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ }
+}
+
+static void xf_cliprdr_check_owner(xfClipboard* clipboard)
+{
+ Window owner = 0;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (clipboard->sync)
+ {
+ owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
+
+ if (clipboard->owner != owner)
+ {
+ clipboard->owner = owner;
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ }
+}
+
+static BOOL xf_cliprdr_is_self_owned(xfClipboard* clipboard)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ return XGetSelectionOwner(xfc->display, clipboard->clipboard_atom) == xfc->drawable;
+}
+
+static void xf_cliprdr_set_raw_transfer_enabled(xfClipboard* clipboard, BOOL enabled)
+{
+ UINT32 data = enabled;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_transfer_atom,
+ XA_INTEGER, 32, PropModeReplace, (BYTE*)&data, 1);
+}
+
+static BOOL xf_cliprdr_is_raw_transfer_available(xfClipboard* clipboard)
+{
+ Atom type = 0;
+ int format = 0;
+ int result = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ UINT32* data = NULL;
+ UINT32 is_enabled = 0;
+ Window owner = None;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
+
+ if (owner != None)
+ {
+ result = LogTagAndXGetWindowProperty(TAG, xfc->display, owner, clipboard->raw_transfer_atom,
+ 0, 4, 0, XA_INTEGER, &type, &format, &length,
+ &bytes_left, (BYTE**)&data);
+ }
+
+ if (data)
+ {
+ is_enabled = *data;
+ XFree(data);
+ }
+
+ if ((owner == None) || (owner == xfc->drawable))
+ return FALSE;
+
+ if (result != Success)
+ return FALSE;
+
+ return is_enabled ? TRUE : FALSE;
+}
+
+static BOOL xf_cliprdr_formats_equal(const CLIPRDR_FORMAT* server, const xfCliprdrFormat* client)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(client);
+
+ if (server->formatName && client->formatName)
+ {
+ /* The server may be using short format names while we store them in full form. */
+ return (0 == strncmp(server->formatName, client->formatName, strlen(server->formatName)));
+ }
+
+ if (!server->formatName && !client->formatName)
+ {
+ return (server->formatId == client->formatToRequest);
+ }
+
+ return FALSE;
+}
+
+static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_id(xfClipboard* clipboard,
+ UINT32 formatId)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (size_t index = 0; index < clipboard->numClientFormats; index++)
+ {
+ const xfCliprdrFormat* format = &(clipboard->clientFormats[index]);
+
+ if (format->formatToRequest == formatId)
+ return format;
+ }
+
+ return NULL;
+}
+
+static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_atom(xfClipboard* clipboard,
+ Atom atom)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
+ {
+ const xfCliprdrFormat* format = &(clipboard->clientFormats[i]);
+
+ if (format->atom == atom)
+ return format;
+ }
+
+ return NULL;
+}
+
+static const CLIPRDR_FORMAT* xf_cliprdr_get_server_format_by_atom(xfClipboard* clipboard, Atom atom)
+{
+ WINPR_ASSERT(clipboard);
+
+ for (size_t i = 0; i < clipboard->numClientFormats; i++)
+ {
+ const xfCliprdrFormat* client_format = &(clipboard->clientFormats[i]);
+
+ if (client_format->atom == atom)
+ {
+ for (size_t j = 0; j < clipboard->numServerFormats; j++)
+ {
+ const CLIPRDR_FORMAT* server_format = &(clipboard->serverFormats[j]);
+
+ if (xf_cliprdr_formats_equal(server_format, client_format))
+ return server_format;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_data_request(xfClipboard* clipboard, UINT32 formatId,
+ const xfCliprdrFormat* cformat)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST request = { 0 };
+ request.requestedFormatId = formatId;
+
+ DEBUG_CLIPRDR("requesting format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]", formatId,
+ ClipboardGetFormatIdString(formatId), cformat->localFormat, cformat->formatName);
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
+ return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_data_response(xfClipboard* clipboard, const xfCliprdrFormat* format,
+ const BYTE* data, size_t size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
+
+ WINPR_ASSERT(clipboard);
+
+ /* No request currently pending, do not send a response. */
+ if (clipboard->requestedFormatId < 0)
+ return CHANNEL_RC_OK;
+
+ if (size == 0)
+ {
+ if (format)
+ DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response {format 0x%08" PRIx32
+ " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest,
+ ClipboardGetFormatIdString(format->formatToRequest), format->localFormat,
+ format->formatName);
+ else
+ DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response");
+ }
+ else
+ {
+ WINPR_ASSERT(format);
+ DEBUG_CLIPRDR("send response format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ }
+ /* Request handled, reset to invalid */
+ clipboard->requestedFormatId = -1;
+
+ response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.common.dataLen = size;
+ response.requestedFormatData = data;
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
+ return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
+}
+
+static wStream* xf_cliprdr_serialize_server_format_list(xfClipboard* clipboard)
+{
+ UINT32 formatCount = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ /* Typical MS Word format list is about 80 bytes long. */
+ if (!(s = Stream_New(NULL, 128)))
+ {
+ WLog_ERR(TAG, "failed to allocate serialized format list");
+ goto error;
+ }
+
+ /* If present, the last format is always synthetic CF_RAW. Do not include it. */
+ formatCount = (clipboard->numServerFormats > 0) ? clipboard->numServerFormats - 1 : 0;
+ Stream_Write_UINT32(s, formatCount);
+
+ for (UINT32 i = 0; i < formatCount; i++)
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
+ size_t name_length = format->formatName ? strlen(format->formatName) : 0;
+
+ DEBUG_CLIPRDR("server announced 0x%08" PRIx32 " [%s][%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + name_length + 1))
+ {
+ WLog_ERR(TAG, "failed to expand serialized format list");
+ goto error;
+ }
+
+ Stream_Write_UINT32(s, format->formatId);
+
+ if (format->formatName)
+ Stream_Write(s, format->formatName, name_length);
+
+ Stream_Write_UINT8(s, '\0');
+ }
+
+ Stream_SealLength(s);
+ return s;
+error:
+ Stream_Free(s, TRUE);
+ return NULL;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_parse_server_format_list(BYTE* data, size_t length,
+ UINT32* numFormats)
+{
+ wStream* s = NULL;
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(data || (length == 0));
+ WINPR_ASSERT(numFormats);
+
+ if (!(s = Stream_New(data, length)))
+ {
+ WLog_ERR(TAG, "failed to allocate stream for parsing serialized format list");
+ goto error;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
+ goto error;
+
+ Stream_Read_UINT32(s, *numFormats);
+
+ if (*numFormats > MAX_CLIPBOARD_FORMATS)
+ {
+ WLog_ERR(TAG, "unexpectedly large number of formats: %" PRIu32 "", *numFormats);
+ goto error;
+ }
+
+ if (!(formats = (CLIPRDR_FORMAT*)calloc(*numFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate format list");
+ goto error;
+ }
+
+ for (UINT32 i = 0; i < *numFormats; i++)
+ {
+ const char* formatName = NULL;
+ size_t formatNameLength = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
+ goto error;
+
+ Stream_Read_UINT32(s, formats[i].formatId);
+ formatName = (const char*)Stream_Pointer(s);
+ formatNameLength = strnlen(formatName, Stream_GetRemainingLength(s));
+
+ if (formatNameLength == Stream_GetRemainingLength(s))
+ {
+ WLog_ERR(TAG, "missing terminating null byte, %" PRIuz " bytes left to read",
+ formatNameLength);
+ goto error;
+ }
+
+ formats[i].formatName = strndup(formatName, formatNameLength);
+ Stream_Seek(s, formatNameLength + 1);
+ }
+
+ Stream_Free(s, FALSE);
+ return formats;
+error:
+ Stream_Free(s, FALSE);
+ free(formats);
+ *numFormats = 0;
+ return NULL;
+}
+
+static void xf_cliprdr_free_formats(CLIPRDR_FORMAT* formats, UINT32 numFormats)
+{
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ for (UINT32 i = 0; i < numFormats; i++)
+ {
+ free(formats[i].formatName);
+ }
+
+ free(formats);
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_raw_server_formats(xfClipboard* clipboard, UINT32* numFormats)
+{
+ Atom type = None;
+ int format = 0;
+ unsigned long length = 0;
+ unsigned long remaining = 0;
+ BYTE* data = NULL;
+ CLIPRDR_FORMAT* formats = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ *numFormats = 0;
+ LogTagAndXGetWindowProperty(
+ TAG, xfc->display, clipboard->owner, clipboard->raw_format_list_atom, 0, 4096, False,
+ clipboard->raw_format_list_atom, &type, &format, &length, &remaining, &data);
+
+ if (data && length > 0 && format == 8 && type == clipboard->raw_format_list_atom)
+ {
+ formats = xf_cliprdr_parse_server_format_list(data, length, numFormats);
+ }
+ else
+ {
+ WLog_ERR(TAG,
+ "failed to retrieve raw format list: data=%p, length=%lu, format=%d, type=%lu "
+ "(expected=%lu)",
+ (void*)data, length, format, (unsigned long)type,
+ (unsigned long)clipboard->raw_format_list_atom);
+ }
+
+ if (data)
+ XFree(data);
+
+ return formats;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_formats_from_targets(xfClipboard* clipboard,
+ UINT32* numFormats)
+{
+ Atom atom = None;
+ BYTE* data = NULL;
+ int format_property = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ xfContext* xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ *numFormats = 0;
+ LogTagAndXGetWindowProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 200,
+ 0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data);
+
+ if (length > 0)
+ {
+ if (!data)
+ {
+ WLog_ERR(TAG, "XGetWindowProperty set length = %lu but data is NULL", length);
+ goto out;
+ }
+
+ if (!(formats = (CLIPRDR_FORMAT*)calloc(length, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate %lu CLIPRDR_FORMAT structs", length);
+ goto out;
+ }
+ }
+
+ for (unsigned long i = 0; i < length; i++)
+ {
+ Atom tatom = ((Atom*)data)[i];
+ const xfCliprdrFormat* format = xf_cliprdr_get_client_format_by_atom(clipboard, tatom);
+
+ if (format)
+ {
+ formats[*numFormats].formatId = format->formatToRequest;
+ formats[*numFormats].formatName = _strdup(format->formatName);
+ *numFormats += 1;
+ }
+ }
+
+out:
+
+ if (data)
+ XFree(data);
+
+ return formats;
+}
+
+static CLIPRDR_FORMAT* xf_cliprdr_get_client_formats(xfClipboard* clipboard, UINT32* numFormats)
+{
+ CLIPRDR_FORMAT* formats = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(numFormats);
+
+ *numFormats = 0;
+
+ if (xf_cliprdr_is_raw_transfer_available(clipboard))
+ {
+ formats = xf_cliprdr_get_raw_server_formats(clipboard, numFormats);
+ }
+
+ if (*numFormats == 0)
+ {
+ xf_cliprdr_free_formats(formats, *numFormats);
+ formats = xf_cliprdr_get_formats_from_targets(clipboard, numFormats);
+ }
+
+ return formats;
+}
+
+static void xf_cliprdr_provide_server_format_list(xfClipboard* clipboard)
+{
+ wStream* formats = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ formats = xf_cliprdr_serialize_server_format_list(clipboard);
+
+ if (formats)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom,
+ clipboard->raw_format_list_atom, 8, PropModeReplace,
+ Stream_Buffer(formats), Stream_Length(formats));
+ }
+ else
+ {
+ LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom);
+ }
+
+ Stream_Free(formats, TRUE);
+}
+
+static BOOL xf_clipboard_format_equal(const CLIPRDR_FORMAT* a, const CLIPRDR_FORMAT* b)
+{
+ WINPR_ASSERT(a);
+ WINPR_ASSERT(b);
+
+ if (a->formatId != b->formatId)
+ return FALSE;
+ if (!a->formatName && !b->formatName)
+ return TRUE;
+ if (!a->formatName || !b->formatName)
+ return FALSE;
+ return strcmp(a->formatName, b->formatName) == 0;
+}
+
+static BOOL xf_clipboard_changed(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ if (clipboard->lastSentNumFormats != numFormats)
+ return TRUE;
+
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* cur = &clipboard->lastSentFormats[x];
+ BOOL contained = FALSE;
+ for (UINT32 y = 0; y < numFormats; y++)
+ {
+ if (xf_clipboard_format_equal(cur, &formats[y]))
+ {
+ contained = TRUE;
+ break;
+ }
+ }
+ if (!contained)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void xf_clipboard_formats_free(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ xf_cliprdr_free_formats(clipboard->lastSentFormats, clipboard->lastSentNumFormats);
+ clipboard->lastSentFormats = NULL;
+ clipboard->lastSentNumFormats = 0;
+}
+
+static BOOL xf_clipboard_copy_formats(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ xf_clipboard_formats_free(clipboard);
+ clipboard->lastSentFormats = calloc(numFormats, sizeof(CLIPRDR_FORMAT));
+ if (!clipboard->lastSentFormats)
+ return FALSE;
+ clipboard->lastSentNumFormats = numFormats;
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ CLIPRDR_FORMAT* lcur = &clipboard->lastSentFormats[x];
+ const CLIPRDR_FORMAT* cur = &formats[x];
+ *lcur = *cur;
+ if (cur->formatName)
+ lcur->formatName = _strdup(cur->formatName);
+ }
+ return FALSE;
+}
+
+static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
+ UINT32 numFormats, BOOL force)
+{
+ union
+ {
+ const CLIPRDR_FORMAT* cpv;
+ CLIPRDR_FORMAT* pv;
+ } cnv = { .cpv = formats };
+ const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = CB_RESPONSE_OK,
+ .numFormats = numFormats,
+ .formats = cnv.pv,
+ .common.msgType = CB_FORMAT_LIST };
+ UINT ret = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(formats || (numFormats == 0));
+
+ if (!force && !xf_clipboard_changed(clipboard, formats, numFormats))
+ return CHANNEL_RC_OK;
+
+#if defined(WITH_DEBUG_CLIPRDR)
+ for (UINT32 x = 0; x < numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &formats[x];
+ DEBUG_CLIPRDR("announcing format 0x%08" PRIx32 " [%s] [%s]", format->formatId,
+ ClipboardGetFormatIdString(format->formatId), format->formatName);
+ }
+#endif
+
+ xf_clipboard_copy_formats(clipboard, formats, numFormats);
+ /* Ensure all pending requests are answered. */
+ xf_cliprdr_send_data_response(clipboard, NULL, NULL, 0);
+
+ xf_cliprdr_clear_cached_data(clipboard);
+
+ ret = cliprdr_file_context_notify_new_client_format_list(clipboard->file);
+ if (ret)
+ return ret;
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatList);
+ return clipboard->context->ClientFormatList(clipboard->context, &formatList);
+}
+
+static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard)
+{
+ UINT32 numFormats = 0;
+ CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
+ xf_cliprdr_send_format_list(clipboard, formats, numFormats, FALSE);
+ xf_cliprdr_free_formats(formats, numFormats);
+}
+
+static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, BOOL hasData, BYTE* data,
+ int size)
+{
+ BOOL bSuccess = 0;
+ UINT32 SrcSize = 0;
+ UINT32 DstSize = 0;
+ UINT32 srcFormatId = 0;
+ BYTE* pDstData = NULL;
+ const xfCliprdrFormat* format = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ if (clipboard->incr_starts && hasData)
+ return;
+
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (!hasData || !data || !format)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ srcFormatId = 0;
+
+ switch (format->formatToRequest)
+ {
+ case CF_RAW:
+ srcFormatId = CF_RAW;
+ break;
+
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ size = strlen((char*)data) + 1;
+ srcFormatId = format->localFormat;
+ break;
+
+ default:
+ srcFormatId = format->localFormat;
+ break;
+ }
+
+ if (srcFormatId == 0)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ ClipboardLock(clipboard->system);
+ SrcSize = (UINT32)size;
+ bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize);
+
+ if (bSuccess)
+ {
+ DstSize = 0;
+ pDstData = (BYTE*)ClipboardGetData(clipboard->system, format->formatToRequest, &DstSize);
+ }
+ ClipboardUnlock(clipboard->system);
+
+ if (!pDstData)
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return;
+ }
+
+ /*
+ * File lists require a bit of postprocessing to convert them from WinPR's FILDESCRIPTOR
+ * format to CLIPRDR_FILELIST expected by the server.
+ *
+ * We check for "FileGroupDescriptorW" format being registered (i.e., nonzero) in order
+ * to not process CF_RAW as a file list in case WinPR does not support file transfers.
+ */
+ ClipboardLock(clipboard->system);
+ if (format->formatToRequest &&
+ (format->formatToRequest ==
+ ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW)))
+ {
+ UINT error = NO_ERROR;
+ FILEDESCRIPTORW* file_array = (FILEDESCRIPTORW*)pDstData;
+ UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW);
+ pDstData = NULL;
+ DstSize = 0;
+
+ const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
+ error = cliprdr_serialize_file_list_ex(flags, file_array, file_count, &pDstData, &DstSize);
+
+ if (error)
+ WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error);
+
+ UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ UINT32 url_size = 0;
+ ClipboardLock(clipboard->system);
+ char* url = ClipboardGetData(clipboard->system, formatId, &url_size);
+ ClipboardUnlock(clipboard->system);
+ cliprdr_file_context_update_client_data(clipboard->file, url, url_size);
+ free(url);
+
+ free(file_array);
+ }
+ ClipboardUnlock(clipboard->system);
+
+ xf_cliprdr_send_data_response(clipboard, format, pDstData, DstSize);
+ free(pDstData);
+}
+
+static BOOL xf_cliprdr_get_requested_data(xfClipboard* clipboard, Atom target)
+{
+ Atom type = 0;
+ BYTE* data = NULL;
+ BOOL has_data = FALSE;
+ int format_property = 0;
+ unsigned long dummy = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ const xfCliprdrFormat* format = NULL;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (!format || (format->atom != target))
+ {
+ xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+ return FALSE;
+ }
+
+ LogTagAndXGetWindowProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 0, 0,
+ target, &type, &format_property, &length, &bytes_left, &data);
+
+ if (data)
+ {
+ XFree(data);
+ data = NULL;
+ }
+
+ if (bytes_left <= 0 && !clipboard->incr_starts)
+ {
+ }
+ else if (type == clipboard->incr_atom)
+ {
+ clipboard->incr_starts = TRUE;
+
+ if (clipboard->incr_data)
+ {
+ free(clipboard->incr_data);
+ clipboard->incr_data = NULL;
+ }
+
+ clipboard->incr_data_length = 0;
+ has_data = TRUE; /* data will be followed in PropertyNotify event */
+ XSelectInput(xfc->display, xfc->drawable, PropertyChangeMask);
+ }
+ else
+ {
+ if (bytes_left <= 0)
+ {
+ /* INCR finish */
+ data = clipboard->incr_data;
+ clipboard->incr_data = NULL;
+ bytes_left = clipboard->incr_data_length;
+ clipboard->incr_data_length = 0;
+ clipboard->incr_starts = 0;
+ has_data = TRUE;
+ }
+ else if (LogTagAndXGetWindowProperty(
+ TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, bytes_left, 0,
+ target, &type, &format_property, &length, &dummy, &data) == Success)
+ {
+ if (clipboard->incr_starts)
+ {
+ BYTE* new_data = NULL;
+ bytes_left = length * format_property / 8;
+ new_data =
+ (BYTE*)realloc(clipboard->incr_data, clipboard->incr_data_length + bytes_left);
+
+ if (new_data)
+ {
+
+ clipboard->incr_data = new_data;
+ CopyMemory(clipboard->incr_data + clipboard->incr_data_length, data,
+ bytes_left);
+ clipboard->incr_data_length += bytes_left;
+ XFree(data);
+ data = NULL;
+ }
+ }
+
+ has_data = TRUE;
+ }
+ else
+ {
+ }
+ }
+
+ LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom);
+ xf_cliprdr_process_requested_data(clipboard, has_data, data, bytes_left);
+
+ if (data)
+ XFree(data);
+
+ return TRUE;
+}
+
+static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target)
+{
+ WINPR_ASSERT(clipboard);
+
+ if (clipboard->numTargets >= ARRAYSIZE(clipboard->targets))
+ return;
+
+ for (size_t i = 0; i < clipboard->numTargets; i++)
+ {
+ if (clipboard->targets[i] == target)
+ return;
+ }
+
+ clipboard->targets[clipboard->numTargets++] = target;
+}
+
+static void xf_cliprdr_provide_targets(xfClipboard* clipboard, const XSelectionEvent* respond)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property, XA_ATOM,
+ 32, PropModeReplace, (BYTE*)clipboard->targets,
+ clipboard->numTargets);
+ }
+}
+
+static void xf_cliprdr_provide_timestamp(xfClipboard* clipboard, const XSelectionEvent* respond)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
+ XA_INTEGER, 32, PropModeReplace,
+ (BYTE*)&clipboard->selection_ownership_timestamp, 1);
+ }
+}
+
+static void xf_cliprdr_provide_data(xfClipboard* clipboard, const XSelectionEvent* respond,
+ const BYTE* data, UINT32 size)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (respond->property != None)
+ {
+ LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
+ respond->target, 8, PropModeReplace, data, size);
+ }
+}
+
+static void log_selection_event(xfContext* xfc, const XEvent* event)
+{
+ const DWORD level = WLOG_TRACE;
+ static wLog* _log_cached_ptr = NULL;
+ if (!_log_cached_ptr)
+ _log_cached_ptr = WLog_Get(TAG);
+ if (WLog_IsLevelActive(_log_cached_ptr, level))
+ {
+
+ switch (event->type)
+ {
+ case SelectionClear:
+ {
+ const XSelectionClearEvent* xevent = &event->xselectionclear;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ WLog_Print(_log_cached_ptr, level, "got event %s [selection %s]",
+ x11_event_string(event->type), selection);
+ XFree(selection);
+ }
+ break;
+ case SelectionNotify:
+ {
+ const XSelectionEvent* xevent = &event->xselection;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ char* target = Safe_XGetAtomName(xfc->display, xevent->target);
+ char* property = Safe_XGetAtomName(xfc->display, xevent->property);
+ WLog_Print(_log_cached_ptr, level,
+ "got event %s [selection %s, target %s, property %s]",
+ x11_event_string(event->type), selection, target, property);
+ XFree(selection);
+ XFree(target);
+ XFree(property);
+ }
+ break;
+ case SelectionRequest:
+ {
+ const XSelectionRequestEvent* xevent = &event->xselectionrequest;
+ char* selection = Safe_XGetAtomName(xfc->display, xevent->selection);
+ char* target = Safe_XGetAtomName(xfc->display, xevent->target);
+ char* property = Safe_XGetAtomName(xfc->display, xevent->property);
+ WLog_Print(_log_cached_ptr, level,
+ "got event %s [selection %s, target %s, property %s]",
+ x11_event_string(event->type), selection, target, property);
+ XFree(selection);
+ XFree(target);
+ XFree(property);
+ }
+ break;
+ case PropertyNotify:
+ {
+ const XPropertyEvent* xevent = &event->xproperty;
+ char* atom = Safe_XGetAtomName(xfc->display, xevent->atom);
+ WLog_Print(_log_cached_ptr, level, "got event %s [atom %s]",
+ x11_event_string(event->type), atom);
+ XFree(atom);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard,
+ const XSelectionEvent* xevent)
+{
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ if (xevent->target == clipboard->targets[1])
+ {
+ if (xevent->property == None)
+ {
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ else
+ {
+ xf_cliprdr_get_requested_targets(clipboard);
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ return xf_cliprdr_get_requested_data(clipboard, xevent->target);
+ }
+}
+
+void xf_cliprdr_clear_cached_data(xfClipboard* clipboard)
+{
+ WINPR_ASSERT(clipboard);
+
+ ClipboardLock(clipboard->system);
+ ClipboardEmpty(clipboard->system);
+ cliprdr_file_context_clear(clipboard->file);
+
+ HashTable_Clear(clipboard->cachedData);
+ HashTable_Clear(clipboard->cachedRawData);
+
+ cliprdr_file_context_clear(clipboard->file);
+ ClipboardUnlock(clipboard->system);
+}
+
+static UINT32 get_dst_format_id_for_local_request(xfClipboard* clipboard,
+ const xfCliprdrFormat* format)
+{
+ UINT32 dstFormatId = 0;
+
+ WINPR_ASSERT(format);
+
+ if (!format->formatName)
+ return format->localFormat;
+
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ ClipboardUnlock(clipboard->system);
+
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ dstFormatId = format->localFormat;
+
+ return dstFormatId;
+}
+
+static void get_src_format_info_for_local_request(xfClipboard* clipboard,
+ const xfCliprdrFormat* format,
+ UINT32* srcFormatId, BOOL* nullTerminated)
+{
+ *srcFormatId = 0;
+ *nullTerminated = FALSE;
+
+ if (format->formatName)
+ {
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ *srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ *nullTerminated = TRUE;
+ }
+ else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ *srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ *nullTerminated = TRUE;
+ }
+ ClipboardUnlock(clipboard->system);
+ }
+ else
+ {
+ *srcFormatId = format->formatToRequest;
+ switch (format->formatToRequest)
+ {
+ case CF_TEXT:
+ case CF_OEMTEXT:
+ case CF_UNICODETEXT:
+ *nullTerminated = TRUE;
+ break;
+ case CF_DIB:
+ *srcFormatId = CF_DIB;
+ break;
+ case CF_TIFF:
+ *srcFormatId = CF_TIFF;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static xfCachedData* convert_data_from_existing_raw_data(xfClipboard* clipboard,
+ xfCachedData* cached_raw_data,
+ UINT32 srcFormatId, BOOL nullTerminated,
+ UINT32 dstFormatId)
+{
+ xfCachedData* cached_data = NULL;
+ BOOL success = 0;
+ BYTE* dst_data = NULL;
+ UINT32 dst_size = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(cached_raw_data);
+ WINPR_ASSERT(cached_raw_data->data);
+
+ ClipboardLock(clipboard->system);
+ success = ClipboardSetData(clipboard->system, srcFormatId, cached_raw_data->data,
+ cached_raw_data->data_length);
+ if (!success)
+ {
+ WLog_WARN(TAG, "Failed to set clipboard data (formatId: %u, data: %p, data_length: %u)",
+ srcFormatId, cached_raw_data->data, cached_raw_data->data_length);
+ ClipboardUnlock(clipboard->system);
+ return NULL;
+ }
+
+ dst_data = ClipboardGetData(clipboard->system, dstFormatId, &dst_size);
+ if (!dst_data)
+ {
+ WLog_WARN(TAG, "Failed to get converted clipboard data");
+ ClipboardUnlock(clipboard->system);
+ return NULL;
+ }
+ ClipboardUnlock(clipboard->system);
+
+ if (nullTerminated)
+ {
+ BYTE* nullTerminator = memchr(dst_data, '\0', dst_size);
+ if (nullTerminator)
+ dst_size = nullTerminator - dst_data;
+ }
+
+ cached_data = xf_cached_data_new(dst_data, dst_size);
+ if (!cached_data)
+ {
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ free(dst_data);
+ return NULL;
+ }
+
+ if (!HashTable_Insert(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId, cached_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_data);
+ return NULL;
+ }
+
+ return cached_data;
+}
+
+static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard,
+ const XSelectionRequestEvent* xevent)
+{
+ int fmt = 0;
+ Atom type = 0;
+ UINT32 formatId = 0;
+ XSelectionEvent* respond = NULL;
+ BYTE* data = NULL;
+ BOOL delayRespond = 0;
+ BOOL rawTransfer = 0;
+ unsigned long length = 0;
+ unsigned long bytes_left = 0;
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (xevent->owner != xfc->drawable)
+ return FALSE;
+
+ delayRespond = FALSE;
+
+ if (!(respond = (XSelectionEvent*)calloc(1, sizeof(XSelectionEvent))))
+ {
+ WLog_ERR(TAG, "failed to allocate XEvent data");
+ return FALSE;
+ }
+
+ respond->property = None;
+ respond->type = SelectionNotify;
+ respond->display = xevent->display;
+ respond->requestor = xevent->requestor;
+ respond->selection = xevent->selection;
+ respond->target = xevent->target;
+ respond->time = xevent->time;
+
+ if (xevent->target == clipboard->targets[0]) /* TIMESTAMP */
+ {
+ /* Someone else requests the selection's timestamp */
+ respond->property = xevent->property;
+ xf_cliprdr_provide_timestamp(clipboard, respond);
+ }
+ else if (xevent->target == clipboard->targets[1]) /* TARGETS */
+ {
+ /* Someone else requests our available formats */
+ respond->property = xevent->property;
+ xf_cliprdr_provide_targets(clipboard, respond);
+ }
+ else
+ {
+ const CLIPRDR_FORMAT* format =
+ xf_cliprdr_get_server_format_by_atom(clipboard, xevent->target);
+ const xfCliprdrFormat* cformat =
+ xf_cliprdr_get_client_format_by_atom(clipboard, xevent->target);
+
+ if (format && (xevent->requestor != xfc->drawable))
+ {
+ formatId = format->formatId;
+ rawTransfer = FALSE;
+ xfCachedData* cached_data = NULL;
+ UINT32 dstFormatId = 0;
+
+ if (formatId == CF_RAW)
+ {
+ if (LogTagAndXGetWindowProperty(
+ TAG, xfc->display, xevent->requestor, clipboard->property_atom, 0, 4, 0,
+ XA_INTEGER, &type, &fmt, &length, &bytes_left, &data) != Success)
+ {
+ }
+
+ if (data)
+ {
+ rawTransfer = TRUE;
+ CopyMemory(&formatId, data, 4);
+ XFree(data);
+ }
+ }
+
+ dstFormatId = get_dst_format_id_for_local_request(clipboard, cformat);
+ DEBUG_CLIPRDR("formatId: %u, dstFormatId: %u", formatId, dstFormatId);
+
+ if (!rawTransfer)
+ cached_data =
+ HashTable_GetItemValue(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId);
+ else
+ cached_data =
+ HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)formatId);
+
+ DEBUG_CLIPRDR("hasCachedData: %u, rawTransfer: %u", cached_data ? 1 : 0, rawTransfer);
+
+ if (!cached_data && !rawTransfer)
+ {
+ UINT32 srcFormatId = 0;
+ BOOL nullTerminated = FALSE;
+ xfCachedData* cached_raw_data = NULL;
+
+ get_src_format_info_for_local_request(clipboard, cformat, &srcFormatId,
+ &nullTerminated);
+ cached_raw_data =
+ HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId);
+
+ DEBUG_CLIPRDR("hasCachedRawData: %u, rawDataLength: %u", cached_raw_data ? 1 : 0,
+ cached_raw_data ? cached_raw_data->data_length : 0);
+
+ if (cached_raw_data && cached_raw_data->data_length != 0)
+ cached_data = convert_data_from_existing_raw_data(
+ clipboard, cached_raw_data, srcFormatId, nullTerminated, dstFormatId);
+ }
+
+ DEBUG_CLIPRDR("hasCachedData: %u", cached_data ? 1 : 0);
+
+ if (cached_data)
+ {
+ /* Cached clipboard data available. Send it now */
+ respond->property = xevent->property;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ xf_cliprdr_provide_data(clipboard, respond, cached_data->data,
+ cached_data->data_length);
+ }
+ else if (clipboard->respond)
+ {
+ /* duplicate request */
+ }
+ else
+ {
+ WINPR_ASSERT(cformat);
+
+ /**
+ * Send clipboard data request to the server.
+ * Response will be postponed after receiving the data
+ */
+ respond->property = xevent->property;
+ clipboard->respond = respond;
+ clipboard->requestedFormat = cformat;
+ clipboard->data_raw_format = rawTransfer;
+ delayRespond = TRUE;
+ xf_cliprdr_send_data_request(clipboard, formatId, cformat);
+ }
+ }
+ }
+
+ if (!delayRespond)
+ {
+ union
+ {
+ XEvent* ev;
+ XSelectionEvent* sev;
+ } conv;
+
+ conv.sev = respond;
+ XSendEvent(xfc->display, xevent->requestor, 0, 0, conv.ev);
+ XFlush(xfc->display);
+ free(respond);
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard,
+ const XSelectionClearEvent* xevent)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(xevent);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ WINPR_UNUSED(xevent);
+
+ if (xf_cliprdr_is_self_owned(clipboard))
+ return FALSE;
+
+ LogTagAndXDeleteProperty(TAG, xfc->display, clipboard->root_window, clipboard->property_atom);
+ return TRUE;
+}
+
+static BOOL xf_cliprdr_process_property_notify(xfClipboard* clipboard, const XPropertyEvent* xevent)
+{
+ const xfCliprdrFormat* format = NULL;
+ xfContext* xfc = NULL;
+
+ if (!clipboard)
+ return TRUE;
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xevent);
+
+ if (xevent->atom == clipboard->timestamp_property_atom)
+ {
+ /* This is the response to the property change we did
+ * in xf_cliprdr_prepare_to_set_selection_owner. Now
+ * we can set ourselves as the selection owner. (See
+ * comments in those functions below.) */
+ xf_cliprdr_set_selection_owner(xfc, clipboard, xevent->time);
+ return TRUE;
+ }
+
+ if (xevent->atom != clipboard->property_atom)
+ return FALSE; /* Not cliprdr-related */
+
+ if (xevent->window == clipboard->root_window)
+ {
+ xf_cliprdr_send_client_format_list(clipboard, FALSE);
+ }
+ else if ((xevent->window == xfc->drawable) && (xevent->state == PropertyNewValue) &&
+ clipboard->incr_starts)
+ {
+ format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId);
+
+ if (format)
+ xf_cliprdr_get_requested_data(clipboard, format->atom);
+ }
+
+ return TRUE;
+}
+
+void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event)
+{
+ xfClipboard* clipboard = NULL;
+
+ if (!xfc || !event)
+ return;
+
+ clipboard = xfc->clipboard;
+
+ if (!clipboard)
+ return;
+
+#ifdef WITH_XFIXES
+
+ if (clipboard->xfixes_supported &&
+ event->type == XFixesSelectionNotify + clipboard->xfixes_event_base)
+ {
+ const XFixesSelectionNotifyEvent* se = (const XFixesSelectionNotifyEvent*)event;
+
+ if (se->subtype == XFixesSetSelectionOwnerNotify)
+ {
+ if (se->selection != clipboard->clipboard_atom)
+ return;
+
+ if (XGetSelectionOwner(xfc->display, se->selection) == xfc->drawable)
+ return;
+
+ clipboard->owner = None;
+ xf_cliprdr_check_owner(clipboard);
+ }
+
+ return;
+ }
+
+#endif
+
+ switch (event->type)
+ {
+ case SelectionNotify:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_notify(clipboard, &event->xselection);
+ break;
+
+ case SelectionRequest:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_request(clipboard, &event->xselectionrequest);
+ break;
+
+ case SelectionClear:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_selection_clear(clipboard, &event->xselectionclear);
+ break;
+
+ case PropertyNotify:
+ log_selection_event(xfc, event);
+ xf_cliprdr_process_property_notify(clipboard, &event->xproperty);
+ break;
+
+ case FocusIn:
+ if (!clipboard->xfixes_supported)
+ {
+ xf_cliprdr_check_owner(clipboard);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
+{
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+
+ WINPR_ASSERT(clipboard);
+
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
+
+ WINPR_ASSERT(clipboard);
+ generalCapabilitySet.generalFlags |= cliprdr_file_context_current_flags(clipboard->file);
+
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientCapabilities);
+ return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force)
+{
+ WINPR_ASSERT(clipboard);
+
+ xfContext* xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ UINT32 numFormats = 0;
+ CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
+
+ const UINT ret = xf_cliprdr_send_format_list(clipboard, formats, numFormats, force);
+
+ if (clipboard->owner && clipboard->owner != xfc->drawable)
+ {
+ /* Request the owner for TARGETS, and wait for SelectionNotify event */
+ XConvertSelection(xfc->display, clipboard->clipboard_atom, clipboard->targets[1],
+ clipboard->property_atom, xfc->drawable, CurrentTime);
+ }
+
+ xf_cliprdr_free_formats(formats, numFormats);
+
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard, BOOL status)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ formatListResponse.common.dataLen = 0;
+
+ WINPR_ASSERT(clipboard);
+ WINPR_ASSERT(clipboard->context);
+ WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
+ return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_monitor_ready(CliprdrClientContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ UINT ret = 0;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ WINPR_UNUSED(monitorReady);
+
+ if ((ret = xf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
+ return ret;
+
+ xf_clipboard_formats_free(clipboard);
+
+ if ((ret = xf_cliprdr_send_client_format_list(clipboard, TRUE)) != CHANNEL_RC_OK)
+ return ret;
+
+ clipboard->sync = TRUE;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = NULL;
+ const BYTE* capsPtr = NULL;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ capsPtr = (const BYTE*)capabilities->capabilitySets;
+ WINPR_ASSERT(capsPtr);
+
+ cliprdr_file_context_remote_set_flags(clipboard->file, 0);
+
+ for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
+
+ cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags);
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void xf_cliprdr_prepare_to_set_selection_owner(xfContext* xfc, xfClipboard* clipboard)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(clipboard);
+ /*
+ * When you're writing to the selection in response to a
+ * normal X event like a mouse click or keyboard action, you
+ * get the selection timestamp by copying the time field out
+ * of that X event. Here, we're doing it on our own
+ * initiative, so we have to _request_ the X server time.
+ *
+ * There isn't a GetServerTime request in the X protocol, so I
+ * work around it by setting a property on our own window, and
+ * waiting for a PropertyNotify event to come back telling me
+ * it's been done - which will have a timestamp we can use.
+ */
+
+ /* We have to set the property to some value, but it doesn't
+ * matter what. Set it to its own name, which we have here
+ * anyway! */
+ Atom value = clipboard->timestamp_property_atom;
+
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->timestamp_property_atom,
+ XA_ATOM, 32, PropModeReplace, (BYTE*)&value, 1);
+ XFlush(xfc->display);
+}
+
+static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(clipboard);
+ /*
+ * Actually set ourselves up as the selection owner, now that
+ * we have a timestamp to use.
+ */
+
+ clipboard->selection_ownership_timestamp = timestamp;
+ XSetSelectionOwner(xfc->display, clipboard->clipboard_atom, xfc->drawable, timestamp);
+ XFlush(xfc->display);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ xfContext* xfc = NULL;
+ UINT ret = 0;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ /* Clear the active SelectionRequest, as it is now invalid */
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+
+ xf_clipboard_formats_free(clipboard);
+ xf_cliprdr_clear_cached_data(clipboard);
+ clipboard->requestedFormat = NULL;
+
+ xf_clipboard_free_server_formats(clipboard);
+
+ clipboard->numServerFormats = formatList->numFormats + 1; /* +1 for CF_RAW */
+
+ if (!(clipboard->serverFormats =
+ (CLIPRDR_FORMAT*)calloc(clipboard->numServerFormats, sizeof(CLIPRDR_FORMAT))))
+ {
+ WLog_ERR(TAG, "failed to allocate %d CLIPRDR_FORMAT structs", clipboard->numServerFormats);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (size_t i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+ CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
+
+ srvFormat->formatId = format->formatId;
+
+ if (format->formatName)
+ {
+ srvFormat->formatName = _strdup(format->formatName);
+
+ if (!srvFormat->formatName)
+ {
+ for (UINT32 k = 0; k < i; k++)
+ free(clipboard->serverFormats[k].formatName);
+
+ clipboard->numServerFormats = 0;
+ free(clipboard->serverFormats);
+ clipboard->serverFormats = NULL;
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ }
+ }
+
+ ret = cliprdr_file_context_notify_new_server_format_list(clipboard->file);
+ if (ret)
+ return ret;
+
+ /* CF_RAW is always implicitly supported by the server */
+ {
+ CLIPRDR_FORMAT* format = &clipboard->serverFormats[formatList->numFormats];
+ format->formatId = CF_RAW;
+ format->formatName = NULL;
+ }
+ xf_cliprdr_provide_server_format_list(clipboard);
+ clipboard->numTargets = 2;
+
+ for (size_t i = 0; i < formatList->numFormats; i++)
+ {
+ const CLIPRDR_FORMAT* format = &formatList->formats[i];
+
+ for (size_t j = 0; j < clipboard->numClientFormats; j++)
+ {
+ const xfCliprdrFormat* clientFormat = &clipboard->clientFormats[j];
+ if (xf_cliprdr_formats_equal(format, clientFormat))
+ {
+ if ((clientFormat->formatName != NULL) &&
+ (strcmp(type_FileGroupDescriptorW, clientFormat->formatName) == 0))
+ {
+ if (!cliprdr_file_context_has_local_support(clipboard->file))
+ continue;
+ }
+ xf_cliprdr_append_target(clipboard, clientFormat->atom);
+ }
+ }
+ }
+
+ ret = xf_cliprdr_send_client_format_list_response(clipboard, TRUE);
+ if (xfc->remote_app)
+ xf_cliprdr_set_selection_owner(xfc, clipboard, CurrentTime);
+ else
+ xf_cliprdr_prepare_to_set_selection_owner(xfc, clipboard);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+ // xfClipboard* clipboard = (xfClipboard*) context->custom;
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ BOOL rawTransfer = 0;
+ const xfCliprdrFormat* format = NULL;
+ UINT32 formatId = 0;
+ xfContext* xfc = NULL;
+ xfClipboard* clipboard = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ formatId = formatDataRequest->requestedFormatId;
+
+ rawTransfer = xf_cliprdr_is_raw_transfer_available(clipboard);
+
+ if (rawTransfer)
+ {
+ format = xf_cliprdr_get_client_format_by_id(clipboard, CF_RAW);
+ LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom,
+ XA_INTEGER, 32, PropModeReplace, (BYTE*)&formatId, 1);
+ }
+ else
+ format = xf_cliprdr_get_client_format_by_id(clipboard, formatId);
+
+ clipboard->requestedFormatId = rawTransfer ? CF_RAW : formatId;
+ if (!format)
+ return xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
+
+ DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ XConvertSelection(xfc->display, clipboard->clipboard_atom, format->atom,
+ clipboard->property_atom, xfc->drawable, CurrentTime);
+ XFlush(xfc->display);
+ /* After this point, we expect a SelectionNotify event from the clipboard owner. */
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+xf_cliprdr_server_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ BOOL bSuccess = 0;
+ BYTE* pDstData = NULL;
+ UINT32 DstSize = 0;
+ UINT32 SrcSize = 0;
+ UINT32 srcFormatId = 0;
+ UINT32 dstFormatId = 0;
+ BOOL nullTerminated = FALSE;
+ UINT32 size = 0;
+ const BYTE* data = NULL;
+ xfContext* xfc = NULL;
+ xfClipboard* clipboard = NULL;
+ xfCachedData* cached_data = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ clipboard = cliprdr_file_context_get_context(context->custom);
+ WINPR_ASSERT(clipboard);
+
+ xfc = clipboard->xfc;
+ WINPR_ASSERT(xfc);
+
+ size = formatDataResponse->common.dataLen;
+ data = formatDataResponse->requestedFormatData;
+
+ if (formatDataResponse->common.msgFlags == CB_RESPONSE_FAIL)
+ {
+ WLog_WARN(TAG, "Format Data Response PDU msgFlags is CB_RESPONSE_FAIL");
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ return CHANNEL_RC_OK;
+ }
+
+ if (!clipboard->respond)
+ return CHANNEL_RC_OK;
+
+ pDstData = NULL;
+ DstSize = 0;
+ srcFormatId = 0;
+ dstFormatId = 0;
+
+ const xfCliprdrFormat* format = clipboard->requestedFormat;
+ if (clipboard->data_raw_format)
+ {
+ srcFormatId = CF_RAW;
+ dstFormatId = CF_RAW;
+ }
+ else if (!format)
+ return ERROR_INTERNAL_ERROR;
+ else if (format->formatName)
+ {
+ ClipboardLock(clipboard->system);
+ if (strcmp(format->formatName, type_HtmlFormat) == 0)
+ {
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
+ nullTerminated = TRUE;
+ }
+
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
+ {
+ if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system, data,
+ size))
+ WLog_WARN(TAG, "failed to update file descriptors");
+
+ srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const xfCliprdrFormat* dstTargetFormat =
+ xf_cliprdr_get_client_format_by_atom(clipboard, clipboard->respond->target);
+ if (!dstTargetFormat)
+ {
+ dstFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ }
+ else
+ {
+ dstFormatId = dstTargetFormat->localFormat;
+ }
+
+ nullTerminated = TRUE;
+ }
+ ClipboardUnlock(clipboard->system);
+ }
+ else
+ {
+ srcFormatId = format->formatToRequest;
+ dstFormatId = format->localFormat;
+ switch (format->formatToRequest)
+ {
+ case CF_TEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_OEMTEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_UNICODETEXT:
+ nullTerminated = TRUE;
+ break;
+
+ case CF_DIB:
+ srcFormatId = CF_DIB;
+ break;
+
+ case CF_TIFF:
+ srcFormatId = CF_TIFF;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
+ format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
+ format->localFormat, format->formatName);
+ SrcSize = (UINT32)size;
+
+ DEBUG_CLIPRDR("srcFormatId: %u, dstFormatId: %u", srcFormatId, dstFormatId);
+
+ ClipboardLock(clipboard->system);
+ bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize);
+
+ BOOL willQuit = FALSE;
+ if (bSuccess)
+ {
+ if (SrcSize == 0)
+ {
+ WLog_DBG(TAG, "skipping, empty data detected!");
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ willQuit = TRUE;
+ }
+ else
+ {
+ pDstData = (BYTE*)ClipboardGetData(clipboard->system, dstFormatId, &DstSize);
+
+ if (!pDstData)
+ {
+ WLog_WARN(TAG, "failed to get clipboard data in format %s [source format %s]",
+ ClipboardGetFormatName(clipboard->system, dstFormatId),
+ ClipboardGetFormatName(clipboard->system, srcFormatId));
+ }
+
+ if (nullTerminated && pDstData)
+ {
+ BYTE* nullTerminator = memchr(pDstData, '\0', DstSize);
+ if (nullTerminator)
+ DstSize = nullTerminator - pDstData;
+ }
+ }
+ }
+ ClipboardUnlock(clipboard->system);
+ if (willQuit)
+ return CHANNEL_RC_OK;
+
+ /* Cache converted and original data to avoid doing a possibly costly
+ * conversion again on subsequent requests */
+ if (pDstData)
+ {
+ cached_data = xf_cached_data_new(pDstData, DstSize);
+ if (!cached_data)
+ {
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ free(pDstData);
+ return CHANNEL_RC_OK;
+ }
+ if (!HashTable_Insert(clipboard->cachedData, (void*)(UINT_PTR)dstFormatId, cached_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_data);
+ return CHANNEL_RC_OK;
+ }
+ }
+
+ /* We have to copy the original data again, as pSrcData is now owned
+ * by clipboard->system. Memory allocation failure is not fatal here
+ * as this is only a cached value. */
+ {
+ // clipboard->cachedData owns cached_data
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc
+ xfCachedData* cached_raw_data = xf_cached_data_new_copy(data, size);
+ if (!cached_raw_data)
+ WLog_WARN(TAG, "Failed to allocate cache entry");
+ else
+ {
+ if (!HashTable_Insert(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId,
+ cached_raw_data))
+ {
+ WLog_WARN(TAG, "Failed to cache clipboard data");
+ xf_cached_data_free(cached_raw_data);
+ }
+ }
+ }
+
+ // clipboard->cachedRawData owns cached_raw_data
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ xf_cliprdr_provide_data(clipboard, clipboard->respond, pDstData, DstSize);
+ {
+ union
+ {
+ XEvent* ev;
+ XSelectionEvent* sev;
+ } conv;
+
+ conv.sev = clipboard->respond;
+
+ XSendEvent(xfc->display, clipboard->respond->requestor, 0, 0, conv.ev);
+ XFlush(xfc->display);
+ }
+ free(clipboard->respond);
+ clipboard->respond = NULL;
+ return CHANNEL_RC_OK;
+}
+
+static BOOL xf_cliprdr_is_valid_unix_filename(LPCWSTR filename)
+{
+ if (!filename)
+ return FALSE;
+
+ if (filename[0] == L'\0')
+ return FALSE;
+
+ /* Reserved characters */
+ for (const WCHAR* c = filename; *c; ++c)
+ {
+ if (*c == L'/')
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction)
+{
+ int n = 0;
+ rdpChannels* channels = NULL;
+ xfClipboard* clipboard = NULL;
+ const char* selectionAtom = NULL;
+ xfCliprdrFormat* clientFormat = NULL;
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ if (!(clipboard = (xfClipboard*)calloc(1, sizeof(xfClipboard))))
+ {
+ WLog_ERR(TAG, "failed to allocate xfClipboard data");
+ return NULL;
+ }
+
+ clipboard->file = cliprdr_file_context_new(clipboard);
+ if (!clipboard->file)
+ goto fail;
+
+ xfc->clipboard = clipboard;
+ clipboard->xfc = xfc;
+ channels = xfc->common.context.channels;
+ clipboard->channels = channels;
+ clipboard->system = ClipboardCreate();
+ clipboard->requestedFormatId = -1;
+ clipboard->root_window = DefaultRootWindow(xfc->display);
+
+ selectionAtom =
+ freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ClipboardUseSelection);
+ if (!selectionAtom)
+ selectionAtom = "CLIPBOARD";
+
+ clipboard->clipboard_atom = XInternAtom(xfc->display, selectionAtom, FALSE);
+
+ if (clipboard->clipboard_atom == None)
+ {
+ WLog_ERR(TAG, "unable to get %s atom", selectionAtom);
+ goto fail;
+ }
+
+ clipboard->timestamp_property_atom =
+ XInternAtom(xfc->display, "_FREERDP_TIMESTAMP_PROPERTY", FALSE);
+ clipboard->property_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR", FALSE);
+ clipboard->raw_transfer_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE);
+ clipboard->raw_format_list_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE);
+ xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE);
+ XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask);
+#ifdef WITH_XFIXES
+
+ if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base,
+ &clipboard->xfixes_error_base))
+ {
+ int xfmajor = 0;
+ int xfminor = 0;
+
+ if (XFixesQueryVersion(xfc->display, &xfmajor, &xfminor))
+ {
+ XFixesSelectSelectionInput(xfc->display, clipboard->root_window,
+ clipboard->clipboard_atom,
+ XFixesSetSelectionOwnerNotifyMask);
+ clipboard->xfixes_supported = TRUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Error querying X Fixes extension version");
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "Error loading X Fixes extension");
+ }
+
+#else
+ WLog_ERR(
+ TAG,
+ "Warning: Using clipboard redirection without XFIXES extension is strongly discouraged!");
+#endif
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, "_FREERDP_RAW", False);
+ clientFormat->localFormat = clientFormat->formatToRequest = CF_RAW;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, "UTF8_STRING", False);
+ clientFormat->formatToRequest = CF_UNICODETEXT;
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XA_STRING;
+ clientFormat->formatToRequest = CF_TEXT;
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, mime_tiff, False);
+ clientFormat->formatToRequest = clientFormat->localFormat = CF_TIFF;
+
+ for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
+ {
+ const char* mime_bmp = mime_bitmap[x];
+ const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
+ if (format == 0)
+ {
+ WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
+ continue;
+ }
+
+ WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->localFormat = format;
+ clientFormat->atom = XInternAtom(xfc->display, mime_bmp, False);
+ clientFormat->formatToRequest = CF_DIB;
+ }
+
+ for (size_t x = 0; x < ARRAYSIZE(mime_images); x++)
+ {
+ const char* mime_bmp = mime_images[x];
+ const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
+ if (format == 0)
+ {
+ WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
+ continue;
+ }
+
+ WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->localFormat = format;
+ clientFormat->atom = XInternAtom(xfc->display, mime_bmp, False);
+ clientFormat->formatToRequest = CF_DIB;
+ }
+
+ clientFormat = &clipboard->clientFormats[n++];
+ clientFormat->atom = XInternAtom(xfc->display, mime_html, False);
+ clientFormat->formatToRequest = ClipboardGetFormatId(xfc->clipboard->system, type_HtmlFormat);
+ clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_html);
+ clientFormat->formatName = _strdup(type_HtmlFormat);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+
+ /*
+ * Existence of registered format IDs for file formats does not guarantee that they are
+ * in fact supported by wClipboard (as further initialization may have failed after format
+ * registration). However, they are definitely not supported if there are no registered
+ * formats. In this case we should not list file formats in TARGETS.
+ */
+ const UINT32 fgid = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
+ const UINT32 uid = ClipboardGetFormatId(clipboard->system, mime_uri_list);
+ if (uid)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_uri_list, False);
+ clientFormat->localFormat = uid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ }
+
+ const UINT32 gid = ClipboardGetFormatId(clipboard->system, mime_gnome_copied_files);
+ if (gid != 0)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_gnome_copied_files, False);
+ clientFormat->localFormat = gid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+
+ clientFormat = &clipboard->clientFormats[n++];
+ }
+
+ const UINT32 mid = ClipboardGetFormatId(clipboard->system, mime_mate_copied_files);
+ if (mid != 0)
+ {
+ cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
+ clientFormat->atom = XInternAtom(xfc->display, mime_mate_copied_files, False);
+ clientFormat->localFormat = mid;
+ clientFormat->formatToRequest = fgid;
+ clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
+
+ if (!clientFormat->formatName)
+ goto fail;
+ }
+
+ clipboard->numClientFormats = n;
+ clipboard->targets[0] = XInternAtom(xfc->display, "TIMESTAMP", FALSE);
+ clipboard->targets[1] = XInternAtom(xfc->display, "TARGETS", FALSE);
+ clipboard->numTargets = 2;
+ clipboard->incr_atom = XInternAtom(xfc->display, "INCR", FALSE);
+
+ if (relieveFilenameRestriction)
+ {
+ WLog_DBG(TAG, "Relieving CLIPRDR filename restriction");
+ ClipboardGetDelegate(clipboard->system)->IsFileNameComponentValid =
+ xf_cliprdr_is_valid_unix_filename;
+ }
+
+ clipboard->cachedData = HashTable_New(TRUE);
+ if (!clipboard->cachedData)
+ goto fail;
+
+ obj = HashTable_ValueObject(clipboard->cachedData);
+ obj->fnObjectFree = xf_cached_data_free;
+
+ clipboard->cachedRawData = HashTable_New(TRUE);
+ if (!clipboard->cachedRawData)
+ goto fail;
+
+ obj = HashTable_ValueObject(clipboard->cachedRawData);
+ obj->fnObjectFree = xf_cached_data_free;
+
+ return clipboard;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ xf_clipboard_free(clipboard);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void xf_clipboard_free(xfClipboard* clipboard)
+{
+ if (!clipboard)
+ return;
+
+ xf_clipboard_free_server_formats(clipboard);
+
+ if (clipboard->numClientFormats)
+ {
+ for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
+ {
+ xfCliprdrFormat* format = &clipboard->clientFormats[i];
+ free(format->formatName);
+ }
+ }
+
+ cliprdr_file_context_free(clipboard->file);
+
+ ClipboardDestroy(clipboard->system);
+ xf_clipboard_formats_free(clipboard);
+ HashTable_Free(clipboard->cachedRawData);
+ HashTable_Free(clipboard->cachedData);
+ free(clipboard->respond);
+ free(clipboard->incr_data);
+ free(clipboard);
+}
+
+void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cliprdr);
+
+ xfc->cliprdr = cliprdr;
+ xfc->clipboard->context = cliprdr;
+
+ cliprdr->MonitorReady = xf_cliprdr_monitor_ready;
+ cliprdr->ServerCapabilities = xf_cliprdr_server_capabilities;
+ cliprdr->ServerFormatList = xf_cliprdr_server_format_list;
+ cliprdr->ServerFormatListResponse = xf_cliprdr_server_format_list_response;
+ cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request;
+ cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response;
+
+ cliprdr_file_context_init(xfc->clipboard->file, cliprdr);
+}
+
+void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cliprdr);
+
+ xfc->cliprdr = NULL;
+
+ if (xfc->clipboard)
+ {
+ cliprdr_file_context_uninit(xfc->clipboard->file, cliprdr);
+ xfc->clipboard->context = NULL;
+ }
+}
diff --git a/client/X11/xf_cliprdr.h b/client/X11/xf_cliprdr.h
new file mode 100644
index 0000000..33d75c8
--- /dev/null
+++ b/client/X11/xf_cliprdr.h
@@ -0,0 +1,38 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * 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_CLIENT_X11_CLIPRDR_H
+#define FREERDP_CLIENT_X11_CLIPRDR_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/client/cliprdr.h>
+
+void xf_clipboard_free(xfClipboard* clipboard);
+
+WINPR_ATTR_MALLOC(xf_clipboard_free, 1)
+xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction);
+
+void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr);
+void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr);
+
+void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event);
+
+#endif /* FREERDP_CLIENT_X11_CLIPRDR_H */
diff --git a/client/X11/xf_disp.c b/client/X11/xf_disp.c
new file mode 100644
index 0000000..f7be118
--- /dev/null
+++ b/client/X11/xf_disp.c
@@ -0,0 +1,550 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Display Control channel
+ *
+ * Copyright 2017 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 <winpr/sysinfo.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XRANDR
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/randr.h>
+
+#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
+#define USABLE_XRANDR
+#endif
+
+#endif
+
+#include "xf_disp.h"
+#include "xf_monitor.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11disp")
+#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
+
+struct s_xfDispContext
+{
+ xfContext* xfc;
+ DispClientContext* disp;
+ BOOL haveXRandr;
+ int eventBase;
+ int errorBase;
+ UINT32 lastSentWidth;
+ UINT32 lastSentHeight;
+ BYTE reserved[4];
+ UINT64 lastSentDate;
+ UINT32 targetWidth;
+ UINT32 targetHeight;
+ BOOL activated;
+ BOOL fullscreen;
+ UINT16 lastSentDesktopOrientation;
+ BYTE reserved2[2];
+ UINT32 lastSentDesktopScaleFactor;
+ UINT32 lastSentDeviceScaleFactor;
+ BYTE reserved3[4];
+};
+
+static UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors,
+ UINT32 nmonitors);
+
+static BOOL xf_disp_settings_changed(xfDispContext* xfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfDisp->lastSentWidth != xfDisp->targetWidth)
+ return TRUE;
+
+ if (xfDisp->lastSentHeight != xfDisp->targetHeight)
+ return TRUE;
+
+ if (xfDisp->lastSentDesktopOrientation !=
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation))
+ return TRUE;
+
+ if (xfDisp->lastSentDesktopScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor))
+ return TRUE;
+
+ if (xfDisp->lastSentDeviceScaleFactor !=
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor))
+ return TRUE;
+
+ if (xfDisp->fullscreen != xfDisp->xfc->fullscreen)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL xf_update_last_sent(xfDispContext* xfDisp)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfDisp->lastSentWidth = xfDisp->targetWidth;
+ xfDisp->lastSentHeight = xfDisp->targetHeight;
+ xfDisp->lastSentDesktopOrientation =
+ freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ xfDisp->lastSentDesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ xfDisp->lastSentDeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ xfDisp->fullscreen = xfDisp->xfc->fullscreen;
+ return TRUE;
+}
+
+static BOOL xf_disp_sendResize(xfDispContext* xfDisp)
+{
+ DISPLAY_CONTROL_MONITOR_LAYOUT layout = { 0 };
+ xfContext* xfc = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xfDisp || !xfDisp->xfc)
+ return FALSE;
+
+ xfc = xfDisp->xfc;
+ settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!xfDisp->activated || !xfDisp->disp)
+ return TRUE;
+
+ if (GetTickCount64() - xfDisp->lastSentDate < RESIZE_MIN_DELAY)
+ return TRUE;
+
+ if (!xf_disp_settings_changed(xfDisp))
+ return TRUE;
+
+ xfDisp->lastSentDate = GetTickCount64();
+
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ if (xfc->fullscreen && (mcount > 0))
+ {
+ const rdpMonitor* monitors =
+ freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+ if (xf_disp_sendLayout(xfDisp->disp, monitors, mcount) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+ else
+ {
+ layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
+ layout.Top = layout.Left = 0;
+ layout.Width = xfDisp->targetWidth;
+ layout.Height = xfDisp->targetHeight;
+ layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation);
+ layout.DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ layout.PhysicalWidth = xfDisp->targetWidth / 75.0 * 25.4;
+ layout.PhysicalHeight = xfDisp->targetHeight / 75.0 * 25.4;
+
+ if (IFCALLRESULT(CHANNEL_RC_OK, xfDisp->disp->SendMonitorLayout, xfDisp->disp, 1,
+ &layout) != CHANNEL_RC_OK)
+ return FALSE;
+ }
+
+ return xf_update_last_sent(xfDisp);
+}
+
+static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 height)
+{
+ if ((xfDisp->targetWidth == (INT64)width) && (xfDisp->targetHeight == (INT64)height))
+ return TRUE;
+ xfDisp->targetWidth = width;
+ xfDisp->targetHeight = height;
+ xfDisp->lastSentDate = GetTickCount64();
+ return xf_disp_sendResize(xfDisp);
+}
+
+static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp)
+{
+ XSizeHints* size_hints = NULL;
+
+ if (!(size_hints = XAllocSizeHints()))
+ return FALSE;
+
+ size_hints->flags = PMinSize | PMaxSize | PWinGravity;
+ size_hints->win_gravity = NorthWestGravity;
+ size_hints->min_width = size_hints->min_height = 320;
+ size_hints->max_width = size_hints->max_height = 8192;
+
+ if (xfDisp->xfc->window)
+ XSetWMNormalHints(xfDisp->xfc->display, xfDisp->xfc->window->handle, size_hints);
+
+ XFree(size_hints);
+ return TRUE;
+}
+
+static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp,
+ rdpSettings** ppSettings)
+{
+ xfContext* xfc = NULL;
+
+ if (!context)
+ return FALSE;
+
+ xfc = (xfContext*)context;
+
+ if (!(xfc->xfDisp))
+ return FALSE;
+
+ if (!xfc->common.context.settings)
+ return FALSE;
+
+ *ppXfc = xfc;
+ *ppXfDisp = xfc->xfDisp;
+ *ppSettings = xfc->common.context.settings;
+ return TRUE;
+}
+
+static void xf_disp_OnActivated(void* context, const ActivatedEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (xfDisp->activated && !xfc->fullscreen)
+ {
+ xf_disp_set_window_resizable(xfDisp);
+
+ if (e->firstActivation)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+ }
+}
+
+static void xf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (xfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ xf_disp_set_window_resizable(xfDisp);
+ xf_disp_sendResize(xfDisp);
+ }
+}
+
+static void xf_disp_OnTimer(void* context, const TimerEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (!xfDisp->activated || xfc->fullscreen)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+}
+
+static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEventArgs* e)
+{
+ xfContext* xfc = NULL;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(e);
+
+ if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
+ return;
+
+ if (!xfDisp->activated || !xfc->fullscreen)
+ return;
+
+ xf_disp_sendResize(xfDisp);
+}
+
+xfDispContext* xf_disp_new(xfContext* xfc)
+{
+ xfDispContext* ret = NULL;
+ const rdpSettings* settings = NULL;
+ wPubSub* pubSub = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ pubSub = xfc->common.context.pubSub;
+ WINPR_ASSERT(pubSub);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ ret = calloc(1, sizeof(xfDispContext));
+
+ if (!ret)
+ return NULL;
+
+ ret->xfc = xfc;
+#ifdef USABLE_XRANDR
+
+ if (XRRQueryExtension(xfc->display, &ret->eventBase, &ret->errorBase))
+ {
+ ret->haveXRandr = TRUE;
+ }
+
+#endif
+ ret->lastSentWidth = ret->targetWidth =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ ret->lastSentHeight = ret->targetHeight =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ PubSub_SubscribeActivated(pubSub, xf_disp_OnActivated);
+ PubSub_SubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset);
+ PubSub_SubscribeTimer(pubSub, xf_disp_OnTimer);
+ PubSub_SubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange);
+ return ret;
+}
+
+void xf_disp_free(xfDispContext* disp)
+{
+ if (!disp)
+ return;
+
+ if (disp->xfc)
+ {
+ wPubSub* pubSub = disp->xfc->common.context.pubSub;
+ PubSub_UnsubscribeActivated(pubSub, xf_disp_OnActivated);
+ PubSub_UnsubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset);
+ PubSub_UnsubscribeTimer(pubSub, xf_disp_OnTimer);
+ PubSub_UnsubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange);
+ }
+
+ free(disp);
+}
+
+UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, UINT32 nmonitors)
+{
+ UINT ret = CHANNEL_RC_OK;
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = NULL;
+
+ WINPR_ASSERT(disp);
+ WINPR_ASSERT(monitors);
+ WINPR_ASSERT(nmonitors > 0);
+
+ xfDisp = (xfDispContext*)disp->custom;
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
+
+ if (!layouts)
+ return CHANNEL_RC_NO_MEMORY;
+
+ for (UINT32 i = 0; i < nmonitors; i++)
+ {
+ const rdpMonitor* monitor = &monitors[i];
+ DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i];
+
+ layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
+ layout->Left = monitor->x;
+ layout->Top = monitor->y;
+ layout->Width = monitor->width;
+ layout->Height = monitor->height;
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ layout->PhysicalWidth = monitor->attributes.physicalWidth;
+ layout->PhysicalHeight = monitor->attributes.physicalHeight;
+
+ switch (monitor->attributes.orientation)
+ {
+ case 90:
+ layout->Orientation = ORIENTATION_PORTRAIT;
+ break;
+
+ case 180:
+ layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+
+ case 270:
+ layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case 0:
+ default:
+ /* MS-RDPEDISP - 2.2.2.2.1:
+ * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
+ * orientation of the monitor in degrees. Valid values are 0, 90, 180
+ * or 270
+ *
+ * So we default to ORIENTATION_LANDSCAPE
+ */
+ layout->Orientation = ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ layout->DesktopScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ layout->DeviceScaleFactor =
+ freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor);
+ }
+
+ ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts);
+ free(layouts);
+ return ret;
+}
+
+BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event)
+{
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+ UINT32 maxWidth = 0;
+ UINT32 maxHeight = 0;
+
+ if (!xfc || !event)
+ return FALSE;
+
+ xfDisp = xfc->xfDisp;
+
+ if (!xfDisp)
+ return FALSE;
+
+ settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ if (!xfDisp->haveXRandr || !xfDisp->disp)
+ return TRUE;
+
+#ifdef USABLE_XRANDR
+
+ if (event->type != xfDisp->eventBase + RRScreenChangeNotify)
+ return TRUE;
+
+#endif
+ xf_detect_monitors(xfc, &maxWidth, &maxHeight);
+ const rdpMonitor* monitors = freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray);
+ const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ return xf_disp_sendLayout(xfDisp->disp, monitors, mcount) == CHANNEL_RC_OK;
+}
+
+BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height)
+{
+ xfDispContext* xfDisp = NULL;
+
+ if (!xfc)
+ return FALSE;
+
+ xfDisp = xfc->xfDisp;
+
+ if (!xfDisp)
+ return FALSE;
+
+ return xf_disp_queueResize(xfDisp, width, height);
+}
+
+static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
+ UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
+{
+ /* we're called only if dynamic resolution update is activated */
+ xfDispContext* xfDisp = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(disp);
+
+ xfDisp = (xfDispContext*)disp->custom;
+ WINPR_ASSERT(xfDisp);
+ WINPR_ASSERT(xfDisp->xfc);
+
+ settings = xfDisp->xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG,
+ "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
+ " MaxMonitorAreaFactorB: %" PRIu32 "",
+ maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
+ xfDisp->activated = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ return CHANNEL_RC_OK;
+
+ WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
+ return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
+}
+
+BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp)
+{
+ rdpSettings* settings = NULL;
+
+ if (!xfDisp || !xfDisp->xfc || !disp)
+ return FALSE;
+
+ settings = xfDisp->xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ xfDisp->disp = disp;
+ disp->custom = (void*)xfDisp;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ disp->DisplayControlCaps = xf_DisplayControlCaps;
+#ifdef USABLE_XRANDR
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ /* ask X11 to notify us of screen changes */
+ XRRSelectInput(xfDisp->xfc->display, DefaultRootWindow(xfDisp->xfc->display),
+ RRScreenChangeNotifyMask);
+ }
+
+#endif
+ }
+
+ return TRUE;
+}
+
+BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp)
+{
+ if (!xfDisp || !disp)
+ return FALSE;
+
+ xfDisp->disp = NULL;
+ return TRUE;
+}
diff --git a/client/X11/xf_disp.h b/client/X11/xf_disp.h
new file mode 100644
index 0000000..c3c8792
--- /dev/null
+++ b/client/X11/xf_disp.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Display Control channel
+ *
+ * Copyright 2017 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 FREERDP_CLIENT_X11_DISP_H
+#define FREERDP_CLIENT_X11_DISP_H
+
+#include <freerdp/types.h>
+#include <freerdp/client/disp.h>
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+FREERDP_API BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp);
+FREERDP_API BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp);
+
+void xf_disp_free(xfDispContext* disp);
+
+WINPR_ATTR_MALLOC(xf_disp_free, 1)
+xfDispContext* xf_disp_new(xfContext* xfc);
+
+BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event);
+BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height);
+void xf_disp_resized(xfDispContext* disp);
+
+#endif /* FREERDP_CLIENT_X11_DISP_H */
diff --git a/client/X11/xf_event.c b/client/X11/xf_event.c
new file mode 100644
index 0000000..6bc4c4d
--- /dev/null
+++ b/client/X11/xf_event.c
@@ -0,0 +1,1314 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Event Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2023 HP Development Company, L.P.
+ *
+ * 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+
+#include <freerdp/log.h>
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_rail.h"
+#include "xf_window.h"
+#include "xf_cliprdr.h"
+#include "xf_disp.h"
+#include "xf_input.h"
+#include "xf_gfx.h"
+#include "xf_graphics.h"
+
+#include "xf_event.h"
+
+#define TAG CLIENT_TAG("x11")
+
+#define CLAMP_COORDINATES(x, y) \
+ if (x < 0) \
+ x = 0; \
+ if (y < 0) \
+ y = 0
+
+const char* x11_event_string(int event)
+{
+ switch (event)
+ {
+ case KeyPress:
+ return "KeyPress";
+
+ case KeyRelease:
+ return "KeyRelease";
+
+ case ButtonPress:
+ return "ButtonPress";
+
+ case ButtonRelease:
+ return "ButtonRelease";
+
+ case MotionNotify:
+ return "MotionNotify";
+
+ case EnterNotify:
+ return "EnterNotify";
+
+ case LeaveNotify:
+ return "LeaveNotify";
+
+ case FocusIn:
+ return "FocusIn";
+
+ case FocusOut:
+ return "FocusOut";
+
+ case KeymapNotify:
+ return "KeymapNotify";
+
+ case Expose:
+ return "Expose";
+
+ case GraphicsExpose:
+ return "GraphicsExpose";
+
+ case NoExpose:
+ return "NoExpose";
+
+ case VisibilityNotify:
+ return "VisibilityNotify";
+
+ case CreateNotify:
+ return "CreateNotify";
+
+ case DestroyNotify:
+ return "DestroyNotify";
+
+ case UnmapNotify:
+ return "UnmapNotify";
+
+ case MapNotify:
+ return "MapNotify";
+
+ case MapRequest:
+ return "MapRequest";
+
+ case ReparentNotify:
+ return "ReparentNotify";
+
+ case ConfigureNotify:
+ return "ConfigureNotify";
+
+ case ConfigureRequest:
+ return "ConfigureRequest";
+
+ case GravityNotify:
+ return "GravityNotify";
+
+ case ResizeRequest:
+ return "ResizeRequest";
+
+ case CirculateNotify:
+ return "CirculateNotify";
+
+ case CirculateRequest:
+ return "CirculateRequest";
+
+ case PropertyNotify:
+ return "PropertyNotify";
+
+ case SelectionClear:
+ return "SelectionClear";
+
+ case SelectionRequest:
+ return "SelectionRequest";
+
+ case SelectionNotify:
+ return "SelectionNotify";
+
+ case ColormapNotify:
+ return "ColormapNotify";
+
+ case ClientMessage:
+ return "ClientMessage";
+
+ case MappingNotify:
+ return "MappingNotify";
+
+ case GenericEvent:
+ return "GenericEvent";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+BOOL xf_event_action_script_init(xfContext* xfc)
+{
+ wObject* obj = NULL;
+ FILE* actionScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+ const rdpSettings* settings = NULL;
+ const char* ActionScript = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfc->xevents = ArrayList_New(TRUE);
+
+ if (!xfc->xevents)
+ return FALSE;
+
+ obj = ArrayList_Object(xfc->xevents);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = winpr_ObjectStringClone;
+ obj->fnObjectFree = winpr_ObjectStringFree;
+ ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s xevent", ActionScript);
+ actionScript = popen(command, "r");
+
+ if (!actionScript)
+ return FALSE;
+
+ while (fgets(buffer, sizeof(buffer), actionScript))
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (!buffer || !ArrayList_Append(xfc->xevents, buffer))
+ {
+ pclose(actionScript);
+ ArrayList_Free(xfc->xevents);
+ xfc->xevents = NULL;
+ return FALSE;
+ }
+ }
+
+ pclose(actionScript);
+ return TRUE;
+}
+
+void xf_event_action_script_free(xfContext* xfc)
+{
+ if (xfc->xevents)
+ {
+ ArrayList_Free(xfc->xevents);
+ xfc->xevents = NULL;
+ }
+}
+
+static BOOL xf_event_execute_action_script(xfContext* xfc, const XEvent* event)
+{
+ int count = 0;
+ char* name = NULL;
+ FILE* actionScript = NULL;
+ BOOL match = FALSE;
+ const char* xeventName = NULL;
+ const char* ActionScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+
+ if (!xfc->actionScriptExists || !xfc->xevents || !xfc->window)
+ return FALSE;
+
+ if (event->type > LASTEvent)
+ return FALSE;
+
+ xeventName = x11_event_string(event->type);
+ count = ArrayList_Count(xfc->xevents);
+
+ for (int index = 0; index < count; index++)
+ {
+ name = (char*)ArrayList_GetItem(xfc->xevents, index);
+
+ if (_stricmp(name, xeventName) == 0)
+ {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ return FALSE;
+
+ ActionScript = freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s xevent %s %lu", ActionScript, xeventName,
+ (unsigned long)xfc->window->handle);
+ actionScript = popen(command, "r");
+
+ if (!actionScript)
+ return FALSE;
+
+ while (fgets(buffer, sizeof(buffer), actionScript))
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+ }
+
+ pclose(actionScript);
+ return TRUE;
+}
+
+void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y)
+{
+ rdpSettings* settings = NULL;
+ INT64 tx = 0;
+ INT64 ty = 0;
+
+ if (!xfc || !xfc->common.context.settings || !y || !x)
+ return;
+
+ settings = xfc->common.context.settings;
+ tx = *x;
+ ty = *y;
+ if (!xfc->remote_app)
+ {
+#ifdef WITH_XRENDER
+
+ if (xf_picture_transform_required(xfc))
+ {
+ double xScalingFactor = xfc->scaledWidth / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopWidth);
+ double yScalingFactor = xfc->scaledHeight / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopHeight);
+ tx = ((tx + xfc->offset_x) * xScalingFactor);
+ ty = ((ty + xfc->offset_y) * yScalingFactor);
+ }
+
+#endif
+ }
+
+ CLAMP_COORDINATES(tx, ty);
+ *x = tx;
+ *y = ty;
+}
+
+void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y)
+{
+ if (!xfc || !xfc->common.context.settings || !y || !x)
+ return;
+
+ if (!xfc->remote_app)
+ {
+#ifdef WITH_XRENDER
+ rdpSettings* settings = xfc->common.context.settings;
+ if (xf_picture_transform_required(xfc))
+ {
+ double xScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) /
+ (double)xfc->scaledWidth;
+ double yScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) /
+ (double)xfc->scaledHeight;
+ *x = (int)((*x - xfc->offset_x) * xScalingFactor);
+ *y = (int)((*y - xfc->offset_y) * yScalingFactor);
+ }
+
+#endif
+ }
+
+ CLAMP_COORDINATES(*x, *y);
+}
+
+static BOOL xf_event_Expose(xfContext* xfc, const XExposeEvent* event, BOOL app)
+{
+ int x = 0;
+ int y = 0;
+ int w = 0;
+ int h = 0;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (!app && (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)))
+ {
+ x = 0;
+ y = 0;
+ w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+ else
+ {
+ x = event->x;
+ y = event->y;
+ w = event->width;
+ h = event->height;
+ }
+
+ if (!app)
+ {
+ if (xfc->common.context.gdi->gfx)
+ {
+ xf_OutputExpose(xfc, x, y, w, h);
+ return TRUE;
+ }
+ xf_draw_screen(xfc, x, y, w, h);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+ if (appWindow)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, x, y, w, h);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_VisibilityNotify(xfContext* xfc, const XVisibilityEvent* event, BOOL app)
+{
+ WINPR_UNUSED(app);
+ xfc->unobscured = event->state == VisibilityUnobscured;
+ return TRUE;
+}
+
+BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app)
+{
+ Window childWindow = None;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.settings);
+
+ rdpInput* input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ if (!freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MouseMotion))
+ {
+ if ((state & (Button1Mask | Button2Mask | Button3Mask)) == 0)
+ return TRUE;
+ }
+
+ if (app)
+ {
+ /* make sure window exists */
+ if (!xf_AppWindowFromX11Window(xfc, window))
+ return TRUE;
+
+ /* Translate to desktop coordinates */
+ XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y, &x, &y,
+ &childWindow);
+ }
+
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+
+ if (xfc->fullscreen && !app)
+ {
+ XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime);
+ }
+
+ return TRUE;
+}
+
+BOOL xf_generic_RawMotionNotify(xfContext* xfc, int x, int y, Window window, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+
+ if (app)
+ {
+ WLog_ERR(TAG, "Relative mouse input is not supported with remoate app mode!");
+ return FALSE;
+ }
+
+ return freerdp_client_send_button_event(&xfc->common, TRUE, PTR_FLAGS_MOVE, x, y);
+}
+
+static BOOL xf_event_MotionNotify(xfContext* xfc, const XMotionEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+
+ if (xfc->window)
+ xf_floatbar_set_root_y(xfc->window->floatbar, event->y);
+
+ return xf_generic_MotionNotify(xfc, event->x, event->y, event->state, event->window, app);
+}
+
+BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app,
+ BOOL down)
+{
+ UINT16 flags = 0;
+ Window childWindow = None;
+
+ WINPR_ASSERT(xfc);
+
+ for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++)
+ {
+ const button_map* cur = &xfc->button_map[i];
+
+ if (cur->button == button)
+ {
+ flags = cur->flags;
+ break;
+ }
+ }
+
+ if (flags != 0)
+ {
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (down)
+ freerdp_client_send_wheel_event(&xfc->common, flags);
+ }
+ else
+ {
+ BOOL extended = FALSE;
+
+ if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2))
+ {
+ extended = TRUE;
+
+ if (down)
+ flags |= PTR_XFLAGS_DOWN;
+ }
+ else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3))
+ {
+ if (down)
+ flags |= PTR_FLAGS_DOWN;
+ }
+
+ if (app)
+ {
+ /* make sure window exists */
+ if (!xf_AppWindowFromX11Window(xfc, window))
+ return TRUE;
+
+ /* Translate to desktop coordinates */
+ XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y,
+ &x, &y, &childWindow);
+ }
+
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ if (extended)
+ freerdp_client_send_extended_button_event(&xfc->common, FALSE, flags, x, y);
+ else
+ freerdp_client_send_button_event(&xfc->common, FALSE, flags, x, y);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_grab_mouse(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (!xfc->window)
+ return FALSE;
+
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_GrabMouse))
+ {
+ XGrabPointer(xfc->display, xfc->window->handle, False,
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask |
+ EnterWindowMask | LeaveWindowMask,
+ GrabModeAsync, GrabModeAsync, xfc->window->handle, None, CurrentTime);
+ xfc->common.mouse_grabbed = TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL xf_grab_kbd(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ if (!xfc->window)
+ return FALSE;
+
+ XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, GrabModeAsync,
+ CurrentTime);
+ return TRUE;
+}
+
+static BOOL xf_event_ButtonPress(xfContext* xfc, const XButtonEvent* event, BOOL app)
+{
+ xf_grab_mouse(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+ return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app, TRUE);
+}
+
+static BOOL xf_event_ButtonRelease(xfContext* xfc, const XButtonEvent* event, BOOL app)
+{
+ xf_grab_mouse(xfc);
+
+ if (xfc->xi_event)
+ return TRUE;
+ return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app,
+ FALSE);
+}
+
+static BOOL xf_event_KeyPress(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ KeySym keysym = 0;
+ char str[256] = { 0 };
+ union
+ {
+ const XKeyEvent* cev;
+ XKeyEvent* ev;
+ } cnv;
+ cnv.cev = event;
+ WINPR_UNUSED(app);
+ XLookupString(cnv.ev, str, sizeof(str), &keysym, NULL);
+ xf_keyboard_key_press(xfc, event, keysym);
+ return TRUE;
+}
+
+static BOOL xf_event_KeyRelease(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ KeySym keysym = 0;
+ char str[256] = { 0 };
+ union
+ {
+ const XKeyEvent* cev;
+ XKeyEvent* ev;
+ } cnv;
+ cnv.cev = event;
+
+ WINPR_UNUSED(app);
+ XLookupString(cnv.ev, str, sizeof(str), &keysym, NULL);
+ xf_keyboard_key_release(xfc, event, keysym);
+ return TRUE;
+}
+
+/* Release a key, but ignore the event in case of autorepeat.
+ */
+static BOOL xf_event_KeyReleaseOrIgnore(xfContext* xfc, const XKeyEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ if ((event->type == KeyRelease) && XEventsQueued(xfc->display, QueuedAfterReading))
+ {
+ XEvent nev = { 0 };
+ XPeekEvent(xfc->display, &nev);
+
+ if ((nev.type == KeyPress) && (nev.xkey.time == event->time) &&
+ (nev.xkey.keycode == event->keycode))
+ {
+ /* Key wasn’t actually released */
+ return TRUE;
+ }
+ }
+
+ return xf_event_KeyRelease(xfc, event, app);
+}
+
+static BOOL xf_event_FocusIn(xfContext* xfc, const XFocusInEvent* event, BOOL app)
+{
+ if (event->mode == NotifyGrab)
+ return TRUE;
+
+ xfc->focused = TRUE;
+
+ if (xfc->mouse_active && !app)
+ {
+ xf_grab_mouse(xfc);
+ if (!xf_grab_kbd(xfc))
+ return FALSE;
+ }
+
+ /* Release all keys, should already be done at FocusOut but might be missed
+ * if the WM decided to use an alternate event order */
+ if (!app)
+ xf_keyboard_release_all_keypress(xfc);
+
+ xf_pointer_update_scale(xfc);
+
+ if (app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* Update the server with any window changes that occurred while the window was not focused.
+ */
+ if (appWindow)
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+
+ xf_keyboard_focus_in(xfc);
+ return TRUE;
+}
+
+static BOOL xf_event_FocusOut(xfContext* xfc, const XFocusOutEvent* event, BOOL app)
+{
+ if (event->mode == NotifyUngrab)
+ return TRUE;
+
+ xfc->focused = FALSE;
+
+ if (event->mode == NotifyWhileGrabbed)
+ XUngrabKeyboard(xfc->display, CurrentTime);
+
+ xf_keyboard_release_all_keypress(xfc);
+
+ return TRUE;
+}
+
+static BOOL xf_event_MappingNotify(xfContext* xfc, const XMappingEvent* event, BOOL app)
+{
+ WINPR_UNUSED(app);
+
+ if (event->request == MappingModifier)
+ return xf_keyboard_update_modifier_map(xfc);
+
+ return TRUE;
+}
+
+static BOOL xf_event_ClientMessage(xfContext* xfc, const XClientMessageEvent* event, BOOL app)
+{
+ if ((event->message_type == xfc->WM_PROTOCOLS) &&
+ ((Atom)event->data.l[0] == xfc->WM_DELETE_WINDOW))
+ {
+ if (app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_CLOSE);
+
+ return TRUE;
+ }
+ else
+ {
+ DEBUG_X11("Main window closed");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_EnterNotify(xfContext* xfc, const XEnterWindowEvent* event, BOOL app)
+{
+ if (!app)
+ {
+ if (!xfc->window)
+ return FALSE;
+
+ xfc->mouse_active = TRUE;
+
+ if (xfc->fullscreen)
+ XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime);
+
+ if (xfc->focused)
+ xf_grab_kbd(xfc);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* keep track of which window has focus so that we can apply pointer updates */
+ xfc->appWindow = appWindow;
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_LeaveNotify(xfContext* xfc, const XLeaveWindowEvent* event, BOOL app)
+{
+ if (event->mode == NotifyGrab || event->mode == NotifyUngrab)
+ return TRUE;
+ if (!app)
+ {
+ xfc->mouse_active = FALSE;
+ XUngrabKeyboard(xfc->display, CurrentTime);
+ }
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ /* keep track of which window has focus so that we can apply pointer updates */
+ if (xfc->appWindow == appWindow)
+ xfc->appWindow = NULL;
+ }
+ return TRUE;
+}
+
+static BOOL xf_event_ConfigureNotify(xfContext* xfc, const XConfigureEvent* event, BOOL app)
+{
+ Window childWindow = None;
+ xfAppWindow* appWindow = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ const rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ WLog_DBG(TAG, "x=%" PRId32 ", y=%" PRId32 ", w=%" PRId32 ", h=%" PRId32, event->x, event->y,
+ event->width, event->height);
+
+ if (!app)
+ {
+ if (!xfc->window)
+ return FALSE;
+
+ if (xfc->window->left != event->x)
+ xfc->window->left = event->x;
+
+ if (xfc->window->top != event->y)
+ xfc->window->top = event->y;
+
+ if (xfc->window->width != event->width || xfc->window->height != event->height)
+ {
+ xfc->window->width = event->width;
+ xfc->window->height = event->height;
+#ifdef WITH_XRENDER
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ xfc->scaledWidth = xfc->window->width;
+ xfc->scaledHeight = xfc->window->height;
+ xf_draw_screen(xfc, 0, 0,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ }
+ else
+ {
+ xfc->scaledWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->scaledHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+#endif
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ int alignedWidth = 0;
+ int alignedHeight = 0;
+ alignedWidth = (xfc->window->width / 2) * 2;
+ alignedHeight = (xfc->window->height / 2) * 2;
+ /* ask the server to resize using the display channel */
+ xf_disp_handle_configureNotify(xfc, alignedWidth, alignedHeight);
+ }
+ }
+ else
+ {
+ appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ {
+ /*
+ * ConfigureNotify coordinates are expressed relative to the window parent.
+ * Translate these to root window coordinates.
+ */
+ XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
+ 0, 0, &appWindow->x, &appWindow->y, &childWindow);
+ appWindow->width = event->width;
+ appWindow->height = event->height;
+
+ xf_AppWindowResize(xfc, appWindow);
+
+ /*
+ * Additional checks for not in a local move and not ignoring configure to send
+ * position update to server, also should the window not be focused then do not
+ * send to server yet (i.e. resizing using window decoration).
+ * The server will be updated when the window gets refocused.
+ */
+ if (appWindow->decorations)
+ {
+ /* moving resizing using window decoration */
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+ else
+ {
+ if ((!event->send_event || appWindow->local_move.state == LMS_NOT_ACTIVE) &&
+ !appWindow->rail_ignore_configure && xfc->focused)
+ xf_rail_adjust_position(xfc, appWindow);
+ }
+ }
+ }
+ return xf_pointer_update_scale(xfc);
+}
+
+static BOOL xf_event_MapNotify(xfContext* xfc, const XMapEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ if (!app)
+ gdi_send_suppress_output(xfc->common.context.gdi, FALSE);
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ {
+ /* local restore event */
+ /* This is now handled as part of the PropertyNotify
+ * Doing this here would inhibit the ability to restore a maximized window
+ * that is minimized back to the maximized state
+ */
+ // xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE);
+ appWindow->is_mapped = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_UnmapNotify(xfContext* xfc, const XUnmapEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ if (!app)
+ xf_keyboard_release_all_keypress(xfc);
+
+ if (!app)
+ gdi_send_suppress_output(xfc->common.context.gdi, TRUE);
+ else
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (appWindow)
+ appWindow->is_mapped = FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_PropertyNotify(xfContext* xfc, const XPropertyEvent* event, BOOL app)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ /*
+ * This section handles sending the appropriate commands to the rail server
+ * when the window has been minimized, maximized, restored locally
+ * ie. not using the buttons on the rail window itself
+ */
+ if ((((Atom)event->atom == xfc->_NET_WM_STATE) && (event->state != PropertyDelete)) ||
+ (((Atom)event->atom == xfc->WM_STATE) && (event->state != PropertyDelete)))
+ {
+ BOOL status = FALSE;
+ BOOL minimized = FALSE;
+ BOOL minimizedChanged = FALSE;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ xfAppWindow* appWindow = NULL;
+
+ if (app)
+ {
+ appWindow = xf_AppWindowFromX11Window(xfc, event->window);
+
+ if (!appWindow)
+ return TRUE;
+ }
+
+ if ((Atom)event->atom == xfc->_NET_WM_STATE)
+ {
+ status = xf_GetWindowProperty(xfc, event->window, xfc->_NET_WM_STATE, 12, &nitems,
+ &bytes, &prop);
+
+ if (status)
+ {
+ if (appWindow)
+ {
+ appWindow->maxVert = FALSE;
+ appWindow->maxHorz = FALSE;
+ }
+ for (unsigned long i = 0; i < nitems; i++)
+ {
+ if ((Atom)((UINT16**)prop)[i] ==
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False))
+ {
+ if (appWindow)
+ appWindow->maxVert = TRUE;
+ }
+
+ if ((Atom)((UINT16**)prop)[i] ==
+ XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False))
+ {
+ if (appWindow)
+ appWindow->maxHorz = TRUE;
+ }
+ }
+
+ XFree(prop);
+ }
+ }
+
+ if ((Atom)event->atom == xfc->WM_STATE)
+ {
+ status =
+ xf_GetWindowProperty(xfc, event->window, xfc->WM_STATE, 1, &nitems, &bytes, &prop);
+
+ if (status)
+ {
+ /* If the window is in the iconic state */
+ if (((UINT32)*prop == 3))
+ {
+ minimized = TRUE;
+ if (appWindow)
+ appWindow->minimized = TRUE;
+ }
+ else
+ {
+ minimized = FALSE;
+ if (appWindow)
+ appWindow->minimized = FALSE;
+ }
+
+ minimizedChanged = TRUE;
+ XFree(prop);
+ }
+ }
+
+ if (app)
+ {
+ WINPR_ASSERT(appWindow);
+ if (appWindow->maxVert && appWindow->maxHorz && !appWindow->minimized)
+ {
+ if (appWindow->rail_state != WINDOW_SHOW_MAXIMIZED)
+ {
+ appWindow->rail_state = WINDOW_SHOW_MAXIMIZED;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MAXIMIZE);
+ }
+ }
+ else if (appWindow->minimized)
+ {
+ if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
+ {
+ appWindow->rail_state = WINDOW_SHOW_MINIMIZED;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MINIMIZE);
+ }
+ }
+ else
+ {
+ if (appWindow->rail_state != WINDOW_SHOW && appWindow->rail_state != WINDOW_HIDE)
+ {
+ appWindow->rail_state = WINDOW_SHOW;
+ xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE);
+ }
+ }
+ }
+ else if (minimizedChanged)
+ gdi_send_suppress_output(xfc->common.context.gdi, minimized);
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_event_suppress_events(xfContext* xfc, xfAppWindow* appWindow, const XEvent* event)
+{
+ if (!xfc->remote_app)
+ return FALSE;
+
+ switch (appWindow->local_move.state)
+ {
+ case LMS_NOT_ACTIVE:
+
+ /* No local move in progress, nothing to do */
+
+ /* Prevent Configure from happening during indeterminant state of Horz or Vert Max only
+ */
+ if ((event->type == ConfigureNotify) && appWindow->rail_ignore_configure)
+ {
+ appWindow->rail_ignore_configure = FALSE;
+ return TRUE;
+ }
+
+ break;
+
+ case LMS_STARTING:
+
+ /* Local move initiated by RDP server, but we have not yet seen any updates from the X
+ * server */
+ switch (event->type)
+ {
+ case ConfigureNotify:
+ /* Starting to see move events from the X server. Local move is now in progress.
+ */
+ appWindow->local_move.state = LMS_ACTIVE;
+ /* Allow these events to be processed during move to keep our state up to date.
+ */
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ case KeyPress:
+ case KeyRelease:
+ case UnmapNotify:
+ /*
+ * A button release event means the X window server did not grab the
+ * mouse before the user released it. In this case we must cancel the
+ * local move. The event will be processed below as normal, below.
+ */
+ break;
+
+ case VisibilityNotify:
+ case PropertyNotify:
+ case Expose:
+ /* Allow these events to pass */
+ break;
+
+ default:
+ /* Eat any other events */
+ return TRUE;
+ }
+
+ break;
+
+ case LMS_ACTIVE:
+
+ /* Local move is in progress */
+ switch (event->type)
+ {
+ case ConfigureNotify:
+ case VisibilityNotify:
+ case PropertyNotify:
+ case Expose:
+ case GravityNotify:
+ /* Keep us up to date on position */
+ break;
+
+ default:
+ /* Any other event terminates move */
+ xf_rail_end_local_move(xfc, appWindow);
+ break;
+ }
+
+ break;
+
+ case LMS_TERMINATING:
+ /* Already sent RDP end move to server. Allow events to pass. */
+ break;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_event_process(freerdp* instance, const XEvent* event)
+{
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = (xfContext*)instance->context;
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (xfc->remote_app)
+ {
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window);
+
+ if (appWindow)
+ {
+ /* Update "current" window for cursor change orders */
+ xfc->appWindow = appWindow;
+
+ if (xf_event_suppress_events(xfc, appWindow, event))
+ return TRUE;
+ }
+ }
+
+ if (xfc->window)
+ {
+ xfFloatbar* floatbar = xfc->window->floatbar;
+ if (xf_floatbar_check_event(floatbar, event))
+ {
+ xf_floatbar_event_process(floatbar, event);
+ return TRUE;
+ }
+
+ if (xf_floatbar_is_locked(floatbar))
+ return TRUE;
+ }
+
+ xf_event_execute_action_script(xfc, event);
+
+ if (event->type != MotionNotify)
+ {
+ DEBUG_X11("%s Event(%d): wnd=0x%08lX", x11_event_string(event->type), event->type,
+ (unsigned long)event->xany.window);
+ }
+
+ switch (event->type)
+ {
+ case Expose:
+ status = xf_event_Expose(xfc, &event->xexpose, xfc->remote_app);
+ break;
+
+ case VisibilityNotify:
+ status = xf_event_VisibilityNotify(xfc, &event->xvisibility, xfc->remote_app);
+ break;
+
+ case MotionNotify:
+ status = xf_event_MotionNotify(xfc, &event->xmotion, xfc->remote_app);
+ break;
+
+ case ButtonPress:
+ status = xf_event_ButtonPress(xfc, &event->xbutton, xfc->remote_app);
+ break;
+
+ case ButtonRelease:
+ status = xf_event_ButtonRelease(xfc, &event->xbutton, xfc->remote_app);
+ break;
+
+ case KeyPress:
+ status = xf_event_KeyPress(xfc, &event->xkey, xfc->remote_app);
+ break;
+
+ case KeyRelease:
+ status = xf_event_KeyReleaseOrIgnore(xfc, &event->xkey, xfc->remote_app);
+ break;
+
+ case FocusIn:
+ status = xf_event_FocusIn(xfc, &event->xfocus, xfc->remote_app);
+ break;
+
+ case FocusOut:
+ status = xf_event_FocusOut(xfc, &event->xfocus, xfc->remote_app);
+ break;
+
+ case EnterNotify:
+ status = xf_event_EnterNotify(xfc, &event->xcrossing, xfc->remote_app);
+ break;
+
+ case LeaveNotify:
+ status = xf_event_LeaveNotify(xfc, &event->xcrossing, xfc->remote_app);
+ break;
+
+ case NoExpose:
+ break;
+
+ case GraphicsExpose:
+ break;
+
+ case ConfigureNotify:
+ status = xf_event_ConfigureNotify(xfc, &event->xconfigure, xfc->remote_app);
+ break;
+
+ case MapNotify:
+ status = xf_event_MapNotify(xfc, &event->xmap, xfc->remote_app);
+ break;
+
+ case UnmapNotify:
+ status = xf_event_UnmapNotify(xfc, &event->xunmap, xfc->remote_app);
+ break;
+
+ case ReparentNotify:
+ break;
+
+ case MappingNotify:
+ status = xf_event_MappingNotify(xfc, &event->xmapping, xfc->remote_app);
+ break;
+
+ case ClientMessage:
+ status = xf_event_ClientMessage(xfc, &event->xclient, xfc->remote_app);
+ break;
+
+ case PropertyNotify:
+ status = xf_event_PropertyNotify(xfc, &event->xproperty, xfc->remote_app);
+ break;
+
+ default:
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDisplayControl))
+ xf_disp_handle_xevent(xfc, event);
+
+ break;
+ }
+
+ xfWindow* window = xfc->window;
+ xfFloatbar* floatbar = NULL;
+ if (window)
+ floatbar = window->floatbar;
+
+ xf_cliprdr_handle_xevent(xfc, event);
+ if (!xf_floatbar_check_event(floatbar, event) && !xf_floatbar_is_locked(floatbar))
+ xf_input_handle_event(xfc, event);
+
+ XSync(xfc->display, FALSE);
+ return status;
+}
+
+BOOL xf_generic_RawButtonEvent(xfContext* xfc, int button, BOOL app, BOOL down)
+{
+ UINT16 flags = 0;
+
+ if (app)
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++)
+ {
+ const button_map* cur = &xfc->button_map[i];
+
+ if (cur->button == button)
+ {
+ flags = cur->flags;
+ break;
+ }
+ }
+
+ if (flags != 0)
+ {
+ if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
+ {
+ if (down)
+ freerdp_client_send_wheel_event(&xfc->common, flags);
+ }
+ else
+ {
+ BOOL extended = FALSE;
+
+ if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2))
+ {
+ extended = TRUE;
+
+ if (down)
+ flags |= PTR_XFLAGS_DOWN;
+ }
+ else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3))
+ {
+ if (down)
+ flags |= PTR_FLAGS_DOWN;
+ }
+
+ if (extended)
+ freerdp_client_send_extended_button_event(&xfc->common, TRUE, flags, 0, 0);
+ else
+ freerdp_client_send_button_event(&xfc->common, TRUE, flags, 0, 0);
+ }
+ }
+
+ return TRUE;
+}
diff --git a/client/X11/xf_event.h b/client/X11/xf_event.h
new file mode 100644
index 0000000..2f4ab07
--- /dev/null
+++ b/client/X11/xf_event.h
@@ -0,0 +1,46 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Event Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_EVENT_H
+#define FREERDP_CLIENT_X11_EVENT_H
+
+#include "xf_keyboard.h"
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+const char* x11_event_string(int event);
+
+BOOL xf_event_action_script_init(xfContext* xfc);
+void xf_event_action_script_free(xfContext* xfc);
+
+BOOL xf_event_process(freerdp* instance, const XEvent* event);
+void xf_event_SendClientEvent(xfContext* xfc, xfWindow* window, Atom atom, unsigned int numArgs,
+ ...);
+
+void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y);
+void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y);
+
+BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app);
+BOOL xf_generic_RawMotionNotify(xfContext* xfc, int x, int y, Window window, BOOL app);
+BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app,
+ BOOL down);
+BOOL xf_generic_RawButtonEvent(xfContext* xfc, int button, BOOL app, BOOL down);
+
+#endif /* FREERDP_CLIENT_X11_EVENT_H */
diff --git a/client/X11/xf_floatbar.c b/client/X11/xf_floatbar.c
new file mode 100644
index 0000000..e4e290a
--- /dev/null
+++ b/client/X11/xf_floatbar.c
@@ -0,0 +1,933 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");n
+ * 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/shape.h>
+#include <X11/cursorfont.h>
+
+#include <winpr/assert.h>
+
+#include "xf_floatbar.h"
+#include "resource/close.xbm"
+#include "resource/lock.xbm"
+#include "resource/unlock.xbm"
+#include "resource/minimize.xbm"
+#include "resource/restore.xbm"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#define FLOATBAR_HEIGHT 26
+#define FLOATBAR_DEFAULT_WIDTH 576
+#define FLOATBAR_MIN_WIDTH 200
+#define FLOATBAR_BORDER 24
+#define FLOATBAR_BUTTON_WIDTH 24
+#define FLOATBAR_COLOR_BACKGROUND "RGB:31/6c/a9"
+#define FLOATBAR_COLOR_BORDER "RGB:75/9a/c8"
+#define FLOATBAR_COLOR_FOREGROUND "RGB:FF/FF/FF"
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#define XF_FLOATBAR_MODE_NONE 0
+#define XF_FLOATBAR_MODE_DRAGGING 1
+#define XF_FLOATBAR_MODE_RESIZE_LEFT 2
+#define XF_FLOATBAR_MODE_RESIZE_RIGHT 3
+
+#define XF_FLOATBAR_BUTTON_CLOSE 1
+#define XF_FLOATBAR_BUTTON_RESTORE 2
+#define XF_FLOATBAR_BUTTON_MINIMIZE 3
+#define XF_FLOATBAR_BUTTON_LOCKED 4
+
+typedef BOOL (*OnClick)(xfFloatbar*);
+
+typedef struct
+{
+ int x;
+ int y;
+ int type;
+ bool focus;
+ bool clicked;
+ OnClick onclick;
+ Window handle;
+} xfFloatbarButton;
+
+struct xf_floatbar
+{
+ int x;
+ int y;
+ int width;
+ int height;
+ int mode;
+ int last_motion_x_root;
+ int last_motion_y_root;
+ BOOL locked;
+ xfFloatbarButton* buttons[4];
+ Window handle;
+ BOOL hasCursor;
+ xfContext* xfc;
+ DWORD flags;
+ BOOL created;
+ Window root_window;
+ char* title;
+ XFontSet fontSet;
+};
+
+static xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type);
+
+static BOOL xf_floatbar_button_onclick_close(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ return freerdp_abort_connect_context(&floatbar->xfc->common.context);
+}
+
+static BOOL xf_floatbar_button_onclick_minimize(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ xfc = floatbar->xfc;
+ xf_SetWindowMinimized(xfc, xfc->window);
+ return TRUE;
+}
+
+static BOOL xf_floatbar_button_onclick_restore(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ xf_toggle_fullscreen(floatbar->xfc);
+ return TRUE;
+}
+
+static BOOL xf_floatbar_button_onclick_locked(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+
+ floatbar->locked = (floatbar->locked) ? FALSE : TRUE;
+ return xf_floatbar_hide_and_show(floatbar);
+}
+
+BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y)
+{
+ if (!floatbar)
+ return FALSE;
+
+ floatbar->last_motion_y_root = y;
+ return TRUE;
+}
+
+BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ if (!floatbar->created)
+ return TRUE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ if (!floatbar->locked)
+ {
+ if ((floatbar->mode == XF_FLOATBAR_MODE_NONE) && (floatbar->last_motion_y_root > 10) &&
+ (floatbar->y > (FLOATBAR_HEIGHT * -1)))
+ {
+ floatbar->y = floatbar->y - 1;
+ XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y);
+ }
+ else if (floatbar->y < 0 && (floatbar->last_motion_y_root < 10))
+ {
+ floatbar->y = floatbar->y + 1;
+ XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y);
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL create_floatbar(xfFloatbar* floatbar)
+{
+ xfContext* xfc = NULL;
+ Status status = 0;
+ XWindowAttributes attr = { 0 };
+
+ WINPR_ASSERT(floatbar);
+ if (floatbar->created)
+ return TRUE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ status = XGetWindowAttributes(xfc->display, floatbar->root_window, &attr);
+ if (status == 0)
+ {
+ WLog_WARN(TAG, "XGetWindowAttributes failed");
+ return FALSE;
+ }
+ floatbar->x = attr.x + attr.width / 2 - FLOATBAR_DEFAULT_WIDTH / 2;
+ floatbar->y = 0;
+
+ if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked)
+ floatbar->y = -FLOATBAR_HEIGHT + 1;
+
+ floatbar->handle =
+ XCreateWindow(xfc->display, floatbar->root_window, floatbar->x, 0, FLOATBAR_DEFAULT_WIDTH,
+ FLOATBAR_HEIGHT, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
+ floatbar->width = FLOATBAR_DEFAULT_WIDTH;
+ floatbar->height = FLOATBAR_HEIGHT;
+ floatbar->mode = XF_FLOATBAR_MODE_NONE;
+ floatbar->buttons[0] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_CLOSE);
+ floatbar->buttons[1] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_RESTORE);
+ floatbar->buttons[2] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_MINIMIZE);
+ floatbar->buttons[3] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_LOCKED);
+ XSelectInput(xfc->display, floatbar->handle,
+ ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
+ FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask |
+ PropertyChangeMask);
+ floatbar->created = TRUE;
+ return TRUE;
+}
+
+BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen)
+{
+ int size = 0;
+ bool visible = False;
+ xfContext* xfc = NULL;
+
+ if (!floatbar || !floatbar->xfc)
+ return FALSE;
+
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc->display);
+
+ /* Only visible if enabled */
+ if (floatbar->flags & 0x0001)
+ {
+ /* Visible if fullscreen and flag visible in fullscreen mode */
+ visible |= ((floatbar->flags & 0x0010) != 0) && fullscreen;
+ /* Visible if window and flag visible in window mode */
+ visible |= ((floatbar->flags & 0x0020) != 0) && !fullscreen;
+ }
+
+ if (visible)
+ {
+ if (!create_floatbar(floatbar))
+ return FALSE;
+
+ XMapWindow(xfc->display, floatbar->handle);
+ size = ARRAYSIZE(floatbar->buttons);
+
+ for (int i = 0; i < size; i++)
+ {
+ xfFloatbarButton* button = floatbar->buttons[i];
+ XMapWindow(xfc->display, button->handle);
+ }
+
+ /* If default is hidden (and not sticky) don't show on fullscreen state changes */
+ if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked)
+ floatbar->y = -FLOATBAR_HEIGHT + 1;
+
+ xf_floatbar_hide_and_show(floatbar);
+ }
+ else if (floatbar->created)
+ {
+ XUnmapSubwindows(xfc->display, floatbar->handle);
+ XUnmapWindow(xfc->display, floatbar->handle);
+ }
+
+ return TRUE;
+}
+
+xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+ WINPR_ASSERT(floatbar->xfc->display);
+ WINPR_ASSERT(floatbar->handle);
+
+ button = (xfFloatbarButton*)calloc(1, sizeof(xfFloatbarButton));
+ button->type = type;
+
+ switch (type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_close;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_restore;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type;
+ button->onclick = xf_floatbar_button_onclick_minimize;
+ break;
+
+ case XF_FLOATBAR_BUTTON_LOCKED:
+ button->x = FLOATBAR_BORDER;
+ button->onclick = xf_floatbar_button_onclick_locked;
+ break;
+
+ default:
+ break;
+ }
+
+ button->y = 0;
+ button->focus = FALSE;
+ button->handle = XCreateWindow(floatbar->xfc->display, floatbar->handle, button->x, 0,
+ FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, CopyFromParent,
+ InputOutput, CopyFromParent, 0, NULL);
+ XSelectInput(floatbar->xfc->display, button->handle,
+ ExposureMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask |
+ LeaveWindowMask | EnterWindowMask | StructureNotifyMask);
+ return button;
+}
+
+xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* name, DWORD flags)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ WINPR_ASSERT(name);
+
+ /* Floatbar not enabled */
+ if ((flags & 0x0001) == 0)
+ return NULL;
+
+ if (!xfc)
+ return NULL;
+
+ /* Force disable with remote app */
+ if (xfc->remote_app)
+ return NULL;
+
+ xfFloatbar* floatbar = (xfFloatbar*)calloc(1, sizeof(xfFloatbar));
+
+ if (!floatbar)
+ return NULL;
+
+ floatbar->title = _strdup(name);
+
+ if (!floatbar->title)
+ goto fail;
+
+ floatbar->root_window = window;
+ floatbar->flags = flags;
+ floatbar->xfc = xfc;
+ floatbar->locked = flags & 0x0002;
+ xf_floatbar_toggle_fullscreen(floatbar, FALSE);
+ char** missingList = NULL;
+ int missingCount = 0;
+ char* defString = NULL;
+ floatbar->fontSet = XCreateFontSet(floatbar->xfc->display, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*",
+ &missingList, &missingCount, &defString);
+ if (floatbar->fontSet == NULL)
+ {
+ WLog_ERR(TAG, "Failed to create fontset");
+ }
+ XFreeStringList(missingList);
+ return floatbar;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ xf_floatbar_free(floatbar);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+static unsigned long xf_floatbar_get_color(xfFloatbar* floatbar, char* rgb_value)
+{
+ XColor color;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+
+ Display* display = floatbar->xfc->display;
+ WINPR_ASSERT(display);
+
+ Colormap cmap = DefaultColormap(display, XDefaultScreen(display));
+ XParseColor(display, cmap, rgb_value, &color);
+ XAllocColor(display, cmap, &color);
+ return color.pixel;
+}
+
+static void xf_floatbar_event_expose(xfFloatbar* floatbar)
+{
+ GC gc = NULL;
+ GC shape_gc = NULL;
+ Pixmap pmap = 0;
+ XPoint shape[5] = { 0 };
+ XPoint border[5] = { 0 };
+ int len = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(floatbar->xfc);
+
+ Display* display = floatbar->xfc->display;
+ WINPR_ASSERT(display);
+
+ /* create the pixmap that we'll use for shaping the window */
+ pmap = XCreatePixmap(display, floatbar->handle, floatbar->width, floatbar->height, 1);
+ gc = XCreateGC(display, floatbar->handle, 0, 0);
+ shape_gc = XCreateGC(display, pmap, 0, 0);
+ /* points for drawing the floatbar */
+ shape[0].x = 0;
+ shape[0].y = 0;
+ shape[1].x = floatbar->width;
+ shape[1].y = 0;
+ shape[2].x = shape[1].x - FLOATBAR_BORDER;
+ shape[2].y = FLOATBAR_HEIGHT;
+ shape[3].x = shape[0].x + FLOATBAR_BORDER;
+ shape[3].y = FLOATBAR_HEIGHT;
+ shape[4].x = shape[0].x;
+ shape[4].y = shape[0].y;
+ /* points for drawing the border of the floatbar */
+ border[0].x = shape[0].x;
+ border[0].y = shape[0].y - 1;
+ border[1].x = shape[1].x - 1;
+ border[1].y = shape[1].y - 1;
+ border[2].x = shape[2].x;
+ border[2].y = shape[2].y - 1;
+ border[3].x = shape[3].x - 1;
+ border[3].y = shape[3].y - 1;
+ border[4].x = border[0].x;
+ border[4].y = border[0].y;
+ /* Fill all pixels with 0 */
+ XSetForeground(display, shape_gc, 0);
+ XFillRectangle(display, pmap, shape_gc, 0, 0, floatbar->width, floatbar->height);
+ /* Fill all pixels which should be shown with 1 */
+ XSetForeground(display, shape_gc, 1);
+ XFillPolygon(display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin);
+ XShapeCombineMask(display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet);
+ /* draw the float bar */
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND));
+ XFillPolygon(display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin);
+ /* draw an border for the floatbar */
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER));
+ XDrawLines(display, floatbar->handle, gc, border, 5, CoordModeOrigin);
+ /* draw the host name connected to (limit to maximum file name) */
+ len = strnlen(floatbar->title, MAX_PATH);
+ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND));
+ if (floatbar->fontSet != NULL)
+ {
+ XmbDrawString(display, floatbar->handle, floatbar->fontSet, gc,
+ floatbar->width / 2 - len * 2, 15, floatbar->title, len);
+ }
+ else
+ {
+ XDrawString(display, floatbar->handle, gc, floatbar->width / 2 - len * 2, 15,
+ floatbar->title, len);
+ }
+ XFreeGC(display, gc);
+ XFreeGC(display, shape_gc);
+}
+
+static xfFloatbarButton* xf_floatbar_get_button(xfFloatbar* floatbar, Window window)
+{
+ WINPR_ASSERT(floatbar);
+ const size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ xfFloatbarButton* button = floatbar->buttons[i];
+ if (button->handle == window)
+ {
+ return button;
+ }
+ }
+
+ return NULL;
+}
+
+static void xf_floatbar_button_update_positon(xfFloatbar* floatbar)
+{
+ xfFloatbarButton* button = NULL;
+ WINPR_ASSERT(floatbar);
+ xfContext* xfc = floatbar->xfc;
+ const size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ button = floatbar->buttons[i];
+
+ switch (button->type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ button->x =
+ floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type;
+ break;
+
+ default:
+ break;
+ }
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ XMoveWindow(xfc->display, button->handle, button->x, button->y);
+ xf_floatbar_event_expose(floatbar);
+ }
+}
+
+static void xf_floatbar_button_event_expose(xfFloatbar* floatbar, Window window)
+{
+ xfFloatbarButton* button = xf_floatbar_get_button(floatbar, window);
+ static unsigned char* bits;
+ GC gc = NULL;
+ Pixmap pattern = 0;
+ xfContext* xfc = floatbar->xfc;
+
+ if (!button)
+ return;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ WINPR_ASSERT(xfc->window);
+
+ gc = XCreateGC(xfc->display, button->handle, 0, 0);
+ floatbar = xfc->window->floatbar;
+ WINPR_ASSERT(floatbar);
+
+ switch (button->type)
+ {
+ case XF_FLOATBAR_BUTTON_CLOSE:
+ bits = close_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_RESTORE:
+ bits = restore_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_MINIMIZE:
+ bits = minimize_bits;
+ break;
+
+ case XF_FLOATBAR_BUTTON_LOCKED:
+ if (floatbar->locked)
+ bits = lock_bits;
+ else
+ bits = unlock_bits;
+
+ break;
+
+ default:
+ break;
+ }
+
+ pattern = XCreateBitmapFromData(xfc->display, button->handle, (const char*)bits,
+ FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH);
+
+ if (!(button->focus))
+ XSetForeground(xfc->display, gc,
+ xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND));
+ else
+ XSetForeground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER));
+
+ XSetBackground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND));
+ XCopyPlane(xfc->display, pattern, button->handle, gc, 0, 0, FLOATBAR_BUTTON_WIDTH,
+ FLOATBAR_BUTTON_WIDTH, 0, 0, 1);
+ XFreePixmap(xfc->display, pattern);
+ XFreeGC(xfc->display, gc);
+}
+
+static void xf_floatbar_button_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(event);
+ xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ button->clicked = TRUE;
+}
+
+static void xf_floatbar_button_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ if (button->clicked)
+ button->onclick(floatbar);
+ button->clicked = FALSE;
+ }
+}
+
+static void xf_floatbar_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ switch (event->button)
+ {
+ case Button1:
+ if (event->x <= FLOATBAR_BORDER)
+ floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT;
+ else if (event->x >= (floatbar->width - FLOATBAR_BORDER))
+ floatbar->mode = XF_FLOATBAR_MODE_RESIZE_RIGHT;
+ else
+ floatbar->mode = XF_FLOATBAR_MODE_DRAGGING;
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void xf_floatbar_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event)
+{
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ switch (event->button)
+ {
+ case Button1:
+ floatbar->mode = XF_FLOATBAR_MODE_NONE;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void xf_floatbar_resize(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int x = 0;
+ int width = 0;
+ int movement = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ /* calculate movement which happened on the root window */
+ movement = event->x_root - floatbar->last_motion_x_root;
+
+ /* set x and width depending if movement happens on the left or right */
+ if (floatbar->mode == XF_FLOATBAR_MODE_RESIZE_LEFT)
+ {
+ x = floatbar->x + movement;
+ width = floatbar->width + movement * -1;
+ }
+ else
+ {
+ x = floatbar->x;
+ width = floatbar->width + movement;
+ }
+
+ /* only resize and move window if still above minimum width */
+ if (FLOATBAR_MIN_WIDTH < width)
+ {
+ XMoveResizeWindow(xfc->display, floatbar->handle, x, 0, width, floatbar->height);
+ floatbar->x = x;
+ floatbar->width = width;
+ }
+}
+
+static void xf_floatbar_dragging(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int x = 0;
+ int movement = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->window);
+ WINPR_ASSERT(xfc->display);
+
+ /* calculate movement and new x position */
+ movement = event->x_root - floatbar->last_motion_x_root;
+ x = floatbar->x + movement;
+
+ /* do nothing if floatbar would be moved out of the window */
+ if (x < 0 || (x + floatbar->width) > xfc->window->width)
+ return;
+
+ /* move window to new x position */
+ XMoveWindow(xfc->display, floatbar->handle, x, 0);
+ /* update struct values for the next event */
+ floatbar->last_motion_x_root = floatbar->last_motion_x_root + movement;
+ floatbar->x = x;
+}
+
+static void xf_floatbar_event_motionnotify(xfFloatbar* floatbar, const XMotionEvent* event)
+{
+ int mode = 0;
+ Cursor cursor = 0;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+
+ mode = floatbar->mode;
+ cursor = XCreateFontCursor(xfc->display, XC_arrow);
+
+ if ((event->state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING))
+ {
+ xf_floatbar_resize(floatbar, event);
+ }
+ else if ((event->state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING))
+ {
+ xf_floatbar_dragging(floatbar, event);
+ }
+ else
+ {
+ if (event->x <= FLOATBAR_BORDER || event->x >= floatbar->width - FLOATBAR_BORDER)
+ cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow);
+ }
+
+ XDefineCursor(xfc->display, xfc->window->handle, cursor);
+ XFreeCursor(xfc->display, cursor);
+ floatbar->last_motion_x_root = event->x_root;
+}
+
+static void xf_floatbar_button_event_focusin(xfFloatbar* floatbar, const XAnyEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ button->focus = TRUE;
+ xf_floatbar_button_event_expose(floatbar, event->window);
+ }
+}
+
+static void xf_floatbar_button_event_focusout(xfFloatbar* floatbar, const XAnyEvent* event)
+{
+ xfFloatbarButton* button = NULL;
+
+ WINPR_ASSERT(floatbar);
+ WINPR_ASSERT(event);
+
+ button = xf_floatbar_get_button(floatbar, event->window);
+
+ if (button)
+ {
+ button->focus = FALSE;
+ xf_floatbar_button_event_expose(floatbar, event->window);
+ }
+}
+
+static void xf_floatbar_event_focusout(xfFloatbar* floatbar)
+{
+ WINPR_ASSERT(floatbar);
+ xfContext* xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+
+ if (xfc->pointer)
+ {
+ WINPR_ASSERT(xfc->window);
+ WINPR_ASSERT(xfc->pointer);
+ XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor);
+ }
+}
+
+BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event)
+{
+ if (!floatbar || !floatbar->xfc || !event)
+ return FALSE;
+
+ if (!floatbar->created)
+ return FALSE;
+
+ if (event->xany.window == floatbar->handle)
+ return TRUE;
+
+ size_t size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ const xfFloatbarButton* button = floatbar->buttons[i];
+
+ if (event->xany.window == button->handle)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event)
+{
+ if (!floatbar || !floatbar->xfc || !event)
+ return FALSE;
+
+ if (!floatbar->created)
+ return FALSE;
+
+ switch (event->type)
+ {
+ case Expose:
+ if (event->xexpose.window == floatbar->handle)
+ xf_floatbar_event_expose(floatbar);
+ else
+ xf_floatbar_button_event_expose(floatbar, event->xexpose.window);
+
+ break;
+
+ case MotionNotify:
+ xf_floatbar_event_motionnotify(floatbar, &event->xmotion);
+ break;
+
+ case ButtonPress:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_buttonpress(floatbar, &event->xbutton);
+ else
+ xf_floatbar_button_event_buttonpress(floatbar, &event->xbutton);
+
+ break;
+
+ case ButtonRelease:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_buttonrelease(floatbar, &event->xbutton);
+ else
+ xf_floatbar_button_event_buttonrelease(floatbar, &event->xbutton);
+
+ break;
+
+ case EnterNotify:
+ case FocusIn:
+ if (event->xany.window != floatbar->handle)
+ xf_floatbar_button_event_focusin(floatbar, &event->xany);
+
+ break;
+
+ case LeaveNotify:
+ case FocusOut:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_event_focusout(floatbar);
+ else
+ xf_floatbar_button_event_focusout(floatbar, &event->xany);
+
+ break;
+
+ case ConfigureNotify:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_button_update_positon(floatbar);
+
+ break;
+
+ case PropertyNotify:
+ if (event->xany.window == floatbar->handle)
+ xf_floatbar_button_update_positon(floatbar);
+
+ break;
+
+ default:
+ break;
+ }
+
+ return floatbar->handle == event->xany.window;
+}
+
+static void xf_floatbar_button_free(xfContext* xfc, xfFloatbarButton* button)
+{
+ if (!button)
+ return;
+
+ if (button->handle)
+ {
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->display);
+ XUnmapWindow(xfc->display, button->handle);
+ XDestroyWindow(xfc->display, button->handle);
+ }
+
+ free(button);
+}
+
+void xf_floatbar_free(xfFloatbar* floatbar)
+{
+ size_t size = 0;
+ xfContext* xfc = NULL;
+
+ if (!floatbar)
+ return;
+
+ free(floatbar->title);
+ xfc = floatbar->xfc;
+ WINPR_ASSERT(xfc);
+
+ size = ARRAYSIZE(floatbar->buttons);
+
+ for (size_t i = 0; i < size; i++)
+ {
+ xf_floatbar_button_free(xfc, floatbar->buttons[i]);
+ floatbar->buttons[i] = NULL;
+ }
+
+ if (floatbar->handle)
+ {
+ WINPR_ASSERT(xfc->display);
+ XUnmapWindow(xfc->display, floatbar->handle);
+ XDestroyWindow(xfc->display, floatbar->handle);
+ }
+
+ free(floatbar);
+}
+
+BOOL xf_floatbar_is_locked(xfFloatbar* floatbar)
+{
+ if (!floatbar)
+ return FALSE;
+ return floatbar->mode != XF_FLOATBAR_MODE_NONE;
+}
diff --git a/client/X11/xf_floatbar.h b/client/X11/xf_floatbar.h
new file mode 100644
index 0000000..1ac7c91
--- /dev/null
+++ b/client/X11/xf_floatbar.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * 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_CLIENT_X11_FLOATBAR_H
+#define FREERDP_CLIENT_X11_FLOATBAR_H
+
+typedef struct xf_floatbar xfFloatbar;
+
+#include "xfreerdp.h"
+
+void xf_floatbar_free(xfFloatbar* floatbar);
+
+WINPR_ATTR_MALLOC(xf_floatbar_free, 1)
+xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* title, DWORD flags);
+
+BOOL xf_floatbar_is_locked(xfFloatbar* floatbar);
+BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event);
+BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event);
+BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool visible);
+BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar);
+BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y);
+
+#endif /* FREERDP_CLIENT_X11_FLOATBAR_H */
diff --git a/client/X11/xf_gfx.c b/client/X11/xf_gfx.c
new file mode 100644
index 0000000..757b424
--- /dev/null
+++ b/client/X11/xf_gfx.c
@@ -0,0 +1,476 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphics Pipeline
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2016 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 <math.h>
+#include <winpr/assert.h>
+#include <freerdp/log.h>
+#include "xf_gfx.h"
+#include "xf_rail.h"
+
+#include <X11/Xutil.h>
+
+#define TAG CLIENT_TAG("x11")
+
+static UINT xf_OutputUpdate(xfContext* xfc, xfGfxSurface* surface)
+{
+ UINT rc = ERROR_INTERNAL_ERROR;
+ UINT32 surfaceX = 0;
+ UINT32 surfaceY = 0;
+ RECTANGLE_16 surfaceRect;
+ rdpGdi* gdi = NULL;
+ const rdpSettings* settings = NULL;
+ UINT32 nbRects = 0;
+ double sx = NAN;
+ double sy = NAN;
+ const RECTANGLE_16* rects = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(surface);
+
+ gdi = xfc->common.context.gdi;
+ WINPR_ASSERT(gdi);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ surfaceX = surface->gdi.outputOriginX;
+ surfaceY = surface->gdi.outputOriginY;
+ surfaceRect.left = 0;
+ surfaceRect.top = 0;
+ surfaceRect.right = surface->gdi.mappedWidth;
+ surfaceRect.bottom = surface->gdi.mappedHeight;
+ XSetClipMask(xfc->display, xfc->gc, None);
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ region16_intersect_rect(&(surface->gdi.invalidRegion), &(surface->gdi.invalidRegion),
+ &surfaceRect);
+ sx = surface->gdi.outputTargetWidth / (double)surface->gdi.mappedWidth;
+ sy = surface->gdi.outputTargetHeight / (double)surface->gdi.mappedHeight;
+
+ if (!(rects = region16_rects(&surface->gdi.invalidRegion, &nbRects)))
+ return CHANNEL_RC_OK;
+
+ for (UINT32 x = 0; x < nbRects; x++)
+ {
+ const RECTANGLE_16* rect = &rects[x];
+ const UINT32 nXSrc = rect->left;
+ const UINT32 nYSrc = rect->top;
+ const UINT32 swidth = rect->right - nXSrc;
+ const UINT32 sheight = rect->bottom - nYSrc;
+ const UINT32 nXDst = surfaceX + nXSrc * sx;
+ const UINT32 nYDst = surfaceY + nYSrc * sy;
+ const UINT32 dwidth = swidth * sx;
+ const UINT32 dheight = sheight * sy;
+
+ if (surface->stage)
+ {
+ if (!freerdp_image_scale(surface->stage, gdi->dstFormat, surface->stageScanline, nXSrc,
+ nYSrc, dwidth, dheight, surface->gdi.data, surface->gdi.format,
+ surface->gdi.scanline, nXSrc, nYSrc, swidth, sheight))
+ goto fail;
+ }
+
+ if (xfc->remote_app)
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ xf_lock_x11(xfc);
+ xf_rail_paint_surface(xfc, surface->gdi.windowId, rect);
+ xf_unlock_x11(xfc);
+ }
+ else
+#ifdef WITH_XRENDER
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ xf_draw_screen(xfc, nXDst, nYDst, dwidth, dheight);
+ }
+ else
+#endif
+ {
+ XPutImage(xfc->display, xfc->drawable, xfc->gc, surface->image, nXSrc, nYSrc, nXDst,
+ nYDst, dwidth, dheight);
+ }
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+ region16_clear(&surface->gdi.invalidRegion);
+ XSetClipMask(xfc->display, xfc->gc, None);
+ XSync(xfc->display, False);
+ return rc;
+}
+
+static UINT xf_WindowUpdate(RdpgfxClientContext* context, xfGfxSurface* surface)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(surface);
+ return IFCALLRESULT(CHANNEL_RC_OK, context->UpdateWindowFromSurface, context, &surface->gdi);
+}
+
+static UINT xf_UpdateSurfaces(RdpgfxClientContext* context)
+{
+ UINT16 count = 0;
+ UINT status = CHANNEL_RC_OK;
+ UINT16* pSurfaceIds = NULL;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ xfContext* xfc = NULL;
+
+ if (!gdi)
+ return status;
+
+ if (gdi->suppressOutput)
+ return CHANNEL_RC_OK;
+
+ xfc = (xfContext*)gdi->context;
+ EnterCriticalSection(&context->mux);
+ context->GetSurfaceIds(context, &pSurfaceIds, &count);
+
+ for (UINT32 index = 0; index < count; index++)
+ {
+ xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface)
+ continue;
+
+ /* If UpdateSurfaceArea callback is available, the output has already been updated. */
+ if (context->UpdateSurfaceArea)
+ {
+ if (surface->gdi.handleInUpdateSurfaceArea)
+ continue;
+ }
+
+ if (surface->gdi.outputMapped)
+ status = xf_OutputUpdate(xfc, surface);
+ else if (surface->gdi.windowMapped)
+ status = xf_WindowUpdate(context, surface);
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ free(pSurfaceIds);
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height)
+{
+ UINT16 count = 0;
+ UINT status = ERROR_INTERNAL_ERROR;
+ RECTANGLE_16 invalidRect = { 0 };
+ RECTANGLE_16 intersection = { 0 };
+ UINT16* pSurfaceIds = NULL;
+ RdpgfxClientContext* context = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xfc->common.context.gdi);
+
+ context = xfc->common.context.gdi->gfx;
+ WINPR_ASSERT(context);
+
+ invalidRect.left = x;
+ invalidRect.top = y;
+ invalidRect.right = x + width;
+ invalidRect.bottom = y + height;
+ status = context->GetSurfaceIds(context, &pSurfaceIds, &count);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+ if (!TryEnterCriticalSection(&context->mux))
+ {
+ free(pSurfaceIds);
+ return CHANNEL_RC_OK;
+ }
+ for (UINT32 index = 0; index < count; index++)
+ {
+ RECTANGLE_16 surfaceRect = { 0 };
+ xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]);
+
+ if (!surface || (!surface->gdi.outputMapped && !surface->gdi.windowMapped))
+ continue;
+
+ surfaceRect.left = surface->gdi.outputOriginX;
+ surfaceRect.top = surface->gdi.outputOriginY;
+ surfaceRect.right = surface->gdi.outputOriginX + surface->gdi.outputTargetWidth;
+ surfaceRect.bottom = surface->gdi.outputOriginY + surface->gdi.outputTargetHeight;
+
+ if (rectangles_intersection(&invalidRect, &surfaceRect, &intersection))
+ {
+ /* Invalid rects are specified relative to surface origin */
+ intersection.left -= surfaceRect.left;
+ intersection.top -= surfaceRect.top;
+ intersection.right -= surfaceRect.left;
+ intersection.bottom -= surfaceRect.top;
+ region16_union_rect(&surface->gdi.invalidRegion, &surface->gdi.invalidRegion,
+ &intersection);
+ }
+ }
+
+ free(pSurfaceIds);
+ LeaveCriticalSection(&context->mux);
+ IFCALLRET(context->UpdateSurfaces, status, context);
+
+ if (status != CHANNEL_RC_OK)
+ goto fail;
+
+fail:
+ return status;
+}
+
+static UINT32 x11_pad_scanline(UINT32 scanline, UINT32 inPad)
+{
+ /* Ensure X11 alignment is met */
+ if (inPad > 0)
+ {
+ const UINT32 align = inPad / 8;
+ const UINT32 pad = align - scanline % align;
+
+ if (align != pad)
+ scanline += pad;
+ }
+
+ /* 16 byte alingment is required for ASM optimized code */
+ if (scanline % 16)
+ scanline += 16 - scanline % 16;
+
+ return scanline;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_CreateSurface(RdpgfxClientContext* context,
+ const RDPGFX_CREATE_SURFACE_PDU* createSurface)
+{
+ UINT ret = CHANNEL_RC_NO_MEMORY;
+ size_t size = 0;
+ xfGfxSurface* surface = NULL;
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ xfContext* xfc = (xfContext*)gdi->context;
+ surface = (xfGfxSurface*)calloc(1, sizeof(xfGfxSurface));
+
+ if (!surface)
+ return CHANNEL_RC_NO_MEMORY;
+
+ surface->gdi.codecs = context->codecs;
+
+ if (!surface->gdi.codecs)
+ {
+ WLog_ERR(TAG, "global GDI codecs aren't set");
+ goto out_free;
+ }
+
+ surface->gdi.surfaceId = createSurface->surfaceId;
+ surface->gdi.width = x11_pad_scanline(createSurface->width, 0);
+ surface->gdi.height = x11_pad_scanline(createSurface->height, 0);
+ surface->gdi.mappedWidth = createSurface->width;
+ surface->gdi.mappedHeight = createSurface->height;
+ surface->gdi.outputTargetWidth = createSurface->width;
+ surface->gdi.outputTargetHeight = createSurface->height;
+
+ switch (createSurface->pixelFormat)
+ {
+ case GFX_PIXEL_FORMAT_ARGB_8888:
+ surface->gdi.format = PIXEL_FORMAT_BGRA32;
+ break;
+
+ case GFX_PIXEL_FORMAT_XRGB_8888:
+ surface->gdi.format = PIXEL_FORMAT_BGRX32;
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown pixelFormat 0x%" PRIx32 "", createSurface->pixelFormat);
+ ret = ERROR_INTERNAL_ERROR;
+ goto out_free;
+ }
+
+ surface->gdi.scanline = surface->gdi.width * FreeRDPGetBytesPerPixel(surface->gdi.format);
+ surface->gdi.scanline = x11_pad_scanline(surface->gdi.scanline, xfc->scanline_pad);
+ size = 1ull * surface->gdi.scanline * surface->gdi.height;
+ surface->gdi.data = (BYTE*)winpr_aligned_malloc(size, 16);
+
+ if (!surface->gdi.data)
+ {
+ WLog_ERR(TAG, "unable to allocate GDI data");
+ goto out_free;
+ }
+
+ ZeroMemory(surface->gdi.data, size);
+
+ if (FreeRDPAreColorFormatsEqualNoAlpha(gdi->dstFormat, surface->gdi.format))
+ {
+ WINPR_ASSERT(xfc->depth != 0);
+ surface->image =
+ XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)surface->gdi.data, surface->gdi.mappedWidth,
+ surface->gdi.mappedHeight, xfc->scanline_pad, surface->gdi.scanline);
+ }
+ else
+ {
+ UINT32 width = surface->gdi.width;
+ UINT32 bytes = FreeRDPGetBytesPerPixel(gdi->dstFormat);
+ surface->stageScanline = width * bytes;
+ surface->stageScanline = x11_pad_scanline(surface->stageScanline, xfc->scanline_pad);
+ size = 1ull * surface->stageScanline * surface->gdi.height;
+ surface->stage = (BYTE*)winpr_aligned_malloc(size, 16);
+
+ if (!surface->stage)
+ {
+ WLog_ERR(TAG, "unable to allocate stage buffer");
+ goto out_free_gdidata;
+ }
+
+ ZeroMemory(surface->stage, size);
+ WINPR_ASSERT(xfc->depth != 0);
+ surface->image =
+ XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0, (char*)surface->stage,
+ surface->gdi.mappedWidth, surface->gdi.mappedHeight, xfc->scanline_pad,
+ surface->stageScanline);
+ }
+
+ if (!surface->image)
+ {
+ WLog_ERR(TAG, "an error occurred when creating the XImage");
+ goto error_surface_image;
+ }
+
+ surface->image->byte_order = LSBFirst;
+ surface->image->bitmap_bit_order = LSBFirst;
+
+ region16_init(&surface->gdi.invalidRegion);
+
+ if (context->SetSurfaceData(context, surface->gdi.surfaceId, (void*)surface) != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "an error occurred during SetSurfaceData");
+ goto error_set_surface_data;
+ }
+
+ return CHANNEL_RC_OK;
+error_set_surface_data:
+ surface->image->data = NULL;
+ XDestroyImage(surface->image);
+error_surface_image:
+ winpr_aligned_free(surface->stage);
+out_free_gdidata:
+ winpr_aligned_free(surface->gdi.data);
+out_free:
+ free(surface);
+ return ret;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_DeleteSurface(RdpgfxClientContext* context,
+ const RDPGFX_DELETE_SURFACE_PDU* deleteSurface)
+{
+ rdpCodecs* codecs = NULL;
+ xfGfxSurface* surface = NULL;
+ UINT status = 0;
+ EnterCriticalSection(&context->mux);
+ surface = (xfGfxSurface*)context->GetSurfaceData(context, deleteSurface->surfaceId);
+
+ if (surface)
+ {
+ if (surface->gdi.windowMapped)
+ IFCALL(context->UnmapWindowForSurface, context, surface->gdi.windowId);
+
+#ifdef WITH_GFX_H264
+ h264_context_free(surface->gdi.h264);
+#endif
+ surface->image->data = NULL;
+ XDestroyImage(surface->image);
+ winpr_aligned_free(surface->gdi.data);
+ winpr_aligned_free(surface->stage);
+ region16_uninit(&surface->gdi.invalidRegion);
+ codecs = surface->gdi.codecs;
+ free(surface);
+ }
+
+ status = context->SetSurfaceData(context, deleteSurface->surfaceId, NULL);
+
+ if (codecs && codecs->progressive)
+ progressive_delete_surface_context(codecs->progressive, deleteSurface->surfaceId);
+
+ LeaveCriticalSection(&context->mux);
+ return status;
+}
+
+static UINT xf_UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(surface);
+
+ rdpGdi* gdi = (rdpGdi*)context->custom;
+ WINPR_ASSERT(gdi);
+
+ xfContext* xfc = (xfContext*)gdi->context;
+ WINPR_ASSERT(gdi->context);
+
+ if (freerdp_settings_get_bool(gdi->context->settings, FreeRDP_RemoteApplicationMode))
+ return xf_AppUpdateWindowFromSurface(xfc, surface);
+
+ WLog_WARN(TAG, "function not implemented");
+ return CHANNEL_RC_OK;
+}
+
+void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx)
+{
+ rdpGdi* gdi = NULL;
+ const rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(gfx);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ gdi = xfc->common.context.gdi;
+
+ gdi_graphics_pipeline_init(gdi, gfx);
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ {
+ gfx->UpdateSurfaces = xf_UpdateSurfaces;
+ gfx->CreateSurface = xf_CreateSurface;
+ gfx->DeleteSurface = xf_DeleteSurface;
+ }
+ gfx->UpdateWindowFromSurface = xf_UpdateWindowFromSurface;
+}
+
+void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx)
+{
+ rdpGdi* gdi = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ gdi = xfc->common.context.gdi;
+ gdi_graphics_pipeline_uninit(gdi, gfx);
+}
diff --git a/client/X11/xf_gfx.h b/client/X11/xf_gfx.h
new file mode 100644
index 0000000..934e85a
--- /dev/null
+++ b/client/X11/xf_gfx.h
@@ -0,0 +1,45 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphics Pipeline
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CLIENT_X11_GFX_H
+#define FREERDP_CLIENT_X11_GFX_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/gdi/gfx.h>
+
+struct xf_gfx_surface
+{
+ gdiGfxSurface gdi;
+ BYTE* stage;
+ UINT32 stageScanline;
+ XImage* image;
+};
+typedef struct xf_gfx_surface xfGfxSurface;
+
+UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height);
+
+void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx);
+
+void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx);
+
+#endif /* FREERDP_CLIENT_X11_GFX_H */
diff --git a/client/X11/xf_graphics.c b/client/X11/xf_graphics.c
new file mode 100644
index 0000000..10b0eb5
--- /dev/null
+++ b/client/X11/xf_graphics.c
@@ -0,0 +1,532 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphical Objects
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#include <float.h>
+#include <math.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/rfx.h>
+
+#include "xf_graphics.h"
+#include "xf_event.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer);
+
+BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color)
+{
+ UINT32 SrcFormat = 0;
+ BYTE r = 0;
+ BYTE g = 0;
+ BYTE b = 0;
+ BYTE a = 0;
+
+ if (!xfc || !color)
+ return FALSE;
+
+ rdpGdi* gdi = xfc->common.context.gdi;
+
+ if (!gdi)
+ return FALSE;
+
+ rdpSettings* settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ switch (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth))
+ {
+ case 32:
+ case 24:
+ SrcFormat = PIXEL_FORMAT_BGR24;
+ break;
+
+ case 16:
+ SrcFormat = PIXEL_FORMAT_RGB16;
+ break;
+
+ case 15:
+ SrcFormat = PIXEL_FORMAT_RGB15;
+ break;
+
+ case 8:
+ SrcFormat = PIXEL_FORMAT_RGB8;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ FreeRDPSplitColor(srcColor, SrcFormat, &r, &g, &b, &a, &gdi->palette);
+ color->blue = (unsigned short)(b << 8);
+ color->green = (unsigned short)(g << 8);
+ color->red = (unsigned short)(r << 8);
+ color->flags = DoRed | DoGreen | DoBlue;
+
+ if (XAllocColor(xfc->display, xfc->colormap, color) == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL xf_Pointer_GetCursorForCurrentScale(rdpContext* context, rdpPointer* pointer,
+ Cursor* cursor)
+{
+#if defined(WITH_XCURSOR) && defined(WITH_XRENDER)
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+ XcursorImage ci = { 0 };
+ int cursorIndex = -1;
+
+ if (!context || !pointer || !context->gdi)
+ return FALSE;
+
+ rdpSettings* settings = xfc->common.context.settings;
+
+ if (!settings)
+ return FALSE;
+
+ const double xscale = (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)
+ ? xfc->scaledWidth / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopWidth)
+ : 1);
+ const double yscale = (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)
+ ? xfc->scaledHeight / (double)freerdp_settings_get_uint32(
+ settings, FreeRDP_DesktopHeight)
+ : 1);
+ const UINT32 xTargetSize = MAX(1, pointer->width * xscale);
+ const UINT32 yTargetSize = MAX(1, pointer->height * yscale);
+
+ WLog_DBG(TAG, "scaled: %" PRIu32 "x%" PRIu32 ", desktop: %" PRIu32 "x%" PRIu32,
+ xfc->scaledWidth, xfc->scaledHeight,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight));
+ for (UINT32 i = 0; i < xpointer->nCursors; i++)
+ {
+ if ((xpointer->cursorWidths[i] == xTargetSize) &&
+ (xpointer->cursorHeights[i] == yTargetSize))
+ {
+ cursorIndex = i;
+ }
+ }
+
+ if (cursorIndex == -1)
+ {
+ UINT32 CursorFormat = 0;
+ xf_lock_x11(xfc);
+
+ if (!xfc->invert)
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32;
+ else
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32;
+
+ if (xpointer->nCursors == xpointer->mCursors)
+ {
+ void* tmp2 = NULL;
+ xpointer->mCursors = (xpointer->mCursors == 0 ? 1 : xpointer->mCursors * 2);
+
+ tmp2 = realloc(xpointer->cursorWidths, sizeof(UINT32) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursorWidths = tmp2;
+
+ tmp2 = realloc(xpointer->cursorHeights, sizeof(UINT32) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursorHeights = (UINT32*)tmp2;
+
+ tmp2 = realloc(xpointer->cursors, sizeof(Cursor) * xpointer->mCursors);
+ if (!tmp2)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ xpointer->cursors = (Cursor*)tmp2;
+ }
+
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = xTargetSize;
+ ci.height = yTargetSize;
+ ci.xhot = pointer->xPos * xscale;
+ ci.yhot = pointer->yPos * yscale;
+ const size_t size = 1ull * ci.height * ci.width * FreeRDPGetBytesPerPixel(CursorFormat);
+
+ void* tmp = winpr_aligned_malloc(size, 16);
+ if (!tmp)
+ {
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ ci.pixels = (XcursorPixel*)tmp;
+
+ const double xs = fabs(fabs(xscale) - 1.0);
+ const double ys = fabs(fabs(yscale) - 1.0);
+
+ WLog_DBG(TAG,
+ "cursorIndex %" PRId32 " scaling pointer %" PRIu32 "x%" PRIu32 " --> %" PRIu32
+ "x%" PRIu32 " [%lfx%lf]",
+ cursorIndex, pointer->width, pointer->height, ci.width, ci.height, xscale, yscale);
+ if ((xs > DBL_EPSILON) || (ys > DBL_EPSILON))
+ {
+ if (!freerdp_image_scale((BYTE*)ci.pixels, CursorFormat, 0, 0, 0, ci.width, ci.height,
+ (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0,
+ pointer->width, pointer->height))
+ {
+ winpr_aligned_free(tmp);
+ xf_unlock_x11(xfc);
+ return FALSE;
+ }
+ }
+ else
+ {
+ ci.pixels = xpointer->cursorPixels;
+ }
+
+ cursorIndex = xpointer->nCursors;
+ xpointer->cursorWidths[cursorIndex] = ci.width;
+ xpointer->cursorHeights[cursorIndex] = ci.height;
+ xpointer->cursors[cursorIndex] = XcursorImageLoadCursor(xfc->display, &ci);
+ xpointer->nCursors += 1;
+
+ winpr_aligned_free(tmp);
+
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ WLog_DBG(TAG, "using cached cursor %" PRId32, cursorIndex);
+ }
+
+ cursor[0] = xpointer->cursors[cursorIndex];
+#endif
+ return TRUE;
+}
+
+/* Pointer Class */
+static Window xf_Pointer_get_window(xfContext* xfc)
+{
+ if (!xfc)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid context");
+ return 0;
+ }
+ if (xfc->remote_app)
+ {
+ if (!xfc->appWindow)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid appWindow");
+ return 0;
+ }
+ return xfc->appWindow->handle;
+ }
+ else
+ {
+ if (!xfc->window)
+ {
+ WLog_WARN(TAG, "xf_Pointer: Invalid window");
+ return 0;
+ }
+ return xfc->window->handle;
+ }
+}
+
+BOOL xf_pointer_update_scale(xfContext* xfc)
+{
+ xfPointer* pointer = NULL;
+ WINPR_ASSERT(xfc);
+
+ pointer = xfc->pointer;
+ if (!pointer)
+ return TRUE;
+
+ return xf_Pointer_Set(&xfc->common.context, &xfc->pointer->pointer);
+}
+
+static BOOL xf_Pointer_New(rdpContext* context, rdpPointer* pointer)
+{
+ BOOL rc = FALSE;
+
+#ifdef WITH_XCURSOR
+ UINT32 CursorFormat = 0;
+ size_t size = 0;
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+
+ if (!context || !pointer || !context->gdi)
+ goto fail;
+
+ if (!xfc->invert)
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32;
+ else
+ CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32;
+
+ xpointer->nCursors = 0;
+ xpointer->mCursors = 0;
+
+ size = 1ull * pointer->height * pointer->width * FreeRDPGetBytesPerPixel(CursorFormat);
+
+ if (!(xpointer->cursorPixels = (XcursorPixel*)winpr_aligned_malloc(size, 16)))
+ goto fail;
+
+ if (!freerdp_image_copy_from_pointer_data(
+ (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, pointer->width, pointer->height,
+ pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData,
+ pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette))
+ {
+ winpr_aligned_free(xpointer->cursorPixels);
+ goto fail;
+ }
+
+#endif
+
+ rc = TRUE;
+
+fail:
+ WLog_DBG(TAG, "%p", rc ? pointer : NULL);
+ return rc;
+}
+
+static void xf_Pointer_Free(rdpContext* context, rdpPointer* pointer)
+{
+ WLog_DBG(TAG, "%p", pointer);
+
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ xfPointer* xpointer = (xfPointer*)pointer;
+
+ xf_lock_x11(xfc);
+
+ winpr_aligned_free(xpointer->cursorPixels);
+ free(xpointer->cursorWidths);
+ free(xpointer->cursorHeights);
+
+ for (UINT32 i = 0; i < xpointer->nCursors; i++)
+ {
+ XFreeCursor(xfc->display, xpointer->cursors[i]);
+ }
+
+ free(xpointer->cursors);
+ xpointer->nCursors = 0;
+ xpointer->mCursors = 0;
+
+ xf_unlock_x11(xfc);
+#endif
+}
+
+static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer)
+{
+ WLog_DBG(TAG, "%p", pointer);
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ Window handle = xf_Pointer_get_window(xfc);
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(pointer);
+
+ xfc->pointer = (xfPointer*)pointer;
+
+ /* in RemoteApp mode, window can be null if none has had focus */
+
+ if (handle)
+ {
+ if (!xf_Pointer_GetCursorForCurrentScale(context, pointer, &(xfc->pointer->cursor)))
+ return FALSE;
+ xf_lock_x11(xfc);
+ XDefineCursor(xfc->display, handle, xfc->pointer->cursor);
+ xf_unlock_x11(xfc);
+ }
+ else
+ {
+ WLog_WARN(TAG, "handle=%ld", handle);
+ }
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetNull(rdpContext* context)
+{
+ WLog_DBG(TAG, "called");
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ static Cursor nullcursor = None;
+ Window handle = xf_Pointer_get_window(xfc);
+ xf_lock_x11(xfc);
+
+ if (nullcursor == None)
+ {
+ XcursorImage ci = { 0 };
+ XcursorPixel xp = 0;
+
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = ci.height = 1;
+ ci.xhot = ci.yhot = 0;
+ ci.pixels = &xp;
+ nullcursor = XcursorImageLoadCursor(xfc->display, &ci);
+ }
+
+ xfc->pointer = NULL;
+
+ if ((handle) && (nullcursor != None))
+ XDefineCursor(xfc->display, handle, nullcursor);
+
+ xf_unlock_x11(xfc);
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetDefault(rdpContext* context)
+{
+ WLog_DBG(TAG, "called");
+#ifdef WITH_XCURSOR
+ xfContext* xfc = (xfContext*)context;
+ Window handle = xf_Pointer_get_window(xfc);
+ xf_lock_x11(xfc);
+ xfc->pointer = NULL;
+
+ if (handle)
+ XUndefineCursor(xfc->display, handle);
+
+ xf_unlock_x11(xfc);
+#endif
+ return TRUE;
+}
+
+static BOOL xf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
+{
+ xfContext* xfc = (xfContext*)context;
+ XWindowAttributes current = { 0 };
+ XSetWindowAttributes tmp = { 0 };
+ BOOL ret = FALSE;
+ Status rc = 0;
+ Window handle = xf_Pointer_get_window(xfc);
+
+ if (!handle)
+ {
+ WLog_WARN(TAG, "focus %d, handle%lu", xfc->focused, handle);
+ return TRUE;
+ }
+
+ WLog_DBG(TAG, "%" PRIu32 "x%" PRIu32, x, y);
+ if (!xfc->focused)
+ return TRUE;
+
+ xf_adjust_coordinates_to_screen(xfc, &x, &y);
+
+ xf_lock_x11(xfc);
+
+ rc = XGetWindowAttributes(xfc->display, handle, &current);
+ if (rc == 0)
+ {
+ WLog_WARN(TAG, "XGetWindowAttributes==%d", rc);
+ goto out;
+ }
+
+ tmp.event_mask = (current.your_event_mask & ~(PointerMotionMask));
+
+ rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp);
+ if (rc == 0)
+ {
+ WLog_WARN(TAG, "XChangeWindowAttributes==%d", rc);
+ goto out;
+ }
+
+ rc = XWarpPointer(xfc->display, handle, handle, 0, 0, 0, 0, x, y);
+ if (rc == 0)
+ WLog_WARN(TAG, "XWarpPointer==%d", rc);
+ tmp.event_mask = current.your_event_mask;
+ rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp);
+ if (rc == 0)
+ WLog_WARN(TAG, "2.try XChangeWindowAttributes==%d", rc);
+ ret = TRUE;
+out:
+ xf_unlock_x11(xfc);
+ return ret;
+}
+
+/* Graphics Module */
+BOOL xf_register_pointer(rdpGraphics* graphics)
+{
+ rdpPointer pointer = { 0 };
+
+ pointer.size = sizeof(xfPointer);
+ pointer.New = xf_Pointer_New;
+ pointer.Free = xf_Pointer_Free;
+ pointer.Set = xf_Pointer_Set;
+ pointer.SetNull = xf_Pointer_SetNull;
+ pointer.SetDefault = xf_Pointer_SetDefault;
+ pointer.SetPosition = xf_Pointer_SetPosition;
+ graphics_register_pointer(graphics, &pointer);
+ return TRUE;
+}
+
+UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned)
+{
+ UINT32 DstFormat = 0;
+ BOOL invert = FALSE;
+
+ if (!xfc)
+ return 0;
+
+ invert = xfc->invert;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ if (xfc->depth == 32)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32;
+ else if (xfc->depth == 30)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32_DEPTH30 : PIXEL_FORMAT_BGRX32_DEPTH30;
+ else if (xfc->depth == 24)
+ {
+ if (aligned)
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32;
+ else
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24;
+ }
+ else if (xfc->depth == 16)
+ DstFormat = PIXEL_FORMAT_RGB16;
+ else if (xfc->depth == 15)
+ DstFormat = PIXEL_FORMAT_RGB15;
+ else
+ DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32;
+
+ return DstFormat;
+}
diff --git a/client/X11/xf_graphics.h b/client/X11/xf_graphics.h
new file mode 100644
index 0000000..a194f43
--- /dev/null
+++ b/client/X11/xf_graphics.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Graphical Objects
+ *
+ * Copyright 2011 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_CLIENT_X11_GRAPHICS_H
+#define FREERDP_CLIENT_X11_GRAPHICS_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+BOOL xf_register_pointer(rdpGraphics* graphics);
+
+BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color);
+UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned);
+
+BOOL xf_pointer_update_scale(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_GRAPHICS_H */
diff --git a/client/X11/xf_input.c b/client/X11/xf_input.c
new file mode 100644
index 0000000..f1cdc83
--- /dev/null
+++ b/client/X11/xf_input.c
@@ -0,0 +1,946 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include <math.h>
+#include <float.h>
+#include <limits.h>
+
+#include "xf_event.h"
+#include "xf_input.h"
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_XI
+
+#define PAN_THRESHOLD 50
+#define ZOOM_THRESHOLD 10
+
+#define MIN_FINGER_DIST 5
+
+static int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype);
+
+#ifdef DEBUG_XINPUT
+static const char* xf_input_get_class_string(int class)
+{
+ if (class == XIKeyClass)
+ return "XIKeyClass";
+ else if (class == XIButtonClass)
+ return "XIButtonClass";
+ else if (class == XIValuatorClass)
+ return "XIValuatorClass";
+ else if (class == XIScrollClass)
+ return "XIScrollClass";
+ else if (class == XITouchClass)
+ return "XITouchClass";
+
+ return "XIUnknownClass";
+}
+#endif
+
+static BOOL register_input_events(xfContext* xfc, Window window)
+{
+#define MAX_NR_MASKS 64
+ int ndevices = 0;
+ int nmasks = 0;
+ XIEventMask evmasks[MAX_NR_MASKS] = { 0 };
+ BYTE masks[MAX_NR_MASKS][XIMaskLen(XI_LASTEVENT)] = { 0 };
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XIDeviceInfo* info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices);
+
+ for (int i = 0; i < MIN(ndevices, MAX_NR_MASKS); i++)
+ {
+ BOOL used = FALSE;
+ XIDeviceInfo* dev = &info[i];
+
+ evmasks[nmasks].mask = masks[nmasks];
+ evmasks[nmasks].mask_len = sizeof(masks[0]);
+ evmasks[nmasks].deviceid = dev->deviceid;
+
+ /* Ignore virtual core pointer */
+ if (strcmp(dev->name, "Virtual core pointer") == 0)
+ continue;
+
+ for (int j = 0; j < dev->num_classes; j++)
+ {
+ const XIAnyClassInfo* class = dev->classes[j];
+
+ switch (class->type)
+ {
+ case XITouchClass:
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ const XITouchClassInfo* t = (const XITouchClassInfo*)class;
+ if (t->mode == XIDirectTouch)
+ {
+ WLog_DBG(
+ TAG,
+ "%s %s touch device (id: %d, mode: %d), supporting %d touches.",
+ dev->name, (t->mode == XIDirectTouch) ? "direct" : "dependent",
+ dev->deviceid, t->mode, t->num_touches);
+ XISetMask(masks[nmasks], XI_TouchBegin);
+ XISetMask(masks[nmasks], XI_TouchUpdate);
+ XISetMask(masks[nmasks], XI_TouchEnd);
+ }
+ }
+ break;
+ case XIButtonClass:
+ {
+ const XIButtonClassInfo* t = (const XIButtonClassInfo*)class;
+ WLog_DBG(TAG, "%s button device (id: %d, mode: %d)", dev->name, dev->deviceid,
+ t->num_buttons);
+ XISetMask(masks[nmasks], XI_ButtonPress);
+ XISetMask(masks[nmasks], XI_ButtonRelease);
+ XISetMask(masks[nmasks], XI_Motion);
+ used = TRUE;
+ break;
+ }
+ case XIValuatorClass:
+ {
+ const XIValuatorClassInfo* t = (const XIValuatorClassInfo*)class;
+ char* name = t->label ? XGetAtomName(xfc->display, t->label) : NULL;
+
+ WLog_DBG(TAG, "%s device (id: %d) valuator %d label %s range %f - %f",
+ dev->name, dev->deviceid, t->number, name ? name : "None", t->min,
+ t->max);
+ free(name);
+
+ if (t->number == 2)
+ {
+ double max_pressure = t->max;
+
+ char devName[200] = { 0 };
+ strncpy(devName, dev->name, ARRAYSIZE(devName) - 1);
+ CharLowerBuffA(devName, ARRAYSIZE(devName));
+
+ if (strstr(devName, "eraser") != NULL)
+ {
+ if (freerdp_client_handle_pen(&xfc->common,
+ FREERDP_PEN_REGISTER |
+ FREERDP_PEN_IS_INVERTED |
+ FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered eraser");
+ }
+ else if (strstr(devName, "stylus") != NULL ||
+ strstr(devName, "pen") != NULL)
+ {
+ if (freerdp_client_handle_pen(
+ &xfc->common, FREERDP_PEN_REGISTER | FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered pen");
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (used)
+ nmasks++;
+ }
+
+ XIFreeDeviceInfo(info);
+
+ if (nmasks > 0)
+ {
+ Status xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks);
+ if (xstatus != 0)
+ WLog_WARN(TAG, "XISelectEvents returned %d", xstatus);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_raw_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_client_use_relative_mouse_events(&xfc->common))
+ {
+ XISetMask(mask_bytes, XI_RawMotion);
+ XISetMask(mask_bytes, XI_RawButtonPress);
+ XISetMask(mask_bytes, XI_RawButtonRelease);
+
+ mask.deviceid = XIAllMasterDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_device_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XISetMask(mask_bytes, XI_DeviceChanged);
+ XISetMask(mask_bytes, XI_HierarchyChanged);
+
+ mask.deviceid = XIAllDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+
+ return TRUE;
+}
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ int major = XI_2_Major;
+ int minor = XI_2_Minor;
+ int opcode = 0;
+ int event = 0;
+ int error = 0;
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfc->firstDist = -1.0;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->active_contacts = 0;
+
+ if (!XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error))
+ {
+ WLog_WARN(TAG, "XInput extension not available.");
+ return -1;
+ }
+
+ xfc->XInputOpcode = opcode;
+ XIQueryVersion(xfc->display, &major, &minor);
+
+ if ((major < XI_2_Major) || ((major == XI_2_Major) && (minor < 2)))
+ {
+ WLog_WARN(TAG, "Server does not support XI 2.2");
+ return -1;
+ }
+ else
+ {
+ int scr = DefaultScreen(xfc->display);
+ Window root = RootWindow(xfc->display, scr);
+
+ if (!register_raw_events(xfc, root))
+ return -1;
+ if (!register_input_events(xfc, window))
+ return -1;
+ if (!register_device_events(xfc, window))
+ return -1;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_is_duplicate(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ if ((xfc->lastEvent.time == event->time) && (xfc->lastEvType == cookie->evtype) &&
+ (xfc->lastEvent.detail == event->detail) &&
+ (fabs(xfc->lastEvent.event_x - event->event_x) < DBL_EPSILON) &&
+ (fabs(xfc->lastEvent.event_y - event->event_y) < DBL_EPSILON))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void xf_input_save_last_event(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ xfc->lastEvType = cookie->evtype;
+ xfc->lastEvent.time = event->time;
+ xfc->lastEvent.detail = event->detail;
+ xfc->lastEvent.event_x = event->event_x;
+ xfc->lastEvent.event_y = event->event_y;
+}
+
+static void xf_input_detect_pan(xfContext* xfc)
+{
+ double dx[2];
+ double dy[2];
+ double px = NAN;
+ double py = NAN;
+ double dist_x = NAN;
+ double dist_y = NAN;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ return;
+ }
+
+ dx[0] = xfc->contacts[0].pos_x - xfc->contacts[0].last_x;
+ dx[1] = xfc->contacts[1].pos_x - xfc->contacts[1].last_x;
+ dy[0] = xfc->contacts[0].pos_y - xfc->contacts[0].last_y;
+ dy[1] = xfc->contacts[1].pos_y - xfc->contacts[1].last_y;
+ px = fabs(dx[0]) < fabs(dx[1]) ? dx[0] : dx[1];
+ py = fabs(dy[0]) < fabs(dy[1]) ? dy[0] : dy[1];
+ xfc->px_vector += px;
+ xfc->py_vector += py;
+ dist_x = fabs(xfc->contacts[0].pos_x - xfc->contacts[1].pos_x);
+ dist_y = fabs(xfc->contacts[0].pos_y - xfc->contacts[1].pos_y);
+
+ if (dist_y > MIN_FINGER_DIST)
+ {
+ if (xfc->px_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->px_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = -5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+
+ if (dist_x > MIN_FINGER_DIST)
+ {
+ if (xfc->py_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = 5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->py_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = -5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+}
+
+static void xf_input_detect_pinch(xfContext* xfc)
+{
+ double dist = NAN;
+ double delta = NAN;
+ ZoomingChangeEventArgs e;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ xfc->firstDist = -1.0;
+ return;
+ }
+
+ /* first calculate the distance */
+ dist = sqrt(pow(xfc->contacts[1].pos_x - xfc->contacts[0].last_x, 2.0) +
+ pow(xfc->contacts[1].pos_y - xfc->contacts[0].last_y, 2.0));
+
+ /* if this is the first 2pt touch */
+ if (xfc->firstDist <= 0)
+ {
+ xfc->firstDist = dist;
+ xfc->lastDist = xfc->firstDist;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ else
+ {
+ delta = xfc->lastDist - dist;
+
+ if (delta > 1.0)
+ delta = 1.0;
+
+ if (delta < -1.0)
+ delta = -1.0;
+
+ /* compare the current distance to the first one */
+ xfc->z_vector += delta;
+ xfc->lastDist = dist;
+
+ if (xfc->z_vector > ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = -10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+
+ if (xfc->z_vector < -ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = 10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ }
+}
+
+static void xf_input_touch_begin(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == 0)
+ {
+ xfc->contacts[i].id = event->detail;
+ xfc->contacts[i].count = 1;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xfc->active_contacts++;
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_update(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].count++;
+ xfc->contacts[i].last_x = xfc->contacts[i].pos_x;
+ xfc->contacts[i].last_y = xfc->contacts[i].pos_y;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xf_input_detect_pinch(xfc);
+ xf_input_detect_pan(xfc);
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_end(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].id = 0;
+ xfc->contacts[i].count = 0;
+ xfc->active_contacts--;
+ break;
+ }
+ }
+}
+
+static int xf_input_handle_event_local(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_begin(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchUpdate:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_update(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchEnd:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_end(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ default:
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#ifdef WITH_DEBUG_X11
+static char* xf_input_touch_state_string(DWORD flags)
+{
+ if (flags & RDPINPUT_CONTACT_FLAG_DOWN)
+ return "RDPINPUT_CONTACT_FLAG_DOWN";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UPDATE)
+ return "RDPINPUT_CONTACT_FLAG_UPDATE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UP)
+ return "RDPINPUT_CONTACT_FLAG_UP";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INRANGE)
+ return "RDPINPUT_CONTACT_FLAG_INRANGE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INCONTACT)
+ return "RDPINPUT_CONTACT_FLAG_INCONTACT";
+ else if (flags & RDPINPUT_CONTACT_FLAG_CANCELED)
+ return "RDPINPUT_CONTACT_FLAG_CANCELED";
+ else
+ return "RDPINPUT_CONTACT_FLAG_UNKNOWN";
+}
+#endif
+
+static void xf_input_hide_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+
+ if (!xfc->cursorHidden)
+ {
+ XcursorImage ci = { 0 };
+ XcursorPixel xp = 0;
+ static Cursor nullcursor = None;
+ xf_lock_x11(xfc);
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = ci.height = 1;
+ ci.xhot = ci.yhot = 0;
+ ci.pixels = &xp;
+ nullcursor = XcursorImageLoadCursor(xfc->display, &ci);
+
+ if ((xfc->window) && (nullcursor != None))
+ XDefineCursor(xfc->display, xfc->window->handle, nullcursor);
+
+ xfc->cursorHidden = TRUE;
+ xf_unlock_x11(xfc);
+ }
+
+#endif
+}
+
+static void xf_input_show_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+ xf_lock_x11(xfc);
+
+ if (xfc->cursorHidden)
+ {
+ if (xfc->window)
+ {
+ if (!xfc->pointer)
+ XUndefineCursor(xfc->display, xfc->window->handle);
+ else
+ XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor);
+ }
+
+ xfc->cursorHidden = FALSE;
+ }
+
+ xf_unlock_x11(xfc);
+#endif
+}
+
+static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype)
+{
+ int x = 0;
+ int y = 0;
+ int touchId = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return 0;
+
+ xf_input_hide_cursor(xfc);
+ touchId = event->detail;
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ switch (evtype)
+ {
+ case XI_TouchBegin:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, 0, x, y);
+ break;
+ case XI_TouchUpdate:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, 0, x, y);
+ break;
+ case XI_TouchEnd:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, 0, x, y);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_pen_remote(xfContext* xfc, XIDeviceEvent* event, int evtype, int deviceid)
+{
+ int x = 0;
+ int y = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ xf_input_hide_cursor(xfc);
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ double pressure = 0.0;
+ double* val = event->valuators.values;
+ for (int i = 0; i < MIN(event->valuators.mask_len * 8, 3); i++)
+ {
+ if (XIMaskIsSet(event->valuators.mask, i))
+ {
+ double value = *val++;
+ if (i == 2)
+ pressure = value;
+ }
+ }
+
+ UINT32 flags = FREERDP_PEN_HAS_PRESSURE;
+ if ((evtype == XI_ButtonPress) || (evtype == XI_ButtonRelease))
+ {
+ WLog_DBG(TAG, "pen button %d", event->detail);
+ switch (event->detail)
+ {
+ case 1:
+ break;
+ case 3:
+ flags |= FREERDP_PEN_BARREL_PRESSED;
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ flags |= FREERDP_PEN_PRESS;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_Motion:
+ flags |= FREERDP_PEN_MOTION;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_ButtonRelease:
+ flags |= FREERDP_PEN_RELEASE;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+static int xf_input_pens_unhover(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ freerdp_client_pen_cancel_all(&xfc->common);
+ return 0;
+}
+
+int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype)
+{
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xevent);
+ WINPR_ASSERT(event);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfWindow* window = xfc->window;
+ if (window)
+ {
+ if (xf_floatbar_is_locked(window->floatbar))
+ return 0;
+ }
+
+ xf_input_show_cursor(xfc);
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, TRUE);
+ break;
+
+ case XI_ButtonRelease:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, FALSE);
+ break;
+
+ case XI_Motion:
+ xfc->xi_event = TRUE;
+ xf_generic_MotionNotify(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app);
+ break;
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ xf_generic_RawButtonEvent(xfc, ev->detail, xfc->remote_app,
+ evtype == XI_RawButtonPress);
+ }
+ break;
+ case XI_RawMotion:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ double x = 0.0;
+ double y = 0.0;
+ if (XIMaskIsSet(ev->valuators.mask, 0))
+ x = ev->raw_values[0];
+ if (XIMaskIsSet(ev->valuators.mask, 1))
+ y = ev->raw_values[1];
+
+ xf_generic_RawMotionNotify(xfc, (int)x, (int)y, event->event, xfc->remote_app);
+ }
+ break;
+ case XI_DeviceChanged:
+ {
+ const XIDeviceChangedEvent* ev = (const XIDeviceChangedEvent*)event;
+ if (ev->reason != XIDeviceChange)
+ break;
+
+ /*
+ * TODO:
+ * 1. Register only changed devices.
+ * 2. Both `XIDeviceChangedEvent` and `XIHierarchyEvent` have no target
+ * `Window` which is used to register xinput events. So assume
+ * `xfc->window` created by `xf_CreateDesktopWindow` is the same
+ * `Window` we registered.
+ */
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ }
+ break;
+ case XI_HierarchyChanged:
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ break;
+
+ default:
+ WLog_WARN(TAG, "Unhandled event %d: Event was registered but is not handled!", evtype);
+ break;
+ }
+
+ return 0;
+}
+
+static int xf_input_handle_event_remote(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ xf_input_pens_unhover(xfc);
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case XI_TouchUpdate:
+ case XI_TouchEnd:
+ xf_input_touch_remote(xfc, cookie.cc->data, cookie.cc->evtype);
+ break;
+ case XI_ButtonPress:
+ case XI_Motion:
+ case XI_ButtonRelease:
+ {
+ WLog_DBG(TAG, "checking for pen");
+ XIDeviceEvent* deviceEvent = (XIDeviceEvent*)cookie.cc->data;
+ int deviceid = deviceEvent->deviceid;
+
+ if (freerdp_client_is_pen(&xfc->common, deviceid))
+ {
+ if (!xf_input_pen_remote(xfc, cookie.cc->data, cookie.cc->evtype, deviceid))
+ {
+ // XXX: don't show cursor
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ }
+ break;
+ }
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ default:
+ xf_input_pens_unhover(xfc);
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#else
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ return 0;
+}
+
+#endif
+
+int xf_input_handle_event(xfContext* xfc, const XEvent* event)
+{
+#ifdef WITH_XI
+ const rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ return xf_input_handle_event_remote(xfc, event);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+ else
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+
+#else
+ return 0;
+#endif
+}
diff --git a/client/X11/xf_input.h b/client/X11/xf_input.h
new file mode 100644
index 0000000..a961512
--- /dev/null
+++ b/client/X11/xf_input.h
@@ -0,0 +1,33 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 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_CLIENT_X11_INPUT_H
+#define FREERDP_CLIENT_X11_INPUT_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+int xf_input_init(xfContext* xfc, Window window);
+int xf_input_handle_event(xfContext* xfc, const XEvent* event);
+
+#endif /* FREERDP_CLIENT_X11_INPUT_H */
diff --git a/client/X11/xf_keyboard.c b/client/X11/xf_keyboard.c
new file mode 100644
index 0000000..9b575c2
--- /dev/null
+++ b/client/X11/xf_keyboard.c
@@ -0,0 +1,746 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Handling
+ *
+ * Copyright 2011 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 <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/assert.h>
+#include <winpr/collections.h>
+
+#include <freerdp/utils/string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <X11/XKBlib.h>
+
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_event.h"
+
+#include "xf_keyboard.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static void xf_keyboard_modifier_map_free(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ if (xfc->modifierMap)
+ {
+ XFreeModifiermap(xfc->modifierMap);
+ xfc->modifierMap = NULL;
+ }
+}
+
+BOOL xf_keyboard_update_modifier_map(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ xf_keyboard_modifier_map_free(xfc);
+ xfc->modifierMap = XGetModifierMapping(xfc->display);
+ return xfc->modifierMap != NULL;
+}
+
+static void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* ev);
+
+static BOOL xf_sync_kbd_state(xfContext* xfc)
+{
+ const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
+
+ WINPR_ASSERT(xfc);
+ return freerdp_input_send_synchronize_event(xfc->common.context.input, syncFlags);
+}
+
+static void xf_keyboard_clear(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ ZeroMemory(xfc->KeyboardState, sizeof(xfc->KeyboardState));
+}
+
+static BOOL xf_keyboard_action_script_init(xfContext* xfc)
+{
+ wObject* obj = NULL;
+ FILE* keyScript = NULL;
+ char buffer[1024] = { 0 };
+ char command[1024] = { 0 };
+ const rdpSettings* settings = NULL;
+ const char* ActionScript = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript);
+ xfc->actionScriptExists = winpr_PathFileExists(ActionScript);
+
+ if (!xfc->actionScriptExists)
+ return FALSE;
+
+ xfc->keyCombinations = ArrayList_New(TRUE);
+
+ if (!xfc->keyCombinations)
+ return FALSE;
+
+ obj = ArrayList_Object(xfc->keyCombinations);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = winpr_ObjectStringClone;
+ obj->fnObjectFree = winpr_ObjectStringFree;
+ sprintf_s(command, sizeof(command), "%s key", ActionScript);
+ keyScript = popen(command, "r");
+
+ if (!keyScript)
+ {
+ xfc->actionScriptExists = FALSE;
+ return FALSE;
+ }
+
+ while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (!buffer || !ArrayList_Append(xfc->keyCombinations, buffer))
+ {
+ ArrayList_Free(xfc->keyCombinations);
+ xfc->actionScriptExists = FALSE;
+ pclose(keyScript);
+ return FALSE;
+ }
+ }
+
+ pclose(keyScript);
+ return xf_event_action_script_init(xfc);
+}
+
+static void xf_keyboard_action_script_free(xfContext* xfc)
+{
+ xf_event_action_script_free(xfc);
+
+ if (xfc->keyCombinations)
+ {
+ ArrayList_Free(xfc->keyCombinations);
+ xfc->keyCombinations = NULL;
+ xfc->actionScriptExists = FALSE;
+ }
+}
+
+BOOL xf_keyboard_init(xfContext* xfc)
+{
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xf_keyboard_clear(xfc);
+ xfc->KeyboardLayout = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout);
+ xfc->KeyboardLayout = freerdp_keyboard_init_ex(
+ xfc->KeyboardLayout, freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList));
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, xfc->KeyboardLayout))
+ return FALSE;
+
+ if (!xf_keyboard_update_modifier_map(xfc))
+ return FALSE;
+
+ xf_keyboard_action_script_init(xfc);
+ return TRUE;
+}
+
+void xf_keyboard_free(xfContext* xfc)
+{
+ xf_keyboard_modifier_map_free(xfc);
+ xf_keyboard_action_script_free(xfc);
+}
+
+void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym)
+{
+ BOOL last = 0;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+ WINPR_ASSERT(event->keycode <= ARRAYSIZE(xfc->KeyboardState));
+
+ last = xfc->KeyboardState[event->keycode];
+ xfc->KeyboardState[event->keycode] = TRUE;
+
+ if (xf_keyboard_handle_special_keys(xfc, keysym))
+ return;
+
+ xf_keyboard_send_key(xfc, TRUE, last, event);
+}
+
+void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+ WINPR_ASSERT(event->keycode <= ARRAYSIZE(xfc->KeyboardState));
+
+ BOOL last = xfc->KeyboardState[event->keycode];
+ xfc->KeyboardState[event->keycode] = FALSE;
+ xf_keyboard_handle_special_keys_release(xfc, keysym);
+ xf_keyboard_send_key(xfc, FALSE, last, event);
+}
+
+void xf_keyboard_release_all_keypress(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ for (size_t keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++)
+ {
+ if (xfc->KeyboardState[keycode])
+ {
+ const DWORD rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode);
+
+ // release tab before releasing the windows key.
+ // this stops the start menu from opening on unfocus event.
+ if (rdp_scancode == RDP_SCANCODE_LWIN)
+ freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE,
+ RDP_SCANCODE_TAB);
+
+ freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE,
+ rdp_scancode);
+ xfc->KeyboardState[keycode] = FALSE;
+ }
+ }
+ xf_sync_kbd_state(xfc);
+}
+
+BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym)
+{
+ KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
+ WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState));
+ return xfc->KeyboardState[keycode];
+}
+
+void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* event)
+{
+ DWORD rdp_scancode = 0;
+ rdpInput* input = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(event->keycode);
+ if (rdp_scancode == RDP_SCANCODE_PAUSE && !xf_keyboard_key_pressed(xfc, XK_Control_L) &&
+ !xf_keyboard_key_pressed(xfc, XK_Control_R))
+ {
+ /* Pause without Ctrl has to be sent as a series of keycodes
+ * in a single input PDU. Pause only happens on "press";
+ * no code is sent on "release".
+ */
+ if (down)
+ {
+ freerdp_input_send_keyboard_pause_event(input);
+ }
+ }
+ else
+ {
+ if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnicodeInput))
+ {
+ wchar_t buffer[32] = { 0 };
+ int xwc = -1;
+
+ switch (rdp_scancode)
+ {
+ case RDP_SCANCODE_RETURN:
+ break;
+ default:
+ {
+ XIM xim = XOpenIM(xfc->display, 0, 0, 0);
+ XIC xic =
+ XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
+
+ KeySym ignore = { 0 };
+ Status return_status = 0;
+ XKeyEvent ev = *event;
+ ev.type = KeyPress;
+ xwc = XwcLookupString(xic, &ev, buffer, ARRAYSIZE(buffer), &ignore,
+ &return_status);
+ }
+ break;
+ }
+
+ if (xwc < 1)
+ {
+ if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode);
+ else
+ freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode);
+ }
+ else
+ freerdp_input_send_unicode_keyboard_event(input, down ? 0 : KBD_FLAGS_RELEASE,
+ buffer[0]);
+ }
+ else if (rdp_scancode == RDP_SCANCODE_UNKNOWN)
+ WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode);
+ else
+ freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode);
+
+ if ((rdp_scancode == RDP_SCANCODE_CAPSLOCK) && (down == FALSE))
+ {
+ xf_sync_kbd_state(xfc);
+ }
+ }
+}
+
+int xf_keyboard_read_keyboard_state(xfContext* xfc)
+{
+ int dummy = 0;
+ Window wdummy = 0;
+ UINT32 state = 0;
+
+ if (!xfc->remote_app && xfc->window)
+ {
+ XQueryPointer(xfc->display, xfc->window->handle, &wdummy, &wdummy, &dummy, &dummy, &dummy,
+ &dummy, &state);
+ }
+ else
+ {
+ XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &wdummy, &wdummy, &dummy,
+ &dummy, &dummy, &dummy, &state);
+ }
+
+ return state;
+}
+
+static int xf_keyboard_get_keymask(xfContext* xfc, int keysym)
+{
+ int keysymMask = 0;
+ KeyCode keycode = XKeysymToKeycode(xfc->display, keysym);
+
+ if (keycode == NoSymbol)
+ return 0;
+
+ WINPR_ASSERT(xfc->modifierMap);
+ for (int modifierpos = 0; modifierpos < 8; modifierpos++)
+ {
+ int offset = xfc->modifierMap->max_keypermod * modifierpos;
+
+ for (int key = 0; key < xfc->modifierMap->max_keypermod; key++)
+ {
+ if (xfc->modifierMap->modifiermap[offset + key] == keycode)
+ {
+ keysymMask |= 1 << modifierpos;
+ }
+ }
+ }
+
+ return keysymMask;
+}
+
+BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, int keysym)
+{
+ int keysymMask = xf_keyboard_get_keymask(xfc, keysym);
+
+ if (!keysymMask)
+ return FALSE;
+
+ return (state & keysymMask) ? TRUE : FALSE;
+}
+
+static BOOL xf_keyboard_set_key_state(xfContext* xfc, BOOL on, int keysym)
+{
+ if (!xfc->xkbAvailable)
+ return FALSE;
+
+ const int keysymMask = xf_keyboard_get_keymask(xfc, keysym);
+
+ if (!keysymMask)
+ {
+ return FALSE;
+ }
+
+ return XkbLockModifiers(xfc->display, XkbUseCoreKbd, keysymMask, on ? keysymMask : 0);
+}
+
+UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc)
+{
+ UINT32 toggleKeysState = 0;
+ const int state = xf_keyboard_read_keyboard_state(xfc);
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Scroll_Lock))
+ toggleKeysState |= KBD_SYNC_SCROLL_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Num_Lock))
+ toggleKeysState |= KBD_SYNC_NUM_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Caps_Lock))
+ toggleKeysState |= KBD_SYNC_CAPS_LOCK;
+
+ if (xf_keyboard_get_key_state(xfc, state, XK_Kana_Lock))
+ toggleKeysState |= KBD_SYNC_KANA_LOCK;
+
+ return toggleKeysState;
+}
+
+static void xk_keyboard_update_modifier_keys(xfContext* xfc)
+{
+ const int keysyms[] = { XK_Shift_L, XK_Shift_R, XK_Alt_L, XK_Alt_R,
+ XK_Control_L, XK_Control_R, XK_Super_L, XK_Super_R };
+
+ xf_keyboard_clear(xfc);
+
+ const int state = xf_keyboard_read_keyboard_state(xfc);
+
+ for (size_t i = 0; i < ARRAYSIZE(keysyms); i++)
+ {
+ if (xf_keyboard_get_key_state(xfc, state, keysyms[i]))
+ {
+ const KeyCode keycode = XKeysymToKeycode(xfc->display, keysyms[i]);
+ WINPR_ASSERT(keycode <= ARRAYSIZE(xfc->KeyboardState));
+ xfc->KeyboardState[keycode] = TRUE;
+ }
+ }
+}
+
+void xf_keyboard_focus_in(xfContext* xfc)
+{
+ UINT32 state = 0;
+ Window w = None;
+ int d = 0;
+ int x = 0;
+ int y = 0;
+
+ WINPR_ASSERT(xfc);
+ if (!xfc->display || !xfc->window)
+ return;
+
+ rdpInput* input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc);
+ freerdp_input_send_focus_in_event(input, syncFlags);
+ xk_keyboard_update_modifier_keys(xfc);
+
+ /* finish with a mouse pointer position like mstsc.exe if required */
+
+ if (xfc->remote_app || !xfc->window)
+ return;
+
+ if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state))
+ {
+ if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height))
+ {
+ xf_event_adjust_coordinates(xfc, &x, &y);
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y);
+ }
+ }
+}
+
+static int xf_keyboard_execute_action_script(xfContext* xfc, XF_MODIFIER_KEYS* mod, KeySym keysym)
+{
+ int status = 1;
+ BOOL match = FALSE;
+ char buffer[1024] = { 0 };
+ char command[2048] = { 0 };
+ char combination[1024] = { 0 };
+
+ if (!xfc->actionScriptExists)
+ return 1;
+
+ if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R) || (keysym == XK_Alt_L) ||
+ (keysym == XK_Alt_R) || (keysym == XK_Control_L) || (keysym == XK_Control_R))
+ {
+ return 1;
+ }
+
+ const char* keyStr = XKeysymToString(keysym);
+
+ if (keyStr == 0)
+ {
+ return 1;
+ }
+
+ if (mod->Shift)
+ winpr_str_append("Shift", combination, sizeof(combination), "+");
+
+ if (mod->Ctrl)
+ winpr_str_append("Ctrl", combination, sizeof(combination), "+");
+
+ if (mod->Alt)
+ winpr_str_append("Alt", combination, sizeof(combination), "+");
+
+ if (mod->Super)
+ winpr_str_append("Super", combination, sizeof(combination), "+");
+
+ winpr_str_append(keyStr, combination, sizeof(combination), NULL);
+
+ const size_t count = ArrayList_Count(xfc->keyCombinations);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ const char* keyCombination = (const char*)ArrayList_GetItem(xfc->keyCombinations, index);
+
+ if (_stricmp(keyCombination, combination) == 0)
+ {
+ match = TRUE;
+ break;
+ }
+ }
+
+ if (!match)
+ return 1;
+
+ const char* ActionScript =
+ freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ActionScript);
+ sprintf_s(command, sizeof(command), "%s key %s", ActionScript, combination);
+ FILE* keyScript = popen(command, "r");
+
+ if (!keyScript)
+ return -1;
+
+ while (fgets(buffer, sizeof(buffer), keyScript) != NULL)
+ {
+ char* context = NULL;
+ strtok_s(buffer, "\n", &context);
+
+ if (strcmp(buffer, "key-local") == 0)
+ status = 0;
+ }
+
+ if (pclose(keyScript) == -1)
+ status = -1;
+
+ return status;
+}
+
+static int xk_keyboard_get_modifier_keys(xfContext* xfc, XF_MODIFIER_KEYS* mod)
+{
+ mod->LeftShift = xf_keyboard_key_pressed(xfc, XK_Shift_L);
+ mod->RightShift = xf_keyboard_key_pressed(xfc, XK_Shift_R);
+ mod->Shift = mod->LeftShift || mod->RightShift;
+ mod->LeftAlt = xf_keyboard_key_pressed(xfc, XK_Alt_L);
+ mod->RightAlt = xf_keyboard_key_pressed(xfc, XK_Alt_R);
+ mod->Alt = mod->LeftAlt || mod->RightAlt;
+ mod->LeftCtrl = xf_keyboard_key_pressed(xfc, XK_Control_L);
+ mod->RightCtrl = xf_keyboard_key_pressed(xfc, XK_Control_R);
+ mod->Ctrl = mod->LeftCtrl || mod->RightCtrl;
+ mod->LeftSuper = xf_keyboard_key_pressed(xfc, XK_Super_L);
+ mod->RightSuper = xf_keyboard_key_pressed(xfc, XK_Super_R);
+ mod->Super = mod->LeftSuper || mod->RightSuper;
+ return 0;
+}
+
+BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym)
+{
+ XF_MODIFIER_KEYS mod = { 0 };
+ xk_keyboard_get_modifier_keys(xfc, &mod);
+
+ // remember state of RightCtrl to ungrab keyboard if next action is release of RightCtrl
+ // do not return anything such that the key could be used by client if ungrab is not the goal
+ if (keysym == XK_Control_R)
+ {
+ if (mod.RightCtrl && xfc->firstPressRightCtrl)
+ {
+ // Right Ctrl is pressed, getting ready to ungrab
+ xfc->ungrabKeyboardWithRightCtrl = TRUE;
+ xfc->firstPressRightCtrl = FALSE;
+ }
+ }
+ else
+ {
+ // some other key has been pressed, abort ungrabbing
+ if (xfc->ungrabKeyboardWithRightCtrl)
+ xfc->ungrabKeyboardWithRightCtrl = FALSE;
+ }
+
+ if (!xf_keyboard_execute_action_script(xfc, &mod, keysym))
+ {
+ return TRUE;
+ }
+
+ if (!xfc->remote_app && xfc->fullscreen_toggle)
+ {
+ if (keysym == XK_Return)
+ {
+ if (mod.Ctrl && mod.Alt)
+ {
+ /* Ctrl-Alt-Enter: toggle full screen */
+ xf_toggle_fullscreen(xfc);
+ return TRUE;
+ }
+ }
+ }
+
+ if ((keysym == XK_c) || (keysym == XK_C))
+ {
+ if (mod.Ctrl && mod.Alt)
+ {
+ /* Ctrl-Alt-C: toggle control */
+ if (freerdp_client_encomsp_toggle_control(xfc->common.encomsp))
+ return TRUE;
+ }
+ }
+
+#if 0 /* set to 1 to enable multi touch gesture simulation via keyboard */
+#ifdef WITH_XRENDER
+
+ if (!xfc->remote_app && freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MultiTouchGestures))
+ {
+ rdpContext* ctx = &xfc->common.context;
+
+ if (mod.Ctrl && mod.Alt)
+ {
+ int pdx = 0;
+ int pdy = 0;
+ int zdx = 0;
+ int zdy = 0;
+
+ switch (keysym)
+ {
+ case XK_0: /* Ctrl-Alt-0: Reset scaling and panning */{
+ const UINT32 sessionWidth = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopWidth);
+ const UINT32 sessionHeight = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopHeight);
+
+ xfc->scaledWidth = sessionWidth;
+ xfc->scaledHeight = sessionHeight;
+ xfc->offset_x = 0;
+ xfc->offset_y = 0;
+
+ if (!xfc->fullscreen && (sessionWidth != xfc->window->width ||
+ sessionHeight != xfc->window->height))
+ {
+ xf_ResizeDesktopWindow(xfc, xfc->window, sessionWidth, sessionHeight);
+ }
+
+ xf_draw_screen(xfc, 0, 0, sessionWidth, sessionHeight);
+ return TRUE;
+}
+
+ case XK_1: /* Ctrl-Alt-1: Zoom in */
+ zdx = zdy = 10;
+ break;
+
+ case XK_2: /* Ctrl-Alt-2: Zoom out */
+ zdx = zdy = -10;
+ break;
+
+ case XK_3: /* Ctrl-Alt-3: Pan left */
+ pdx = -10;
+ break;
+
+ case XK_4: /* Ctrl-Alt-4: Pan right */
+ pdx = 10;
+ break;
+
+ case XK_5: /* Ctrl-Alt-5: Pan up */
+ pdy = -10;
+ break;
+
+ case XK_6: /* Ctrl-Alt-6: Pan up */
+ pdy = 10;
+ break;
+ }
+
+ if (pdx != 0 || pdy != 0)
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = pdx;
+ e.dy = pdy;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ return TRUE;
+ }
+
+ if (zdx != 0 || zdy != 0)
+ {
+ ZoomingChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = zdx;
+ e.dy = zdy;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ return TRUE;
+ }
+ }
+ }
+
+#endif /* WITH_XRENDER defined */
+#endif /* pinch/zoom/pan simulation */
+ return FALSE;
+}
+
+void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym)
+{
+ if (keysym != XK_Control_R)
+ return;
+
+ xfc->firstPressRightCtrl = TRUE;
+
+ if (!xfc->ungrabKeyboardWithRightCtrl)
+ return;
+
+ // all requirements for ungrab are fulfilled, ungrabbing now
+ XF_MODIFIER_KEYS mod = { 0 };
+ xk_keyboard_get_modifier_keys(xfc, &mod);
+
+ if (!mod.RightCtrl)
+ {
+ if (!xfc->fullscreen)
+ {
+ freerdp_client_encomsp_toggle_control(xfc->common.encomsp);
+ }
+
+ xfc->mouse_active = FALSE;
+ xf_ungrab(xfc);
+ }
+
+ // ungrabbed
+ xfc->ungrabKeyboardWithRightCtrl = FALSE;
+}
+
+BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags)
+{
+ xfContext* xfc = (xfContext*)context;
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_SCROLL_LOCK, XK_Scroll_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_NUM_LOCK, XK_Num_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_CAPS_LOCK, XK_Caps_Lock);
+ xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_KANA_LOCK, XK_Kana_Lock);
+ return TRUE;
+}
+
+BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ if (!context)
+ return FALSE;
+
+ WLog_WARN(TAG,
+ "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32
+ ", imeConvMode=%08" PRIx32 ") ignored",
+ imeId, imeState, imeConvMode);
+ return TRUE;
+}
+
+BOOL xf_ungrab(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+ XUngrabKeyboard(xfc->display, CurrentTime);
+ XUngrabPointer(xfc->display, CurrentTime);
+ xfc->common.mouse_grabbed = FALSE;
+ return TRUE;
+}
diff --git a/client/X11/xf_keyboard.h b/client/X11/xf_keyboard.h
new file mode 100644
index 0000000..a9374b8
--- /dev/null
+++ b/client/X11/xf_keyboard.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Keyboard Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_XF_KEYBOARD_H
+#define FREERDP_CLIENT_X11_XF_KEYBOARD_H
+
+#include <freerdp/locale/keyboard.h>
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+typedef struct
+{
+ BOOL Shift;
+ BOOL LeftShift;
+ BOOL RightShift;
+ BOOL Alt;
+ BOOL LeftAlt;
+ BOOL RightAlt;
+ BOOL Ctrl;
+ BOOL LeftCtrl;
+ BOOL RightCtrl;
+ BOOL Super;
+ BOOL LeftSuper;
+ BOOL RightSuper;
+} XF_MODIFIER_KEYS;
+
+BOOL xf_keyboard_init(xfContext* xfc);
+void xf_keyboard_free(xfContext* xfc);
+
+void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym);
+void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym);
+
+void xf_keyboard_release_all_keypress(xfContext* xfc);
+BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym);
+
+int xf_keyboard_read_keyboard_state(xfContext* xfc);
+BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, int keysym);
+UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc);
+void xf_keyboard_focus_in(xfContext* xfc);
+BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym);
+void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym);
+BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags);
+BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode);
+
+BOOL xf_ungrab(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_XF_KEYBOARD_H */
diff --git a/client/X11/xf_monitor.c b/client/X11/xf_monitor.c
new file mode 100644
index 0000000..d872a4c
--- /dev/null
+++ b/client/X11/xf_monitor.c
@@ -0,0 +1,654 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ * Copyright 2018 Kai Harms <kharms@rangee.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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+
+#include <freerdp/log.h>
+
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#ifdef WITH_XRANDR
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/randr.h>
+
+#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
+#define USABLE_XRANDR
+#endif
+
+#endif
+
+#include "xf_monitor.h"
+
+/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
+ */
+
+int xf_list_monitors(xfContext* xfc)
+{
+ Display* display = NULL;
+ int major = 0;
+ int minor = 0;
+ int nmonitors = 0;
+ display = XOpenDisplay(NULL);
+
+ if (!display)
+ {
+ WLog_ERR(TAG, "failed to open X display");
+ return -1;
+ }
+
+#if defined(USABLE_XRANDR)
+
+ if (XRRQueryExtension(display, &major, &minor) &&
+ (XRRQueryVersion(display, &major, &minor) == True) && (major * 100 + minor >= 105))
+ {
+ XRRMonitorInfo* monitors =
+ XRRGetMonitors(display, DefaultRootWindow(display), 1, &nmonitors);
+
+ for (int i = 0; i < nmonitors; i++)
+ {
+ printf(" %s [%d] %dx%d\t+%d+%d\n", monitors[i].primary ? "*" : " ", i,
+ monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y);
+ }
+
+ XRRFreeMonitors(monitors);
+ }
+ else
+#endif
+#ifdef WITH_XINERAMA
+ if (XineramaQueryExtension(display, &major, &minor))
+ {
+ if (XineramaIsActive(display))
+ {
+ XineramaScreenInfo* screen = XineramaQueryScreens(display, &nmonitors);
+
+ for (int i = 0; i < nmonitors; i++)
+ {
+ printf(" %s [%d] %hdx%hd\t+%hd+%hd\n", (i == 0) ? "*" : " ", i,
+ screen[i].width, screen[i].height, screen[i].x_org, screen[i].y_org);
+ }
+
+ XFree(screen);
+ }
+ }
+ else
+#else
+ {
+ Screen* screen = ScreenOfDisplay(display, DefaultScreen(display));
+ printf(" * [0] %dx%d\t+0+0\n", WidthOfScreen(screen), HeightOfScreen(screen));
+ }
+
+#endif
+ XCloseDisplay(display);
+ return 0;
+}
+
+static BOOL xf_is_monitor_id_active(xfContext* xfc, UINT32 id)
+{
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 NumMonitorIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (NumMonitorIds == 0)
+ return TRUE;
+
+ for (UINT32 index = 0; index < NumMonitorIds; index++)
+ {
+ const UINT32* cur = freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index);
+ if (cur && (*cur == id))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight)
+{
+ BOOL rc = FALSE;
+ int nmonitors = 0;
+ UINT32 monitor_index = 0;
+ BOOL primaryMonitorFound = FALSE;
+ VIRTUAL_SCREEN* vscreen = NULL;
+ rdpSettings* settings = NULL;
+ int mouse_x = 0;
+ int mouse_y = 0;
+ int _dummy_i = 0;
+ Window _dummy_w = 0;
+ int current_monitor = 0;
+ Screen* screen = NULL;
+#if defined WITH_XINERAMA || defined WITH_XRANDR
+ int major = 0;
+ int minor = 0;
+#endif
+#if defined(USABLE_XRANDR)
+ XRRMonitorInfo* rrmonitors = NULL;
+ BOOL useXRandr = FALSE;
+#endif
+
+ if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->common.context.settings)
+ return FALSE;
+
+ settings = xfc->common.context.settings;
+ vscreen = &xfc->vscreen;
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+
+ if (freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId) > 0)
+ {
+ xfc->workArea.x = 0;
+ xfc->workArea.y = 0;
+ xfc->workArea.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ xfc->workArea.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ return TRUE;
+ }
+
+ /* get mouse location */
+ if (!XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &_dummy_w, &_dummy_w,
+ &mouse_x, &mouse_y, &_dummy_i, &_dummy_i, (void*)&_dummy_i))
+ mouse_x = mouse_y = 0;
+
+#if defined(USABLE_XRANDR)
+
+ if (XRRQueryExtension(xfc->display, &major, &minor) &&
+ (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105))
+ {
+ rrmonitors =
+ XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &vscreen->nmonitors);
+
+ if (vscreen->nmonitors > 16)
+ vscreen->nmonitors = 0;
+
+ if (vscreen->nmonitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_INFO* cur_vscreen = &vscreen->monitors[i];
+ const XRRMonitorInfo* cur_monitor = &rrmonitors[i];
+ cur_vscreen->area.left = cur_monitor->x;
+ cur_vscreen->area.top = cur_monitor->y;
+ cur_vscreen->area.right = cur_monitor->x + cur_monitor->width - 1;
+ cur_vscreen->area.bottom = cur_monitor->y + cur_monitor->height - 1;
+ cur_vscreen->primary = cur_monitor->primary > 0;
+ }
+ }
+
+ useXRandr = TRUE;
+ }
+ else
+#endif
+#ifdef WITH_XINERAMA
+ if (XineramaQueryExtension(xfc->display, &major, &minor) && XineramaIsActive(xfc->display))
+ {
+ XineramaScreenInfo* screenInfo = XineramaQueryScreens(xfc->display, &vscreen->nmonitors);
+
+ if (vscreen->nmonitors > 16)
+ vscreen->nmonitors = 0;
+
+ if (vscreen->nmonitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_INFO* monitor = &vscreen->monitors[i];
+ monitor->area.left = screenInfo[i].x_org;
+ monitor->area.top = screenInfo[i].y_org;
+ monitor->area.right = screenInfo[i].x_org + screenInfo[i].width - 1;
+ monitor->area.bottom = screenInfo[i].y_org + screenInfo[i].height - 1;
+ }
+ }
+
+ XFree(screenInfo);
+ }
+
+#endif
+ xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = xfc->fullscreenMonitors.left =
+ xfc->fullscreenMonitors.right = 0;
+
+ /* Determine which monitor that the mouse cursor is on */
+ if (vscreen->monitors)
+ {
+ for (int i = 0; i < vscreen->nmonitors; i++)
+ {
+ const MONITOR_INFO* monitor = &vscreen->monitors[i];
+
+ if ((mouse_x >= monitor->area.left) && (mouse_x <= monitor->area.right) &&
+ (mouse_y >= monitor->area.top) && (mouse_y <= monitor->area.bottom))
+ {
+ current_monitor = i;
+ break;
+ }
+ }
+ }
+
+ /*
+ Even for a single monitor, we need to calculate the virtual screen to support
+ window managers that do not implement all X window state hints.
+
+ If the user did not request multiple monitor or is using workarea
+ without remote app, we force the number of monitors be 1 so later
+ the rest of the client don't end up using more monitors than the user desires.
+ */
+ if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
+ (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
+ {
+ /* If no monitors were specified on the command-line then set the current monitor as active
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
+ {
+ UINT32 id = current_monitor;
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1))
+ goto fail;
+ }
+
+ /* Always sets number of monitors from command-line to just 1.
+ * If the monitor is invalid then we will default back to current monitor
+ * later as a fallback. So, there is no need to validate command-line entry here.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
+ goto fail;
+ }
+
+ /* WORKAROUND: With Remote Application Mode - using NET_WM_WORKAREA
+ * causes issues with the ability to fully size the window vertically
+ * (the bottom of the window area is never updated). So, we just set
+ * the workArea to match the full Screen width/height.
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode) || !xf_GetWorkArea(xfc))
+ {
+ /*
+ if only 1 monitor is enabled, use monitor area
+ this is required in case of a screen composed of more than one monitor
+ but user did not enable multimonitor
+ */
+ if ((freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 1) &&
+ (vscreen->nmonitors > current_monitor))
+ {
+ MONITOR_INFO* monitor = vscreen->monitors + current_monitor;
+
+ if (!monitor)
+ goto fail;
+
+ xfc->workArea.x = monitor->area.left;
+ xfc->workArea.y = monitor->area.top;
+ xfc->workArea.width = monitor->area.right - monitor->area.left + 1;
+ xfc->workArea.height = monitor->area.bottom - monitor->area.top + 1;
+ }
+ else
+ {
+ xfc->workArea.x = 0;
+ xfc->workArea.y = 0;
+ xfc->workArea.width = WidthOfScreen(xfc->screen);
+ xfc->workArea.height = HeightOfScreen(xfc->screen);
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ *pMaxWidth = WidthOfScreen(xfc->screen);
+ *pMaxHeight = HeightOfScreen(xfc->screen);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ *pMaxWidth = xfc->workArea.width;
+ *pMaxHeight = xfc->workArea.height;
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen))
+ {
+ /* If we have specific monitor information then limit the PercentScreen value
+ * to only affect the current monitor vs. the entire desktop
+ */
+ if (vscreen->nmonitors > 0)
+ {
+ if (!vscreen->monitors)
+ goto fail;
+
+ *pMaxWidth = vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1;
+ *pMaxHeight = vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth = ((vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1) *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight = ((vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1) *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+ }
+ else
+ {
+ *pMaxWidth = xfc->workArea.width;
+ *pMaxHeight = xfc->workArea.height;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
+ *pMaxWidth = (xfc->workArea.width *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
+ *pMaxHeight = (xfc->workArea.height *
+ freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
+ 100;
+ }
+ }
+ else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
+ {
+ *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ }
+
+ /* Create array of all active monitors by taking into account monitors requested on the
+ * command-line */
+ {
+ UINT32 nr = 0;
+
+ {
+ const UINT32* ids = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ if (ids)
+ nr = *ids;
+ }
+ for (UINT32 i = 0; i < vscreen->nmonitors; i++)
+ {
+ MONITOR_ATTRIBUTES* attrs = NULL;
+
+ if (!xf_is_monitor_id_active(xfc, (UINT32)i))
+ continue;
+
+ if (!vscreen->monitors)
+ goto fail;
+
+ rdpMonitor* monitor = freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_MonitorDefArray, nmonitors);
+ monitor->x = (vscreen->monitors[i].area.left *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->y = (vscreen->monitors[i].area.top *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->width =
+ ((vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1) *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->height =
+ ((vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1) *
+ (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100)) /
+ 100;
+ monitor->orig_screen = i;
+#ifdef USABLE_XRANDR
+
+ if (useXRandr && rrmonitors)
+ {
+ Rotation rot = 0;
+ Rotation ret = 0;
+ attrs = &monitor->attributes;
+ attrs->physicalWidth = rrmonitors[i].mwidth;
+ attrs->physicalHeight = rrmonitors[i].mheight;
+ ret = XRRRotations(xfc->display, i, &rot);
+ attrs->orientation = ret;
+ }
+
+#endif
+
+ if ((UINT32)i == nr)
+ {
+ monitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, monitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, monitor->y))
+ goto fail;
+ primaryMonitorFound = TRUE;
+ }
+
+ nmonitors++;
+ }
+ }
+
+ /* If no monitor is active(bogus command-line monitor specification) - then lets try to fallback
+ * to go fullscreen on the current monitor only */
+ if (nmonitors == 0 && vscreen->nmonitors > 0)
+ {
+ INT32 width = 0;
+ INT32 height = 0;
+ if (!vscreen->monitors)
+ goto fail;
+
+ width = vscreen->monitors[current_monitor].area.right -
+ vscreen->monitors[current_monitor].area.left + 1L;
+ height = vscreen->monitors[current_monitor].area.bottom -
+ vscreen->monitors[current_monitor].area.top + 1L;
+
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, 0);
+ if (!monitor)
+ goto fail;
+
+ monitor->x = vscreen->monitors[current_monitor].area.left;
+ monitor->y = vscreen->monitors[current_monitor].area.top;
+ monitor->width = MIN(width, (INT64)(*pMaxWidth));
+ monitor->height = MIN(height, (INT64)(*pMaxHeight));
+ monitor->orig_screen = current_monitor;
+ nmonitors = 1;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, nmonitors))
+ goto fail;
+
+ /* If we have specific monitor information */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 0)
+ {
+ const rdpMonitor* cmonitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0);
+ if (!cmonitor)
+ goto fail;
+
+ /* Initialize bounding rectangle for all monitors */
+ int vX = cmonitor->x;
+ int vY = cmonitor->y;
+ int vR = vX + cmonitor->width;
+ int vB = vY + cmonitor->height;
+ xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom =
+ xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right = cmonitor->orig_screen;
+
+ /* Calculate bounding rectangle around all monitors to be used AND
+ * also set the Xinerama indices which define left/top/right/bottom monitors.
+ */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, i);
+
+ /* does the same as gdk_rectangle_union */
+ int destX = MIN(vX, monitor->x);
+ int destY = MIN(vY, monitor->y);
+ int destR = MAX(vR, monitor->x + monitor->width);
+ int destB = MAX(vB, monitor->y + monitor->height);
+
+ if (vX != destX)
+ xfc->fullscreenMonitors.left = monitor->orig_screen;
+
+ if (vY != destY)
+ xfc->fullscreenMonitors.top = monitor->orig_screen;
+
+ if (vR != destR)
+ xfc->fullscreenMonitors.right = monitor->orig_screen;
+
+ if (vB != destB)
+ xfc->fullscreenMonitors.bottom = monitor->orig_screen;
+
+ vX = destX / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vY = destY / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vR = destR / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ vB = destB / ((freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)
+ : 100) /
+ 100.);
+ }
+
+ vscreen->area.left = 0;
+ vscreen->area.right = vR - vX - 1;
+ vscreen->area.top = 0;
+ vscreen->area.bottom = vB - vY - 1;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
+ {
+ vscreen->area.top = xfc->workArea.y;
+ vscreen->area.bottom = xfc->workArea.height + xfc->workArea.y - 1;
+ }
+
+ if (!primaryMonitorFound)
+ {
+ /* If we have a command line setting we should use it */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) > 0)
+ {
+ /* The first monitor is the first in the setting which should be used */
+ UINT32* ids =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0);
+ if (ids)
+ monitor_index = *ids;
+ }
+ else
+ {
+ /* This is the same as when we would trust the Xinerama results..
+ and set the monitor index to zero.
+ The monitor listed with /list:monitor on index zero is always the primary
+ */
+ screen = DefaultScreenOfDisplay(xfc->display);
+ monitor_index = XScreenNumberOfScreen(screen);
+ }
+
+ UINT32 j = monitor_index;
+ rdpMonitor* pmonitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, j);
+
+ /* If the "default" monitor is not 0,0 use it */
+ if ((pmonitor->x != 0) || (pmonitor->y != 0))
+ {
+ pmonitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX, pmonitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY, pmonitor->y))
+ goto fail;
+ }
+ else
+ {
+ /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a
+ * fallback*/
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount);
+ i++)
+ {
+ rdpMonitor* monitor = freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_MonitorDefArray, i);
+ if (!primaryMonitorFound && monitor->x == 0 && monitor->y == 0)
+ {
+ monitor->is_primary = TRUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftX,
+ monitor->x))
+ goto fail;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorLocalShiftY,
+ monitor->y))
+ goto fail;
+ primaryMonitorFound = TRUE;
+ }
+ }
+ }
+ }
+
+ /* Subtract monitor shift from monitor variables for server-side use.
+ * We maintain monitor shift value as Window requires the primary monitor to have a
+ * coordinate of 0,0 In some X configurations, no monitor may have a coordinate of 0,0. This
+ * can also be happen if the user requests specific monitors from the command-line as well.
+ * So, we make sure to translate our primary monitor's upper-left corner to 0,0 on the
+ * server.
+ */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, i);
+ monitor->x =
+ monitor->x - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ monitor->y =
+ monitor->y - freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ }
+
+ /* Set the desktop width and height according to the bounding rectangle around the active
+ * monitors */
+ *pMaxWidth = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1);
+ *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1);
+ }
+
+ /* some 2008 server freeze at logon if we announce support for monitor layout PDU with
+ * #monitors < 2. So let's announce it only if we have more than 1 monitor.
+ */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+#ifdef USABLE_XRANDR
+
+ if (rrmonitors)
+ XRRFreeMonitors(rrmonitors);
+
+#endif
+ return rc;
+}
diff --git a/client/X11/xf_monitor.h b/client/X11/xf_monitor.h
new file mode 100644
index 0000000..c27c88f
--- /dev/null
+++ b/client/X11/xf_monitor.h
@@ -0,0 +1,48 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Monitor Handling
+ *
+ * Copyright 2011 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_CLIENT_X11_MONITOR_H
+#define FREERDP_CLIENT_X11_MONITOR_H
+
+#include <freerdp/api.h>
+#include <freerdp/freerdp.h>
+
+typedef struct
+{
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ BOOL primary;
+} MONITOR_INFO;
+
+typedef struct
+{
+ int nmonitors;
+ RECTANGLE_16 area;
+ RECTANGLE_16 workarea;
+ MONITOR_INFO* monitors;
+} VIRTUAL_SCREEN;
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+FREERDP_API int xf_list_monitors(xfContext* xfc);
+FREERDP_API BOOL xf_detect_monitors(xfContext* xfc, UINT32* pWidth, UINT32* pHeight);
+FREERDP_API void xf_monitors_free(xfContext* xfc);
+
+#endif /* FREERDP_CLIENT_X11_MONITOR_H */
diff --git a/client/X11/xf_rail.c b/client/X11/xf_rail.c
new file mode 100644
index 0000000..7402bb2
--- /dev/null
+++ b/client/X11/xf_rail.c
@@ -0,0 +1,1184 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 RAIL
+ *
+ * Copyright 2011 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 <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+
+#include <winpr/assert.h>
+#include <winpr/wlog.h>
+#include <winpr/print.h>
+
+#include <freerdp/client/rail.h>
+
+#include "xf_window.h"
+#include "xf_rail.h"
+#include "xf_utils.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+static const char* error_code_names[] = { "RAIL_EXEC_S_OK",
+ "RAIL_EXEC_E_HOOK_NOT_LOADED",
+ "RAIL_EXEC_E_DECODE_FAILED",
+ "RAIL_EXEC_E_NOT_IN_ALLOWLIST",
+ "RAIL_EXEC_E_FILE_NOT_FOUND",
+ "RAIL_EXEC_E_FAIL",
+ "RAIL_EXEC_E_SESSION_LOCKED" };
+
+#ifdef WITH_DEBUG_RAIL
+static const char* movetype_names[] = {
+ "(invalid)", "RAIL_WMSZ_LEFT", "RAIL_WMSZ_RIGHT",
+ "RAIL_WMSZ_TOP", "RAIL_WMSZ_TOPLEFT", "RAIL_WMSZ_TOPRIGHT",
+ "RAIL_WMSZ_BOTTOM", "RAIL_WMSZ_BOTTOMLEFT", "RAIL_WMSZ_BOTTOMRIGHT",
+ "RAIL_WMSZ_MOVE", "RAIL_WMSZ_KEYMOVE", "RAIL_WMSZ_KEYSIZE"
+};
+#endif
+
+struct xf_rail_icon
+{
+ long* data;
+ int length;
+};
+typedef struct xf_rail_icon xfRailIcon;
+
+struct xf_rail_icon_cache
+{
+ xfRailIcon* entries;
+ UINT32 numCaches;
+ UINT32 numCacheEntries;
+ xfRailIcon scratch;
+};
+
+typedef struct
+{
+ xfContext* xfc;
+ const RECTANGLE_16* rect;
+} rail_paint_fn_arg_t;
+
+void xf_rail_enable_remoteapp_mode(xfContext* xfc)
+{
+ if (!xfc->remote_app)
+ {
+ xfc->remote_app = TRUE;
+ xfc->drawable = xf_CreateDummyWindow(xfc);
+ xf_DestroyDesktopWindow(xfc, xfc->window);
+ xfc->window = NULL;
+ }
+}
+
+void xf_rail_disable_remoteapp_mode(xfContext* xfc)
+{
+ if (xfc->remote_app)
+ {
+ xfc->remote_app = FALSE;
+ xf_DestroyDummyWindow(xfc, xfc->drawable);
+ xf_create_window(xfc);
+ xf_create_image(xfc);
+ }
+}
+
+void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled)
+{
+ RAIL_ACTIVATE_ORDER activate;
+ xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, xwindow);
+
+ if (!appWindow)
+ return;
+
+ if (enabled)
+ xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
+ else
+ xf_SetWindowStyle(xfc, appWindow, 0, 0);
+
+ activate.windowId = appWindow->windowId;
+ activate.enabled = enabled;
+ xfc->rail->ClientActivate(xfc->rail, &activate);
+}
+
+void xf_rail_send_client_system_command(xfContext* xfc, UINT32 windowId, UINT16 command)
+{
+ RAIL_SYSCOMMAND_ORDER syscommand;
+ syscommand.windowId = windowId;
+ syscommand.command = command;
+ xfc->rail->ClientSystemCommand(xfc->rail, &syscommand);
+}
+
+/**
+ * The position of the X window can become out of sync with the RDP window
+ * if the X window is moved locally by the window manager. In this event
+ * send an update to the RDP server informing it of the new window position
+ * and size.
+ */
+void xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow)
+{
+ RAIL_WINDOW_MOVE_ORDER windowMove;
+
+ if (!appWindow->is_mapped || appWindow->local_move.state != LMS_NOT_ACTIVE)
+ return;
+
+ /* If current window position disagrees with RDP window position, send update to RDP server */
+ if (appWindow->x != appWindow->windowOffsetX || appWindow->y != appWindow->windowOffsetY ||
+ appWindow->width != (INT64)appWindow->windowWidth ||
+ appWindow->height != (INT64)appWindow->windowHeight)
+ {
+ windowMove.windowId = appWindow->windowId;
+ /*
+ * Calculate new size/position for the rail window(new values for
+ * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
+ */
+ windowMove.left = appWindow->x - appWindow->resizeMarginLeft;
+ windowMove.top = appWindow->y - appWindow->resizeMarginTop;
+ windowMove.right = appWindow->x + appWindow->width + appWindow->resizeMarginRight;
+ windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom;
+ xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
+ }
+}
+
+void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow)
+{
+ int x = 0;
+ int y = 0;
+ int child_x = 0;
+ int child_y = 0;
+ unsigned int mask = 0;
+ Window root_window = 0;
+ Window child_window = 0;
+ rdpInput* input = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ input = xfc->common.context.input;
+ WINPR_ASSERT(input);
+
+ if ((appWindow->local_move.direction == _NET_WM_MOVERESIZE_MOVE_KEYBOARD) ||
+ (appWindow->local_move.direction == _NET_WM_MOVERESIZE_SIZE_KEYBOARD))
+ {
+ RAIL_WINDOW_MOVE_ORDER windowMove;
+
+ /*
+ * For keyboard moves send and explicit update to RDP server
+ */
+ windowMove.windowId = appWindow->windowId;
+ /*
+ * Calculate new size/position for the rail window(new values for
+ * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
+ *
+ */
+ windowMove.left = appWindow->x - appWindow->resizeMarginLeft;
+ windowMove.top = appWindow->y - appWindow->resizeMarginTop;
+ windowMove.right =
+ appWindow->x + appWindow->width +
+ appWindow
+ ->resizeMarginRight; /* In the update to RDP the position is one past the window */
+ windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom;
+ xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
+ }
+
+ /*
+ * Simulate button up at new position to end the local move (per RDP spec)
+ */
+ XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x,
+ &child_y, &mask);
+
+ /* only send the mouse coordinates if not a keyboard move or size */
+ if ((appWindow->local_move.direction != _NET_WM_MOVERESIZE_MOVE_KEYBOARD) &&
+ (appWindow->local_move.direction != _NET_WM_MOVERESIZE_SIZE_KEYBOARD))
+ {
+ freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_BUTTON1, x, y);
+ }
+
+ /*
+ * Proactively update the RAIL window dimensions. There is a race condition where
+ * we can start to receive GDI orders for the new window dimensions before we
+ * receive the RAIL ORDER for the new window size. This avoids that race condition.
+ */
+ appWindow->windowOffsetX = appWindow->x;
+ appWindow->windowOffsetY = appWindow->y;
+ appWindow->windowWidth = appWindow->width;
+ appWindow->windowHeight = appWindow->height;
+ appWindow->local_move.state = LMS_TERMINATING;
+}
+
+BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect)
+{
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, windowId);
+
+ WINPR_ASSERT(rect);
+
+ if (!appWindow)
+ return FALSE;
+
+ const RECTANGLE_16 windowRect = { .left = MAX(appWindow->x, 0),
+ .top = MAX(appWindow->y, 0),
+ .right = MAX(appWindow->x + appWindow->width, 0),
+ .bottom = MAX(appWindow->y + appWindow->height, 0) };
+
+ REGION16 windowInvalidRegion = { 0 };
+ region16_init(&windowInvalidRegion);
+ region16_union_rect(&windowInvalidRegion, &windowInvalidRegion, &windowRect);
+ region16_intersect_rect(&windowInvalidRegion, &windowInvalidRegion, rect);
+
+ if (!region16_is_empty(&windowInvalidRegion))
+ {
+ const RECTANGLE_16* extents = region16_extents(&windowInvalidRegion);
+ const RECTANGLE_16 updateRect = { .left = extents->left - appWindow->x,
+ .top = extents->top - appWindow->y,
+ .right = extents->right - appWindow->x,
+ .bottom = extents->bottom - appWindow->y };
+
+ xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top,
+ updateRect.right - updateRect.left, updateRect.bottom - updateRect.top);
+ }
+ region16_uninit(&windowInvalidRegion);
+ return TRUE;
+}
+
+static BOOL rail_paint_fn(const void* pvkey, void* value, void* pvarg)
+{
+ rail_paint_fn_arg_t* arg = pvarg;
+ WINPR_ASSERT(pvkey);
+ WINPR_ASSERT(arg);
+
+ const UINT64 key = *(const UINT64*)pvkey;
+ return xf_rail_paint_surface(arg->xfc, key, arg->rect);
+}
+
+BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect)
+{
+ rail_paint_fn_arg_t arg = { .xfc = xfc, .rect = rect };
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(rect);
+
+ if (!xfc->railWindows)
+ return TRUE;
+
+ return HashTable_Foreach(xfc->railWindows, rail_paint_fn, &arg);
+}
+
+/* RemoteApp Core Protocol Extension */
+
+static BOOL xf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ xfAppWindow* appWindow = NULL;
+ xfContext* xfc = (xfContext*)context;
+ UINT32 fieldFlags = orderInfo->fieldFlags;
+ BOOL position_or_size_updated = FALSE;
+ appWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (fieldFlags & WINDOW_ORDER_STATE_NEW)
+ {
+ if (!appWindow)
+ appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX,
+ windowState->windowOffsetY, windowState->windowWidth,
+ windowState->windowHeight, 0xFFFFFFFF);
+
+ if (!appWindow)
+ return FALSE;
+
+ appWindow->dwStyle = windowState->style;
+ appWindow->dwExStyle = windowState->extendedStyle;
+
+ /* Ensure window always gets a window title */
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+ char* title = NULL;
+
+ cnv.b = windowState->titleInfo.string;
+ if (windowState->titleInfo.length == 0)
+ {
+ if (!(title = _strdup("")))
+ {
+ WLog_ERR(TAG, "failed to duplicate empty window title string");
+ /* error handled below */
+ }
+ }
+ else if (!(title = ConvertWCharNToUtf8Alloc(
+ cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
+ {
+ WLog_ERR(TAG, "failed to convert window title");
+ /* error handled below */
+ }
+
+ appWindow->title = title;
+ }
+ else
+ {
+ if (!(appWindow->title = _strdup("RdpRailWindow")))
+ WLog_ERR(TAG, "failed to duplicate default window title string");
+ }
+
+ if (!appWindow->title)
+ {
+ free(appWindow);
+ return FALSE;
+ }
+
+ xf_AppWindowInit(xfc, appWindow);
+ }
+
+ if (!appWindow)
+ return FALSE;
+
+ /* Keep track of any position/size update so that we can force a refresh of the window */
+ if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) ||
+ (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY))
+ {
+ position_or_size_updated = TRUE;
+ }
+
+ /* Update Parameters */
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
+ {
+ appWindow->windowOffsetX = windowState->windowOffsetX;
+ appWindow->windowOffsetY = windowState->windowOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
+ {
+ appWindow->windowWidth = windowState->windowWidth;
+ appWindow->windowHeight = windowState->windowHeight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
+ {
+ appWindow->resizeMarginLeft = windowState->resizeMarginLeft;
+ appWindow->resizeMarginRight = windowState->resizeMarginRight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
+ {
+ appWindow->resizeMarginTop = windowState->resizeMarginTop;
+ appWindow->resizeMarginBottom = windowState->resizeMarginBottom;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_OWNER)
+ {
+ appWindow->ownerWindowId = windowState->ownerWindowId;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ appWindow->dwStyle = windowState->style;
+ appWindow->dwExStyle = windowState->extendedStyle;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ appWindow->showState = windowState->showState;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ char* title = NULL;
+ union
+ {
+ WCHAR* wc;
+ BYTE* b;
+ } cnv;
+
+ cnv.b = windowState->titleInfo.string;
+ if (windowState->titleInfo.length == 0)
+ {
+ if (!(title = _strdup("")))
+ {
+ WLog_ERR(TAG, "failed to duplicate empty window title string");
+ return FALSE;
+ }
+ }
+ else if (!(title = ConvertWCharNToUtf8Alloc(
+ cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
+ {
+ WLog_ERR(TAG, "failed to convert window title");
+ return FALSE;
+ }
+
+ free(appWindow->title);
+ appWindow->title = title;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
+ {
+ appWindow->clientOffsetX = windowState->clientOffsetX;
+ appWindow->clientOffsetY = windowState->clientOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
+ {
+ appWindow->clientAreaWidth = windowState->clientAreaWidth;
+ appWindow->clientAreaHeight = windowState->clientAreaHeight;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
+ {
+ appWindow->windowClientDeltaX = windowState->windowClientDeltaX;
+ appWindow->windowClientDeltaY = windowState->windowClientDeltaY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ if (appWindow->windowRects)
+ {
+ free(appWindow->windowRects);
+ appWindow->windowRects = NULL;
+ }
+
+ appWindow->numWindowRects = windowState->numWindowRects;
+
+ if (appWindow->numWindowRects)
+ {
+ appWindow->windowRects =
+ (RECTANGLE_16*)calloc(appWindow->numWindowRects, sizeof(RECTANGLE_16));
+
+ if (!appWindow->windowRects)
+ return FALSE;
+
+ CopyMemory(appWindow->windowRects, windowState->windowRects,
+ appWindow->numWindowRects * sizeof(RECTANGLE_16));
+ }
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
+ {
+ appWindow->visibleOffsetX = windowState->visibleOffsetX;
+ appWindow->visibleOffsetY = windowState->visibleOffsetY;
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
+ {
+ if (appWindow->visibilityRects)
+ {
+ free(appWindow->visibilityRects);
+ appWindow->visibilityRects = NULL;
+ }
+
+ appWindow->numVisibilityRects = windowState->numVisibilityRects;
+
+ if (appWindow->numVisibilityRects)
+ {
+ appWindow->visibilityRects =
+ (RECTANGLE_16*)calloc(appWindow->numVisibilityRects, sizeof(RECTANGLE_16));
+
+ if (!appWindow->visibilityRects)
+ return FALSE;
+
+ CopyMemory(appWindow->visibilityRects, windowState->visibilityRects,
+ appWindow->numVisibilityRects * sizeof(RECTANGLE_16));
+ }
+ }
+
+ /* Update Window */
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
+ {
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
+ {
+ xf_ShowWindow(xfc, appWindow, appWindow->showState);
+ }
+
+ if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
+ {
+ if (appWindow->title)
+ xf_SetWindowText(xfc, appWindow, appWindow->title);
+ }
+
+ if (position_or_size_updated)
+ {
+ UINT32 visibilityRectsOffsetX =
+ (appWindow->visibleOffsetX -
+ (appWindow->clientOffsetX - appWindow->windowClientDeltaX));
+ UINT32 visibilityRectsOffsetY =
+ (appWindow->visibleOffsetY -
+ (appWindow->clientOffsetY - appWindow->windowClientDeltaY));
+
+ /*
+ * The rail server like to set the window to a small size when it is minimized even though
+ * it is hidden in some cases this can cause the window not to restore back to its original
+ * size. Therefore we don't update our local window when that rail window state is minimized
+ */
+ if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
+ {
+ /* Redraw window area if already in the correct position */
+ if (appWindow->x == (INT64)appWindow->windowOffsetX &&
+ appWindow->y == (INT64)appWindow->windowOffsetY &&
+ appWindow->width == (INT64)appWindow->windowWidth &&
+ appWindow->height == (INT64)appWindow->windowHeight)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth,
+ appWindow->windowHeight);
+ }
+ else
+ {
+ xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX, appWindow->windowOffsetY,
+ appWindow->windowWidth, appWindow->windowHeight);
+ }
+
+ xf_SetWindowVisibilityRects(xfc, appWindow, visibilityRectsOffsetX,
+ visibilityRectsOffsetY, appWindow->visibilityRects,
+ appWindow->numVisibilityRects);
+ }
+ }
+
+ /* We should only be using the visibility rects for shaping the window */
+ /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
+ {
+ xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects);
+ }*/
+ return TRUE;
+}
+
+static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ xfContext* xfc = (xfContext*)context;
+ return xf_rail_del_window(xfc, orderInfo->windowId);
+}
+
+static xfRailIconCache* RailIconCache_New(rdpSettings* settings)
+{
+ xfRailIconCache* cache = NULL;
+ cache = calloc(1, sizeof(xfRailIconCache));
+
+ if (!cache)
+ return NULL;
+
+ cache->numCaches = freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
+ cache->numCacheEntries =
+ freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
+ cache->entries = calloc(1ull * cache->numCaches * cache->numCacheEntries, sizeof(xfRailIcon));
+
+ if (!cache->entries)
+ {
+ WLog_ERR(TAG, "failed to allocate icon cache %" PRIu32 " x %" PRIu32 " entries",
+ cache->numCaches, cache->numCacheEntries);
+ free(cache);
+ return NULL;
+ }
+
+ return cache;
+}
+
+static void RailIconCache_Free(xfRailIconCache* cache)
+{
+ if (cache)
+ {
+ for (UINT32 i = 0; i < cache->numCaches * cache->numCacheEntries; i++)
+ {
+ free(cache->entries[i].data);
+ }
+
+ free(cache->scratch.data);
+ free(cache->entries);
+ free(cache);
+ }
+}
+
+static xfRailIcon* RailIconCache_Lookup(xfRailIconCache* cache, UINT8 cacheId, UINT16 cacheEntry)
+{
+ /*
+ * MS-RDPERP 2.2.1.2.3 Icon Info (TS_ICON_INFO)
+ *
+ * CacheId (1 byte):
+ * If the value is 0xFFFF, the icon SHOULD NOT be cached.
+ *
+ * Yes, the spec says "0xFFFF" in the 2018-03-16 revision,
+ * but the actual protocol field is 1-byte wide.
+ */
+ if (cacheId == 0xFF)
+ return &cache->scratch;
+
+ if (cacheId >= cache->numCaches)
+ return NULL;
+
+ if (cacheEntry >= cache->numCacheEntries)
+ return NULL;
+
+ return &cache->entries[cache->numCacheEntries * cacheId + cacheEntry];
+}
+
+/*
+ * _NET_WM_ICON format is defined as "array of CARDINAL" values which for
+ * Xlib must be represented with an array of C's "long" values. Note that
+ * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast
+ * the bitmap data as (unsigned char*), we have to copy all the pixels.
+ *
+ * The first two values are width and height followed by actual color data
+ * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal,
+ * left-to-right top-down order.
+ */
+static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon)
+{
+ BYTE* argbPixels = NULL;
+ BYTE* nextPixel = NULL;
+ long* pixels = NULL;
+ int nelements = 0;
+ argbPixels = calloc(1ull * iconInfo->width * iconInfo->height, 4);
+
+ if (!argbPixels)
+ goto error;
+
+ if (!freerdp_image_copy_from_icon_data(
+ argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0, iconInfo->width, iconInfo->height,
+ iconInfo->bitsColor, iconInfo->cbBitsColor, iconInfo->bitsMask, iconInfo->cbBitsMask,
+ iconInfo->colorTable, iconInfo->cbColorTable, iconInfo->bpp))
+ goto error;
+
+ nelements = 2 + iconInfo->width * iconInfo->height;
+ pixels = realloc(railIcon->data, nelements * sizeof(long));
+
+ if (!pixels)
+ goto error;
+
+ railIcon->data = pixels;
+ railIcon->length = nelements;
+ pixels[0] = iconInfo->width;
+ pixels[1] = iconInfo->height;
+ nextPixel = argbPixels;
+
+ for (int i = 2; i < nelements; i++)
+ {
+ pixels[i] = FreeRDPReadColor(nextPixel, PIXEL_FORMAT_BGRA32);
+ nextPixel += 4;
+ }
+
+ free(argbPixels);
+ return TRUE;
+error:
+ free(argbPixels);
+ return FALSE;
+}
+
+static void xf_rail_set_window_icon(xfContext* xfc, xfAppWindow* railWindow, xfRailIcon* icon,
+ BOOL replace)
+{
+ WINPR_ASSERT(xfc);
+
+ LogTagAndXChangeProperty(TAG, xfc->display, railWindow->handle, xfc->_NET_WM_ICON, XA_CARDINAL,
+ 32, replace ? PropModeReplace : PropModeAppend,
+ (unsigned char*)icon->data, icon->length);
+ XFlush(xfc->display);
+}
+
+static BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ xfContext* xfc = (xfContext*)context;
+ xfAppWindow* railWindow = NULL;
+ xfRailIcon* icon = NULL;
+ BOOL replaceIcon = 0;
+ railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (!railWindow)
+ return TRUE;
+
+ icon = RailIconCache_Lookup(xfc->railIconCache, windowIcon->iconInfo->cacheId,
+ windowIcon->iconInfo->cacheEntry);
+
+ if (!icon)
+ {
+ WLog_WARN(TAG, "failed to get icon from cache %02X:%04X", windowIcon->iconInfo->cacheId,
+ windowIcon->iconInfo->cacheEntry);
+ return FALSE;
+ }
+
+ if (!convert_rail_icon(windowIcon->iconInfo, icon))
+ {
+ WLog_WARN(TAG, "failed to convert icon for window %08X", orderInfo->windowId);
+ return FALSE;
+ }
+
+ replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
+ xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
+ return TRUE;
+}
+
+static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ xfContext* xfc = (xfContext*)context;
+ xfAppWindow* railWindow = NULL;
+ xfRailIcon* icon = NULL;
+ BOOL replaceIcon = 0;
+ railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
+
+ if (!railWindow)
+ return TRUE;
+
+ icon = RailIconCache_Lookup(xfc->railIconCache, windowCachedIcon->cachedIcon.cacheId,
+ windowCachedIcon->cachedIcon.cacheEntry);
+
+ if (!icon)
+ {
+ WLog_WARN(TAG, "failed to get icon from cache %02X:%04X",
+ windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry);
+ return FALSE;
+ }
+
+ replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
+ xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
+ return TRUE;
+}
+
+static BOOL xf_rail_notify_icon_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
+ {
+ }
+
+ if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
+ {
+ }
+
+ return TRUE;
+}
+
+static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
+}
+
+static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
+}
+
+static BOOL xf_rail_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ return TRUE;
+}
+
+static BOOL xf_rail_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ return TRUE;
+}
+
+static BOOL xf_rail_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ xfContext* xfc = (xfContext*)context;
+ xf_rail_disable_remoteapp_mode(xfc);
+ return TRUE;
+}
+
+static void xf_rail_register_update_callbacks(rdpUpdate* update)
+{
+ rdpWindowUpdate* window = update->window;
+ window->WindowCreate = xf_rail_window_common;
+ window->WindowUpdate = xf_rail_window_common;
+ window->WindowDelete = xf_rail_window_delete;
+ window->WindowIcon = xf_rail_window_icon;
+ window->WindowCachedIcon = xf_rail_window_cached_icon;
+ window->NotifyIconCreate = xf_rail_notify_icon_create;
+ window->NotifyIconUpdate = xf_rail_notify_icon_update;
+ window->NotifyIconDelete = xf_rail_notify_icon_delete;
+ window->MonitoredDesktop = xf_rail_monitored_desktop;
+ window->NonMonitoredDesktop = xf_rail_non_monitored_desktop;
+}
+
+/* RemoteApp Virtual Channel Extension */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_execute_result(RailClientContext* context,
+ const RAIL_EXEC_RESULT_ORDER* execResult)
+{
+ xfContext* xfc = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(execResult);
+
+ xfc = (xfContext*)context->custom;
+ WINPR_ASSERT(xfc);
+
+ if (execResult->execResult != RAIL_EXEC_S_OK)
+ {
+ WLog_ERR(TAG, "RAIL exec error: execResult=%s NtError=0x%X\n",
+ error_code_names[execResult->execResult], execResult->rawResult);
+ freerdp_abort_connect_context(&xfc->common.context);
+ }
+ else
+ {
+ xf_rail_enable_remoteapp_mode(xfc);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_system_param(RailClientContext* context,
+ const RAIL_SYSPARAM_ORDER* sysparam)
+{
+ // TODO: Actually apply param
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_handshake(RailClientContext* context,
+ const RAIL_HANDSHAKE_ORDER* handshake)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_handshake_ex(RailClientContext* context,
+ const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_local_move_size(RailClientContext* context,
+ const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
+{
+ int x = 0;
+ int y = 0;
+ int direction = 0;
+ Window child_window = 0;
+ xfContext* xfc = (xfContext*)context->custom;
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId);
+
+ if (!appWindow)
+ return ERROR_INTERNAL_ERROR;
+
+ switch (localMoveSize->moveSizeType)
+ {
+ case RAIL_WMSZ_LEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_LEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_RIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_RIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOP:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOP;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOPLEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOPLEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_TOPRIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOM:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOM;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOMLEFT:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_BOTTOMRIGHT:
+ direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ break;
+
+ case RAIL_WMSZ_MOVE:
+ direction = _NET_WM_MOVERESIZE_MOVE;
+ XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
+ localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window);
+ break;
+
+ case RAIL_WMSZ_KEYMOVE:
+ direction = _NET_WM_MOVERESIZE_MOVE_KEYBOARD;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ /* FIXME: local keyboard moves not working */
+ return CHANNEL_RC_OK;
+
+ case RAIL_WMSZ_KEYSIZE:
+ direction = _NET_WM_MOVERESIZE_SIZE_KEYBOARD;
+ x = localMoveSize->posX;
+ y = localMoveSize->posY;
+ /* FIXME: local keyboard moves not working */
+ return CHANNEL_RC_OK;
+ }
+
+ if (localMoveSize->isMoveSizeStart)
+ xf_StartLocalMoveSize(xfc, appWindow, direction, x, y);
+ else
+ xf_EndLocalMoveSize(xfc, appWindow);
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_min_max_info(RailClientContext* context,
+ const RAIL_MINMAXINFO_ORDER* minMaxInfo)
+{
+ xfContext* xfc = (xfContext*)context->custom;
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId);
+
+ if (appWindow)
+ {
+ xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight,
+ minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth,
+ minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth,
+ minMaxInfo->maxTrackHeight);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_language_bar_info(RailClientContext* context,
+ const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
+{
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT xf_rail_server_get_appid_response(RailClientContext* context,
+ const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
+{
+ return CHANNEL_RC_OK;
+}
+
+static BOOL rail_window_key_equals(const void* key1, const void* key2)
+{
+ const UINT64* k1 = (const UINT64*)key1;
+ const UINT64* k2 = (const UINT64*)key2;
+
+ if (!k1 || !k2)
+ return FALSE;
+
+ return *k1 == *k2;
+}
+
+static UINT32 rail_window_key_hash(const void* key)
+{
+ const UINT64* k1 = (const UINT64*)key;
+ return (UINT32)*k1;
+}
+
+static void rail_window_free(void* value)
+{
+ xfAppWindow* appWindow = (xfAppWindow*)value;
+
+ if (!appWindow)
+ return;
+
+ xf_DestroyWindow(appWindow->xfc, appWindow);
+}
+
+int xf_rail_init(xfContext* xfc, RailClientContext* rail)
+{
+ rdpContext* context = (rdpContext*)xfc;
+
+ if (!xfc || !rail)
+ return 0;
+
+ xfc->rail = rail;
+ xf_rail_register_update_callbacks(context->update);
+ rail->custom = (void*)xfc;
+ rail->ServerExecuteResult = xf_rail_server_execute_result;
+ rail->ServerSystemParam = xf_rail_server_system_param;
+ rail->ServerHandshake = xf_rail_server_handshake;
+ rail->ServerHandshakeEx = xf_rail_server_handshake_ex;
+ rail->ServerLocalMoveSize = xf_rail_server_local_move_size;
+ rail->ServerMinMaxInfo = xf_rail_server_min_max_info;
+ rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info;
+ rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response;
+ xfc->railWindows = HashTable_New(TRUE);
+
+ if (!xfc->railWindows)
+ return 0;
+
+ if (!HashTable_SetHashFunction(xfc->railWindows, rail_window_key_hash))
+ goto fail;
+ {
+ wObject* obj = HashTable_KeyObject(xfc->railWindows);
+ obj->fnObjectEquals = rail_window_key_equals;
+ }
+ {
+ wObject* obj = HashTable_ValueObject(xfc->railWindows);
+ obj->fnObjectFree = rail_window_free;
+ }
+ xfc->railIconCache = RailIconCache_New(xfc->common.context.settings);
+
+ if (!xfc->railIconCache)
+ {
+ }
+
+ return 1;
+fail:
+ HashTable_Free(xfc->railWindows);
+ return 0;
+}
+
+int xf_rail_uninit(xfContext* xfc, RailClientContext* rail)
+{
+ WINPR_UNUSED(rail);
+
+ if (xfc->rail)
+ {
+ xfc->rail->custom = NULL;
+ xfc->rail = NULL;
+ }
+
+ if (xfc->railWindows)
+ {
+ HashTable_Free(xfc->railWindows);
+ xfc->railWindows = NULL;
+ }
+
+ if (xfc->railIconCache)
+ {
+ RailIconCache_Free(xfc->railIconCache);
+ xfc->railIconCache = NULL;
+ }
+
+ return 1;
+}
+
+xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width,
+ UINT32 height, UINT32 surfaceId)
+{
+ xfAppWindow* appWindow = NULL;
+
+ if (!xfc)
+ return NULL;
+
+ appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow));
+
+ if (!appWindow)
+ return NULL;
+
+ appWindow->xfc = xfc;
+ appWindow->windowId = id;
+ appWindow->surfaceId = surfaceId;
+ appWindow->x = x;
+ appWindow->y = y;
+ appWindow->width = width;
+ appWindow->height = height;
+
+ if (!xf_AppWindowCreate(xfc, appWindow))
+ goto fail;
+ if (!HashTable_Insert(xfc->railWindows, &appWindow->windowId, (void*)appWindow))
+ goto fail;
+ return appWindow;
+fail:
+ rail_window_free(appWindow);
+ return NULL;
+}
+
+BOOL xf_rail_del_window(xfContext* xfc, UINT64 id)
+{
+ if (!xfc)
+ return FALSE;
+
+ if (!xfc->railWindows)
+ return FALSE;
+
+ return HashTable_Remove(xfc->railWindows, &id);
+}
+
+xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id)
+{
+ if (!xfc)
+ return NULL;
+
+ if (!xfc->railWindows)
+ return FALSE;
+
+ return HashTable_GetItemValue(xfc->railWindows, &id);
+}
diff --git a/client/X11/xf_rail.h b/client/X11/xf_rail.h
new file mode 100644
index 0000000..32b5f44
--- /dev/null
+++ b/client/X11/xf_rail.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 RAIL
+ *
+ * Copyright 2011 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_CLIENT_X11_RAIL_H
+#define FREERDP_CLIENT_X11_RAIL_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+#include <freerdp/client/rail.h>
+
+BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect);
+BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect);
+
+void xf_rail_send_client_system_command(xfContext* xfc, UINT32 windowId, UINT16 command);
+void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled);
+void xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow);
+void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow);
+void xf_rail_enable_remoteapp_mode(xfContext* xfc);
+void xf_rail_disable_remoteapp_mode(xfContext* xfc);
+
+xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width,
+ UINT32 height, UINT32 surfaceId);
+xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id);
+
+BOOL xf_rail_del_window(xfContext* xfc, UINT64 id);
+
+int xf_rail_init(xfContext* xfc, RailClientContext* rail);
+int xf_rail_uninit(xfContext* xfc, RailClientContext* rail);
+
+#endif /* FREERDP_CLIENT_X11_RAIL_H */
diff --git a/client/X11/xf_tsmf.c b/client/X11/xf_tsmf.c
new file mode 100644
index 0000000..1c5e5b3
--- /dev/null
+++ b/client/X11/xf_tsmf.c
@@ -0,0 +1,471 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Video Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * 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 <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XShm.h>
+
+#include <freerdp/log.h>
+#include <freerdp/client/tsmf.h>
+
+#include "xf_tsmf.h"
+
+#ifdef WITH_XV
+
+#include <X11/extensions/Xv.h>
+#include <X11/extensions/Xvlib.h>
+
+static long xv_port = 0;
+
+struct xf_xv_context
+{
+ long xv_port;
+ Atom xv_colorkey_atom;
+ int xv_image_size;
+ int xv_shmid;
+ char* xv_shmaddr;
+ UINT32* xv_pixfmts;
+};
+typedef struct xf_xv_context xfXvContext;
+
+#define TAG CLIENT_TAG("x11")
+
+static BOOL xf_tsmf_is_format_supported(xfXvContext* xv, UINT32 pixfmt)
+{
+ if (!xv->xv_pixfmts)
+ return FALSE;
+
+ for (int i = 0; xv->xv_pixfmts[i]; i++)
+ {
+ if (xv->xv_pixfmts[i] == pixfmt)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int xf_tsmf_xv_video_frame_event(TsmfClientContext* tsmf, TSMF_VIDEO_FRAME_EVENT* event)
+{
+ int x = 0;
+ int y = 0;
+ UINT32 width = 0;
+ UINT32 height = 0;
+ BYTE* data1 = NULL;
+ BYTE* data2 = NULL;
+ UINT32 pixfmt = 0;
+ UINT32 xvpixfmt = 0;
+ XvImage* image = NULL;
+ int colorkey = 0;
+ int numRects = 0;
+ xfContext* xfc = NULL;
+ xfXvContext* xv = NULL;
+ XRectangle* xrects = NULL;
+ XShmSegmentInfo shminfo;
+ BOOL converti420yv12 = FALSE;
+
+ if (!tsmf)
+ return -1;
+
+ xfc = (xfContext*)tsmf->custom;
+
+ if (!xfc)
+ return -1;
+
+ xv = (xfXvContext*)xfc->xv_context;
+
+ if (!xv)
+ return -1;
+
+ if (xv->xv_port == 0)
+ return -1001;
+
+ /* In case the player is minimized */
+ if (event->x < -2048 || event->y < -2048 || event->numVisibleRects == 0)
+ {
+ return -1002;
+ }
+
+ xrects = NULL;
+ numRects = event->numVisibleRects;
+
+ if (numRects > 0)
+ {
+ xrects = (XRectangle*)calloc(numRects, sizeof(XRectangle));
+
+ if (!xrects)
+ return -1;
+
+ for (int i = 0; i < numRects; i++)
+ {
+ x = event->x + event->visibleRects[i].left;
+ y = event->y + event->visibleRects[i].top;
+ width = event->visibleRects[i].right - event->visibleRects[i].left;
+ height = event->visibleRects[i].bottom - event->visibleRects[i].top;
+
+ xrects[i].x = x;
+ xrects[i].y = y;
+ xrects[i].width = width;
+ xrects[i].height = height;
+ }
+ }
+
+ if (xv->xv_colorkey_atom != None)
+ {
+ XvGetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom, &colorkey);
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+ XSetForeground(xfc->display, xfc->gc, colorkey);
+
+ if (event->numVisibleRects < 1)
+ {
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+ else
+ {
+ XFillRectangles(xfc->display, xfc->window->handle, xfc->gc, xrects, numRects);
+ }
+ }
+ else
+ {
+ XSetFunction(xfc->display, xfc->gc, GXcopy);
+ XSetFillStyle(xfc->display, xfc->gc, FillSolid);
+
+ if (event->numVisibleRects < 1)
+ {
+ XSetClipMask(xfc->display, xfc->gc, None);
+ }
+ else
+ {
+ XSetClipRectangles(xfc->display, xfc->gc, 0, 0, xrects, numRects, YXBanded);
+ }
+ }
+
+ pixfmt = event->framePixFmt;
+
+ if (xf_tsmf_is_format_supported(xv, pixfmt))
+ {
+ xvpixfmt = pixfmt;
+ }
+ else if (pixfmt == RDP_PIXFMT_I420 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_YV12))
+ {
+ xvpixfmt = RDP_PIXFMT_YV12;
+ converti420yv12 = TRUE;
+ }
+ else if (pixfmt == RDP_PIXFMT_YV12 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_I420))
+ {
+ xvpixfmt = RDP_PIXFMT_I420;
+ converti420yv12 = TRUE;
+ }
+ else
+ {
+ WLog_DBG(TAG, "pixel format 0x%" PRIX32 " not supported by hardware.", pixfmt);
+ free(xrects);
+ return -1003;
+ }
+
+ image = XvShmCreateImage(xfc->display, xv->xv_port, xvpixfmt, 0, event->frameWidth,
+ event->frameHeight, &shminfo);
+
+ if (xv->xv_image_size != image->data_size)
+ {
+ if (xv->xv_image_size > 0)
+ {
+ shmdt(xv->xv_shmaddr);
+ shmctl(xv->xv_shmid, IPC_RMID, NULL);
+ }
+
+ xv->xv_image_size = image->data_size;
+ xv->xv_shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777);
+ xv->xv_shmaddr = shmat(xv->xv_shmid, 0, 0);
+ }
+
+ shminfo.shmid = xv->xv_shmid;
+ shminfo.shmaddr = image->data = xv->xv_shmaddr;
+ shminfo.readOnly = FALSE;
+
+ if (!XShmAttach(xfc->display, &shminfo))
+ {
+ XFree(image);
+ free(xrects);
+ WLog_DBG(TAG, "XShmAttach failed.");
+ return -1004;
+ }
+
+ /* The video driver may align each line to a different size
+ and we need to convert our original image data. */
+ switch (pixfmt)
+ {
+ case RDP_PIXFMT_I420:
+ case RDP_PIXFMT_YV12:
+ /* Y */
+ if (image->pitches[0] == event->frameWidth)
+ {
+ CopyMemory(image->data + image->offsets[0], event->frameData,
+ event->frameWidth * event->frameHeight);
+ }
+ else
+ {
+ for (int i = 0; i < event->frameHeight; i++)
+ {
+ CopyMemory(image->data + image->offsets[0] + i * image->pitches[0],
+ event->frameData + i * event->frameWidth, event->frameWidth);
+ }
+ }
+ /* UV */
+ /* Conversion between I420 and YV12 is to simply swap U and V */
+ if (!converti420yv12)
+ {
+ data1 = event->frameData + event->frameWidth * event->frameHeight;
+ data2 = event->frameData + event->frameWidth * event->frameHeight +
+ event->frameWidth * event->frameHeight / 4;
+ }
+ else
+ {
+ data2 = event->frameData + event->frameWidth * event->frameHeight;
+ data1 = event->frameData + event->frameWidth * event->frameHeight +
+ event->frameWidth * event->frameHeight / 4;
+ image->id = pixfmt == RDP_PIXFMT_I420 ? RDP_PIXFMT_YV12 : RDP_PIXFMT_I420;
+ }
+
+ if (image->pitches[1] * 2 == event->frameWidth)
+ {
+ CopyMemory(image->data + image->offsets[1], data1,
+ event->frameWidth * event->frameHeight / 4);
+ CopyMemory(image->data + image->offsets[2], data2,
+ event->frameWidth * event->frameHeight / 4);
+ }
+ else
+ {
+ for (int i = 0; i < event->frameHeight / 2; i++)
+ {
+ CopyMemory(image->data + image->offsets[1] + i * image->pitches[1],
+ data1 + i * event->frameWidth / 2, event->frameWidth / 2);
+ CopyMemory(image->data + image->offsets[2] + i * image->pitches[2],
+ data2 + i * event->frameWidth / 2, event->frameWidth / 2);
+ }
+ }
+ break;
+
+ default:
+ if (image->data_size < 0)
+ {
+ free(xrects);
+ return -2000;
+ }
+ else
+ {
+ const size_t size = ((UINT32)image->data_size <= event->frameSize)
+ ? (UINT32)image->data_size
+ : event->frameSize;
+ CopyMemory(image->data, event->frameData, size);
+ }
+ break;
+ }
+
+ XvShmPutImage(xfc->display, xv->xv_port, xfc->window->handle, xfc->gc, image, 0, 0,
+ image->width, image->height, event->x, event->y, event->width, event->height,
+ FALSE);
+
+ if (xv->xv_colorkey_atom == None)
+ XSetClipMask(xfc->display, xfc->gc, None);
+
+ XSync(xfc->display, FALSE);
+
+ XShmDetach(xfc->display, &shminfo);
+ XFree(image);
+
+ free(xrects);
+
+ return 1;
+}
+
+static int xf_tsmf_xv_init(xfContext* xfc, TsmfClientContext* tsmf)
+{
+ int ret = 0;
+ unsigned int version = 0;
+ unsigned int release = 0;
+ unsigned int event_base = 0;
+ unsigned int error_base = 0;
+ unsigned int request_base = 0;
+ unsigned int num_adaptors = 0;
+ xfXvContext* xv = NULL;
+ XvAdaptorInfo* ai = NULL;
+ XvAttribute* attr = NULL;
+ XvImageFormatValues* fo = NULL;
+
+ if (xfc->xv_context)
+ return 1; /* context already created */
+
+ xv = (xfXvContext*)calloc(1, sizeof(xfXvContext));
+
+ if (!xv)
+ return -1;
+
+ xfc->xv_context = xv;
+
+ xv->xv_colorkey_atom = None;
+ xv->xv_image_size = 0;
+ xv->xv_port = xv_port;
+
+ if (!XShmQueryExtension(xfc->display))
+ {
+ WLog_DBG(TAG, "no xshm available.");
+ return -1;
+ }
+
+ ret =
+ XvQueryExtension(xfc->display, &version, &release, &request_base, &event_base, &error_base);
+
+ if (ret != Success)
+ {
+ WLog_DBG(TAG, "XvQueryExtension failed %d.", ret);
+ return -1;
+ }
+
+ WLog_DBG(TAG, "version %u release %u", version, release);
+
+ ret = XvQueryAdaptors(xfc->display, DefaultRootWindow(xfc->display), &num_adaptors, &ai);
+
+ if (ret != Success)
+ {
+ WLog_DBG(TAG, "XvQueryAdaptors failed %d.", ret);
+ return -1;
+ }
+
+ for (unsigned int i = 0; i < num_adaptors; i++)
+ {
+ WLog_DBG(TAG, "adapter port %lu-%lu (%s)", ai[i].base_id,
+ ai[i].base_id + ai[i].num_ports - 1, ai[i].name);
+
+ if (xv->xv_port == 0 && i == num_adaptors - 1)
+ xv->xv_port = ai[i].base_id;
+ }
+
+ if (num_adaptors > 0)
+ XvFreeAdaptorInfo(ai);
+
+ if (xv->xv_port == 0)
+ {
+ WLog_DBG(TAG, "no adapter selected, video frames will not be processed.");
+ return -1;
+ }
+ WLog_DBG(TAG, "selected %ld", xv->xv_port);
+
+ attr = XvQueryPortAttributes(xfc->display, xv->xv_port, &ret);
+
+ unsigned int i = 0;
+ for (; i < (unsigned int)ret; i++)
+ {
+ if (strcmp(attr[i].name, "XV_COLORKEY") == 0)
+ {
+ xv->xv_colorkey_atom = XInternAtom(xfc->display, "XV_COLORKEY", FALSE);
+ XvSetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom,
+ attr[i].min_value + 1);
+ break;
+ }
+ }
+ XFree(attr);
+
+ WLog_DBG(TAG, "xf_tsmf_init: pixel format ");
+
+ fo = XvListImageFormats(xfc->display, xv->xv_port, &ret);
+
+ if (ret > 0)
+ {
+ xv->xv_pixfmts = (UINT32*)calloc((ret + 1), sizeof(UINT32));
+
+ for (unsigned int i = 0; i < (unsigned int)ret; i++)
+ {
+ xv->xv_pixfmts[i] = fo[i].id;
+ WLog_DBG(TAG, "%c%c%c%c ", ((char*)(xv->xv_pixfmts + i))[0],
+ ((char*)(xv->xv_pixfmts + i))[1], ((char*)(xv->xv_pixfmts + i))[2],
+ ((char*)(xv->xv_pixfmts + i))[3]);
+ }
+ xv->xv_pixfmts[i] = 0;
+ }
+ XFree(fo);
+
+ if (tsmf)
+ {
+ xfc->tsmf = tsmf;
+ tsmf->custom = (void*)xfc;
+
+ tsmf->FrameEvent = xf_tsmf_xv_video_frame_event;
+ }
+
+ return 1;
+}
+
+static int xf_tsmf_xv_uninit(xfContext* xfc, TsmfClientContext* tsmf)
+{
+ xfXvContext* xv = (xfXvContext*)xfc->xv_context;
+
+ WINPR_UNUSED(tsmf);
+ if (xv)
+ {
+ if (xv->xv_image_size > 0)
+ {
+ shmdt(xv->xv_shmaddr);
+ shmctl(xv->xv_shmid, IPC_RMID, NULL);
+ }
+ if (xv->xv_pixfmts)
+ {
+ free(xv->xv_pixfmts);
+ xv->xv_pixfmts = NULL;
+ }
+ free(xv);
+ xfc->xv_context = NULL;
+ }
+
+ if (xfc->tsmf)
+ {
+ xfc->tsmf->custom = NULL;
+ xfc->tsmf = NULL;
+ }
+
+ return 1;
+}
+
+#endif
+
+int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf)
+{
+#ifdef WITH_XV
+ return xf_tsmf_xv_init(xfc, tsmf);
+#else
+ return 1;
+#endif
+}
+
+int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf)
+{
+#ifdef WITH_XV
+ return xf_tsmf_xv_uninit(xfc, tsmf);
+#else
+ return 1;
+#endif
+}
diff --git a/client/X11/xf_tsmf.h b/client/X11/xf_tsmf.h
new file mode 100644
index 0000000..63a973a
--- /dev/null
+++ b/client/X11/xf_tsmf.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Video Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ *
+ * 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_CLIENT_X11_TSMF_H
+#define FREERDP_CLIENT_X11_TSMF_H
+
+#include "xf_client.h"
+#include "xfreerdp.h"
+
+int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf);
+int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf);
+
+#endif /* FREERDP_CLIENT_X11_TSMF_H */
diff --git a/client/X11/xf_utils.c b/client/X11/xf_utils.c
new file mode 100644
index 0000000..4cce7c1
--- /dev/null
+++ b/client/X11/xf_utils.c
@@ -0,0 +1,123 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 helper utilities
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyringht 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 <winpr/assert.h>
+
+#include "xf_utils.h"
+
+static const DWORD log_level = WLOG_TRACE;
+
+static void write_log(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
+{
+ va_list ap;
+ va_start(ap, line);
+ WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
+ va_end(ap);
+}
+
+char* Safe_XGetAtomName(Display* display, Atom atom)
+{
+ if (atom == None)
+ return strdup("Atom_None");
+ return XGetAtomName(display, atom);
+}
+
+int LogTagAndXChangeProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, const unsigned char* data, int nelements)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXChangeProperty_ex(log, file, fkt, line, display, w, property, type, format,
+ mode, data, nelements);
+}
+
+int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, const unsigned char* data, int nelements)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ char* typestr = Safe_XGetAtomName(display, type);
+ write_log(log, log_level, file, fkt, line,
+ "XChangeProperty(%p, %d, %s [%d], %s [%d], %d, %d, %p, %d)", display, w, propstr,
+ property, typestr, type, format, mode, data, nelements);
+ XFree(propstr);
+ XFree(typestr);
+ }
+ return XChangeProperty(display, w, property, type, format, mode, data, nelements);
+}
+
+int LogTagAndXDeleteProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXDeleteProperty_ex(log, file, fkt, line, display, w, property);
+}
+
+int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ write_log(log, log_level, file, fkt, line, "XDeleteProperty(%p, %d, %s [%d])", display, w,
+ propstr, property);
+ XFree(propstr);
+ }
+ return XDeleteProperty(display, w, property);
+}
+
+int LogTagAndXGetWindowProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, int delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return)
+{
+ wLog* log = WLog_Get(tag);
+ return LogDynAndXGetWindowProperty_ex(
+ log, file, fkt, line, display, w, property, long_offset, long_length, delete, req_type,
+ actual_type_return, actual_format_return, nitems_return, bytes_after_return, prop_return);
+}
+
+int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, int delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return)
+{
+ if (WLog_IsLevelActive(log, log_level))
+ {
+ char* propstr = Safe_XGetAtomName(display, property);
+ char* req_type_str = Safe_XGetAtomName(display, req_type);
+ write_log(log, log_level, file, fkt, line,
+ "XGetWindowProperty(%p, %d, %s [%d], %ld, %ld, %d, %s [%d], %p, %p, %p, %p, %p)",
+ display, w, propstr, property, long_offset, long_length, delete, req_type_str,
+ req_type, actual_type_return, actual_format_return, nitems_return,
+ bytes_after_return, prop_return);
+ XFree(propstr);
+ XFree(req_type_str);
+ }
+ return XGetWindowProperty(display, w, property, long_offset, long_length, delete, req_type,
+ actual_type_return, actual_format_return, nitems_return,
+ bytes_after_return, prop_return);
+}
diff --git a/client/X11/xf_utils.h b/client/X11/xf_utils.h
new file mode 100644
index 0000000..f946554
--- /dev/null
+++ b/client/X11/xf_utils.h
@@ -0,0 +1,78 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 helper utilities
+ *
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyringht 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.
+ */
+#pragma once
+
+#include <winpr/wlog.h>
+
+#include <X11/Xlib.h>
+
+char* Safe_XGetAtomName(Display* display, Atom atom);
+
+#define LogTagAndXGetWindowProperty(tag, display, w, property, long_offset, long_length, delete, \
+ req_type, actual_type_return, actual_format_return, \
+ nitems_return, bytes_after_return, prop_return) \
+ LogTagAndXGetWindowProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), \
+ (property), (long_offset), (long_length), (delete), (req_type), \
+ (actual_type_return), (actual_format_return), (nitems_return), \
+ (bytes_after_return), (prop_return))
+int LogTagAndXGetWindowProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, Bool delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return);
+
+#define LogDynAndXGetWindowProperty(log, display, w, property, long_offset, long_length, delete, \
+ req_type, actual_type_return, actual_format_return, \
+ nitems_return, bytes_after_return, prop_return) \
+ LogDynAndXGetWindowProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), \
+ (property), (long_offset), (long_length), (delete), (req_type), \
+ (actual_type_return), (actual_format_return), (nitems_return), \
+ (bytes_after_return), (prop_return))
+int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, long long_offset,
+ long long_length, Bool delete, Atom req_type,
+ Atom* actual_type_return, int* actual_format_return,
+ unsigned long* nitems_return, unsigned long* bytes_after_return,
+ unsigned char** prop_return);
+
+#define LogTagAndXChangeProperty(tag, display, w, property, type, format, mode, data, nelements) \
+ LogTagAndXChangeProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), (property), \
+ (type), (format), (mode), (data), (nelements))
+int LogTagAndXChangeProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, _Xconst unsigned char* data, int nelements);
+
+#define LogDynAndXChangeProperty(log, display, w, property, type, format, mode, data, nelements) \
+ LogDynAndXChangeProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property), \
+ (type), (format), (mode), (data), (nelements))
+int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property, Atom type, int format,
+ int mode, _Xconst unsigned char* data, int nelements);
+
+#define LogTagAndXDeleteProperty(tag, display, w, property) \
+ LogTagAndXDeleteProperty_ex((tag), __FILE__, __func__, __LINE__, (display), (w), (property))
+int LogTagAndXDeleteProperty_ex(const char* tag, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property);
+
+#define LogDynAndXDeleteProperty(log, display, w, property) \
+ LogDynAndXDeleteProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property))
+int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
+ Display* display, Window w, Atom property);
diff --git a/client/X11/xf_video.c b/client/X11/xf_video.c
new file mode 100644
index 0000000..461f33d
--- /dev/null
+++ b/client/X11/xf_video.c
@@ -0,0 +1,132 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * Copyright 2017 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/client/geometry.h>
+#include <freerdp/client/video.h>
+#include <freerdp/gdi/video.h>
+
+#include "xf_video.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("video")
+
+typedef struct
+{
+ VideoSurface base;
+ XImage* image;
+} xfVideoSurface;
+
+static VideoSurface* xfVideoCreateSurface(VideoClientContext* video, UINT32 x, UINT32 y,
+ UINT32 width, UINT32 height)
+{
+ xfContext* xfc = NULL;
+ xfVideoSurface* ret = NULL;
+
+ WINPR_ASSERT(video);
+ ret = (xfVideoSurface*)VideoClient_CreateCommonContext(sizeof(xfContext), x, y, width, height);
+ if (!ret)
+ return NULL;
+
+ xfc = (xfContext*)video->custom;
+ WINPR_ASSERT(xfc);
+
+ ret->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)ret->base.data, width, height, 8, ret->base.scanline);
+
+ if (!ret->image)
+ {
+ WLog_ERR(TAG, "unable to create surface image");
+ VideoClient_DestroyCommonContext(&ret->base);
+ return NULL;
+ }
+
+ return &ret->base;
+}
+
+static BOOL xfVideoShowSurface(VideoClientContext* video, const VideoSurface* surface,
+ UINT32 destinationWidth, UINT32 destinationHeight)
+{
+ const xfVideoSurface* xfSurface = (const xfVideoSurface*)surface;
+ xfContext* xfc = NULL;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(video);
+ WINPR_ASSERT(xfSurface);
+
+ xfc = video->custom;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+#ifdef WITH_XRENDER
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
+ freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ XPutImage(xfc->display, xfc->primary, xfc->gc, xfSurface->image, 0, 0, surface->x,
+ surface->y, surface->w, surface->h);
+ xf_draw_screen(xfc, surface->x, surface->y, surface->w, surface->h);
+ }
+ else
+#endif
+ {
+ XPutImage(xfc->display, xfc->drawable, xfc->gc, xfSurface->image, 0, 0, surface->x,
+ surface->y, surface->w, surface->h);
+ }
+
+ return TRUE;
+}
+
+static BOOL xfVideoDeleteSurface(VideoClientContext* video, VideoSurface* surface)
+{
+ xfVideoSurface* xfSurface = (xfVideoSurface*)surface;
+
+ WINPR_UNUSED(video);
+
+ if (xfSurface)
+ XFree(xfSurface->image);
+
+ VideoClient_DestroyCommonContext(surface);
+ return TRUE;
+}
+
+void xf_video_control_init(xfContext* xfc, VideoClientContext* video)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(video);
+
+ gdi_video_control_init(xfc->common.context.gdi, video);
+
+ /* X11 needs to be able to handle 32bpp colors directly. */
+ if (xfc->depth >= 24)
+ {
+ video->custom = xfc;
+ video->createSurface = xfVideoCreateSurface;
+ video->showSurface = xfVideoShowSurface;
+ video->deleteSurface = xfVideoDeleteSurface;
+ }
+}
+
+void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video)
+{
+ WINPR_ASSERT(xfc);
+ gdi_video_control_uninit(xfc->common.context.gdi, video);
+}
diff --git a/client/X11/xf_video.h b/client/X11/xf_video.h
new file mode 100644
index 0000000..385b4ea
--- /dev/null
+++ b/client/X11/xf_video.h
@@ -0,0 +1,35 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Optimized Remoting Virtual Channel Extension for X11
+ *
+ * Copyright 2017 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 CLIENT_X11_XF_VIDEO_H_
+#define CLIENT_X11_XF_VIDEO_H_
+
+#include "xfreerdp.h"
+
+#include <freerdp/channels/geometry.h>
+#include <freerdp/channels/video.h>
+
+void xf_video_control_init(xfContext* xfc, VideoClientContext* video);
+void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video);
+
+void xf_video_free(xfVideoContext* context);
+
+WINPR_ATTR_MALLOC(xf_video_free, 1)
+xfVideoContext* xf_video_new(xfContext* xfc);
+
+#endif /* CLIENT_X11_XF_VIDEO_H_ */
diff --git a/client/X11/xf_window.c b/client/X11/xf_window.c
new file mode 100644
index 0000000..a5e68c7
--- /dev/null
+++ b/client/X11/xf_window.c
@@ -0,0 +1,1323 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2012 HP Development Company, LLC
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@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 <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <winpr/assert.h>
+#include <winpr/thread.h>
+#include <winpr/crt.h>
+#include <winpr/string.h>
+
+#include <freerdp/rail.h>
+#include <freerdp/log.h>
+
+#ifdef WITH_XEXT
+#include <X11/extensions/shape.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include "xf_gfx.h"
+#include "xf_rail.h"
+#include "xf_input.h"
+#include "xf_keyboard.h"
+#include "xf_utils.h"
+
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_DEBUG_X11
+#define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
+#else
+#define DEBUG_X11(...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#include <FreeRDP_Icon_256px.h>
+#define xf_icon_prop FreeRDP_Icon_256px_prop
+
+#include "xf_window.h"
+
+/* Extended Window Manager Hints: http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html */
+
+/* bit definitions for MwmHints.flags */
+#define MWM_HINTS_FUNCTIONS (1L << 0)
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define MWM_HINTS_INPUT_MODE (1L << 2)
+#define MWM_HINTS_STATUS (1L << 3)
+
+/* bit definitions for MwmHints.functions */
+#define MWM_FUNC_ALL (1L << 0)
+#define MWM_FUNC_RESIZE (1L << 1)
+#define MWM_FUNC_MOVE (1L << 2)
+#define MWM_FUNC_MINIMIZE (1L << 3)
+#define MWM_FUNC_MAXIMIZE (1L << 4)
+#define MWM_FUNC_CLOSE (1L << 5)
+
+/* bit definitions for MwmHints.decorations */
+#define MWM_DECOR_ALL (1L << 0)
+#define MWM_DECOR_BORDER (1L << 1)
+#define MWM_DECOR_RESIZEH (1L << 2)
+#define MWM_DECOR_TITLE (1L << 3)
+#define MWM_DECOR_MENU (1L << 4)
+#define MWM_DECOR_MINIMIZE (1L << 5)
+#define MWM_DECOR_MAXIMIZE (1L << 6)
+
+#define PROP_MOTIF_WM_HINTS_ELEMENTS 5
+
+typedef struct
+{
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+} PropMotifWmHints;
+
+static void xf_SetWindowTitleText(xfContext* xfc, Window window, const char* name)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(name);
+
+ const size_t i = strnlen(name, MAX_PATH);
+ XStoreName(xfc->display, window, name);
+ Atom wm_Name = xfc->_NET_WM_NAME;
+ Atom utf8Str = xfc->UTF8_STRING;
+ LogTagAndXChangeProperty(TAG, xfc->display, window, wm_Name, utf8Str, 8, PropModeReplace,
+ (const unsigned char*)name, (int)i);
+}
+
+/**
+ * Post an event from the client to the X server
+ */
+void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...)
+{
+ XEvent xevent = { 0 };
+ va_list argp;
+ va_start(argp, numArgs);
+
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.serial = 0;
+ xevent.xclient.send_event = False;
+ xevent.xclient.display = xfc->display;
+ xevent.xclient.window = window;
+ xevent.xclient.message_type = atom;
+ xevent.xclient.format = 32;
+
+ for (size_t i = 0; i < numArgs; i++)
+ {
+ xevent.xclient.data.l[i] = va_arg(argp, int);
+ }
+
+ DEBUG_X11("Send ClientMessage Event: wnd=0x%04lX", (unsigned long)xevent.xclient.window);
+ XSendEvent(xfc->display, RootWindowOfScreen(xfc->screen), False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
+ XSync(xfc->display, False);
+ va_end(argp);
+}
+
+void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window)
+{
+ XIconifyWindow(xfc->display, window->handle, xfc->screen_number);
+}
+
+void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen)
+{
+ const rdpSettings* settings = NULL;
+ int startX = 0;
+ int startY = 0;
+ UINT32 width = window->width;
+ UINT32 height = window->height;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ /* xfc->decorations is set by caller depending on settings and whether it is fullscreen or not
+ */
+ window->decorations = xfc->decorations;
+ /* show/hide decorations (e.g. title bar) as guided by xfc->decorations */
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ DEBUG_X11(TAG, "X window decoration set to %d", (int)window->decorations);
+ xf_floatbar_toggle_fullscreen(xfc->window->floatbar, fullscreen);
+
+ if (fullscreen)
+ {
+ xfc->savedWidth = xfc->window->width;
+ xfc->savedHeight = xfc->window->height;
+ xfc->savedPosX = xfc->window->left;
+ xfc->savedPosY = xfc->window->top;
+
+ startX = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX)
+ : 0;
+ startY = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX)
+ ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY)
+ : 0;
+ }
+ else
+ {
+ width = xfc->savedWidth;
+ height = xfc->savedHeight;
+ startX = xfc->savedPosX;
+ startY = xfc->savedPosY;
+ }
+
+ /* Determine the x,y starting location for the fullscreen window */
+ if (fullscreen)
+ {
+ const rdpMonitor* firstMonitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0);
+ /* Initialize startX and startY with reasonable values */
+ startX = firstMonitor->x;
+ startY = firstMonitor->y;
+
+ /* Search all monitors to find the lowest startX and startY values */
+ for (size_t i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
+ {
+ const rdpMonitor* monitor =
+ freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, i);
+ startX = MIN(startX, monitor->x);
+ startY = MIN(startY, monitor->y);
+ }
+
+ /* Lastly apply any monitor shift(translation from remote to local coordinate system)
+ * to startX and startY values
+ */
+ startX += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
+ startY += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
+ }
+
+ /*
+ It is safe to proceed with simply toogling _NET_WM_STATE_FULLSCREEN window state on the
+ following conditions:
+ - The window manager supports multiple monitor full screen
+ - The user requested to use a single monitor to render the remote desktop
+ */
+ if (xfc->_NET_WM_FULLSCREEN_MONITORS != None ||
+ freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) == 1)
+ {
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+
+ if (fullscreen)
+ {
+ /* enter full screen: move the window before adding NET_WM_STATE_FULLSCREEN */
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+
+ /* Set the fullscreen state */
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
+ xfc->_NET_WM_STATE_FULLSCREEN, 0, 0);
+
+ if (!fullscreen)
+ {
+ /* leave full screen: move the window after removing NET_WM_STATE_FULLSCREEN
+ * Resize the window again, the previous call to xf_SendClientEvent might have
+ * changed the window size (borders, ...)
+ */
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+
+ /* Set monitor bounds */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_FULLSCREEN_MONITORS, 5,
+ xfc->fullscreenMonitors.top, xfc->fullscreenMonitors.bottom,
+ xfc->fullscreenMonitors.left, xfc->fullscreenMonitors.right, 1);
+ }
+ }
+ else
+ {
+ if (fullscreen)
+ {
+ xf_SetWindowDecorations(xfc, window->handle, FALSE);
+
+ if (xfc->fullscreenMonitors.top)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->fullscreenMonitors.top, 0, 0);
+ }
+ else
+ {
+ XSetWindowAttributes xswa;
+ xswa.override_redirect = True;
+ XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
+ XRaiseWindow(xfc->display, window->handle);
+ xswa.override_redirect = False;
+ XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
+ }
+
+ /* if window is in maximized state, save and remove */
+ if (xfc->_NET_WM_STATE_MAXIMIZED_VERT != None)
+ {
+ BYTE state = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ BYTE* prop = NULL;
+
+ if (xf_GetWindowProperty(xfc, window->handle, xfc->_NET_WM_STATE, 255, &nitems,
+ &bytes, &prop))
+ {
+ state = 0;
+
+ while (nitems-- > 0)
+ {
+ if (((Atom*)prop)[nitems] == xfc->_NET_WM_STATE_MAXIMIZED_VERT)
+ state |= 0x01;
+
+ if (((Atom*)prop)[nitems] == xfc->_NET_WM_STATE_MAXIMIZED_HORZ)
+ state |= 0x02;
+ }
+
+ if (state)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_VERT,
+ 0, 0);
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
+ _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0, 0);
+ xfc->savedMaximizedState = state;
+ }
+
+ XFree(prop);
+ }
+ }
+
+ width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
+ height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
+ DEBUG_X11("X window move and resize %dx%d@%dx%d", startX, startY, width, height);
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+ }
+ else
+ {
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ XMoveWindow(xfc->display, window->handle, startX, startY);
+
+ if (xfc->fullscreenMonitors.top)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
+ xfc->fullscreenMonitors.top, 0, 0);
+ }
+
+ /* restore maximized state, if the window was maximized before setting fullscreen */
+ if (xfc->savedMaximizedState & 0x01)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, 0, 0);
+ }
+
+ if (xfc->savedMaximizedState & 0x02)
+ {
+ xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_HORZ, 0, 0);
+ }
+
+ xfc->savedMaximizedState = 0;
+ }
+ }
+}
+
+/* http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html */
+
+BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length,
+ unsigned long* nitems, unsigned long* bytes, BYTE** prop)
+{
+ int status = 0;
+ Atom actual_type = None;
+ int actual_format = 0;
+
+ if (property == None)
+ return FALSE;
+
+ status = LogTagAndXGetWindowProperty(TAG, xfc->display, window, property, 0, length, False,
+ AnyPropertyType, &actual_type, &actual_format, nitems,
+ bytes, prop);
+
+ if (status != Success)
+ return FALSE;
+
+ if (actual_type == None)
+ {
+ WLog_DBG(TAG, "Property %lu does not exist", (unsigned long)property);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL xf_GetCurrentDesktop(xfContext* xfc)
+{
+ BOOL status = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ status = xf_GetWindowProperty(xfc, DefaultRootWindow(xfc->display), xfc->_NET_CURRENT_DESKTOP,
+ 1, &nitems, &bytes, &prop);
+
+ if (!status)
+ return FALSE;
+
+ xfc->current_desktop = (int)*prop;
+ free(prop);
+ return TRUE;
+}
+
+BOOL xf_GetWorkArea(xfContext* xfc)
+{
+ long* plong = NULL;
+ BOOL status = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes = 0;
+ unsigned char* prop = NULL;
+ status = xf_GetCurrentDesktop(xfc);
+
+ if (!status)
+ return FALSE;
+
+ status = xf_GetWindowProperty(xfc, DefaultRootWindow(xfc->display), xfc->_NET_WORKAREA, 32 * 4,
+ &nitems, &bytes, &prop);
+
+ if (!status)
+ return FALSE;
+
+ if ((xfc->current_desktop * 4 + 3) >= (INT64)nitems)
+ {
+ free(prop);
+ return FALSE;
+ }
+
+ plong = (long*)prop;
+ xfc->workArea.x = plong[xfc->current_desktop * 4 + 0];
+ xfc->workArea.y = plong[xfc->current_desktop * 4 + 1];
+ xfc->workArea.width = plong[xfc->current_desktop * 4 + 2];
+ xfc->workArea.height = plong[xfc->current_desktop * 4 + 3];
+ free(prop);
+ return TRUE;
+}
+
+void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show)
+{
+ PropMotifWmHints hints = { .decorations = (show) ? MWM_DECOR_ALL : 0,
+ .functions = MWM_FUNC_ALL,
+ .flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS,
+ .inputMode = 0,
+ .status = 0 };
+ WINPR_ASSERT(xfc);
+ LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_MOTIF_WM_HINTS, xfc->_MOTIF_WM_HINTS,
+ 32, PropModeReplace, (BYTE*)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
+}
+
+void xf_SetWindowUnlisted(xfContext* xfc, Window window)
+{
+ WINPR_ASSERT(xfc);
+ Atom window_state[] = { xfc->_NET_WM_STATE_SKIP_PAGER, xfc->_NET_WM_STATE_SKIP_TASKBAR };
+ LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_NET_WM_STATE, XA_ATOM, 32,
+ PropModeReplace, (BYTE*)window_state, 2);
+}
+
+static void xf_SetWindowPID(xfContext* xfc, Window window, pid_t pid)
+{
+ Atom am_wm_pid = 0;
+
+ WINPR_ASSERT(xfc);
+ if (!pid)
+ pid = getpid();
+
+ am_wm_pid = xfc->_NET_WM_PID;
+ LogTagAndXChangeProperty(TAG, xfc->display, window, am_wm_pid, XA_CARDINAL, 32, PropModeReplace,
+ (BYTE*)&pid, 1);
+}
+
+static const char* get_shm_id(void)
+{
+ static char shm_id[64];
+ sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId());
+ return shm_id;
+}
+
+Window xf_CreateDummyWindow(xfContext* xfc)
+{
+ return XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
+ xfc->workArea.y, 1, 1, 0, xfc->depth, InputOutput, xfc->visual,
+ xfc->attribs_mask, &xfc->attribs);
+}
+
+void xf_DestroyDummyWindow(xfContext* xfc, Window window)
+{
+ if (window)
+ XDestroyWindow(xfc->display, window);
+}
+
+xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height)
+{
+ XEvent xevent = { 0 };
+ int input_mask = 0;
+ XClassHint* classHints = NULL;
+ xfWindow* window = (xfWindow*)calloc(1, sizeof(xfWindow));
+
+ if (!window)
+ return NULL;
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ Window parentWindow = (Window)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId);
+ window->width = width;
+ window->height = height;
+ window->decorations = xfc->decorations;
+ window->is_mapped = FALSE;
+ window->is_transient = FALSE;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ window->handle =
+ XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
+ xfc->workArea.y, xfc->workArea.width, xfc->workArea.height, 0, xfc->depth,
+ InputOutput, xfc->visual, xfc->attribs_mask, &xfc->attribs);
+ window->shmid = shm_open(get_shm_id(), (O_CREAT | O_RDWR), (S_IREAD | S_IWRITE));
+
+ if (window->shmid < 0)
+ {
+ DEBUG_X11("xf_CreateDesktopWindow: failed to get access to shared memory - shmget()\n");
+ }
+ else
+ {
+ int rc = ftruncate(window->shmid, sizeof(window->handle));
+ if (rc != 0)
+ {
+#ifdef WITH_DEBUG_X11
+ char ebuffer[256] = { 0 };
+ DEBUG_X11("ftruncate failed with %s [%d]", winpr_strerror(rc, ebuffer, sizeof(ebuffer)),
+ rc);
+#endif
+ }
+ else
+ {
+ void* mem = mmap(0, sizeof(window->handle), PROT_READ | PROT_WRITE, MAP_SHARED,
+ window->shmid, 0);
+
+ if (mem == MAP_FAILED)
+ {
+ DEBUG_X11(
+ "xf_CreateDesktopWindow: failed to assign pointer to the memory address - "
+ "shmat()\n");
+ }
+ else
+ {
+ window->xfwin = mem;
+ *window->xfwin = window->handle;
+ }
+ }
+ }
+
+ classHints = XAllocClassHint();
+
+ if (classHints)
+ {
+ classHints->res_name = "xfreerdp";
+
+ char* res_class = NULL;
+ const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
+ if (WmClass)
+ res_class = _strdup(WmClass);
+ else
+ res_class = _strdup("xfreerdp");
+
+ XSetClassHint(xfc->display, window->handle, classHints);
+ XFree(classHints);
+ free(res_class);
+ }
+
+ xf_ResizeDesktopWindow(xfc, window, width, height);
+ xf_SetWindowDecorations(xfc, window->handle, window->decorations);
+ xf_SetWindowPID(xfc, window->handle, 0);
+ input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | PointerMotionMask |
+ ExposureMask | PropertyChangeMask;
+
+ if (xfc->grab_keyboard)
+ input_mask |= EnterWindowMask | LeaveWindowMask;
+
+ LogTagAndXChangeProperty(TAG, xfc->display, window->handle, xfc->_NET_WM_ICON, XA_CARDINAL, 32,
+ PropModeReplace, (BYTE*)xf_icon_prop, ARRAYSIZE(xf_icon_prop));
+
+ if (parentWindow)
+ XReparentWindow(xfc->display, window->handle, parentWindow, 0, 0);
+
+ XSelectInput(xfc->display, window->handle, input_mask);
+ XClearWindow(xfc->display, window->handle);
+ xf_SetWindowTitleText(xfc, window->handle, name);
+ XMapWindow(xfc->display, window->handle);
+ xf_input_init(xfc, window->handle);
+
+ /*
+ * NOTE: This must be done here to handle reparenting the window,
+ * so that we don't miss the event and hang waiting for the next one
+ */
+ do
+ {
+ XMaskEvent(xfc->display, VisibilityChangeMask, &xevent);
+ } while (xevent.type != VisibilityNotify);
+
+ /*
+ * The XCreateWindow call will start the window in the upper-left corner of our current
+ * monitor instead of the upper-left monitor for remote app mode (which uses all monitors).
+ * This extra call after the window is mapped will position the login window correctly
+ */
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ {
+ XMoveWindow(xfc->display, window->handle, 0, 0);
+ }
+ else if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) &&
+ (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX))
+ {
+ XMoveWindow(xfc->display, window->handle,
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX),
+ freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY));
+ }
+
+ window->floatbar = xf_floatbar_new(xfc, window->handle, name,
+ freerdp_settings_get_uint32(settings, FreeRDP_Floatbar));
+
+ if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
+ xf_SendClientEvent(xfc, window->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
+
+ return window;
+}
+
+void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height)
+{
+ XSizeHints* size_hints = NULL;
+ rdpSettings* settings = NULL;
+
+ if (!xfc || !window)
+ return;
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (!(size_hints = XAllocSizeHints()))
+ return;
+
+ size_hints->flags = PMinSize | PMaxSize | PWinGravity;
+ size_hints->win_gravity = NorthWestGravity;
+ size_hints->min_width = size_hints->min_height = 1;
+ size_hints->max_width = size_hints->max_height = 16384;
+ XResizeWindow(xfc->display, window->handle, width, height);
+#ifdef WITH_XRENDER
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+#endif
+ {
+ if (!xfc->fullscreen)
+ {
+ /* min == max is an hint for the WM to indicate that the window should
+ * not be resizable */
+ size_hints->min_width = size_hints->max_width = width;
+ size_hints->min_height = size_hints->max_height = height;
+ }
+ }
+
+ XSetWMNormalHints(xfc->display, window->handle, size_hints);
+ XFree(size_hints);
+}
+
+void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window)
+{
+ if (!window)
+ return;
+
+ if (xfc->window == window)
+ xfc->window = NULL;
+
+ xf_floatbar_free(window->floatbar);
+
+ if (window->gc)
+ XFreeGC(xfc->display, window->gc);
+
+ if (window->handle)
+ {
+ XUnmapWindow(xfc->display, window->handle);
+ XDestroyWindow(xfc->display, window->handle);
+ }
+
+ if (window->xfwin)
+ munmap(0, sizeof(*window->xfwin));
+
+ if (window->shmid >= 0)
+ close(window->shmid);
+
+ shm_unlink(get_shm_id());
+ window->xfwin = (Window*)-1;
+ window->shmid = -1;
+ free(window);
+}
+
+void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style)
+{
+ Atom window_type = 0;
+ BOOL redirect = FALSE;
+
+ if ((ex_style & WS_EX_NOACTIVATE) || (ex_style & WS_EX_TOOLWINDOW))
+ {
+ redirect = TRUE;
+ appWindow->is_transient = TRUE;
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+ window_type = xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+ }
+ /*
+ * TOPMOST window that is not a tool window is treated like a regular window (i.e. task
+ * manager). Want to do this here, since the window may have type WS_POPUP
+ */
+ else if (ex_style & WS_EX_TOPMOST)
+ {
+ window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
+ }
+ else if (style & WS_POPUP)
+ {
+ /* this includes dialogs, popups, etc, that need to be full-fledged windows */
+ appWindow->is_transient = TRUE;
+ window_type = xfc->_NET_WM_WINDOW_TYPE_DIALOG;
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+ }
+ else
+ {
+ window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
+ }
+
+ {
+ /*
+ * Tooltips and menu items should be unmanaged windows
+ * (called "override redirect" in X windows parlance)
+ * If they are managed, there are issues with window focus that
+ * cause the windows to behave improperly. For example, a mouse
+ * press will dismiss a drop-down menu because the RDP server
+ * sees that as a focus out event from the window owning the
+ * dropdown.
+ */
+ XSetWindowAttributes attrs;
+ attrs.override_redirect = redirect ? True : False;
+ XChangeWindowAttributes(xfc->display, appWindow->handle, CWOverrideRedirect, &attrs);
+ }
+
+ LogTagAndXChangeProperty(TAG, xfc->display, appWindow->handle, xfc->_NET_WM_WINDOW_TYPE,
+ XA_ATOM, 32, PropModeReplace, (BYTE*)&window_type, 1);
+}
+
+void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name)
+{
+ xf_SetWindowTitleText(xfc, appWindow->handle, name);
+}
+
+static void xf_FixWindowCoordinates(xfContext* xfc, int* x, int* y, int* width, int* height)
+{
+ int vscreen_width = 0;
+ int vscreen_height = 0;
+ vscreen_width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
+ vscreen_height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
+
+ if (*x < xfc->vscreen.area.left)
+ {
+ *width += *x;
+ *x = xfc->vscreen.area.left;
+ }
+
+ if (*y < xfc->vscreen.area.top)
+ {
+ *height += *y;
+ *y = xfc->vscreen.area.top;
+ }
+
+ if (*width > vscreen_width)
+ {
+ *width = vscreen_width;
+ }
+
+ if (*height > vscreen_height)
+ {
+ *height = vscreen_height;
+ }
+
+ if (*width < 1)
+ {
+ *width = 1;
+ }
+
+ if (*height < 1)
+ {
+ *height = 1;
+ }
+}
+
+int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (!xfc || !appWindow)
+ return -1;
+
+ xf_SetWindowDecorations(xfc, appWindow->handle, appWindow->decorations);
+ xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
+ xf_SetWindowPID(xfc, appWindow->handle, 0);
+ xf_ShowWindow(xfc, appWindow, WINDOW_SHOW);
+ XClearWindow(xfc->display, appWindow->handle);
+ XMapWindow(xfc->display, appWindow->handle);
+ /* Move doesn't seem to work until window is mapped. */
+ xf_MoveWindow(xfc, appWindow, appWindow->x, appWindow->y, appWindow->width, appWindow->height);
+ xf_SetWindowText(xfc, appWindow, appWindow->title);
+ return 1;
+}
+
+BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow)
+{
+ XGCValues gcv = { 0 };
+ int input_mask = 0;
+ XWMHints* InputModeHint = NULL;
+ XClassHint* class_hints = NULL;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xf_FixWindowCoordinates(xfc, &appWindow->x, &appWindow->y, &appWindow->width,
+ &appWindow->height);
+ appWindow->shmid = -1;
+ appWindow->decorations = FALSE;
+ appWindow->fullscreen = FALSE;
+ appWindow->local_move.state = LMS_NOT_ACTIVE;
+ appWindow->is_mapped = FALSE;
+ appWindow->is_transient = FALSE;
+ appWindow->rail_state = 0;
+ appWindow->maxVert = FALSE;
+ appWindow->maxHorz = FALSE;
+ appWindow->minimized = FALSE;
+ appWindow->rail_ignore_configure = FALSE;
+
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->handle =
+ XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), appWindow->x, appWindow->y,
+ appWindow->width, appWindow->height, 0, xfc->depth, InputOutput, xfc->visual,
+ xfc->attribs_mask, &xfc->attribs);
+
+ if (!appWindow->handle)
+ return FALSE;
+
+ appWindow->gc = XCreateGC(xfc->display, appWindow->handle, GCGraphicsExposures, &gcv);
+
+ if (!xf_AppWindowResize(xfc, appWindow))
+ return FALSE;
+
+ class_hints = XAllocClassHint();
+
+ if (class_hints)
+ {
+ char* strclass = NULL;
+
+ const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
+ if (WmClass)
+ strclass = _strdup(WmClass);
+ else
+ {
+ size_t size = 0;
+ winpr_asprintf(&strclass, &size, "RAIL:%08" PRIX64 "", appWindow->windowId);
+ }
+ class_hints->res_class = strclass;
+ class_hints->res_name = "RAIL";
+ XSetClassHint(xfc->display, appWindow->handle, class_hints);
+ XFree(class_hints);
+ free(strclass);
+ }
+
+ /* Set the input mode hint for the WM */
+ InputModeHint = XAllocWMHints();
+ InputModeHint->flags = (1L << 0);
+ InputModeHint->input = True;
+ XSetWMHints(xfc->display, appWindow->handle, InputModeHint);
+ XFree(InputModeHint);
+ XSetWMProtocols(xfc->display, appWindow->handle, &(xfc->WM_DELETE_WINDOW), 1);
+ input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask | Button1MotionMask |
+ Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask | ExposureMask | VisibilityChangeMask |
+ StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask |
+ FocusChangeMask | PropertyChangeMask | ColormapChangeMask | OwnerGrabButtonMask;
+ XSelectInput(xfc->display, appWindow->handle, input_mask);
+
+ if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
+
+ return TRUE;
+}
+
+void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight,
+ int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight,
+ int maxTrackWidth, int maxTrackHeight)
+{
+ XSizeHints* size_hints = NULL;
+ size_hints = XAllocSizeHints();
+
+ if (size_hints)
+ {
+ size_hints->flags = PMinSize | PMaxSize | PResizeInc;
+ size_hints->min_width = minTrackWidth;
+ size_hints->min_height = minTrackHeight;
+ size_hints->max_width = maxTrackWidth;
+ size_hints->max_height = maxTrackHeight;
+ /* to speedup window drawing we need to select optimal value for sizing step. */
+ size_hints->width_inc = size_hints->height_inc = 1;
+ XSetWMNormalHints(xfc->display, appWindow->handle, size_hints);
+ XFree(size_hints);
+ }
+}
+
+void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y)
+{
+ if (appWindow->local_move.state != LMS_NOT_ACTIVE)
+ return;
+
+ /*
+ * Save original mouse location relative to root. This will be needed
+ * to end local move to RDP server and/or X server
+ */
+ appWindow->local_move.root_x = x;
+ appWindow->local_move.root_y = y;
+ appWindow->local_move.state = LMS_STARTING;
+ appWindow->local_move.direction = direction;
+
+ xf_ungrab(xfc);
+
+ xf_SendClientEvent(
+ xfc, appWindow->handle,
+ xfc->_NET_WM_MOVERESIZE, /* request X window manager to initiate a local move */
+ 5, /* 5 arguments to follow */
+ x, /* x relative to root window */
+ y, /* y relative to root window */
+ direction, /* extended ICCM direction flag */
+ 1, /* simulated mouse button 1 */
+ 1); /* 1 == application request per extended ICCM */
+}
+
+void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (appWindow->local_move.state == LMS_NOT_ACTIVE)
+ return;
+
+ if (appWindow->local_move.state == LMS_STARTING)
+ {
+ /*
+ * The move never was property started. This can happen due to race
+ * conditions between the mouse button up and the communications to the
+ * RDP server for local moves. We must cancel the X window manager move.
+ * Per ICCM, the X client can ask to cancel an active move.
+ */
+ xf_SendClientEvent(
+ xfc, appWindow->handle,
+ xfc->_NET_WM_MOVERESIZE, /* request X window manager to abort a local move */
+ 5, /* 5 arguments to follow */
+ appWindow->local_move.root_x, /* x relative to root window */
+ appWindow->local_move.root_y, /* y relative to root window */
+ _NET_WM_MOVERESIZE_CANCEL, /* extended ICCM direction flag */
+ 1, /* simulated mouse button 1 */
+ 1); /* 1 == application request per extended ICCM */
+ }
+
+ appWindow->local_move.state = LMS_NOT_ACTIVE;
+}
+
+void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height)
+{
+ BOOL resize = FALSE;
+
+ if ((width * height) < 1)
+ return;
+
+ if ((appWindow->width != width) || (appWindow->height != height))
+ resize = TRUE;
+
+ if (appWindow->local_move.state == LMS_STARTING || appWindow->local_move.state == LMS_ACTIVE)
+ return;
+
+ appWindow->x = x;
+ appWindow->y = y;
+ appWindow->width = width;
+ appWindow->height = height;
+
+ if (resize)
+ XMoveResizeWindow(xfc->display, appWindow->handle, x, y, width, height);
+ else
+ XMoveWindow(xfc->display, appWindow->handle, x, y);
+
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, width, height);
+}
+
+void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ switch (state)
+ {
+ case WINDOW_HIDE:
+ XWithdrawWindow(xfc->display, appWindow->handle, xfc->screen_number);
+ break;
+
+ case WINDOW_SHOW_MINIMIZED:
+ appWindow->minimized = TRUE;
+ XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number);
+ break;
+
+ case WINDOW_SHOW_MAXIMIZED:
+ /* Set the window as maximized */
+ appWindow->maxHorz = TRUE;
+ appWindow->maxVert = TRUE;
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0);
+
+ /*
+ * This is a workaround for the case where the window is maximized locally before the
+ * rail server is told to maximize the window, this appears to be a race condition where
+ * the local window with incomplete data and once the window is actually maximized on
+ * the server
+ * - an update of the new areas may not happen. So, we simply to do a full update of the
+ * entire window once the rail server notifies us that the window is now maximized.
+ */
+ if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
+ {
+ xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth,
+ appWindow->windowHeight);
+ }
+
+ break;
+
+ case WINDOW_SHOW:
+ /* Ensure the window is not maximized */
+ xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
+ xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
+ 0);
+
+ /*
+ * Ignore configure requests until both the Maximized properties have been processed
+ * to prevent condition where WM overrides size of request due to one or both of these
+ * properties still being set - which causes a position adjustment to be sent back to
+ * the server thus causing the window to not return to its original size
+ */
+ if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
+ appWindow->rail_ignore_configure = TRUE;
+
+ if (appWindow->is_transient)
+ xf_SetWindowUnlisted(xfc, appWindow->handle);
+
+ XMapWindow(xfc->display, appWindow->handle);
+ break;
+ }
+
+ /* Save the current rail state of this window */
+ appWindow->rail_state = state;
+ XFlush(xfc->display);
+}
+
+void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects)
+{
+ XRectangle* xrects = NULL;
+
+ if (nrects < 1)
+ return;
+
+#ifdef WITH_XEXT
+ xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
+
+ for (int i = 0; i < nrects; i++)
+ {
+ xrects[i].x = rects[i].left;
+ xrects[i].y = rects[i].top;
+ xrects[i].width = rects[i].right - rects[i].left;
+ xrects[i].height = rects[i].bottom - rects[i].top;
+ }
+
+ XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, 0, 0, xrects, nrects,
+ ShapeSet, 0);
+ free(xrects);
+#endif
+}
+
+void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX,
+ UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects)
+{
+ XRectangle* xrects = NULL;
+
+ if (nrects < 1)
+ return;
+
+#ifdef WITH_XEXT
+ xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
+
+ for (int i = 0; i < nrects; i++)
+ {
+ xrects[i].x = rects[i].left;
+ xrects[i].y = rects[i].top;
+ xrects[i].width = rects[i].right - rects[i].left;
+ xrects[i].height = rects[i].bottom - rects[i].top;
+ }
+
+ XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, rectsOffsetX,
+ rectsOffsetY, xrects, nrects, ShapeSet, 0);
+ free(xrects);
+#endif
+}
+
+void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width,
+ int height)
+{
+ int ax = 0;
+ int ay = 0;
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (appWindow == NULL)
+ return;
+
+ if (appWindow->surfaceId < UINT16_MAX)
+ return;
+
+ ax = x + appWindow->windowOffsetX;
+ ay = y + appWindow->windowOffsetY;
+
+ if (ax + width > appWindow->windowOffsetX + appWindow->width)
+ width = (appWindow->windowOffsetX + appWindow->width - 1) - ax;
+
+ if (ay + height > appWindow->windowOffsetY + appWindow->height)
+ height = (appWindow->windowOffsetY + appWindow->height - 1) - ay;
+
+ xf_lock_x11(xfc);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
+ {
+ XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, xfc->image, ax, ay, x, y, width,
+ height);
+ }
+
+ XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, x, y, width,
+ height, x, y);
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+}
+
+static void xf_AppWindowDestroyImage(xfAppWindow* appWindow)
+{
+ WINPR_ASSERT(appWindow);
+ if (appWindow->image)
+ {
+ appWindow->image->data = NULL;
+ XDestroyImage(appWindow->image);
+ appWindow->image = NULL;
+ }
+}
+
+void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow)
+{
+ if (!appWindow)
+ return;
+
+ if (xfc->appWindow == appWindow)
+ xfc->appWindow = NULL;
+
+ if (appWindow->gc)
+ XFreeGC(xfc->display, appWindow->gc);
+
+ if (appWindow->pixmap)
+ XFreePixmap(xfc->display, appWindow->pixmap);
+
+ xf_AppWindowDestroyImage(appWindow);
+
+ if (appWindow->handle)
+ {
+ XUnmapWindow(xfc->display, appWindow->handle);
+ XDestroyWindow(xfc->display, appWindow->handle);
+ }
+
+ if (appWindow->xfwin)
+ munmap(0, sizeof(*appWindow->xfwin));
+
+ if (appWindow->shmid >= 0)
+ close(appWindow->shmid);
+
+ shm_unlink(get_shm_id());
+ appWindow->xfwin = (Window*)-1;
+ appWindow->shmid = -1;
+ free(appWindow->title);
+ free(appWindow->windowRects);
+ free(appWindow->visibilityRects);
+ free(appWindow);
+}
+
+xfAppWindow* xf_AppWindowFromX11Window(xfContext* xfc, Window wnd)
+{
+ ULONG_PTR* pKeys = NULL;
+
+ WINPR_ASSERT(xfc);
+ if (!xfc->railWindows)
+ return NULL;
+
+ size_t count = HashTable_GetKeys(xfc->railWindows, &pKeys);
+
+ for (size_t index = 0; index < count; index++)
+ {
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, *(UINT64*)pKeys[index]);
+
+ if (!appWindow)
+ {
+ free(pKeys);
+ return NULL;
+ }
+
+ if (appWindow->handle == wnd)
+ {
+ free(pKeys);
+ return appWindow;
+ }
+ }
+
+ free(pKeys);
+ return NULL;
+}
+
+UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface)
+{
+ XImage* image = NULL;
+ UINT rc = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(surface);
+
+ xfAppWindow* appWindow = xf_rail_get_window(xfc, surface->windowId);
+ if (!appWindow)
+ {
+ WLog_VRB(TAG, "Failed to find a window for id=0x%08" PRIx64, surface->windowId);
+ return CHANNEL_RC_OK;
+ }
+
+ const BOOL swGdi = freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_SoftwareGdi);
+ UINT32 nrects = 0;
+ const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nrects);
+
+ xf_lock_x11(xfc);
+ if (swGdi)
+ {
+ if (appWindow->surfaceId != surface->surfaceId)
+ {
+ xf_AppWindowDestroyImage(appWindow);
+ appWindow->surfaceId = surface->surfaceId;
+ }
+ if (appWindow->width != (INT64)surface->width)
+ xf_AppWindowDestroyImage(appWindow);
+ if (appWindow->height != (INT64)surface->height)
+ xf_AppWindowDestroyImage(appWindow);
+
+ if (!appWindow->image)
+ {
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
+ (char*)surface->data, surface->width, surface->height,
+ xfc->scanline_pad, surface->scanline);
+ if (!appWindow->image)
+ {
+ WLog_WARN(TAG,
+ "Failed create a XImage[%" PRIu32 "x%" PRIu32 ", scanline=%" PRIu32
+ ", bpp=%" PRIu32 "] for window id=0x%08" PRIx64,
+ surface->width, surface->height, surface->scanline, xfc->depth,
+ surface->windowId);
+ goto fail;
+ }
+ appWindow->image->byte_order = LSBFirst;
+ appWindow->image->bitmap_bit_order = LSBFirst;
+ }
+
+ image = appWindow->image;
+ }
+ else
+ {
+ xfGfxSurface* xfSurface = (xfGfxSurface*)surface;
+ image = xfSurface->image;
+ }
+
+ for (UINT32 x = 0; x < nrects; x++)
+ {
+ const RECTANGLE_16* rect = &rects[x];
+ const UINT32 width = rect->right - rect->left;
+ const UINT32 height = rect->bottom - rect->top;
+
+ XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, image, rect->left, rect->top,
+ rect->left, rect->top, width, height);
+
+ XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, rect->left,
+ rect->top, width, height, rect->left, rect->top);
+ }
+
+ rc = CHANNEL_RC_OK;
+fail:
+ XFlush(xfc->display);
+ xf_unlock_x11(xfc);
+ return rc;
+}
+
+BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(appWindow);
+
+ if (appWindow->pixmap != 0)
+ XFreePixmap(xfc->display, appWindow->pixmap);
+
+ WINPR_ASSERT(xfc->depth != 0);
+ appWindow->pixmap =
+ XCreatePixmap(xfc->display, xfc->drawable, appWindow->width, appWindow->height, xfc->depth);
+ xf_AppWindowDestroyImage(appWindow);
+
+ return appWindow->pixmap != 0;
+}
diff --git a/client/X11/xf_window.h b/client/X11/xf_window.h
new file mode 100644
index 0000000..9f30280
--- /dev/null
+++ b/client/X11/xf_window.h
@@ -0,0 +1,207 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Windows
+ *
+ * Copyright 2011 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_CLIENT_X11_WINDOW_H
+#define FREERDP_CLIENT_X11_WINDOW_H
+
+#include <X11/Xlib.h>
+
+#include <winpr/platform.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gfx.h>
+
+typedef struct xf_app_window xfAppWindow;
+
+typedef struct xf_localmove xfLocalMove;
+typedef struct xf_window xfWindow;
+
+#include "xf_client.h"
+#include "xf_floatbar.h"
+#include "xfreerdp.h"
+
+// Extended ICCM flags http://standards.freedesktop.org/wm-spec/wm-spec-latest.html
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
+#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */
+#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */
+#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
+#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
+
+#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
+#define _NET_WM_STATE_ADD 1 /* add/set property */
+#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
+
+WINPR_PRAGMA_DIAG_POP
+
+enum xf_localmove_state
+{
+ LMS_NOT_ACTIVE,
+ LMS_STARTING,
+ LMS_ACTIVE,
+ LMS_TERMINATING
+};
+
+struct xf_localmove
+{
+ int root_x;
+ int root_y;
+ int window_x;
+ int window_y;
+ enum xf_localmove_state state;
+ int direction;
+};
+
+struct xf_window
+{
+ GC gc;
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int width;
+ int height;
+ int shmid;
+ Window handle;
+ Window* xfwin;
+ xfFloatbar* floatbar;
+ BOOL decorations;
+ BOOL is_mapped;
+ BOOL is_transient;
+};
+
+struct xf_app_window
+{
+ xfContext* xfc;
+
+ int x;
+ int y;
+ int width;
+ int height;
+ char* title;
+
+ UINT32 surfaceId;
+ UINT64 windowId;
+ UINT32 ownerWindowId;
+
+ UINT32 dwStyle;
+ UINT32 dwExStyle;
+ UINT32 showState;
+
+ INT32 clientOffsetX;
+ INT32 clientOffsetY;
+ UINT32 clientAreaWidth;
+ UINT32 clientAreaHeight;
+
+ INT32 windowOffsetX;
+ INT32 windowOffsetY;
+ INT32 windowClientDeltaX;
+ INT32 windowClientDeltaY;
+ UINT32 windowWidth;
+ UINT32 windowHeight;
+ UINT32 numWindowRects;
+ RECTANGLE_16* windowRects;
+
+ INT32 visibleOffsetX;
+ INT32 visibleOffsetY;
+ UINT32 numVisibilityRects;
+ RECTANGLE_16* visibilityRects;
+
+ UINT32 localWindowOffsetCorrX;
+ UINT32 localWindowOffsetCorrY;
+
+ UINT32 resizeMarginLeft;
+ UINT32 resizeMarginTop;
+ UINT32 resizeMarginRight;
+ UINT32 resizeMarginBottom;
+
+ GC gc;
+ int shmid;
+ Window handle;
+ Window* xfwin;
+ BOOL fullscreen;
+ BOOL decorations;
+ BOOL is_mapped;
+ BOOL is_transient;
+ xfLocalMove local_move;
+ BYTE rail_state;
+ BOOL maxVert;
+ BOOL maxHorz;
+ BOOL minimized;
+ BOOL rail_ignore_configure;
+
+ Pixmap pixmap;
+ XImage* image;
+};
+
+void xf_ewmhints_init(xfContext* xfc);
+
+BOOL xf_GetCurrentDesktop(xfContext* xfc);
+BOOL xf_GetWorkArea(xfContext* xfc);
+
+void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen);
+void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window);
+void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show);
+void xf_SetWindowUnlisted(xfContext* xfc, Window window);
+
+xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height);
+void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height);
+void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window);
+
+Window xf_CreateDummyWindow(xfContext* xfc);
+void xf_DestroyDummyWindow(xfContext* xfc, Window window);
+
+BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length,
+ unsigned long* nitems, unsigned long* bytes, BYTE** prop);
+void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...);
+
+BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow);
+int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow);
+
+BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow);
+
+void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name);
+void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height);
+void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state);
+// void xf_SetWindowIcon(xfContext* xfc, xfAppWindow* appWindow, rdpIcon* icon);
+void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects);
+void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX,
+ UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects);
+void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style);
+void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width,
+ int height);
+UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface);
+
+void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow);
+void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight,
+ int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight,
+ int maxTrackWidth, int maxTrackHeight);
+void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y);
+void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow);
+xfAppWindow* xf_AppWindowFromX11Window(xfContext* xfc, Window wnd);
+
+#endif /* FREERDP_CLIENT_X11_WINDOW_H */
diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h
new file mode 100644
index 0000000..314c63d
--- /dev/null
+++ b/client/X11/xfreerdp.h
@@ -0,0 +1,389 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Client
+ *
+ * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Thincast Technologies GmbH
+ * Copyright 2016 Armin Novak <armin.novak@thincast.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_CLIENT_X11_FREERDP_H
+#define FREERDP_CLIENT_X11_FREERDP_H
+
+#include <freerdp/config.h>
+
+typedef struct xf_context xfContext;
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include <freerdp/api.h>
+
+#include "xf_window.h"
+#include "xf_monitor.h"
+#include "xf_channels.h"
+
+#if defined(CHANNEL_TSMF_CLIENT)
+#include <freerdp/client/tsmf.h>
+#endif
+
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/codec/rfx.h>
+#include <freerdp/codec/nsc.h>
+#include <freerdp/codec/clear.h>
+#include <freerdp/codec/color.h>
+#include <freerdp/codec/bitmap.h>
+#include <freerdp/codec/h264.h>
+#include <freerdp/codec/progressive.h>
+#include <freerdp/codec/region.h>
+
+#if !defined(XcursorUInt)
+typedef unsigned int XcursorUInt;
+#endif
+
+#if !defined(XcursorPixel)
+typedef XcursorUInt XcursorPixel;
+#endif
+
+struct xf_FullscreenMonitors
+{
+ UINT32 top;
+ UINT32 bottom;
+ UINT32 left;
+ UINT32 right;
+};
+typedef struct xf_FullscreenMonitors xfFullscreenMonitors;
+
+struct xf_WorkArea
+{
+ UINT32 x;
+ UINT32 y;
+ UINT32 width;
+ UINT32 height;
+};
+typedef struct xf_WorkArea xfWorkArea;
+
+struct xf_pointer
+{
+ rdpPointer pointer;
+ XcursorPixel* cursorPixels;
+ UINT32 nCursors;
+ UINT32 mCursors;
+ UINT32* cursorWidths;
+ UINT32* cursorHeights;
+ Cursor* cursors;
+ Cursor cursor;
+};
+typedef struct xf_pointer xfPointer;
+
+struct xf_bitmap
+{
+ rdpBitmap bitmap;
+ Pixmap pixmap;
+ XImage* image;
+};
+typedef struct xf_bitmap xfBitmap;
+
+struct xf_glyph
+{
+ rdpGlyph glyph;
+ Pixmap pixmap;
+};
+typedef struct xf_glyph xfGlyph;
+
+typedef struct xf_clipboard xfClipboard;
+typedef struct s_xfDispContext xfDispContext;
+typedef struct s_xfVideoContext xfVideoContext;
+typedef struct xf_rail_icon_cache xfRailIconCache;
+
+/* Number of buttons that are mapped from X11 to RDP button events. */
+#define NUM_BUTTONS_MAPPED 11
+
+typedef struct
+{
+ int button;
+ UINT16 flags;
+} button_map;
+
+#if defined(WITH_XI)
+#define MAX_CONTACTS 20
+
+typedef struct touch_contact
+{
+ int id;
+ int count;
+ double pos_x;
+ double pos_y;
+ double last_x;
+ double last_y;
+
+} touchContact;
+
+#endif
+
+struct xf_context
+{
+ rdpClientContext common;
+
+ GC gc;
+ int xfds;
+ int depth;
+
+ GC gc_mono;
+ BOOL invert;
+ Screen* screen;
+ XImage* image;
+ Pixmap primary;
+ Pixmap drawing;
+ Visual* visual;
+ Display* display;
+ Drawable drawable;
+ Pixmap bitmap_mono;
+ Colormap colormap;
+ int screen_number;
+ int scanline_pad;
+ BOOL big_endian;
+ BOOL fullscreen;
+ BOOL decorations;
+ BOOL grab_keyboard;
+ BOOL unobscured;
+ BOOL debug;
+ HANDLE x11event;
+ xfWindow* window;
+ xfAppWindow* appWindow;
+ xfPointer* pointer;
+ xfWorkArea workArea;
+ xfFullscreenMonitors fullscreenMonitors;
+ int current_desktop;
+ BOOL remote_app;
+ HANDLE mutex;
+ BOOL UseXThreads;
+ BOOL cursorHidden;
+
+ UINT32 bitmap_size;
+ BYTE* bitmap_buffer;
+
+ BOOL frame_begin;
+
+ int XInputOpcode;
+
+ int savedWidth;
+ int savedHeight;
+ int savedPosX;
+ int savedPosY;
+
+#ifdef WITH_XRENDER
+ int scaledWidth;
+ int scaledHeight;
+ int offset_x;
+ int offset_y;
+#endif
+
+ BOOL focused;
+ BOOL mouse_active;
+ BOOL fullscreen_toggle;
+ UINT32 KeyboardLayout;
+ BOOL KeyboardState[256];
+ XModifierKeymap* modifierMap;
+ wArrayList* keyCombinations;
+ wArrayList* xevents;
+ BOOL actionScriptExists;
+
+ int attribs_mask;
+ XSetWindowAttributes attribs;
+ BOOL complex_regions;
+ VIRTUAL_SCREEN vscreen;
+#if defined(CHANNEL_TSMF_CLIENT)
+ void* xv_context;
+#endif
+
+ Atom* supportedAtoms;
+ unsigned long supportedAtomCount;
+
+ Atom UTF8_STRING;
+
+ Atom _XWAYLAND_MAY_GRAB_KEYBOARD;
+
+ Atom _NET_WM_ICON;
+ Atom _MOTIF_WM_HINTS;
+ Atom _NET_CURRENT_DESKTOP;
+ Atom _NET_WORKAREA;
+
+ Atom _NET_SUPPORTED;
+ Atom _NET_SUPPORTING_WM_CHECK;
+
+ Atom _NET_WM_STATE;
+ Atom _NET_WM_STATE_FULLSCREEN;
+ Atom _NET_WM_STATE_MAXIMIZED_HORZ;
+ Atom _NET_WM_STATE_MAXIMIZED_VERT;
+ Atom _NET_WM_STATE_SKIP_TASKBAR;
+ Atom _NET_WM_STATE_SKIP_PAGER;
+
+ Atom _NET_WM_FULLSCREEN_MONITORS;
+
+ Atom _NET_WM_NAME;
+ Atom _NET_WM_PID;
+
+ Atom _NET_WM_WINDOW_TYPE;
+ Atom _NET_WM_WINDOW_TYPE_NORMAL;
+ Atom _NET_WM_WINDOW_TYPE_DIALOG;
+ Atom _NET_WM_WINDOW_TYPE_UTILITY;
+ Atom _NET_WM_WINDOW_TYPE_POPUP;
+ Atom _NET_WM_WINDOW_TYPE_POPUP_MENU;
+ Atom _NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
+
+ Atom _NET_WM_MOVERESIZE;
+ Atom _NET_MOVERESIZE_WINDOW;
+
+ Atom WM_STATE;
+ Atom WM_PROTOCOLS;
+ Atom WM_DELETE_WINDOW;
+
+ /* Channels */
+#if defined(CHANNEL_TSMF_CLIENT)
+ TsmfClientContext* tsmf;
+#endif
+
+ xfClipboard* clipboard;
+ CliprdrClientContext* cliprdr;
+ xfVideoContext* xfVideo;
+ xfDispContext* xfDisp;
+
+ RailClientContext* rail;
+ wHashTable* railWindows;
+ xfRailIconCache* railIconCache;
+
+ BOOL xkbAvailable;
+ BOOL xrenderAvailable;
+
+ /* value to be sent over wire for each logical client mouse button */
+ button_map button_map[NUM_BUTTONS_MAPPED];
+ BYTE savedMaximizedState;
+ UINT32 locked;
+ BOOL firstPressRightCtrl;
+ BOOL ungrabKeyboardWithRightCtrl;
+
+#if defined(WITH_XI)
+ touchContact contacts[MAX_CONTACTS];
+ int active_contacts;
+ int lastEvType;
+ XIDeviceEvent lastEvent;
+ double firstDist;
+ double lastDist;
+ double z_vector;
+ double px_vector;
+ double py_vector;
+#endif
+ BOOL xi_rawevent;
+ BOOL xi_event;
+ HANDLE pipethread;
+};
+
+BOOL xf_create_window(xfContext* xfc);
+BOOL xf_create_image(xfContext* xfc);
+void xf_toggle_fullscreen(xfContext* xfc);
+
+enum XF_EXIT_CODE
+{
+ /* section 0-15: protocol-independent codes */
+ XF_EXIT_SUCCESS = 0,
+ XF_EXIT_DISCONNECT = 1,
+ XF_EXIT_LOGOFF = 2,
+ XF_EXIT_IDLE_TIMEOUT = 3,
+ XF_EXIT_LOGON_TIMEOUT = 4,
+ XF_EXIT_CONN_REPLACED = 5,
+ XF_EXIT_OUT_OF_MEMORY = 6,
+ XF_EXIT_CONN_DENIED = 7,
+ XF_EXIT_CONN_DENIED_FIPS = 8,
+ XF_EXIT_USER_PRIVILEGES = 9,
+ XF_EXIT_FRESH_CREDENTIALS_REQUIRED = 10,
+ XF_EXIT_DISCONNECT_BY_USER = 11,
+
+ /* section 16-31: license error set */
+ XF_EXIT_LICENSE_INTERNAL = 16,
+ XF_EXIT_LICENSE_NO_LICENSE_SERVER = 17,
+ XF_EXIT_LICENSE_NO_LICENSE = 18,
+ XF_EXIT_LICENSE_BAD_CLIENT_MSG = 19,
+ XF_EXIT_LICENSE_HWID_DOESNT_MATCH = 20,
+ XF_EXIT_LICENSE_BAD_CLIENT = 21,
+ XF_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22,
+ XF_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23,
+ XF_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24,
+ XF_EXIT_LICENSE_CANT_UPGRADE = 25,
+ XF_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26,
+
+ /* section 32-127: RDP protocol error set */
+ XF_EXIT_RDP = 32,
+
+ /* section 128-254: xfreerdp specific exit codes */
+ XF_EXIT_PARSE_ARGUMENTS = 128,
+ XF_EXIT_MEMORY = 129,
+ XF_EXIT_PROTOCOL = 130,
+ XF_EXIT_CONN_FAILED = 131,
+ XF_EXIT_AUTH_FAILURE = 132,
+ XF_EXIT_NEGO_FAILURE = 133,
+ XF_EXIT_LOGON_FAILURE = 134,
+ XF_EXIT_ACCOUNT_LOCKED_OUT = 135,
+ XF_EXIT_PRE_CONNECT_FAILED = 136,
+ XF_EXIT_CONNECT_UNDEFINED = 137,
+ XF_EXIT_POST_CONNECT_FAILED = 138,
+ XF_EXIT_DNS_ERROR = 139,
+ XF_EXIT_DNS_NAME_NOT_FOUND = 140,
+ XF_EXIT_CONNECT_FAILED = 141,
+ XF_EXIT_MCS_CONNECT_INITIAL_ERROR = 142,
+ XF_EXIT_TLS_CONNECT_FAILED = 143,
+ XF_EXIT_INSUFFICIENT_PRIVILEGES = 144,
+ XF_EXIT_CONNECT_CANCELLED = 145,
+
+ XF_EXIT_CONNECT_TRANSPORT_FAILED = 147,
+ XF_EXIT_CONNECT_PASSWORD_EXPIRED = 148,
+ XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149,
+ XF_EXIT_CONNECT_KDC_UNREACHABLE = 150,
+ XF_EXIT_CONNECT_ACCOUNT_DISABLED = 151,
+ XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152,
+ XF_EXIT_CONNECT_CLIENT_REVOKED = 153,
+ XF_EXIT_CONNECT_WRONG_PASSWORD = 154,
+ XF_EXIT_CONNECT_ACCESS_DENIED = 155,
+ XF_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156,
+ XF_EXIT_CONNECT_ACCOUNT_EXPIRED = 157,
+ XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158,
+ XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159,
+
+ XF_EXIT_UNKNOWN = 255,
+};
+
+#define xf_lock_x11(xfc) xf_lock_x11_(xfc, __func__)
+#define xf_unlock_x11(xfc) xf_unlock_x11_(xfc, __func__)
+
+void xf_lock_x11_(xfContext* xfc, const char* fkt);
+void xf_unlock_x11_(xfContext* xfc, const char* fkt);
+
+BOOL xf_picture_transform_required(xfContext* xfc);
+
+#define xf_draw_screen(_xfc, _x, _y, _w, _h) \
+ xf_draw_screen_((_xfc), (_x), (_y), (_w), (_h), __func__, __FILE__, __LINE__)
+void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file,
+ int line);
+
+BOOL xf_keyboard_update_modifier_map(xfContext* xfc);
+
+DWORD xf_exit_code_from_disconnect_reason(DWORD reason);
+
+#endif /* FREERDP_CLIENT_X11_FREERDP_H */
diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt
new file mode 100644
index 0000000..6040bf3
--- /dev/null
+++ b/client/common/CMakeLists.txt
@@ -0,0 +1,110 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Client 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-client")
+set(MODULE_PREFIX "FREERDP_CLIENT")
+
+# 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(SRCS
+ client.c
+ cmdline.c
+ cmdline.h
+ file.c
+ client_cliprdr_file.c
+ geometry.c
+ smartcard_cli.c)
+
+foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS})
+ get_filename_component(NINC ${FREERDP_CHANNELS_CLIENT_SRC} PATH)
+ include_directories(${NINC})
+ list(APPEND SRCS "${FREERDP_CHANNELS_CLIENT_SRC}")
+endforeach()
+
+if (NOT APPLE AND NOT WIN32 AND NOT ANDROID)
+ set(OPT_FUSE_DEFAULT ON)
+else()
+ set(OPT_FUSE_DEFAULT OFF)
+endif()
+
+option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT})
+if(WITH_FUSE)
+ find_package(PkgConfig REQUIRED)
+
+ pkg_check_modules(FUSE3 REQUIRED fuse3)
+ include_directories(${FUSE3_INCLUDE_DIRS})
+ add_definitions(-DWITH_FUSE)
+ list(APPEND LIBS ${FUSE3_LIBRARIES})
+
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32 AND BUILD_SHARED_LIBS)
+ 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)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_library(${MODULE_NAME} ${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()
+
+list(APPEND LIBS freerdp winpr)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_CLIENT_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC ${LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ClientTargets
+ 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 "Client/Common")
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+if (WITH_MANPAGES)
+ add_subdirectory(man)
+endif()
diff --git a/client/common/client.c b/client/common/client.c
new file mode 100644
index 0000000..9d6ec03
--- /dev/null
+++ b/client/common/client.c
@@ -0,0 +1,2156 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client 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 <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <float.h>
+
+#include <freerdp/client.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/assistance.h>
+#include <freerdp/client/file.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/client/ainput.h>
+#include <freerdp/channels/ainput.h>
+#endif
+
+#if defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/client/video.h>
+#include <freerdp/channels/video.h>
+#endif
+
+#if defined(CHANNEL_RDPGFX_CLIENT)
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/gdi/gfx.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+#include <freerdp/client/geometry.h>
+#include <freerdp/channels/geometry.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/gdi/video.h>
+#endif
+
+#ifdef WITH_AAD
+#include <freerdp/utils/http.h>
+#include <freerdp/utils/aad.h>
+#endif
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+static void set_default_callbacks(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ instance->AuthenticateEx = client_cli_authenticate_ex;
+ instance->ChooseSmartcard = client_cli_choose_smartcard;
+ instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
+ instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
+ instance->PresentGatewayMessage = client_cli_present_gateway_message;
+ instance->LogonErrorInfo = client_cli_logon_error_info;
+ instance->GetAccessToken = client_cli_get_access_token;
+ instance->RetryDialog = client_common_retry_dialog;
+}
+
+static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ instance->LoadChannels = freerdp_client_load_channels;
+ set_default_callbacks(instance);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context);
+}
+
+static void freerdp_client_common_free(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ IFCALL(pEntryPoints->ClientFree, instance, context);
+}
+
+/* Common API */
+
+rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ freerdp* instance = NULL;
+ rdpContext* context = NULL;
+
+ if (!pEntryPoints)
+ return NULL;
+
+ IFCALL(pEntryPoints->GlobalInit);
+ instance = freerdp_new();
+
+ if (!instance)
+ return NULL;
+
+ instance->ContextSize = pEntryPoints->ContextSize;
+ instance->ContextNew = freerdp_client_common_new;
+ instance->ContextFree = freerdp_client_common_free;
+ instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size);
+
+ if (!instance->pClientEntryPoints)
+ goto out_fail;
+
+ CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size);
+
+ if (!freerdp_context_new_ex(instance, pEntryPoints->settings))
+ goto out_fail2;
+
+ context = instance->context;
+ context->instance = instance;
+
+#if defined(WITH_CHANNELS)
+ if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
+ CHANNEL_RC_OK)
+ goto out_fail2;
+#endif
+
+ return context;
+out_fail2:
+ free(instance->pClientEntryPoints);
+out_fail:
+ freerdp_free(instance);
+ return NULL;
+}
+
+void freerdp_client_context_free(rdpContext* context)
+{
+ freerdp* instance = NULL;
+
+ if (!context)
+ return;
+
+ instance = context->instance;
+
+ if (instance)
+ {
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
+ freerdp_context_free(instance);
+
+ if (pEntryPoints)
+ IFCALL(pEntryPoints->GlobalUninit);
+
+ free(instance->pClientEntryPoints);
+ freerdp_free(instance);
+ }
+}
+
+int freerdp_client_start(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
+ set_default_callbacks(context->instance);
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context);
+}
+
+int freerdp_client_stop(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context);
+}
+
+freerdp* freerdp_client_get_instance(rdpContext* context)
+{
+ if (!context || !context->instance)
+ return NULL;
+
+ return context->instance;
+}
+
+HANDLE freerdp_client_get_thread(rdpContext* context)
+{
+ if (!context)
+ return NULL;
+
+ return ((rdpClientContext*)context)->thread;
+}
+
+static BOOL freerdp_client_settings_post_process(rdpSettings* settings)
+{
+ /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled))
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ if (Username)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username))
+ goto out_error;
+ }
+
+ if (Domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain))
+ goto out_error;
+ }
+
+ if (freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ if (!freerdp_settings_set_string(
+ settings, FreeRDP_GatewayPassword,
+ freerdp_settings_get_string(settings, FreeRDP_Password)))
+ goto out_error;
+ }
+ }
+ }
+
+ /* Moved logic for Multimon and Span monitors to force fullscreen, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+
+ /* deal with the smartcard / smartcard logon stuff */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE);
+ }
+
+ return TRUE;
+out_error:
+ return FALSE;
+}
+
+int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv,
+ BOOL allowUnknown)
+{
+ int status = 0;
+
+ if (argc < 1)
+ return 0;
+
+ if (!argv)
+ return -1;
+
+ status =
+ freerdp_client_settings_parse_command_line_arguments(settings, argc, argv, allowUnknown);
+
+ if (status < 0)
+ return status;
+
+ /* This function will call logic that is applicable to the settings
+ * from command line parsing AND the rdp file parsing */
+ if (!freerdp_client_settings_post_process(settings))
+ status = -1;
+
+ WLog_DBG(TAG, "This is %s %s", freerdp_get_version_string(), freerdp_get_build_config());
+ return status;
+}
+
+int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_parse_rdp_file(file, filename))
+ goto out;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer,
+ size_t size)
+{
+ rdpFile* file = NULL;
+ int status = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) &&
+ freerdp_client_populate_settings_from_rdp_file(file, settings))
+ {
+ status = 0;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ return status;
+}
+
+int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename,
+ BOOL unicode)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto out;
+
+ if (!freerdp_client_write_rdp_file(file, filename, unicode))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[])
+{
+ int status = 0;
+ int ret = -1;
+ char* filename = NULL;
+ char* password = NULL;
+ rdpAssistanceFile* file = NULL;
+
+ if (!settings || !argv || (argc < 2))
+ return -1;
+
+ filename = argv[1];
+
+ for (int x = 2; x < argc; x++)
+ {
+ const char* key = strstr(argv[x], "assistance:");
+
+ if (key)
+ password = strchr(key, ':') + 1;
+ }
+
+ file = freerdp_assistance_file_new();
+
+ if (!file)
+ return -1;
+
+ status = freerdp_assistance_parse_file(file, filename, password);
+
+ if (status < 0)
+ goto out;
+
+ if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_assistance_file_free(file);
+ return ret;
+}
+
+/** Callback set in the rdp_freerdp structure, and used to get the user's password,
+ * if required to establish the connection.
+ * This function is actually called in credssp_ntlmssp_client_init()
+ * @see rdp_server_accept_nego() and rdp_check_fds()
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param username - unused
+ * @param password - on return: pointer to a character string that will be filled by the password
+ * entered by the user. Note that this character string will be allocated inside the function, and
+ * needs to be deallocated by the caller using free(), even in case this function fails.
+ * @param domain - unused
+ * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more
+ * details.
+ */
+static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username,
+ char** password, char** domain)
+{
+ static const size_t password_size = 512;
+ const char* auth[] = { "Username: ", "Domain: ", "Password: " };
+ const char* authPin[] = { "Username: ", "Domain: ", "Smartcard-Pin: " };
+ const char* gw[] = { "GatewayUsername: ", "GatewayDomain: ", "GatewayPassword: " };
+ const char** prompt = NULL;
+ BOOL pinOnly = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ switch (reason)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ pinOnly = TRUE;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!username || !password || !domain)
+ return FALSE;
+
+ if (!*username && !pinOnly)
+ {
+ size_t username_size = 0;
+ printf("%s", prompt[0]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, username, &username_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*username)
+ {
+ *username = StrSep(username, "\r");
+ *username = StrSep(username, "\n");
+ }
+ }
+
+ if (!*domain && !pinOnly)
+ {
+ size_t domain_size = 0;
+ printf("%s", prompt[1]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, domain, &domain_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*domain)
+ {
+ *domain = StrSep(domain, "\r");
+ *domain = StrSep(domain, "\n");
+ }
+ }
+
+ if (!*password)
+ {
+ *password = calloc(password_size, sizeof(char));
+
+ if (!*password)
+ goto fail;
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (freerdp_passphrase_read(instance->context, prompt[2], *password, password_size,
+ fromStdin) == NULL)
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = NULL;
+ *domain = NULL;
+ *password = NULL;
+ return FALSE;
+}
+
+BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(username);
+ WINPR_ASSERT(password);
+ WINPR_ASSERT(domain);
+
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ break;
+ default:
+ return FALSE;
+ }
+
+ return client_cli_authenticate_raw(instance, reason, username, password, domain);
+}
+
+BOOL client_cli_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ unsigned long answer = 0;
+ char* p = NULL;
+
+ printf("Multiple smartcards are available for use:\n");
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, NULL);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, NULL);
+
+ printf("[%" PRIu32
+ "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n",
+ i, container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ free(reader);
+ free(container_name);
+ }
+
+ while (1)
+ {
+ char input[10] = { 0 };
+
+ printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ",
+ gateway ? "gateway authentication" : "logon", count - 1);
+ fflush(stdout);
+ if (!fgets(input, 10, stdin))
+ {
+ WLog_ERR(TAG, "could not read from stdin");
+ return FALSE;
+ }
+
+ answer = strtoul(input, &p, 10);
+ if ((*p == '\n' && p != input) && answer < count)
+ {
+ *choice = answer;
+ return TRUE;
+ }
+ }
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon))
+ {
+ WLog_INFO(TAG, "Authentication via smartcard");
+ return TRUE;
+ }
+
+ return client_cli_authenticate_raw(instance, FALSE, username, password, domain);
+}
+
+BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ return client_cli_authenticate_raw(instance, TRUE, username, password, domain);
+}
+#endif
+
+static DWORD client_cli_accept_certificate(freerdp* instance)
+{
+ int answer = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ const rdpSettings* settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (fromStdin)
+ return 0;
+
+ while (1)
+ {
+ printf("Do you trust the above certificate? (Y/T/N) ");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.");
+
+ if (fromStdin)
+ printf(" - Run without parameter \"--from-stdin\" to set trust.");
+
+ printf("\n");
+ return 0;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 1;
+
+ case 't':
+ case 'T':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 2;
+
+ case 'n':
+ case 'N':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 0;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param host_mismatch Indicates the certificate host does not match.
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, BOOL host_mismatch)
+{
+ WINPR_UNUSED(common_name);
+ WINPR_UNUSED(host_mismatch);
+
+ printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n");
+ printf("Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type);
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_changed_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param old_subject
+ * @param old_issuer
+ * @param old_fingerprint
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint)
+{
+ WINPR_UNUSED(common_name);
+
+ printf("WARNING: This callback is deprecated, migrate to "
+ "client_cli_verify_changed_certificate_ex\n");
+ printf("!!! Certificate has changed !!!\n");
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection
+ * settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param old_subject The subject of the previous certificate
+ * @param old_issuer The previous certificate issuer name
+ * @param old_fingerprint The fingerprint of the previous certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type);
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", old_fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
+ {
+ printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n");
+ printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n");
+ printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n");
+ printf("\tAll manually accepted certificates must be reconfirmed!\n");
+ printf("\n");
+ }
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message)
+{
+ int answer = 0;
+ const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (!isDisplayMandatory && !isConsentMandatory)
+ return TRUE;
+
+ printf("%s:\n", msgType);
+#if defined(WIN32)
+ printf("%.*S\n", (int)length, message);
+#else
+ {
+ LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), NULL);
+ if (!msg)
+ {
+ printf("Failed to convert message!\n");
+ return FALSE;
+ }
+ printf("%s\n", msg);
+ free(msg);
+ }
+#endif
+
+ while (isConsentMandatory)
+ {
+ printf("I understand and agree to the terms of this policy (Y/N) \n");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.\n");
+ return FALSE;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return FALSE;
+ return TRUE;
+
+ case 'n':
+ case 'N':
+ freerdp_interruptible_getc(instance->context, stdin);
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+
+ return TRUE;
+}
+
+static char* extract_authorization_code(char* url)
+{
+ WINPR_ASSERT(url);
+
+ for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
+ {
+ if (strncmp(p, "code=", 5) != 0)
+ continue;
+
+ char* end = NULL;
+ p += 5;
+
+ end = strchr(p, '&');
+ if (end)
+ *end = 0;
+ else
+ end = strchr(p, '\0');
+
+ return p;
+ }
+
+ return NULL;
+}
+
+static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
+ const char* req_cnf, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(scope);
+ WINPR_ASSERT(req_cnf);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(&token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%"
+ "s&req_cnf=%s",
+ code, client_id, scope, redirect_uri, req_cnf) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+ const char* scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(
+ &token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", code,
+ client_id, scope, redirect_uri) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+ switch (tokenType)
+ {
+ case ACCESS_TOKEN_TYPE_AAD:
+ {
+ if (count < 2)
+ {
+ WLog_ERR(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", aborting",
+ count);
+ return FALSE;
+ }
+ else if (count > 2)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ va_list ap;
+ va_start(ap, count);
+ const char* scope = va_arg(ap, const char*);
+ const char* req_cnf = va_arg(ap, const char*);
+ const BOOL rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token);
+ va_end(ap);
+ return rc;
+ }
+ case ACCESS_TOKEN_TYPE_AVD:
+ if (count != 0)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ return client_cli_get_avd_access_token(instance, token);
+ default:
+ WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
+ return FALSE;
+ }
+}
+
+BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token)
+{
+#ifdef WITH_AAD
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(token);
+
+ BOOL ret = FALSE;
+ long resp_code = 0;
+ BYTE* response = NULL;
+ size_t response_length = 0;
+
+ wLog* log = WLog_Get(TAG);
+
+ if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", request,
+ &resp_code, &response, &response_length))
+ {
+ WLog_ERR(TAG, "access token request failed");
+ return FALSE;
+ }
+
+ if (resp_code != HTTP_STATUS_OK)
+ {
+ char buffer[64] = { 0 };
+
+ WLog_Print(log, WLOG_ERROR,
+ "Server unwilling to provide access token; returned status code %s",
+ freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer)));
+ if (response_length > 0)
+ WLog_Print(log, WLOG_ERROR, "[status message] %s", response);
+ goto cleanup;
+ }
+
+ *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length);
+ if (*token)
+ ret = TRUE;
+
+cleanup:
+ free(response);
+ return ret;
+#else
+ return FALSE;
+#endif
+}
+
+SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current,
+ void* userarg)
+{
+ WINPR_UNUSED(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_UNUSED(userarg);
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(what);
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ WLog_ERR(TAG, "Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what);
+ }
+
+ const rdpSettings* settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ if (!enabled)
+ {
+ WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ WLog_ERR(TAG,
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL client_auto_reconnect(freerdp* instance)
+{
+ return client_auto_reconnect_ex(instance, NULL);
+}
+
+BOOL client_auto_reconnect_ex(freerdp* instance, BOOL (*window_events)(freerdp* instance))
+{
+ BOOL retry = TRUE;
+ UINT32 error = 0;
+ UINT32 numRetries = 0;
+ rdpSettings* settings = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 maxRetries =
+ freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+
+ /* Only auto reconnect on network disconnects. */
+ error = freerdp_error_info(instance);
+ switch (error)
+ {
+ case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
+ /* A network disconnect was detected */
+ WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]",
+ freerdp_get_error_info_string(error));
+ break;
+ case ERRINFO_SUCCESS:
+ /* A network disconnect was detected */
+ WLog_INFO(TAG, "Network disconnect!");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
+ {
+ /* No auto-reconnect - just quit */
+ return FALSE;
+ }
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Connection aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Perform an auto-reconnect. */
+ while (retry)
+ {
+ /* Quit retrying if max retries has been exceeded */
+ if ((maxRetries > 0) && (numRetries++ >= maxRetries))
+ {
+ return FALSE;
+ }
+
+ /* Attempt the next reconnect */
+ WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries);
+
+ IFCALL(instance->RetryDialog, instance, "connection", numRetries, NULL);
+
+ if (freerdp_reconnect(instance))
+ return TRUE;
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Autoreconnect aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+ for (UINT32 x = 0; x < 50; x++)
+ {
+ if (!IFCALLRESULT(TRUE, window_events, instance))
+ return FALSE;
+
+ Sleep(10);
+ }
+ }
+
+ WLog_ERR(TAG, "Maximum reconnect retries exceeded");
+ return FALSE;
+}
+
+int freerdp_client_common_stop(rdpContext* context)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+ WINPR_ASSERT(cctx);
+
+ freerdp_abort_connect_context(&cctx->context);
+
+ if (cctx->thread)
+ {
+ WaitForSingleObject(cctx->thread, INFINITE);
+ CloseHandle(cctx->thread);
+ cctx->thread = NULL;
+ }
+
+ return 0;
+}
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp)
+{
+ rdpClientContext* cctx = NULL;
+ BOOL state = 0;
+
+ if (!encomsp)
+ return FALSE;
+
+ cctx = (rdpClientContext*)encomsp->custom;
+
+ state = cctx->controlToggle;
+ cctx->controlToggle = !cctx->controlToggle;
+ return freerdp_client_encomsp_set_control(encomsp, state);
+}
+
+BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control)
+{
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = { 0 };
+
+ if (!encomsp)
+ return FALSE;
+
+ pdu.ParticipantId = encomsp->participantId;
+ pdu.Flags = ENCOMSP_REQUEST_VIEW;
+
+ if (control)
+ pdu.Flags |= ENCOMSP_REQUEST_INTERACT;
+
+ encomsp->ChangeParticipantControlLevel(encomsp, &pdu);
+
+ return TRUE;
+}
+
+static UINT
+client_encomsp_participant_created(EncomspClientContext* context,
+ const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated)
+{
+ rdpClientContext* cctx = NULL;
+ rdpSettings* settings = NULL;
+ BOOL request = 0;
+
+ if (!context || !context->custom || !participantCreated)
+ return ERROR_INVALID_PARAMETER;
+
+ cctx = (rdpClientContext*)context->custom;
+ WINPR_ASSERT(cctx);
+
+ settings = cctx->context.settings;
+ WINPR_ASSERT(settings);
+
+ if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT)
+ context->participantId = participantCreated->ParticipantId;
+
+ request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl);
+ if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) &&
+ !(participantCreated->Flags & ENCOMSP_MAY_INTERACT))
+ {
+ if (!freerdp_client_encomsp_set_control(context, TRUE))
+ return ERROR_INTERNAL_ERROR;
+
+ /* if auto-request-control setting is enabled then only request control once upon connect,
+ * otherwise it will auto request control again every time server turns off control which
+ * is a bit annoying */
+ freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ cctx->encomsp = encomsp;
+ encomsp->custom = (void*)cctx;
+ encomsp->ParticipantCreated = client_encomsp_participant_created;
+}
+
+static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ if (encomsp)
+ {
+ encomsp->custom = NULL;
+ encomsp->ParticipantCreated = NULL;
+ }
+
+ if (cctx)
+ cctx->encomsp = NULL;
+}
+#endif
+
+void freerdp_client_OnChannelConnectedEventHandler(void* context,
+ const ChannelConnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = (AInputClientContext*)e->pInterface;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = (RdpeiClientContext*)e->pInterface;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+void freerdp_client_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = NULL;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = NULL;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT rc = 0;
+ UINT64 flags = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 value = mflags & 0xFF;
+
+ if (mflags & PTR_FLAGS_WHEEL_NEGATIVE)
+ value = -1 * (0x100 - value);
+
+ /* We have discrete steps, scale this so we can also support high
+ * resolution wheels. */
+ value *= 0x10000;
+
+ if (mflags & PTR_FLAGS_WHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ y = value;
+ }
+
+ if (mflags & PTR_FLAGS_HWHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ x = value;
+ }
+
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+ if (rc == CHANNEL_RC_OK)
+ handled = TRUE;
+ }
+#endif
+
+ if (!handled)
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0);
+
+ return TRUE;
+}
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+static INLINE BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y)
+{
+ UINT rc = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(cctx->ainput);
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+
+ return rc == CHANNEL_RC_OK;
+}
+#endif
+
+BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x,
+ INT32 y)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+ const BOOL relativeInput = freerdp_client_use_relative_mouse_events(cctx);
+ if (relative && relativeInput)
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (cctx->mouse_grabbed && relativeInput)
+ flags |= AINPUT_FLAGS_HAVE_REL;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+
+ if (mflags & PTR_FLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_FLAGS_BUTTON1)
+ flags |= AINPUT_FLAGS_BUTTON1;
+ if (mflags & PTR_FLAGS_BUTTON2)
+ flags |= AINPUT_FLAGS_BUTTON2;
+ if (mflags & PTR_FLAGS_BUTTON3)
+ flags |= AINPUT_FLAGS_BUTTON3;
+ if (mflags & PTR_FLAGS_MOVE)
+ flags |= AINPUT_FLAGS_MOVE;
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags,
+ INT32 x, INT32 y)
+{
+ BOOL handled = FALSE;
+ WINPR_ASSERT(cctx);
+
+ if (relative && freerdp_client_use_relative_mouse_events(cctx))
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+ if (mflags & PTR_XFLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_XFLAGS_BUTTON1)
+ flags |= AINPUT_XFLAGS_BUTTON1;
+ if (mflags & PTR_XFLAGS_BUTTON2)
+ flags |= AINPUT_XFLAGS_BUTTON2;
+
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ // Ensure contact position is unchanged from "engaged" to "out of range" state
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ contactFlags, contact->pressure);
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchEnd);
+ rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ // Emulate mouse click if touch is not possible, like in login screen
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_DOWN;
+ flags |= PTR_FLAGS_MOVE;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchBegin);
+ rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_MOVE;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchUpdate);
+ rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId,
+ UINT32 pressure, INT32 x, INT32 y,
+ FreeRDP_TouchContact* pcontact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(pcontact);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++)
+ {
+ FreeRDP_TouchContact* contact = &cctx->contacts[i];
+
+ const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0));
+ if (newcontact || (contact->id == touchId))
+ {
+ contact->id = touchId;
+ contact->flags = flags;
+ contact->pressure = pressure;
+ contact->x = x;
+ contact->y = y;
+
+ *pcontact = *contact;
+
+ const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0;
+ if (resetcontact)
+ {
+ FreeRDP_TouchContact empty = { 0 };
+ *contact = empty;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
+ UINT32 pressure, INT32 x, INT32 y)
+{
+ const UINT32 mask = FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION;
+ WINPR_ASSERT(cctx);
+
+ FreeRDP_TouchContact contact = { 0 };
+
+ if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact))
+ return FALSE;
+
+ switch (flags & mask)
+ {
+ case FREERDP_TOUCH_DOWN:
+ return freerdp_handle_touch_down(cctx, &contact);
+ case FREERDP_TOUCH_UP:
+ return freerdp_handle_touch_up(cctx, &contact);
+ case FREERDP_TOUCH_MOTION:
+ return freerdp_handle_touch_motion(cctx, &contact);
+ default:
+ WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %d, ignoring", flags);
+ return FALSE;
+ }
+}
+
+BOOL freerdp_client_load_channels(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings))
+ {
+ WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError());
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int client_cli_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 FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid,
+ size_t* pos)
+{
+ WINPR_ASSERT(cctx);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (deviceid == pen->deviceid)
+ {
+ if (pos)
+ *pos = i;
+ return pen;
+ }
+ }
+ return NULL;
+}
+
+static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
+ double pressure)
+{
+ static const INT32 null_deviceid = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0);
+ if (freerdp_client_is_pen(cctx, deviceid))
+ {
+ WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid);
+ return FALSE;
+ }
+
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos);
+ if (pen)
+ {
+ const FreeRDP_PenDevice empty = { 0 };
+ *pen = empty;
+
+ pen->deviceid = deviceid;
+ pen->max_pressure = pressure;
+ pen->flags = flags;
+
+ WLog_DBG(TAG, "registered pen at index %" PRIuz, pos);
+ return TRUE;
+ }
+
+ WLog_WARN(TAG, "No free slots for an additiona pen device, skipping");
+ return TRUE;
+}
+
+BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...)
+{
+ if ((flags & FREERDP_PEN_REGISTER) != 0)
+ {
+ va_list args;
+
+ va_start(args, deviceid);
+ double pressure = va_arg(args, double);
+ va_end(args);
+ return freerdp_client_register_pen(cctx, flags, deviceid, pressure);
+ }
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos);
+ if (!pen)
+ {
+ WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+ }
+
+ UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT;
+ UINT32 penFlags =
+ ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0;
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+ WINPR_ASSERT(rdpei);
+
+ UINT32 normalizedpressure = 1024;
+ INT32 x = 0;
+ INT32 y = 0;
+ UINT16 rotation = 0;
+ INT16 tiltX = 0;
+ INT16 tiltY = 0;
+ va_list args;
+ va_start(args, deviceid);
+
+ x = va_arg(args, INT32);
+ y = va_arg(args, INT32);
+ if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0)
+ {
+ const double pressure = va_arg(args, double);
+ normalizedpressure = (pressure * 1024) / pen->max_pressure;
+ WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_ROTATION) != 0)
+ {
+ rotation = va_arg(args, unsigned);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTX) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTY) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT;
+ }
+ va_end(args);
+
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ // Ensure that only one button is pressed
+ if (pen->pressed)
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED));
+ else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ pen->flags |= FREERDP_PEN_BARREL_PRESSED;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ if (!pen->pressed ||
+ ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED)))
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED));
+ else
+ pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED;
+ }
+
+ flags |= pen->flags;
+ if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED;
+ if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED;
+
+ pen->last_x = x;
+ pen->last_y = y;
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ WLog_DBG(TAG, "Pen press %" PRId32, deviceid);
+ pen->hovering = FALSE;
+ pen->pressed = TRUE;
+
+ WINPR_ASSERT(rdpei->PenBegin);
+ const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_MOTION) != 0)
+ {
+ UINT rc = ERROR_INTERNAL_ERROR;
+ if (pen->pressed)
+ {
+ WLog_DBG(TAG, "Pen update %" PRId32, deviceid);
+
+ // TODO: what if no rotation is supported but tilt is?
+ WINPR_ASSERT(rdpei->PenUpdate);
+ rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure,
+ rotation, tiltX, tiltY);
+ }
+ else if (pen->hovering)
+ {
+ WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid);
+
+ WINPR_ASSERT(rdpei->PenHoverUpdate);
+ rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid);
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenHoverBegin);
+ rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ WLog_DBG(TAG, "Pen release %" PRId32, deviceid);
+ pen->pressed = FALSE;
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenUpdate);
+ const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ if (rc != CHANNEL_RC_OK)
+ return FALSE;
+ WINPR_ASSERT(rdpei->PenEnd);
+ const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ return re == CHANNEL_RC_OK;
+ }
+
+ WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+}
+
+BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx)
+{
+ WINPR_ASSERT(cctx);
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (pen->hovering)
+ {
+ WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid);
+ pen->hovering = FALSE;
+ rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y);
+ }
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid)
+{
+ WINPR_ASSERT(cctx);
+
+ if (deviceid == 0)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++)
+ {
+ const FreeRDP_PenDevice* pen = &cctx->pens[x];
+ if (pen->deviceid == deviceid)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* ccontext)
+{
+ WINPR_ASSERT(ccontext);
+
+ const rdpSettings* settings = ccontext->context.settings;
+ const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove);
+ const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent);
+ BOOL ainput = false;
+#if defined(CHANNEL_AINPUT_SERVER)
+ ainput = ccontext->ainput != NULL;
+#endif
+
+ return useRelative && (haveRelative || ainput);
+}
diff --git a/client/common/client_cliprdr_file.c b/client/common/client_cliprdr_file.c
new file mode 100644
index 0000000..9b3ee22
--- /dev/null
+++ b/client/common/client_cliprdr_file.c
@@ -0,0 +1,2556 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <armin.novak@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 <stdlib.h>
+#include <errno.h>
+
+#ifdef WITH_FUSE
+#define FUSE_USE_VERSION 30
+#include <fuse_lowlevel.h>
+#endif
+
+#if defined(WITH_FUSE)
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <time.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+#include <winpr/path.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#define MAX_CLIP_DATA_DIR_LEN 10
+#define MAX_CLIPBOARD_FORMATS 255
+#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
+#define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C(11644473600)
+
+#ifdef WITH_DEBUG_CLIPRDR
+#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
+#else
+#define DEBUG_CLIPRDR(log, ...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#if defined(WITH_FUSE)
+typedef enum eFuseLowlevelOperationType
+{
+ FUSE_LL_OPERATION_NONE,
+ FUSE_LL_OPERATION_LOOKUP,
+ FUSE_LL_OPERATION_GETATTR,
+ FUSE_LL_OPERATION_READ,
+} FuseLowlevelOperationType;
+
+typedef struct sCliprdrFuseFile CliprdrFuseFile;
+
+struct sCliprdrFuseFile
+{
+ CliprdrFuseFile* parent;
+ wArrayList* children;
+
+ char* filename;
+ char* filename_with_root;
+ UINT32 list_idx;
+ fuse_ino_t ino;
+
+ BOOL is_directory;
+ BOOL is_readonly;
+
+ BOOL has_size;
+ UINT64 size;
+
+ BOOL has_last_write_time;
+ UINT64 last_write_time_unix;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+};
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ CliprdrFuseFile* clip_data_dir;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} CliprdrFuseClipDataEntry;
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ wArrayList* fuse_files;
+
+ BOOL all_files;
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} FuseFileClearContext;
+
+typedef struct
+{
+ FuseLowlevelOperationType operation_type;
+ CliprdrFuseFile* fuse_file;
+ fuse_req_t fuse_req;
+ UINT32 stream_id;
+} CliprdrFuseRequest;
+
+typedef struct
+{
+ CliprdrFuseFile* parent;
+ char* parent_path;
+} CliprdrFuseFindParentContext;
+#endif
+
+typedef struct
+{
+ char* name;
+ FILE* fp;
+ INT64 size;
+ CliprdrFileContext* context;
+} CliprdrLocalFile;
+
+typedef struct
+{
+ UINT32 lockId;
+ BOOL locked;
+ size_t count;
+ CliprdrLocalFile* files;
+ CliprdrFileContext* context;
+} CliprdrLocalStream;
+
+struct cliprdr_file_context
+{
+#if defined(WITH_FUSE)
+ /* FUSE related**/
+ HANDLE fuse_start_sync;
+ HANDLE fuse_stop_sync;
+ HANDLE fuse_thread;
+ struct fuse_session* fuse_sess;
+#if FUSE_USE_VERSION < 30
+ struct fuse_chan* ch;
+#endif
+
+ wHashTable* inode_table;
+ wHashTable* clip_data_table;
+ wHashTable* request_table;
+
+ CliprdrFuseFile* root_dir;
+ CliprdrFuseClipDataEntry* clip_data_entry_without_id;
+ UINT32 current_clip_data_id;
+
+ fuse_ino_t next_ino;
+ UINT32 next_clip_data_id;
+ UINT32 next_stream_id;
+#endif
+
+ /* File clipping */
+ BOOL file_formats_registered;
+ UINT32 file_capability_flags;
+
+ UINT32 local_lock_id;
+
+ wHashTable* local_streams;
+ wLog* log;
+ void* clipboard;
+ CliprdrClientContext* context;
+ char* path;
+ char* exposed_path;
+ BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+ BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+};
+
+#if defined(WITH_FUSE)
+static void fuse_file_free(void* data)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ if (!fuse_file)
+ return;
+
+ ArrayList_Free(fuse_file->children);
+ free(fuse_file->filename_with_root);
+
+ free(fuse_file);
+}
+
+static CliprdrFuseFile* fuse_file_new(void)
+{
+ CliprdrFuseFile* fuse_file = NULL;
+
+ fuse_file = calloc(1, sizeof(CliprdrFuseFile));
+ if (!fuse_file)
+ return NULL;
+
+ fuse_file->children = ArrayList_New(FALSE);
+ if (!fuse_file->children)
+ {
+ free(fuse_file);
+ return NULL;
+ }
+
+ return fuse_file;
+}
+
+static void clip_data_entry_free(void* data)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = data;
+
+ if (!clip_data_entry)
+ return;
+
+ if (clip_data_entry->has_clip_data_id)
+ {
+ CliprdrFileContext* file_context = clip_data_entry->file_context;
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ file_context->context->ClientUnlockClipboardData(file_context->context,
+ &unlock_clipboard_data);
+ clip_data_entry->has_clip_data_id = FALSE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+ }
+
+ free(clip_data_entry);
+}
+
+static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA)
+ return TRUE;
+
+ return FALSE;
+}
+
+static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
+{
+ UINT32 clip_data_id = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ clip_data_id = file_context->next_clip_data_id;
+ while (clip_data_id == 0 ||
+ HashTable_GetItemValue(file_context->clip_data_table, (void*)(UINT_PTR)clip_data_id))
+ ++clip_data_id;
+
+ file_context->next_clip_data_id = clip_data_id + 1;
+ HashTable_Unlock(file_context->inode_table);
+
+ return clip_data_id;
+}
+
+static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
+ BOOL needs_clip_data_id)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry));
+ if (!clip_data_entry)
+ return NULL;
+
+ clip_data_entry->file_context = file_context;
+ clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
+
+ if (!needs_clip_data_id)
+ return clip_data_entry;
+
+ lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
+ lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
+ {
+ HashTable_Lock(file_context->inode_table);
+ clip_data_entry_free(clip_data_entry);
+ HashTable_Unlock(file_context->inode_table);
+ return NULL;
+ }
+ clip_data_entry->has_clip_data_id = TRUE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+
+ return clip_data_entry;
+}
+
+static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
+ BOOL has_clip_data_id, BOOL clip_data_id)
+{
+ if (all_files)
+ return TRUE;
+
+ if (fuse_file->ino == FUSE_ROOT_ID)
+ return FALSE;
+ if (!fuse_file->has_clip_data_id && !has_clip_data_id)
+ return TRUE;
+ if (fuse_file->has_clip_data_id && has_clip_data_id && fuse_file->clip_data_id == clip_data_id)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg)
+{
+ CliprdrFuseRequest* fuse_request = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+ CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
+
+ WINPR_ASSERT(file_context);
+
+ if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ return TRUE;
+
+ DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ HashTable_Remove(file_context->request_table, key);
+ free(fuse_request);
+
+ return TRUE;
+}
+
+static BOOL maybe_steal_inode(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+
+ WINPR_ASSERT(file_context);
+
+ if (should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ {
+ if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to append FUSE file to list for deletion");
+
+ HashTable_Remove(file_context->inode_table, key);
+ }
+
+ return TRUE;
+}
+
+static BOOL notify_delete_child(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* child = data;
+
+ WINPR_ASSERT(child);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ WINPR_ASSERT(file_context->fuse_sess);
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
+ strlen(child->filename));
+
+ return TRUE;
+}
+
+static BOOL invalidate_inode(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ WINPR_ASSERT(fuse_file);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->fuse_sess);
+
+ ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
+
+ DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino,
+ fuse_file->filename);
+ fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
+ WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino);
+
+ return TRUE;
+}
+
+static void clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ FuseFileClearContext clear_context = { 0 };
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ root_dir = file_context->root_dir;
+ WINPR_ASSERT(root_dir);
+
+ clear_context.file_context = file_context;
+ clear_context.fuse_files = ArrayList_New(FALSE);
+ WINPR_ASSERT(clear_context.fuse_files);
+
+ wObject* aobj = ArrayList_Object(clear_context.fuse_files);
+ WINPR_ASSERT(aobj);
+ aobj->fnObjectFree = fuse_file_free;
+
+ if (clip_data_entry)
+ {
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ clip_data_entry->clip_data_dir = NULL;
+
+ WINPR_ASSERT(clip_data_dir);
+
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+
+ clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
+ clear_context.clip_data_id = clip_data_dir->clip_data_id;
+ }
+ clear_context.all_files = all_selections;
+
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s",
+ all_selections ? "s" : "");
+
+ HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context);
+ HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (file_context->fuse_sess)
+ {
+ /*
+ * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a
+ * FUSE request (e.g. read()), then FUSE would block in read(), since the
+ * mutex of the inode_table would still be locked, if we wouldn't unlock it
+ * here.
+ * So, to avoid a deadlock here, unlock the mutex and reply all incoming
+ * operations with -ENOENT until the invalidation process is complete.
+ */
+ ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context);
+ if (clip_data_dir)
+ {
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, file_context->root_dir->ino,
+ clip_data_dir->ino, clip_data_dir->filename,
+ strlen(clip_data_dir->filename));
+ }
+ }
+ ArrayList_Free(clear_context.fuse_files);
+
+ HashTable_Lock(file_context->inode_table);
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : "");
+}
+
+static void clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ WINPR_ASSERT(clip_data_entry);
+
+ if (!clip_data_entry->clip_data_dir)
+ return;
+
+ clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
+}
+
+static void clear_no_cdi_entry(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (!file_context->clip_data_entry_without_id)
+ return;
+
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_entry_selection(file_context->clip_data_entry_without_id);
+
+ clip_data_entry_free(file_context->clip_data_entry_without_id);
+ file_context->clip_data_entry_without_id = NULL;
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static BOOL clear_clip_data_entries(const void* key, void* value, void* arg)
+{
+ clear_entry_selection(value);
+
+ return TRUE;
+}
+
+static void clear_cdi_entries(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = clip_data_entry_new(file_context, TRUE);
+ if (!clip_data_entry)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (!HashTable_Insert(file_context->clip_data_table,
+ (void*)(UINT_PTR)clip_data_entry->clip_data_id, clip_data_entry))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry");
+ clip_data_entry_free(clip_data_entry);
+ return ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ // HashTable_Insert owns clip_data_entry
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ file_context->current_clip_data_id = clip_data_entry->clip_data_id;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(!file_context->clip_data_entry_without_id);
+
+ file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
+ if (!file_context->clip_data_entry_without_id)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+
+ if (does_server_support_clipdata_locking(file_context))
+ return prepare_clip_data_entry_with_id(file_context);
+ else
+ return prepare_clip_data_entry_without_id(file_context);
+#else
+ return CHANNEL_RC_OK;
+#endif
+}
+
+UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+#endif
+
+ return CHANNEL_RC_OK;
+}
+
+static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
+ const char* data, size_t size);
+static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
+static BOOL local_stream_discard(const void* key, void* value, void* arg);
+
+static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
+{
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ va_list ap;
+ va_start(ap, line);
+ WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
+ va_end(ap);
+}
+
+#if defined(WITH_FUSE)
+static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name);
+static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+
+static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
+ .lookup = cliprdr_file_fuse_lookup,
+ .getattr = cliprdr_file_fuse_getattr,
+ .readdir = cliprdr_file_fuse_readdir,
+ .open = cliprdr_file_fuse_open,
+ .read = cliprdr_file_fuse_read,
+ .opendir = cliprdr_file_fuse_opendir,
+};
+
+static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
+{
+ WINPR_ASSERT(file_context);
+
+ return HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)fuse_ino);
+}
+
+static CliprdrFuseFile* get_fuse_file_by_name_from_parent(CliprdrFileContext* file_context,
+ CliprdrFuseFile* parent, const char* name)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ for (size_t i = 0; i < ArrayList_Count(parent->children); ++i)
+ {
+ CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
+
+ WINPR_ASSERT(child);
+
+ if (strcmp(name, child->filename) == 0)
+ return child;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist",
+ name, parent->filename);
+
+ return NULL;
+}
+
+static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
+ CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
+ FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ UINT32 stream_id = file_context->next_stream_id;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = calloc(1, sizeof(CliprdrFuseRequest));
+ if (!fuse_request)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ return NULL;
+ }
+
+ fuse_request->fuse_file = fuse_file;
+ fuse_request->fuse_req = fuse_req;
+ fuse_request->operation_type = operation_type;
+
+ while (stream_id == 0 ||
+ HashTable_GetItemValue(file_context->request_table, (void*)(UINT_PTR)stream_id))
+ ++stream_id;
+ fuse_request->stream_id = stream_id;
+
+ file_context->next_stream_id = stream_id + 1;
+
+ if (!HashTable_Insert(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id,
+ fuse_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ free(fuse_request);
+ return NULL;
+ }
+
+ return fuse_request;
+}
+
+static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_SIZE;
+ file_contents_request.cbRequested = 0x8;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ free(fuse_request);
+ return FALSE;
+ }
+ DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u",
+ fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+}
+
+static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr)
+{
+ memset(attr, 0, sizeof(struct stat));
+
+ if (!fuse_file)
+ return;
+
+ attr->st_ino = fuse_file->ino;
+ if (fuse_file->is_directory)
+ {
+ attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
+ attr->st_nlink = 2;
+ }
+ else
+ {
+ attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
+ attr->st_nlink = 1;
+ attr->st_size = fuse_file->size;
+ }
+ attr->st_uid = getuid();
+ attr->st_gid = getgid();
+ attr->st_atime = attr->st_mtime = attr->st_ctime =
+ (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(NULL));
+}
+
+static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* parent = NULL;
+ CliprdrFuseFile* fuse_file = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name);
+ DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"",
+ parent->filename_with_root, fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ entry.ino = fuse_file->ino;
+ write_file_attributes(fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_entry(fuse_req, &entry);
+}
+
+static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ struct stat attr = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ write_file_attributes(fuse_file, &attr);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_attr(fuse_req, &attr, 1.0);
+}
+
+static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ /* Important for KDE to get file correctly */
+ file_info->direct_io = 1;
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, off_t offset, size_t requested_size)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request =
+ cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_RANGE;
+ file_contents_request.nPositionLow = offset & 0xFFFFFFFF;
+ file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF;
+ file_contents_request.cbRequested = requested_size;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ return FALSE;
+ }
+
+ // file_context->request_table owns fuse_request
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc)
+ DEBUG_CLIPRDR(
+ file_context->log,
+ "Requested file range (%zu Bytes at offset %lu) for file \"%s\" with stream id %u",
+ requested_size, offset, fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+ // NOLINTEND(clang-analyzer-unix.Malloc)
+}
+
+static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ BOOL result = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ if (!fuse_file->has_size || offset > fuse_file->size)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EINVAL);
+ return;
+ }
+
+ size = MIN(size, 8 * 1024 * 1024);
+
+ result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+}
+
+static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ CliprdrFuseFile* child = NULL;
+ struct stat attr = { 0 };
+ size_t written_size = 0;
+ size_t entry_size = 0;
+ char* filename = NULL;
+ char* buf = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %lu",
+ fuse_file->filename_with_root, offset);
+
+ if (offset >= ArrayList_Count(fuse_file->children))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_buf(fuse_req, NULL, 0);
+ return;
+ }
+
+ buf = calloc(max_size, sizeof(char));
+ if (!buf)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOMEM);
+ return;
+ }
+ written_size = 0;
+
+ for (off_t i = offset; i < 2; ++i)
+ {
+ if (i == 0)
+ {
+ write_file_attributes(fuse_file, &attr);
+ filename = ".";
+ }
+ else if (i == 1)
+ {
+ write_file_attributes(fuse_file->parent, &attr);
+ attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
+ attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
+ filename = "..";
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ }
+
+ /**
+ * buf needs to be large enough to hold the entry. If it's not, then the
+ * entry is not filled in but the size of the entry is still returned.
+ */
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+
+ for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
+ {
+ if (i < offset)
+ continue;
+
+ child = ArrayList_GetItem(fuse_file->children, j);
+
+ write_file_attributes(child, &attr);
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ child->filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_buf(fuse_req, buf, written_size);
+ free(buf);
+}
+
+static void fuse_abort(int sig, const char* signame, void* context)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)context;
+
+ if (file)
+ {
+ WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
+ cliprdr_file_session_terminate(file, FALSE);
+ }
+}
+
+static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)arg;
+
+ WINPR_ASSERT(file);
+
+ DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
+
+ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+ fuse_opt_add_arg(&args, file->path);
+ file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
+ sizeof(cliprdr_file_fuse_oper), (void*)file);
+ SetEvent(file->fuse_start_sync);
+
+ if (file->fuse_sess != NULL)
+ {
+ freerdp_add_signal_cleanup_handler(file, fuse_abort);
+ if (0 == fuse_session_mount(file->fuse_sess, file->path))
+ {
+ fuse_session_loop(file->fuse_sess);
+ fuse_session_unmount(file->fuse_sess);
+ }
+ freerdp_del_signal_cleanup_handler(file, fuse_abort);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
+ if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
+ fuse_session_destroy(file->fuse_sess);
+ }
+ fuse_opt_free_args(&args);
+
+ DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
+
+ ExitThread(0);
+ return 0;
+}
+
+static UINT cliprdr_file_context_server_file_contents_response(
+ CliprdrClientContext* cliprdr_context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
+{
+ CliprdrFileContext* file_context = NULL;
+ CliprdrFuseRequest* fuse_request = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(cliprdr_context);
+ WINPR_ASSERT(file_contents_response);
+
+ file_context = cliprdr_context->custom;
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ fuse_request = HashTable_GetItemValue(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+ if (!fuse_request)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ return CHANNEL_RC_OK;
+ }
+ HashTable_Remove(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+
+ if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "FileContentsRequests for file \"%s\" was unsuccessful",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
+ file_contents_response->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "Received invalid file size for file \"%s\" from the client",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+
+ fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData);
+ fuse_request->fuse_file->has_size = TRUE;
+
+ entry.ino = fuse_request->fuse_file->ino;
+ write_file_attributes(fuse_request->fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ }
+ else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ switch (fuse_request->operation_type)
+ {
+ case FUSE_LL_OPERATION_NONE:
+ break;
+ case FUSE_LL_OPERATION_LOOKUP:
+ fuse_reply_entry(fuse_request->fuse_req, &entry);
+ break;
+ case FUSE_LL_OPERATION_GETATTR:
+ fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
+ break;
+ case FUSE_LL_OPERATION_READ:
+ fuse_reply_buf(fuse_request->fuse_req,
+ (const char*)file_contents_response->requestedData,
+ file_contents_response->cbRequested);
+ break;
+ }
+
+ free(fuse_request);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static UINT cliprdr_file_context_send_file_contents_failure(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(fileContentsRequest);
+
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
+ ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
+ fileContentsRequest->clipDataId, fileContentsRequest->streamId,
+ fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
+
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = fileContentsRequest->streamId;
+
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static UINT
+cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* request,
+ const void* data, size_t size)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
+ .requestedData = data,
+ .cbRequested = size,
+ .common.msgFlags = CB_RESPONSE_OK };
+
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
+ response.streamId, response.cbRequested);
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static BOOL dump_streams(const void* key, void* value, void* arg)
+{
+ const UINT32* ukey = key;
+ CliprdrLocalStream* cur = value;
+
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
+ cur->lockId, cur->count, cur->locked);
+ for (size_t x = 0; x < cur->count; x++)
+ {
+ const CliprdrLocalFile* file = &cur->files[x];
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, "file [%" PRIuz "] ",
+ x, file->name, file->size);
+ }
+ return TRUE;
+}
+
+static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
+ UINT32 listIndex)
+{
+ WINPR_ASSERT(file);
+
+ CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (cur)
+ {
+ if (listIndex < cur->count)
+ {
+ CliprdrLocalFile* f = &cur->files[listIndex];
+ return f;
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIu32
+ "] [locked %d]",
+ lockId, listIndex, cur->count, cur->locked);
+ }
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
+ HashTable_Foreach(file->local_streams, dump_streams, file);
+ }
+
+ return NULL;
+}
+
+static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
+{
+ CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
+ if (f)
+ {
+ if (!f->fp)
+ {
+ const char* name = f->name;
+ f->fp = winpr_fopen(name, "rb");
+ }
+ if (!f->fp)
+ {
+ char ebuffer[256] = { 0 };
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[lockID %" PRIu32 ", index %" PRIu32
+ "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
+ lockId, listIndex, f->name, f->size,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return NULL;
+ }
+ }
+
+ return f;
+}
+
+static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
+ UINT64 size)
+{
+ WINPR_ASSERT(file);
+
+ if (res != 0)
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
+ file->name, res);
+ }
+ else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
+ }
+ else
+ {
+ // TODO: we need to keep track of open files to avoid running out of file descriptors
+ // TODO: for the time being just close again.
+ }
+ if (file->fp)
+ fclose(file->fp);
+ file->fp = NULL;
+}
+
+static UINT cliprdr_file_context_server_file_size_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ WINPR_ASSERT(fileContentsRequest);
+
+ if (fileContentsRequest->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
+ fileContentsRequest->cbRequested);
+ }
+
+ HashTable_Lock(file->local_streams);
+
+ UINT res = CHANNEL_RC_OK;
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ const INT64 size = _ftelli64(rfile->fp);
+ rfile->size = size;
+ cliprdr_local_file_try_close(rfile, res, 0, 0);
+
+ res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
+ sizeof(size));
+ }
+ }
+
+ HashTable_Unlock(file->local_streams);
+ return res;
+}
+
+static UINT cliprdr_file_context_server_file_range_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ BYTE* data = NULL;
+
+ WINPR_ASSERT(fileContentsRequest);
+
+ HashTable_Lock(file->local_streams);
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ goto fail;
+
+ if (_fseeki64(rfile->fp, offset, SEEK_SET) < 0)
+ goto fail;
+
+ data = malloc(fileContentsRequest->cbRequested);
+ if (!data)
+ goto fail;
+
+ const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
+ const UINT rc = cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
+ free(data);
+
+ cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
+ HashTable_Unlock(file->local_streams);
+ return rc;
+fail:
+ if (rfile)
+ cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
+ fileContentsRequest->cbRequested);
+ free(data);
+ HashTable_Unlock(file->local_streams);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+}
+
+static void cliprdr_local_stream_free(void* obj);
+
+static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
+{
+ UINT rc = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(file);
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (lock && !stream)
+ {
+ stream = cliprdr_local_stream_new(file, lockId, NULL, 0);
+ if (!HashTable_Insert(file->local_streams, &lockId, stream))
+ {
+ rc = ERROR_INTERNAL_ERROR;
+ cliprdr_local_stream_free(stream);
+ stream = NULL;
+ }
+ file->local_lock_id = lockId;
+ }
+ if (stream)
+ {
+ stream->locked = lock;
+ stream->lockId = lockId;
+ }
+
+ if (!lock)
+ {
+ if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
+ rc = ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, lockClipboardData->clipDataId, TRUE);
+}
+
+static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, unlockClipboardData->clipDataId, FALSE);
+}
+
+static UINT cliprdr_file_context_server_file_contents_request(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ UINT error = NO_ERROR;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ CliprdrFileContext* file = (context->custom);
+ WINPR_ASSERT(file);
+
+ /*
+ * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
+ * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
+ */
+ if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
+ (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
+ error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
+ error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
+
+ if (error)
+ {
+ WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
+ error);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->custom = file;
+ file->context = cliprdr;
+
+ cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
+ cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
+ cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
+#endif
+
+ return TRUE;
+}
+
+#if defined(WITH_FUSE)
+static void clear_all_selections(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_selection(file_context, TRUE, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+#endif
+
+BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ // Clear all data before the channel is closed
+ // the cleanup handlers are dependent on a working channel.
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+#endif
+
+ HashTable_Clear(file->local_streams);
+
+ file->context = NULL;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = NULL;
+#endif
+
+ return TRUE;
+}
+
+static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
+ size_t size)
+{
+
+ BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
+
+ if (hsize < sizeof(hash))
+ return FALSE;
+
+ if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
+ return FALSE;
+
+ const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
+ if (changed)
+ memcpy(ihash, hash, sizeof(hash));
+ return changed;
+}
+
+static BOOL cliprdr_file_server_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->server_data_hash,
+ sizeof(file->server_data_hash), data, size);
+}
+
+static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->client_data_hash,
+ sizeof(file->client_data_hash), data, size);
+}
+
+#if defined(WITH_FUSE)
+static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
+{
+ fuse_ino_t ino = 0;
+
+ WINPR_ASSERT(file_context);
+
+ ino = file_context->next_ino;
+ while (ino == 0 || ino == FUSE_ROOT_ID ||
+ HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)ino))
+ ++ino;
+
+ file_context->next_ino = ino + 1;
+
+ return ino;
+}
+
+static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
+ UINT32 clip_data_id)
+{
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+ size_t path_length = 0;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_dir = fuse_file_new();
+ if (!clip_data_dir)
+ return NULL;
+
+ path_length = 1 + MAX_CLIP_DATA_DIR_LEN + 1;
+
+ clip_data_dir->filename_with_root = calloc(path_length, sizeof(char));
+ if (!clip_data_dir->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ if (has_clip_data_id)
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%u", (unsigned)clip_data_id);
+ else
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%" PRIu64, NO_CLIP_DATA_ID);
+
+ clip_data_dir->filename = strrchr(clip_data_dir->filename_with_root, '/') + 1;
+
+ clip_data_dir->ino = get_next_free_inode(file_context);
+ clip_data_dir->is_directory = TRUE;
+ clip_data_dir->is_readonly = TRUE;
+ clip_data_dir->has_clip_data_id = has_clip_data_id;
+ clip_data_dir->clip_data_id = clip_data_id;
+
+ root_dir = file_context->root_dir;
+ if (!ArrayList_Append(root_dir->children, clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+ clip_data_dir->parent = root_dir;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)clip_data_dir->ino,
+ clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ return clip_data_dir;
+}
+
+static char* get_parent_path(const char* filepath)
+{
+ char* base = NULL;
+ size_t parent_path_length = 0;
+ char* parent_path = NULL;
+
+ base = strrchr(filepath, '/');
+ WINPR_ASSERT(base);
+
+ while (base > filepath && *base == '/')
+ --base;
+
+ parent_path_length = 1 + base - filepath;
+ parent_path = calloc(parent_path_length + 1, sizeof(char));
+ if (!parent_path)
+ return NULL;
+
+ memcpy(parent_path, filepath, parent_path_length);
+
+ return parent_path;
+}
+
+static BOOL is_fuse_file_not_parent(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ CliprdrFuseFindParentContext* find_context = arg;
+
+ if (!fuse_file->is_directory)
+ return TRUE;
+
+ if (strcmp(find_context->parent_path, fuse_file->filename_with_root) == 0)
+ {
+ find_context->parent = fuse_file;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
+{
+ CliprdrFuseFindParentContext find_context = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(path);
+
+ find_context.parent_path = get_parent_path(path);
+ if (!find_context.parent_path)
+ return NULL;
+
+ WINPR_ASSERT(!find_context.parent);
+
+ if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context))
+ {
+ free(find_context.parent_path);
+ return NULL;
+ }
+ WINPR_ASSERT(find_context.parent);
+
+ free(find_context.parent_path);
+
+ return find_context.parent;
+}
+
+static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
+ CliprdrFuseClipDataEntry* clip_data_entry,
+ FILEDESCRIPTORW* files, UINT32 n_files)
+{
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip_data_entry);
+ WINPR_ASSERT(files);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
+
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+ for (UINT32 i = 0; i < n_files; ++i)
+ {
+ FILEDESCRIPTORW* file = &files[i];
+ CliprdrFuseFile* fuse_file = NULL;
+ char* filename = NULL;
+ size_t path_length = 0;
+
+ fuse_file = fuse_file_new();
+ if (!fuse_file)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ filename = ConvertWCharToUtf8Alloc(file->cFileName, NULL);
+ if (!filename)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ for (size_t j = 0; filename[j]; ++j)
+ {
+ if (filename[j] == '\\')
+ filename[j] = '/';
+ }
+
+ path_length = strlen(clip_data_dir->filename_with_root) + 1 + strlen(filename) + 1;
+ fuse_file->filename_with_root = calloc(path_length, sizeof(char));
+ if (!fuse_file->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ free(filename);
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ _snprintf(fuse_file->filename_with_root, path_length, "%s/%s",
+ clip_data_dir->filename_with_root, filename);
+ free(filename);
+
+ fuse_file->filename = strrchr(fuse_file->filename_with_root, '/') + 1;
+
+ fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
+ if (!fuse_file->parent)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ fuse_file->list_idx = i;
+ fuse_file->ino = get_next_free_inode(file_context);
+ fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
+ fuse_file->clip_data_id = clip_data_entry->clip_data_id;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ fuse_file->is_directory = TRUE;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+ fuse_file->is_readonly = TRUE;
+ if (file->dwFlags & FD_FILESIZE)
+ {
+ fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
+ fuse_file->has_size = TRUE;
+ }
+ if (file->dwFlags & FD_WRITESTIME)
+ {
+ UINT64 filetime = 0;
+
+ filetime = file->ftLastWriteTime.dwHighDateTime;
+ filetime <<= 32;
+ filetime += file->ftLastWriteTime.dwLowDateTime;
+
+ fuse_file->last_write_time_unix =
+ filetime / (10 * 1000 * 1000) - WIN32_FILETIME_TO_UNIX_EPOCH;
+ fuse_file->has_last_write_time = TRUE;
+ }
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)fuse_file->ino,
+ fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+ }
+ // NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
+
+ return TRUE;
+}
+
+static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ wClipboardDelegate* delegate = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+ WINPR_ASSERT(clip_data_entry);
+
+ delegate = ClipboardGetDelegate(clip);
+ WINPR_ASSERT(delegate);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ free(file_context->exposed_path);
+ file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
+ if (file_context->exposed_path)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
+ file_context->exposed_path);
+
+ delegate->basePath = file_context->exposed_path;
+
+ return delegate->basePath != NULL;
+}
+#endif
+
+BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
+ const void* data, size_t size)
+{
+#if defined(WITH_FUSE)
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ FILEDESCRIPTORW* files = NULL;
+ UINT32 n_files = 0;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+
+ if (cliprdr_parse_file_list(data, size, &files, &n_files))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
+ return FALSE;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (does_server_support_clipdata_locking(file_context))
+ clip_data_entry = HashTable_GetItemValue(
+ file_context->clip_data_table, (void*)(UINT_PTR)file_context->current_clip_data_id);
+ else
+ clip_data_entry = file_context->clip_data_entry_without_id;
+
+ WINPR_ASSERT(clip_data_entry);
+
+ clear_entry_selection(clip_data_entry);
+ WINPR_ASSERT(!clip_data_entry->clip_data_dir);
+
+ clip_data_entry->clip_data_dir =
+ clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
+ file_context->current_clip_data_id);
+ if (!clip_data_entry->clip_data_dir)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!update_exposed_path(file_context, clip, clip_data_entry))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+void* cliprdr_file_context_get_context(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->clipboard;
+}
+
+void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
+ if (file->fuse_sess)
+ fuse_session_exit(file->fuse_sess);
+
+ if (stop_thread)
+ {
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
+ SetEvent(file->fuse_stop_sync);
+ }
+#endif
+ /* not elegant but works for umounting FUSE
+ fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
+ */
+#if defined(WITH_FUSE)
+ WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
+#endif
+ winpr_PathFileExists(file->path);
+}
+
+void cliprdr_file_context_free(CliprdrFileContext* file)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+
+ if (file->fuse_thread)
+ {
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
+ cliprdr_file_session_terminate(file, TRUE);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
+ WaitForSingleObject(file->fuse_thread, INFINITE);
+ CloseHandle(file->fuse_thread);
+ }
+ if (file->fuse_stop_sync)
+ CloseHandle(file->fuse_stop_sync);
+ if (file->fuse_start_sync)
+ CloseHandle(file->fuse_start_sync);
+
+ HashTable_Free(file->request_table);
+ HashTable_Free(file->clip_data_table);
+ HashTable_Free(file->inode_table);
+#endif
+ HashTable_Free(file->local_streams);
+ winpr_RemoveDirectory(file->path);
+ free(file->path);
+ free(file->exposed_path);
+ free(file);
+}
+
+static BOOL create_base_path(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ char base[64] = { 0 };
+ _snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32, GetCurrentProcessId());
+
+ file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
+ if (!file->path)
+ return FALSE;
+
+ if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void cliprdr_local_file_free(CliprdrLocalFile* file)
+{
+ const CliprdrLocalFile empty = { 0 };
+ if (!file)
+ return;
+ if (file->fp)
+ {
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
+ fclose(file->fp);
+ }
+ free(file->name);
+ *file = empty;
+}
+
+static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
+ const char* path)
+{
+ const CliprdrLocalFile empty = { 0 };
+ WINPR_ASSERT(f);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(path);
+
+ *f = empty;
+ f->context = context;
+ f->name = winpr_str_url_decode(path, strlen(path));
+ if (!f->name)
+ goto fail;
+
+ return TRUE;
+fail:
+ cliprdr_local_file_free(f);
+ return FALSE;
+}
+
+static void cliprdr_local_files_free(CliprdrLocalStream* stream)
+{
+ WINPR_ASSERT(stream);
+
+ for (size_t x = 0; x < stream->count; x++)
+ cliprdr_local_file_free(&stream->files[x]);
+ free(stream->files);
+
+ stream->files = NULL;
+ stream->count = 0;
+}
+
+static void cliprdr_local_stream_free(void* obj)
+{
+ CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
+ if (stream)
+ cliprdr_local_files_free(stream);
+
+ free(stream);
+}
+
+static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
+{
+ CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
+ if (!tmp)
+ return FALSE;
+ stream->files = tmp;
+ CliprdrLocalFile* f = &stream->files[stream->count++];
+
+ return cliprdr_local_file_new(stream->context, f, path);
+}
+
+static BOOL is_directory(const char* path)
+{
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, NULL);
+ if (!wpath)
+ return FALSE;
+
+ HANDLE hFile =
+ CreateFileW(wpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(wpath);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
+ const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
+ CloseHandle(hFile);
+ if (!status)
+ return FALSE;
+
+ return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
+}
+
+static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
+{
+ char* wildcardpath = GetCombinedPath(path, "*");
+ if (!wildcardpath)
+ return FALSE;
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, NULL);
+ free(wildcardpath);
+ if (!wpath)
+ return FALSE;
+
+ WIN32_FIND_DATAW FindFileData = { 0 };
+ HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
+ free(wpath);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* next = NULL;
+
+ WCHAR dotbuffer[6] = { 0 };
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+ do
+ {
+ if (_wcscmp(FindFileData.cFileName, dot) == 0)
+ continue;
+ if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
+ continue;
+
+ char cFileName[MAX_PATH] = { 0 };
+ ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName), cFileName,
+ ARRAYSIZE(cFileName));
+
+ free(next);
+ next = GetCombinedPath(path, cFileName);
+ if (!next)
+ goto fail;
+
+ if (!append_entry(stream, next))
+ goto fail;
+ if (is_directory(next))
+ {
+ if (!add_directory(stream, next))
+ goto fail;
+ }
+ } while (FindNextFileW(hFind, &FindFileData));
+
+ rc = TRUE;
+fail:
+ free(next);
+ FindClose(hFind);
+
+ return rc;
+}
+
+static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(stream);
+ if (size == 0)
+ return TRUE;
+
+ cliprdr_local_files_free(stream);
+
+ stream->files = calloc(size, sizeof(CliprdrLocalFile));
+ if (!stream->files)
+ return FALSE;
+
+ char* copy = strndup(data, size);
+ if (!copy)
+ return FALSE;
+ char* ptr = strtok(copy, "\r\n");
+ while (ptr)
+ {
+ const char* name = ptr;
+ if (strncmp("file:///", ptr, 8) == 0)
+ name = &ptr[7];
+ else if (strncmp("file:/", ptr, 6) == 0)
+ name = &ptr[5];
+
+ if (!append_entry(stream, name))
+ goto fail;
+
+ if (is_directory(name))
+ {
+ const BOOL res = add_directory(stream, name);
+ if (!res)
+ goto fail;
+ }
+ ptr = strtok(NULL, "\r\n");
+ }
+
+ rc = TRUE;
+fail:
+ free(copy);
+ return rc;
+}
+
+CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 lockId,
+ const char* data, size_t size)
+{
+ WINPR_ASSERT(context);
+ CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
+ if (!stream)
+ return NULL;
+
+ stream->context = context;
+ if (!cliprdr_local_stream_update(stream, data, size))
+ goto fail;
+
+ stream->lockId = lockId;
+ return stream;
+
+fail:
+ cliprdr_local_stream_free(stream);
+ return NULL;
+}
+
+static UINT32 UINTPointerHash(const void* id)
+{
+ WINPR_ASSERT(id);
+ return *((const UINT32*)id);
+}
+
+static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
+{
+ if (!pointer1 || !pointer2)
+ return pointer1 == pointer2;
+
+ const UINT32* a = pointer1;
+ const UINT32* b = pointer2;
+ return *a == *b;
+}
+
+static void* UINTPointerClone(const void* other)
+{
+ const UINT32* src = other;
+ if (!src)
+ return NULL;
+
+ UINT32* copy = calloc(1, sizeof(UINT32));
+ if (!copy)
+ return NULL;
+
+ *copy = *src;
+ return copy;
+}
+
+#if defined(WITH_FUSE)
+static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
+{
+ CliprdrFuseFile* root_dir = NULL;
+
+ root_dir = fuse_file_new();
+ if (!root_dir)
+ return NULL;
+
+ root_dir->filename_with_root = calloc(2, sizeof(char));
+ if (!root_dir->filename_with_root)
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ _snprintf(root_dir->filename_with_root, 2, "/");
+ root_dir->filename = root_dir->filename_with_root;
+
+ root_dir->ino = FUSE_ROOT_ID;
+ root_dir->is_directory = TRUE;
+ root_dir->is_readonly = TRUE;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)root_dir->ino, root_dir))
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ return root_dir;
+}
+#endif
+
+CliprdrFileContext* cliprdr_file_context_new(void* context)
+{
+ CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
+ if (!file)
+ return NULL;
+
+ file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
+ file->clipboard = context;
+
+ file->local_streams = HashTable_New(FALSE);
+ if (!file->local_streams)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
+ goto fail;
+
+ wObject* hkobj = HashTable_KeyObject(file->local_streams);
+ WINPR_ASSERT(hkobj);
+ hkobj->fnObjectEquals = UINTPointerCompare;
+ hkobj->fnObjectFree = free;
+ hkobj->fnObjectNew = UINTPointerClone;
+
+ wObject* hobj = HashTable_ValueObject(file->local_streams);
+ WINPR_ASSERT(hobj);
+ hobj->fnObjectFree = cliprdr_local_stream_free;
+
+#if defined(WITH_FUSE)
+ file->inode_table = HashTable_New(FALSE);
+ file->clip_data_table = HashTable_New(FALSE);
+ file->request_table = HashTable_New(FALSE);
+ if (!file->inode_table || !file->clip_data_table || !file->request_table)
+ goto fail;
+
+ wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
+ WINPR_ASSERT(ctobj);
+ ctobj->fnObjectFree = clip_data_entry_free;
+
+ file->root_dir = fuse_file_new_root(file);
+ if (!file->root_dir)
+ goto fail;
+#endif
+
+ if (!create_base_path(file))
+ goto fail;
+
+#if defined(WITH_FUSE)
+ if (!(file->fuse_start_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_stop_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL)))
+ goto fail;
+
+ if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
+#endif
+ return file;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cliprdr_file_context_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL local_stream_discard(const void* key, void* value, void* arg)
+{
+ CliprdrFileContext* file = arg;
+ CliprdrLocalStream* stream = value;
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(stream);
+
+ if (!stream->locked)
+ HashTable_Remove(file->local_streams, key);
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
+
+ HashTable_Lock(file->local_streams);
+ HashTable_Foreach(file->local_streams, local_stream_discard, file);
+ HashTable_Unlock(file->local_streams);
+
+ memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
+ memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
+ size_t size)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(file);
+ if (!cliprdr_file_client_content_changed_and_update(file, data, size))
+ return TRUE;
+
+ if (!cliprdr_file_context_clear(file))
+ return FALSE;
+
+ UINT32 lockId = file->local_lock_id;
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+
+ WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", stream);
+ if (stream)
+ rc = cliprdr_local_stream_update(stream, data, size);
+ else
+ {
+ stream = cliprdr_local_stream_new(file, lockId, data, size);
+ rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
+ if (!rc)
+ cliprdr_local_stream_free(stream);
+ }
+ // HashTable_Insert owns stream
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
+ return 0;
+
+ if (!file->file_formats_registered)
+ return 0;
+
+ return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
+ CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
+}
+
+BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
+{
+ WINPR_ASSERT(file);
+ file->file_formats_registered = available;
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
+{
+ WINPR_ASSERT(file);
+ file->file_capability_flags = flags;
+ return TRUE;
+}
+
+UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->file_capability_flags;
+}
+
+BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
+{
+ WINPR_UNUSED(file);
+
+#if defined(WITH_FUSE)
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
diff --git a/client/common/cmdline.c b/client/common/cmdline.c
new file mode 100644
index 0000000..2ce693b
--- /dev/null
+++ b/client/common/cmdline.c
@@ -0,0 +1,5922 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@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 <ctype.h>
+#include <errno.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/ncrypt.h>
+#include <winpr/environment.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+#include <freerdp/client.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdp2tcp.h>
+#include <freerdp/channels/remdesk.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/utils/proxy_utils.h>
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpdr.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/channels/ainput.h>
+#endif
+
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/echo.h>
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/version.h>
+#include <freerdp/client/utils/smartcard_cli.h>
+
+#include <openssl/tls1.h>
+#include "cmdline.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common.cmdline")
+
+static const char* option_starts_with(const char* what, const char* val);
+static BOOL option_ends_with(const char* str, const char* ext);
+static BOOL option_equals(const char* what, const char* val);
+
+static BOOL freerdp_client_print_codepages(const char* arg)
+{
+ size_t count = 0;
+ DWORD column = 2;
+ const char* filter = NULL;
+ RDP_CODEPAGE* pages = NULL;
+
+ if (arg)
+ {
+ filter = strchr(arg, ',');
+ if (!filter)
+ filter = arg;
+ else
+ filter++;
+ }
+ pages = freerdp_keyboard_get_matching_codepages(column, filter, &count);
+ if (!pages)
+ return TRUE;
+
+ printf("%-10s %-8s %-60s %-36s %-48s\n", "<id>", "<locale>", "<win langid>", "<language>",
+ "<country>");
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_CODEPAGE* page = &pages[x];
+ char buffer[520] = { 0 };
+
+ if (strnlen(page->subLanguageSymbol, ARRAYSIZE(page->subLanguageSymbol)) > 0)
+ _snprintf(buffer, sizeof(buffer), "[%s|%s]", page->primaryLanguageSymbol,
+ page->subLanguageSymbol);
+ else
+ _snprintf(buffer, sizeof(buffer), "[%s]", page->primaryLanguageSymbol);
+ printf("id=0x%04" PRIx16 ": [%-6s] %-60s %-36s %-48s\n", page->id, page->locale, buffer,
+ page->primaryLanguage, page->subLanguage);
+ }
+ freerdp_codepages_free(pages);
+ return TRUE;
+}
+
+static BOOL freerdp_path_valid(const char* path, BOOL* special)
+{
+ const char DynamicDrives[] = "DynamicDrives";
+ BOOL isPath = FALSE;
+ BOOL isSpecial = 0;
+ if (!path)
+ return FALSE;
+
+ isSpecial =
+ (option_equals("*", path) || option_equals(DynamicDrives, path) || option_equals("%", path))
+ ? TRUE
+ : FALSE;
+ if (!isSpecial)
+ isPath = winpr_PathFileExists(path);
+
+ if (special)
+ *special = isSpecial;
+
+ return isSpecial || isPath;
+}
+
+static BOOL freerdp_sanitize_drive_name(char* name, const char* invalid, const char* replacement)
+{
+ if (!name || !invalid || !replacement)
+ return FALSE;
+ if (strlen(invalid) != strlen(replacement))
+ return FALSE;
+
+ while (*invalid != '\0')
+ {
+ const char what = *invalid++;
+ const char with = *replacement++;
+
+ char* cur = name;
+ while ((cur = strchr(cur, what)) != NULL)
+ *cur = with;
+ }
+ return TRUE;
+}
+
+static char* name_from_path(const char* path)
+{
+ const char* name = "NULL";
+ if (path)
+ {
+ if (option_equals("%", path))
+ name = "home";
+ else if (option_equals("*", path))
+ name = "hotplug-all";
+ else if (option_equals("DynamicDrives", path))
+ name = "hotplug";
+ else
+ name = path;
+ }
+ return _strdup(name);
+}
+
+static BOOL freerdp_client_add_drive(rdpSettings* settings, const char* path, const char* name)
+{
+ char* dname = NULL;
+ RDPDR_DEVICE* device = NULL;
+
+ if (name)
+ {
+ BOOL skip = FALSE;
+ if (path)
+ {
+ switch (path[0])
+ {
+ case '*':
+ case '%':
+ skip = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ /* Path was entered as secondary argument, swap */
+ if (!skip && winpr_PathFileExists(name))
+ {
+ if (!winpr_PathFileExists(path) || (!PathIsRelativeA(name) && PathIsRelativeA(path)))
+ {
+ const char* tmp = path;
+ path = name;
+ name = tmp;
+ }
+ }
+ }
+
+ if (name)
+ dname = _strdup(name);
+ else /* We need a name to send to the server. */
+ dname = name_from_path(path);
+
+ if (freerdp_sanitize_drive_name(dname, "\\/", "__"))
+ {
+ const char* args[] = { dname, path };
+ device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args);
+ }
+ free(dname);
+ if (!device)
+ goto fail;
+
+ if (!path)
+ goto fail;
+ else
+ {
+ BOOL isSpecial = FALSE;
+ BOOL isPath = freerdp_path_valid(path, &isSpecial);
+
+ if (!isPath && !isSpecial)
+ goto fail;
+ }
+
+ if (!freerdp_device_collection_add(settings, device))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_device_free(device);
+ return FALSE;
+}
+
+static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
+{
+ long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoi64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max)
+{
+ unsigned long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoui64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+BOOL freerdp_client_print_version(void)
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ return TRUE;
+}
+
+BOOL freerdp_client_print_buildconfig(void)
+{
+ printf("%s", freerdp_get_build_config());
+ return TRUE;
+}
+
+static void freerdp_client_print_scancodes(void)
+{
+ printf("RDP scancodes and their name for use with /kbd:remap\n");
+
+ for (UINT32 x = 0; x < UINT16_MAX; x++)
+ {
+ const char* name = freerdp_keyboard_scancode_name(x);
+ if (name)
+ printf("0x%04" PRIx32 " --> %s\n", x, name);
+ }
+}
+
+static BOOL is_delimiter(char c, const char* delimiters)
+{
+ char d = 0;
+ while ((d = *delimiters++) != '\0')
+ {
+ if (c == d)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const char* get_last(const char* start, size_t len, const char* delimiters)
+{
+ const char* last = NULL;
+ for (size_t x = 0; x < len; x++)
+ {
+ char c = start[x];
+ if (is_delimiter(c, delimiters))
+ last = &start[x];
+ }
+ return last;
+}
+
+static SSIZE_T next_delimiter(const char* text, size_t len, size_t max, const char* delimiters)
+{
+ if (len < max)
+ return -1;
+
+ const char* last = get_last(text, max, delimiters);
+ if (!last)
+ return -1;
+
+ return (SSIZE_T)(last - text);
+}
+
+static SSIZE_T forced_newline_at(const char* text, size_t len, size_t limit,
+ const char* force_newline)
+{
+ char d = 0;
+ while ((d = *force_newline++) != '\0')
+ {
+ const char* tok = strchr(text, d);
+ if (tok)
+ {
+ const size_t offset = tok - text;
+ if ((offset > len) || (offset > limit))
+ continue;
+ return (SSIZE_T)(offset);
+ }
+ }
+ return -1;
+}
+
+static BOOL print_align(size_t start_offset, size_t* current)
+{
+ WINPR_ASSERT(current);
+ if (*current < start_offset)
+ {
+ const int rc = printf("%*c", (int)(start_offset - *current), ' ');
+ if (rc < 0)
+ return FALSE;
+ *current += (size_t)rc;
+ }
+ return TRUE;
+}
+
+static char* print_token(char* text, size_t start_offset, size_t* current, size_t limit,
+ const char* delimiters, const char* force_newline)
+{
+ int rc = 0;
+ const size_t tlen = strnlen(text, limit);
+ size_t len = tlen;
+ const SSIZE_T force_at = forced_newline_at(text, len, limit - *current, force_newline);
+ BOOL isForce = (force_at > 0);
+
+ if (isForce)
+ len = MIN(len, (size_t)force_at);
+
+ if (!print_align(start_offset, current))
+ return NULL;
+
+ const SSIZE_T delim = next_delimiter(text, len, limit - *current, delimiters);
+ const BOOL isDelim = delim > 0;
+ if (isDelim)
+ {
+ len = MIN(len, (size_t)delim + 1);
+ }
+
+ rc = printf("%.*s", (int)len, text);
+ if (rc < 0)
+ return NULL;
+
+ if (isForce || isDelim)
+ {
+ printf("\n");
+ *current = 0;
+
+ const size_t offset = len + (isForce ? 1 : 0);
+ return &text[offset];
+ }
+
+ *current += (size_t)rc;
+
+ if (tlen == (size_t)rc)
+ return NULL;
+ return &text[(size_t)rc];
+}
+
+static size_t print_optionals(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset + 1, &current, limit, "[], ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ return current;
+}
+
+static size_t print_description(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset, &current, limit, " ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ current += (size_t)printf("\n");
+ return current;
+}
+
+static int cmp_cmdline_args(const void* pva, const void* pvb)
+{
+ const COMMAND_LINE_ARGUMENT_A* a = (const COMMAND_LINE_ARGUMENT_A*)pva;
+ const COMMAND_LINE_ARGUMENT_A* b = (const COMMAND_LINE_ARGUMENT_A*)pvb;
+
+ if (!a->Name && !b->Name)
+ return 0;
+ if (!a->Name)
+ return 1;
+ if (!b->Name)
+ return -1;
+ return strcmp(a->Name, b->Name);
+}
+
+static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* parg, size_t count)
+{
+ if (!parg)
+ return;
+
+ qsort(parg, count, sizeof(COMMAND_LINE_ARGUMENT_A), cmp_cmdline_args);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = parg;
+ do
+ {
+ int rc = 0;
+ size_t pos = 0;
+ const size_t description_offset = 30 + 8;
+
+ if (arg->Flags & (COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_FLAG))
+ {
+ if ((arg->Flags & ~COMMAND_LINE_VALUE_BOOL) == 0)
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ else if ((arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0)
+ rc = printf(" [%s|/]%s", arg->Default ? "-" : "+", arg->Name);
+ else
+ {
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ }
+ }
+ else
+ rc = printf(" /%s", arg->Name);
+
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ if (arg->Format)
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)
+ {
+ rc = printf("[:");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ rc = printf("]");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+ else
+ {
+ rc = printf(":");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ }
+
+ if (pos > description_offset)
+ {
+ printf("\n");
+ pos = 0;
+ }
+ }
+ }
+
+ rc = printf("%*c", (int)(description_offset - pos), ' ');
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ rc = printf("%s ", arg->Default ? "Disable" : "Enable");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+
+ print_description(arg->Text, description_offset, pos);
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+}
+
+BOOL freerdp_client_print_command_line_help(int argc, char** argv)
+{
+ return freerdp_client_print_command_line_help_ex(argc, argv, NULL);
+}
+
+static COMMAND_LINE_ARGUMENT_A* create_merged_args(const COMMAND_LINE_ARGUMENT_A* custom,
+ SSIZE_T count, size_t* pcount)
+{
+ WINPR_ASSERT(pcount);
+ if (count < 0)
+ {
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ count = 0;
+ while (cur && cur->Name)
+ {
+ count++;
+ cur++;
+ }
+ }
+
+ COMMAND_LINE_ARGUMENT_A* largs =
+ calloc(count + ARRAYSIZE(global_cmd_args), sizeof(COMMAND_LINE_ARGUMENT_A));
+ *pcount = 0;
+ if (!largs)
+ return NULL;
+
+ size_t lcount = 0;
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+
+ cur = global_cmd_args;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+ *pcount = lcount;
+ return largs;
+}
+
+BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const char* name = "FreeRDP";
+
+ /* allocate a merged copy of implementation defined and default arguments */
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(custom, -1, &lcount);
+ if (!largs)
+ return FALSE;
+
+ if (argc > 0)
+ name = argv[0];
+
+ printf("\n");
+ printf("FreeRDP - A Free Remote Desktop Protocol Implementation\n");
+ printf("See www.freerdp.com for more information\n");
+ printf("\n");
+ printf("Usage: %s [file] [options] [/v:<server>[:port]]\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");
+
+ freerdp_client_print_command_line_args(largs, lcount);
+ free(largs);
+
+ printf("\n");
+ printf("Examples:\n");
+ printf(" %s connection.rdp /p:Pwd123! /f\n", name);
+ printf(" %s /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 "
+ "/v:192.168.1.100\n",
+ name);
+ printf(" %s /u:\\AzureAD\\user@corp.example /p:pwd /v:host\n", name);
+ printf("\n");
+ printf("Clipboard Redirection: +clipboard\n");
+ printf("\n");
+ printf("Drive Redirection: /drive:home,/home/user\n");
+ printf("Smartcard Redirection: /smartcard:<device>\n");
+ printf("Smartcard logon with Kerberos authentication: /smartcard-logon /sec:nla\n");
+
+ printf("Serial Port Redirection: /serial:<name>,<device>,[SerCx2|SerCx|Serial],[permissive]\n");
+ printf("Serial Port Redirection: /serial:COM1,/dev/ttyS0\n");
+ printf("Parallel Port Redirection: /parallel:<name>,<device>\n");
+ printf("Printer Redirection: /printer:<device>,<driver>,[default]\n");
+ printf("TCP redirection: /rdp2tcp:/usr/bin/rdp2tcp\n");
+ printf("\n");
+ printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n");
+ printf("Audio Output Redirection: /sound:sys:alsa\n");
+ printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n");
+ printf("Audio Input Redirection: /microphone:sys:alsa\n");
+ printf("\n");
+ printf("Multimedia Redirection: /video\n");
+#ifdef CHANNEL_URBDRC_CLIENT
+ printf("USB Device Redirection: /usb:id:054c:0268#4669:6e6b,addr:04:0c\n");
+#endif
+ printf("\n");
+ printf("For Gateways, the https_proxy environment variable is respected:\n");
+#ifdef _WIN32
+ printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n");
+#else
+ printf(" export https_proxy=http://proxy.contoso.com:3128/\n");
+#endif
+ printf(" %s /g:rdp.contoso.com ...\n", name);
+ printf("\n");
+ printf("More documentation is coming, in the meantime consult source files\n");
+ printf("\n");
+ return TRUE;
+}
+
+static BOOL option_is_rdp_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".rdp"))
+ return TRUE;
+ if (option_ends_with(option, ".rdpw"))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL option_is_incident_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".msrcIncident"))
+ return TRUE;
+ return FALSE;
+}
+
+static int freerdp_client_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ if (index == 1)
+ {
+ size_t length = 0;
+ rdpSettings* settings = NULL;
+
+ if (argc <= index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ConnectionFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+BOOL freerdp_client_add_device_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(params);
+ WINPR_ASSERT(count > 0);
+
+ if (option_equals(params[0], "drive"))
+ {
+ BOOL rc = 0;
+ if (count < 2)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ if (count < 3)
+ rc = freerdp_client_add_drive(settings, params[1], NULL);
+ else
+ rc = freerdp_client_add_drive(settings, params[2], params[1]);
+
+ return rc;
+ }
+ else if (option_equals(params[0], "printer"))
+ {
+ RDPDR_DEVICE* printer = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ printer = freerdp_device_new(RDPDR_DTYP_PRINT, count - 1, &params[1]);
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "smartcard"))
+ {
+ RDPDR_DEVICE* smartcard = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, count - 1, &params[1]);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "serial"))
+ {
+ RDPDR_DEVICE* serial = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ serial = freerdp_device_new(RDPDR_DTYP_SERIAL, count - 1, &params[1]);
+
+ if (!serial)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, serial))
+ {
+ freerdp_device_free(serial);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "parallel"))
+ {
+ RDPDR_DEVICE* parallel = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ parallel = freerdp_device_new(RDPDR_DTYP_PARALLEL, count - 1, &params[1]);
+
+ if (!parallel)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, parallel))
+ {
+ freerdp_device_free(parallel);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_del_static_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_static_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_static_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_static_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, (const char**)params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+BOOL freerdp_client_del_dynamic_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_dynamic_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_dynamic_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_dynamic_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+static BOOL read_pem_file(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* file)
+{
+ size_t length = 0;
+ char* pem = crypto_read_pem(file, &length);
+ if (!pem || (length == 0))
+ return FALSE;
+
+ BOOL rc = freerdp_settings_set_string_len(settings, id, pem, length);
+ free(pem);
+ return rc;
+}
+
+/** @brief suboption type */
+typedef enum
+{
+ CMDLINE_SUBOPTION_STRING,
+ CMDLINE_SUBOPTION_FILE,
+} CmdLineSubOptionType;
+
+typedef BOOL (*CmdLineSubOptionCb)(const char* value, rdpSettings* settings);
+typedef struct
+{
+ const char* optname;
+ FreeRDP_Settings_Keys_String id;
+ CmdLineSubOptionType opttype;
+ CmdLineSubOptionCb cb;
+} CmdLineSubOptions;
+
+static BOOL parseSubOptions(rdpSettings* settings, const CmdLineSubOptions* opts, size_t count,
+ const char* arg)
+{
+ BOOL found = FALSE;
+
+ for (size_t xx = 0; xx < count; xx++)
+ {
+ const CmdLineSubOptions* opt = &opts[xx];
+
+ if (option_starts_with(opt->optname, arg))
+ {
+ const size_t optlen = strlen(opt->optname);
+ const char* val = &arg[optlen];
+ BOOL status = 0;
+
+ switch (opt->opttype)
+ {
+ case CMDLINE_SUBOPTION_STRING:
+ status = freerdp_settings_set_string(settings, opt->id, val);
+ break;
+ case CMDLINE_SUBOPTION_FILE:
+ status = read_pem_file(settings, opt->id, val);
+ break;
+ default:
+ WLog_ERR(TAG, "invalid subOption type");
+ return FALSE;
+ }
+
+ if (!status)
+ return FALSE;
+
+ if (opt->cb && !opt->cb(val, settings))
+ return FALSE;
+
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ WLog_ERR(TAG, "option %s not handled", arg);
+
+ return found;
+}
+
+static int freerdp_client_command_line_post_filter(void* context, COMMAND_LINE_ARGUMENT_A* arg)
+{
+ rdpSettings* settings = (rdpSettings*)context;
+ BOOL status = TRUE;
+ BOOL enable = arg->Value ? TRUE : FALSE;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "a")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if ((status = freerdp_client_add_device_channel(settings, count, ptr.pc)))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ }
+
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "kerberos")
+ {
+ size_t count = 0;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("kerberos", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "kdc-url:", FreeRDP_KerberosKdcUrl, CMDLINE_SUBOPTION_STRING, NULL },
+ { "start-time:", FreeRDP_KerberosStartTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "lifetime:", FreeRDP_KerberosLifeTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "renewable-lifetime:", FreeRDP_KerberosRenewableLifeTime,
+ CMDLINE_SUBOPTION_STRING, NULL },
+ { "cache:", FreeRDP_KerberosCache, CMDLINE_SUBOPTION_STRING, NULL },
+ { "armor:", FreeRDP_KerberosArmor, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ }
+
+ CommandLineSwitchCase(arg, "vc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "dvc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "drive")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "serial")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "parallel")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "smartcard")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "printer")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "usb")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(URBDRC_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "multitouch")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gestures")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "echo")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportEchoChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ssh-agent")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportSSHAgentChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "disp")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "geometry")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "video")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking,
+ enable)) /* this requires geometry tracking */
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sound")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(RDPSND_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ if (status)
+ {
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ }
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "microphone")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(AUDIN_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ CommandLineSwitchCase(arg, "multimedia")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tsmf", arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#endif
+ CommandLineSwitchCase(arg, "heartbeat")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multitransport")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, enable))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 flags = 0;
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ flags =
+ (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultitransportFlags, flags))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchEnd(arg)
+
+ return status
+ ? 1
+ : -1;
+}
+
+static BOOL freerdp_parse_username_ptr(const char* username, const char** user, size_t* userlen,
+ const char** domain, size_t* domainlen)
+{
+ const char* p = strchr(username, '\\');
+
+ *user = NULL;
+ *userlen = 0;
+
+ *domain = NULL;
+ *domainlen = 0;
+
+ if (p)
+ {
+ const size_t length = (size_t)(p - username);
+ *user = &p[1];
+ *userlen = strlen(*user);
+
+ *domain = username;
+ *domainlen = length;
+ }
+ else if (username)
+ {
+ /* Do not break up the name for '@'; both credSSP and the
+ * ClientInfo PDU expect 'user@corp.net' to be transmitted
+ * as username 'user@corp.net', domain empty (not NULL!).
+ */
+ *user = username;
+ *userlen = strlen(username);
+ }
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL freerdp_parse_username_settings(const char* username, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String userID,
+ FreeRDP_Settings_Keys_String domainID)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_string_len(settings, userID, user, userlen))
+ return FALSE;
+ return freerdp_settings_set_string_len(settings, domainID, domain, domainlen);
+}
+
+BOOL freerdp_parse_username(const char* username, char** puser, char** pdomain)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ *puser = NULL;
+ *pdomain = NULL;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+
+ if (userlen > 0)
+ {
+ *puser = strndup(user, userlen);
+ if (!*puser)
+ return FALSE;
+ }
+
+ if (domainlen > 0)
+ {
+ *pdomain = strndup(domain, domainlen);
+ if (!*pdomain)
+ {
+ free(*puser);
+ *puser = NULL;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port)
+{
+ char* p = NULL;
+ p = strrchr(hostname, ':');
+
+ if (p)
+ {
+ size_t length = (size_t)(p - hostname);
+ LONGLONG val = 0;
+
+ if (!value_to_int(p + 1, &val, 1, UINT16_MAX))
+ return FALSE;
+
+ *host = (char*)calloc(length + 1UL, sizeof(char));
+
+ if (!(*host))
+ return FALSE;
+
+ CopyMemory(*host, hostname, length);
+ (*host)[length] = '\0';
+ *port = (UINT16)val;
+ }
+ else
+ {
+ *host = _strdup(hostname);
+
+ if (!(*host))
+ return FALSE;
+
+ *port = -1;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_apply_connection_type(rdpSettings* settings, UINT32 type)
+{
+ struct network_settings
+ {
+ FreeRDP_Settings_Keys_Bool id;
+ BOOL value[7];
+ };
+ const struct network_settings config[] = {
+ { FreeRDP_DisableWallpaper, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_AllowFontSmoothing, { FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE } },
+ { FreeRDP_AllowDesktopComposition, { FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE } },
+ { FreeRDP_DisableFullWindowDrag, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableMenuAnims, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableThemes, { TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE } },
+ { FreeRDP_NetworkAutoDetect, { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE } }
+ };
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ case CONNECTION_TYPE_SATELLITE:
+ case CONNECTION_TYPE_WAN:
+ case CONNECTION_TYPE_LAN:
+ case CONNECTION_TYPE_AUTODETECT:
+ break;
+ default:
+ WLog_WARN(TAG, "Invalid ConnectionType %" PRIu32 ", aborting", type);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < ARRAYSIZE(config); x++)
+ {
+ const struct network_settings* cur = &config[x];
+ if (!freerdp_settings_set_bool(settings, cur->id, cur->value[type - 1]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 type)
+{
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type))
+ return FALSE;
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_SATELLITE:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_WAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_LAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_AUTODETECT:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ /* Automatically activate GFX and RFX codec support */
+#ifdef WITH_GFX_H264
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE))
+ return FALSE;
+#endif
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT32 freerdp_get_keyboard_layout_for_type(const char* name, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts =
+ freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, &count);
+
+ if (!layouts || (count == 0))
+ return FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ if (option_equals(layout->name, name))
+ {
+ return layout->code;
+ }
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+ return 0;
+}
+
+static UINT32 freerdp_map_keyboard_layout_name_to_id(const char* name)
+{
+ const UINT32 variants[] = { RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, RDP_KEYBOARD_LAYOUT_TYPE_VARIANT,
+ RDP_KEYBOARD_LAYOUT_TYPE_IME };
+
+ for (size_t x = 0; x < ARRAYSIZE(variants); x++)
+ {
+ UINT32 rc = freerdp_get_keyboard_layout_for_type(name, variants[x]);
+ if (rc > 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ size_t length = 0;
+ WINPR_UNUSED(context);
+
+ if (index == 1)
+ {
+ if (argc < index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_windows_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static BOOL freerdp_client_detect_command_line(int argc, char** argv, DWORD* flags)
+{
+ int posix_cli_status = 0;
+ size_t posix_cli_count = 0;
+ int windows_cli_status = 0;
+ size_t windows_cli_count = 0;
+ const BOOL ignoreUnknown = TRUE;
+ windows_cli_status = freerdp_detect_windows_style_command_line_syntax(
+ argc, argv, &windows_cli_count, ignoreUnknown);
+ posix_cli_status =
+ freerdp_detect_posix_style_command_line_syntax(argc, argv, &posix_cli_count, ignoreUnknown);
+
+ /* Default is POSIX syntax */
+ *flags = COMMAND_LINE_SEPARATOR_SPACE;
+ *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ *flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (posix_cli_status <= COMMAND_LINE_STATUS_PRINT)
+ return FALSE;
+
+ /* Check, if this may be windows style syntax... */
+ if ((windows_cli_count && (windows_cli_count >= posix_cli_count)) ||
+ (windows_cli_status <= COMMAND_LINE_STATUS_PRINT))
+ {
+ windows_cli_count = 1;
+ *flags = COMMAND_LINE_SEPARATOR_COLON;
+ *flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ }
+
+ WLog_DBG(TAG, "windows: %d/%" PRIuz " posix: %d/%" PRIuz "", windows_cli_status,
+ windows_cli_count, posix_cli_status, posix_cli_count);
+ if ((posix_cli_count == 0) && (windows_cli_count == 0))
+ {
+ if ((posix_cli_status == COMMAND_LINE_ERROR) && (windows_cli_status == COMMAND_LINE_ERROR))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int freerdp_client_settings_command_line_status_print(rdpSettings* settings, int status, int argc,
+ char** argv)
+{
+ return freerdp_client_settings_command_line_status_print_ex(settings, status, argc, argv, NULL);
+}
+
+static void freerdp_client_print_keyboard_type_list(const char* msg, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts = NULL;
+ layouts = freerdp_keyboard_get_layouts(type, &count);
+
+ printf("\n%s\n", msg);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ printf("0x%08" PRIX32 "\t%s\n", layout->code, layout->name);
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+}
+
+static void freerdp_client_print_keyboard_list(void)
+{
+ freerdp_client_print_keyboard_type_list("Keyboard Layouts", RDP_KEYBOARD_LAYOUT_TYPE_STANDARD);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_VARIANT);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_IME);
+}
+
+static void freerdp_client_print_tune_list(const rdpSettings* settings)
+{
+ SSIZE_T type = 0;
+
+ printf("%s\t%50s\t%s\t%s", "<index>", "<key>", "<type>", "<default value>\n");
+ for (size_t x = 0; x < FreeRDP_Settings_StableAPI_MAX; x++)
+ {
+ const char* name = freerdp_settings_get_name_for_key(x);
+ type = freerdp_settings_get_type_for_key(x);
+
+ switch (type)
+ {
+ case RDP_SETTINGS_TYPE_BOOL:
+ printf("%" PRIuz "\t%50s\tBOOL\t%s\n", x, name,
+ freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)x)
+ ? "TRUE"
+ : "FALSE");
+ break;
+ case RDP_SETTINGS_TYPE_UINT16:
+ printf("%" PRIuz "\t%50s\tUINT16\t%" PRIu16 "\n", x, name,
+ freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT16:
+ printf("%" PRIuz "\t%50s\tINT16\t%" PRId16 "\n", x, name,
+ freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT32:
+ printf("%" PRIuz "\t%50s\tUINT32\t%" PRIu32 "\n", x, name,
+ freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT32:
+ printf("%" PRIuz "\t%50s\tINT32\t%" PRId32 "\n", x, name,
+ freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT64:
+ printf("%" PRIuz "\t%50s\tUINT64\t%" PRIu64 "\n", x, name,
+ freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT64:
+ printf("%" PRIuz "\t%50s\tINT64\t%" PRId64 "\n", x, name,
+ freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)x));
+ break;
+ case RDP_SETTINGS_TYPE_STRING:
+ printf("%" PRIuz "\t%50s\tSTRING\t%s"
+ "\n",
+ x, name,
+ freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)x));
+ break;
+ case RDP_SETTINGS_TYPE_POINTER:
+ printf("%" PRIuz "\t%50s\tPOINTER\t%p"
+ "\n",
+ x, name,
+ freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)x));
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, int status,
+ int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
+ {
+ freerdp_client_print_version();
+ goto out;
+ }
+
+ if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
+ {
+ freerdp_client_print_version();
+ freerdp_client_print_buildconfig();
+ goto out;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT)
+ {
+ CommandLineParseArgumentsA(argc, argv, largs, 0x112, NULL, NULL, NULL);
+
+ arg = CommandLineFindArgumentA(largs, "list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ if (option_equals("tune", arg->Value))
+ freerdp_client_print_tune_list(settings);
+ else if (option_equals("kbd", arg->Value))
+ freerdp_client_print_keyboard_list();
+ else if (option_starts_with("kbd-lang", arg->Value))
+ {
+ const char* val = NULL;
+ if (option_starts_with("kbd-lang:", arg->Value))
+ val = &arg->Value[9];
+ else if (!option_equals("kbd-lang", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (val && strchr(val, ','))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_client_print_codepages(val);
+ }
+ else if (option_equals("kbd-scancode", arg->Value))
+ freerdp_client_print_scancodes();
+ else if (option_equals("monitor", arg->Value))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("smartcard", arg->Value))
+ {
+ BOOL opts = FALSE;
+ if (option_starts_with("smartcard:", arg->Value))
+ opts = TRUE;
+ else if (!option_equals("smartcard", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (opts)
+ {
+ const char* sub = strchr(arg->Value, ':') + 1;
+ const CmdLineSubOptions options[] = {
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING,
+ NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ size_t count = 0;
+
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("smartcard", sub, &count);
+ if (!ptr)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (count < 2)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ if (!parseSubOptions(settings, options, ARRAYSIZE(options), cur))
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ free(ptr);
+ }
+
+ freerdp_smartcard_list(settings);
+ }
+ else
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ arg = CommandLineFindArgumentA(largs, "tune-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /tune-list is deprecated, use /list:tune instead");
+ freerdp_client_print_tune_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-lang-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-lang-list is deprecated, use /list:kbd-lang instead");
+ freerdp_client_print_codepages(arg->Value);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-list is deprecated, use /list:kbd instead");
+ freerdp_client_print_keyboard_list();
+ }
+
+ arg = CommandLineFindArgumentA(largs, "monitor-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /monitor-list is deprecated, use /list:monitor instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "smartcard-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /smartcard-list is deprecated, use /list:smartcard instead");
+ freerdp_smartcard_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-scancode-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG,
+ "Option /kbd-scancode-list is deprecated, use /list:kbd-scancode instead");
+ freerdp_client_print_scancodes();
+ goto out;
+ }
+#endif
+ goto out;
+ }
+ else if (status < 0)
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ goto out;
+ }
+
+out:
+ if (status <= COMMAND_LINE_STATUS_PRINT && status >= COMMAND_LINE_STATUS_PRINT_LAST)
+ return 0;
+ return status;
+}
+
+/**
+ * parses a string value with the format <v1>x<v2>
+ *
+ * @param input input string
+ * @param v1 pointer to output v1
+ * @param v2 pointer to output v2
+ * @return if the parsing was successful
+ */
+static BOOL parseSizeValue(const char* input, unsigned long* v1, unsigned long* v2)
+{
+ const char* xcharpos = NULL;
+ char* endPtr = NULL;
+ unsigned long v = 0;
+ errno = 0;
+ v = strtoul(input, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (v1)
+ *v1 = v;
+
+ xcharpos = strchr(input, 'x');
+
+ if (!xcharpos || xcharpos != endPtr)
+ return FALSE;
+
+ errno = 0;
+ v = strtoul(xcharpos + 1, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (*endPtr != '\0')
+ return FALSE;
+
+ if (v2)
+ *v2 = v;
+
+ return TRUE;
+}
+
+static BOOL prepare_default_settings(rdpSettings* settings, COMMAND_LINE_ARGUMENT_A* args,
+ BOOL rdp_file)
+{
+ const char* arguments[] = { "network", "gfx", "rfx", "bpp" };
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(args);
+
+ if (rdp_file)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(arguments); x++)
+ {
+ const char* arg = arguments[x];
+ const COMMAND_LINE_ARGUMENT_A* p = CommandLineFindArgumentA(args, arg);
+ if (p && (p->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ return FALSE;
+ }
+
+ return freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT);
+}
+
+static BOOL setSmartcardEmulation(const char* value, rdpSettings* settings)
+{
+ return freerdp_settings_set_bool(settings, FreeRDP_SmartcardEmulation, TRUE);
+}
+
+const char* option_starts_with(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ const size_t wlen = strlen(what);
+
+ if (_strnicmp(what, val, wlen) != 0)
+ return NULL;
+ return &val[wlen];
+}
+
+BOOL option_ends_with(const char* str, const char* ext)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(ext);
+ const size_t strLen = strlen(str);
+ const size_t extLen = strlen(ext);
+
+ if (strLen < extLen)
+ return FALSE;
+
+ return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
+}
+
+BOOL option_equals(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ return _stricmp(what, val) == 0;
+}
+
+typedef enum
+{
+ PARSE_ON,
+ PARSE_OFF,
+ PARSE_NONE,
+ PARSE_FAIL
+} PARSE_ON_OFF_RESULT;
+
+static PARSE_ON_OFF_RESULT parse_on_off_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return PARSE_NONE;
+ if (option_equals("on", &sep[1]))
+ return PARSE_ON;
+ if (option_equals("off", &sep[1]))
+ return PARSE_OFF;
+ return PARSE_FAIL;
+}
+
+typedef enum
+{
+ CLIP_DIR_PARSE_ALL,
+ CLIP_DIR_PARSE_OFF,
+ CLIP_DIR_PARSE_LOCAL,
+ CLIP_DIR_PARSE_REMOTE,
+ CLIP_DIR_PARSE_FAIL
+} PARSE_CLIP_DIR_RESULT;
+
+static PARSE_CLIP_DIR_RESULT parse_clip_direciton_to_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return CLIP_DIR_PARSE_FAIL;
+ if (option_equals("all", &sep[1]))
+ return CLIP_DIR_PARSE_ALL;
+ if (option_equals("off", &sep[1]))
+ return CLIP_DIR_PARSE_OFF;
+ if (option_equals("local", &sep[1]))
+ return CLIP_DIR_PARSE_LOCAL;
+ if (option_equals("remote", &sep[1]))
+ return CLIP_DIR_PARSE_REMOTE;
+ return CLIP_DIR_PARSE_FAIL;
+}
+
+static int parse_tls_ciphers(rdpSettings* settings, const char* Value)
+{
+ const char* ciphers = NULL;
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(Value, "netmon"))
+ {
+ ciphers = "ALL:!ECDH:!ADH:!DHE";
+ }
+ else if (option_equals(Value, "ma"))
+ {
+ ciphers = "AES128-SHA";
+ }
+ else
+ {
+ ciphers = Value;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AllowedTlsCiphers, ciphers))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_seclevel(rdpSettings* settings, const char* Value)
+{
+ LONGLONG val = 0;
+
+ if (!value_to_int(Value, &val, 0, 5))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_secrets_file(rdpSettings* settings, const char* Value)
+{
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_enforce(rdpSettings* settings, const char* Value)
+{
+ UINT16 version = TLS1_2_VERSION;
+
+ if (Value)
+ {
+ struct map_t
+ {
+ const char* name;
+ UINT16 version;
+ };
+ const struct map_t map[] = {
+ { "1.0", TLS1_VERSION },
+ { "1.1", TLS1_1_VERSION },
+ { "1.2", TLS1_2_VERSION }
+#if defined(TLS1_3_VERSION)
+ ,
+ { "1.3", TLS1_3_VERSION }
+#endif
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(map); x++)
+ {
+ const struct map_t* cur = &map[x];
+ if (option_equals(cur->name, Value))
+ {
+ version = cur->version;
+ break;
+ }
+ }
+ }
+
+ if (!(freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, version) &&
+ freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, version)))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_cipher_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "tls")
+ {
+ if (option_starts_with("ciphers:", arg->Value))
+ rc = parse_tls_ciphers(settings, &arg->Value[8]);
+ else if (option_starts_with("seclevel:", arg->Value))
+ rc = parse_tls_seclevel(settings, &arg->Value[9]);
+ else if (option_starts_with("secrets-file:", arg->Value))
+ rc = parse_tls_secrets_file(settings, &arg->Value[13]);
+ else if (option_starts_with("enforce:", arg->Value))
+ rc = parse_tls_enforce(settings, &arg->Value[8]);
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "Option /tls-ciphers is deprecated, use /tls:ciphers instead");
+ rc = parse_tls_ciphers(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG, "Option /tls-seclevel is deprecated, use /tls:seclevel instead");
+ rc = parse_tls_seclevel(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "Option /tls-secrets-file is deprecated, use /tls:secrets-file instead");
+ rc = parse_tls_secrets_file(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "Option /enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ rc = parse_tls_enforce(settings, "1.2");
+ }
+#endif
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+
+ return rc;
+}
+
+static int parse_tls_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ COMMAND_LINE_ARGUMENT_A larg = *arg;
+ larg.Value = ptr[x];
+
+ int rc = parse_tls_cipher_options(settings, &larg);
+ if (rc != 0)
+ {
+ free(ptr);
+ return rc;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_gfx_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ BOOL GfxH264 = FALSE;
+ BOOL GfxAVC444 = FALSE;
+ BOOL RemoteFxCodec = FALSE;
+ BOOL GfxProgressive = FALSE;
+ BOOL codecSelected = FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+#ifdef WITH_GFX_H264
+ if (option_starts_with("AVC444", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxAVC444 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("AVC420", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxH264 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else
+#endif
+ if (option_starts_with("RFX", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ RemoteFxCodec = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("progressive", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxProgressive = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("mask:", val))
+ {
+ ULONGLONG v = 0;
+ const char* uv = &val[5];
+ if (!value_to_uint(uv, &v, 0, UINT32_MAX))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GfxCapsFilter, v))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ else if (option_starts_with("small-cache", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("thin-client", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ if ((rc == CHANNEL_RC_OK) && (bval > 0))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ if ((rc == CHANNEL_RC_OK) && codecSelected)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, GfxH264))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, RemoteFxCodec))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, GfxProgressive))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ free(ptr);
+ if (rc != CHANNEL_RC_OK)
+ return rc;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_kbd_layout(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ int rc = 0;
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(value, &ival, 1, UINT32_MAX);
+ if (!isInt)
+ {
+ ival = freerdp_map_keyboard_layout_name_to_id(value);
+
+ if (ival == 0)
+ {
+ WLog_ERR(TAG, "Could not identify keyboard layout: %s", value);
+ WLog_ERR(TAG, "Use /list:kbd to list available layouts");
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ return rc;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+static int parse_codec_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE);
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+}
+#endif
+
+static BOOL check_kbd_remap_valid(const char* token)
+{
+ DWORD key = 0;
+ DWORD value = 0;
+
+ WINPR_ASSERT(token);
+ /* The remapping is only allowed for scancodes, so maximum is 999=999 */
+ if (strlen(token) > 10)
+ return FALSE;
+
+ int rc = sscanf(token, "%" PRIu32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIx32 "", &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIu32 "=%" PRIx32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ {
+ WLog_WARN(TAG, "/kbd:remap invalid entry '%s'", token);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int parse_host_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_settings_set_string(settings, FreeRDP_ServerHostname, NULL);
+ char* p = strchr(arg->Value, '[');
+
+ /* ipv4 */
+ if (!p)
+ {
+ const char scheme[] = "://";
+ const char* val = strstr(arg->Value, scheme);
+ if (val)
+ val += strnlen(scheme, sizeof(scheme));
+ else
+ val = arg->Value;
+ p = strchr(val, ':');
+
+ if (p)
+ {
+ LONGLONG val = 0;
+ size_t length = 0;
+
+ if (!value_to_int(&p[1], &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p - arg->Value);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, arg->Value,
+ length))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ }
+ else /* ipv6 */
+ {
+ size_t length = 0;
+ char* p2 = strchr(arg->Value, ']');
+
+ /* not a valid [] ipv6 addr found */
+ if (!p2)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p2 - p);
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, p + 1, length - 1))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (*(p2 + 1) == ':')
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(&p2[2], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ printf("hostname %s port %" PRIu32 "\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
+ }
+ return 0;
+}
+
+static int parse_redirect_prefer_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char* cur = arg->Value;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, 0))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 value = 0;
+ do
+ {
+ UINT32 mask = 0;
+ char* next = strchr(cur, ',');
+
+ if (next)
+ {
+ *next = '\0';
+ next++;
+ }
+
+ if (option_equals("fqdn", cur))
+ mask = 0x06U;
+ else if (option_equals("ip", cur))
+ mask = 0x05U;
+ else if (option_equals("netbios", cur))
+ mask = 0x03U;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ cur = next;
+ mask = (mask & 0x07);
+ value |= mask << (count * 3);
+ count++;
+ } while (cur != NULL);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, value))
+ return COMMAND_LINE_ERROR;
+
+ if (count > 3)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_prevent_session_lock_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, 180))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, (UINT32)val))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+
+ return 0;
+}
+
+static int parse_vmconnect_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 2179))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ return 0;
+}
+
+static int parse_size_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int status = 0;
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ char* p = strchr(arg->Value, 'x');
+
+ if (p)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ char* str = _strdup(arg->Value);
+ if (!str)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ p = strchr(str, '%');
+
+ if (p)
+ {
+ BOOL partial = FALSE;
+
+ status = COMMAND_LINE_ERROR;
+ if (strchr(p, 'w'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (strchr(p, 'h'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (!partial)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ }
+
+ *p = '\0';
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(str, &val, 0, 100))
+ {
+ status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ goto fail;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PercentScreen, (UINT32)val))
+ goto fail;
+ }
+
+ status = 0;
+ }
+
+ fail:
+ free(str);
+ }
+
+ return status;
+}
+
+static int parse_monitors_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ UINT32* MonitorIds = NULL;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (count > 16)
+ count = 16;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(ptr.p);
+ return FALSE;
+ }
+
+ MonitorIds = freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0);
+ for (UINT32 i = 0; i < count; i++)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(ptr.pc[i], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ MonitorIds[i] = (UINT32)val;
+ }
+
+ free(ptr.p);
+ }
+
+ return 0;
+}
+
+static int parse_dynamic_resolution_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ return 0;
+}
+
+static int parse_smart_sizing_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ return 0;
+}
+
+static int parse_bpp_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 32:
+ case 24:
+ case 16:
+ case 15:
+ case 8:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_kbd_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("remap:", val))
+ {
+ /* Append this new occurance to the already existing list */
+ char* now = _strdup(&val[6]);
+ const char* old =
+ freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList);
+
+ /* Basic sanity test. Entries must be like <key>=<value>, e.g. 1=2 */
+ if (!check_kbd_remap_valid(now))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (old)
+ {
+ const size_t olen = strlen(old);
+ const size_t alen = strlen(now);
+ const size_t tlen = olen + alen + 2;
+ char* tmp = calloc(tlen, sizeof(char));
+ if (!tmp)
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else
+ _snprintf(tmp, tlen, "%s,%s", old, now);
+ free(now);
+ now = tmp;
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, now))
+ rc = COMMAND_LINE_ERROR;
+ }
+ free(now);
+ }
+ else if (option_starts_with("layout:", val))
+ {
+ rc = parse_kbd_layout(settings, &val[7]);
+ }
+ else if (option_starts_with("lang:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("type:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("subtype:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[8], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("fn-key:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[7], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("unicode", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("pipe:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardPipeName, &val[5]))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ else if (count == 1)
+ {
+ /* Legacy, allow /kbd:<value> for setting keyboard layout */
+ rc = parse_kbd_layout(settings, val);
+ }
+#endif
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+ return rc;
+}
+
+static int parse_proxy_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* initial value */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ const char* cur = arg->Value;
+
+ if (!cur)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ /* value is [scheme://][user:password@]hostname:port */
+ if (!proxy_parse_uri(settings, cur))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Option http-proxy needs argument.");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_dump_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BOOL failed = FALSE;
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!args || (count != 2))
+ failed = TRUE;
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, args[1]))
+ failed = TRUE;
+ else if (option_equals(args[0], "replay"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, FALSE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE))
+ failed = TRUE;
+ }
+ else if (option_equals(args[0], "record"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, TRUE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, FALSE))
+ failed = TRUE;
+ }
+ else
+ {
+ failed = TRUE;
+ }
+ }
+ free(args);
+ if (failed)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_clipboard_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Value == BoolValueTrue || arg->Value == BoolValueFalse)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ (arg->Value == BoolValueTrue)))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char* usesel = "use-selection:";
+
+ const char* cur = ptr.pc[x];
+ if (option_starts_with(usesel, cur))
+ {
+ const char* val = &cur[strlen(usesel)];
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClipboardUseSelection, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("direction-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("files-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |=
+ CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int parse_audio_mode_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_NONE:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_network_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ UINT32 type = 0;
+
+ if (option_equals(arg->Value, "modem"))
+ type = CONNECTION_TYPE_MODEM;
+ else if (option_equals(arg->Value, "broadband"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "broadband-low"))
+ type = CONNECTION_TYPE_BROADBAND_LOW;
+ else if (option_equals(arg->Value, "broadband-high"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "wan"))
+ type = CONNECTION_TYPE_WAN;
+ else if (option_equals(arg->Value, "lan"))
+ type = CONNECTION_TYPE_LAN;
+ else if ((option_equals(arg->Value, "autodetect")) || (option_equals(arg->Value, "auto")) ||
+ (option_equals(arg->Value, "detect")))
+ {
+ type = CONNECTION_TYPE_AUTODETECT;
+ }
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, 7))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ type = (UINT32)val;
+ }
+
+ if (!freerdp_set_connection_type(settings, type))
+ return COMMAND_LINE_ERROR;
+ return 0;
+}
+
+static int parse_sec_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ FreeRDP_Settings_Keys_Bool singleOptionWithoutOnOff = FreeRDP_BOOL_UNUSED;
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ const BOOL val = bval != PARSE_OFF;
+ FreeRDP_Settings_Keys_Bool id = FreeRDP_BOOL_UNUSED;
+ if (option_starts_with("rdp", cur)) /* Standard RDP */
+ {
+ id = FreeRDP_RdpSecurity;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("tls", cur)) /* TLS */
+ id = FreeRDP_TlsSecurity;
+ else if (option_starts_with("nla", cur)) /* NLA */
+ id = FreeRDP_NlaSecurity;
+ else if (option_starts_with("ext", cur)) /* NLA Extended */
+ id = FreeRDP_ExtSecurity;
+ else if (option_equals("aad", cur)) /* RDSAAD */
+ id = FreeRDP_AadSecurity;
+ else
+ {
+ WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if ((bval == PARSE_NONE) && (count == 1))
+ singleOptionWithoutOnOff = id;
+ if (!freerdp_settings_set_bool(settings, id, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (singleOptionWithoutOnOff != FreeRDP_BOOL_UNUSED)
+ {
+ const FreeRDP_Settings_Keys_Bool options[] = { FreeRDP_AadSecurity,
+ FreeRDP_UseRdpSecurityLayer,
+ FreeRDP_RdpSecurity, FreeRDP_NlaSecurity,
+ FreeRDP_TlsSecurity };
+
+ for (size_t i = 0; i < ARRAYSIZE(options); i++)
+ {
+ if (!freerdp_settings_set_bool(settings, options[i], FALSE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, singleOptionWithoutOnOff, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (singleOptionWithoutOnOff == FreeRDP_RdpSecurity)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_encryption_methods_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ UINT32 EncryptionMethods = 0;
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if (option_equals(ptr.pc[i], "40"))
+ EncryptionMethods |= ENCRYPTION_METHOD_40BIT;
+ else if (option_equals(ptr.pc[i], "56"))
+ EncryptionMethods |= ENCRYPTION_METHOD_56BIT;
+ else if (option_equals(ptr.pc[i], "128"))
+ EncryptionMethods |= ENCRYPTION_METHOD_128BIT;
+ else if (option_equals(ptr.pc[i], "FIPS"))
+ EncryptionMethods |= ENCRYPTION_METHOD_FIPS;
+ else
+ WLog_ERR(TAG, "unknown encryption method '%s'", ptr.pc[i]);
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, EncryptionMethods))
+ return COMMAND_LINE_ERROR;
+ free(ptr.p);
+ }
+ return 0;
+}
+
+static int parse_cert_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char deny[] = "deny";
+ const char ignore[] = "ignore";
+ const char tofu[] = "tofu";
+ const char name[] = "name:";
+ const char fingerprints[] = "fingerprint:";
+
+ const char* cur = ptr.pc[x];
+ if (option_equals(deny, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(ignore, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(tofu, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with(name, cur))
+ {
+ const char* val = &cur[strnlen(name, sizeof(name))];
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else if (option_starts_with(fingerprints, cur))
+ {
+ const char* val = &cur[strnlen(fingerprints, sizeof(fingerprints))];
+ if (!freerdp_settings_append_string(settings, FreeRDP_CertificateAcceptedFingerprints,
+ ",", val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ return rc;
+}
+
+static int parse_mouse_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("mouse", arg->Value, &count);
+ UINT rc = 0;
+ if (ptr)
+ {
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ const BOOL val = bval != PARSE_OFF;
+
+ if (option_starts_with("relative", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("grab", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+
+ return rc;
+}
+
+static int parse_floatbar_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* Defaults are enabled, visible, sticky, fullscreen */
+ UINT32 Floatbar = 0x0017;
+
+ if (arg->Value)
+ {
+ char* start = arg->Value;
+
+ do
+ {
+ char* cur = start;
+ start = strchr(start, ',');
+
+ if (start)
+ {
+ *start = '\0';
+ start = start + 1;
+ }
+
+ /* sticky:[on|off] */
+ if (option_starts_with("sticky:", cur))
+ {
+ Floatbar &= ~0x02u;
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ switch (bval)
+ {
+ case PARSE_ON:
+ case PARSE_NONE:
+ Floatbar |= 0x02u;
+ break;
+ case PARSE_OFF:
+ Floatbar &= ~0x02u;
+ break;
+ case PARSE_FAIL:
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ /* default:[visible|hidden] */
+ else if (option_starts_with("default:", cur))
+ {
+ const char* val = cur + 8;
+ Floatbar &= ~0x04u;
+
+ if (option_equals("visible", val))
+ Floatbar |= 0x04u;
+ else if (option_equals("hidden", val))
+ Floatbar &= ~0x04u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ /* show:[always|fullscreen|window] */
+ else if (option_starts_with("show:", cur))
+ {
+ const char* val = cur + 5;
+ Floatbar &= ~0x30u;
+
+ if (option_equals("always", val))
+ Floatbar |= 0x30u;
+ else if (option_equals("fullscreen", val))
+ Floatbar |= 0x10u;
+ else if (option_equals("window", val))
+ Floatbar |= 0x20u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ } while (start);
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_Floatbar, Floatbar))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_reconnect_cookie_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BYTE* base64 = NULL;
+ size_t length = 0;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ crypto_base64_decode((const char*)(arg->Value), strlen(arg->Value), &base64, &length);
+
+ if ((base64 != NULL) && (length == sizeof(ARC_SC_PRIVATE_PACKET)))
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerAutoReconnectCookie, base64,
+ 1))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value);
+ }
+
+ free(base64);
+ return 0;
+}
+
+static int parse_scale_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_scale_device_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_smartcard_logon_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartcardLogon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("smartcard-logon", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "cert:", FreeRDP_SmartcardCertificate, CMDLINE_SUBOPTION_FILE,
+ setSmartcardEmulation },
+ { "key:", FreeRDP_SmartcardPrivateKey, CMDLINE_SUBOPTION_FILE, setSmartcardEmulation },
+ { "pin:", FreeRDP_Password, CMDLINE_SUBOPTION_STRING, NULL },
+ { "csp:", FreeRDP_CspName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "reader:", FreeRDP_ReaderName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "card:", FreeRDP_CardName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "container:", FreeRDP_ContainerName, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_tune_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tune", arg->Value, &count);
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR;
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ char* sep = strchr(cur, ':');
+ if (!sep)
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ *sep++ = '\0';
+ if (!freerdp_settings_set_value_for_name(settings, cur, sep))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_app_option_program(rdpSettings* settings, const char* cmd)
+{
+ const FreeRDP_Settings_Keys_Bool ids[] = { FreeRDP_RemoteApplicationMode,
+ FreeRDP_RemoteAppLanguageBarSupported,
+ FreeRDP_Workarea, FreeRDP_DisableWallpaper,
+ FreeRDP_DisableFullWindowDrag };
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram, cmd))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ for (size_t y = 0; y < ARRAYSIZE(ids); y++)
+ {
+ if (!freerdp_settings_set_bool(settings, ids[y], TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_app_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ struct app_map
+ {
+ const char* name;
+ FreeRDP_Settings_Keys_String id;
+ int (*fkt)(rdpSettings* settings, const char* value);
+ };
+ const struct app_map amap[] = {
+ { "program:", FreeRDP_RemoteApplicationProgram, parse_app_option_program },
+ { "workdir:", FreeRDP_RemoteApplicationWorkingDir, NULL },
+ { "name:", FreeRDP_RemoteApplicationName, NULL },
+ { "icon:", FreeRDP_RemoteApplicationIcon, NULL },
+ { "cmd:", FreeRDP_RemoteApplicationCmdLine, NULL },
+ { "file:", FreeRDP_RemoteApplicationFile, NULL },
+ { "guid:", FreeRDP_RemoteApplicationGuid, NULL },
+ };
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL handled = FALSE;
+ const char* val = ptr[x];
+
+ for (size_t y = 0; y < ARRAYSIZE(amap); y++)
+ {
+ const struct app_map* cur = &amap[y];
+ if (option_starts_with(cur->name, val))
+ {
+ const char* xval = &val[strlen(cur->name)];
+ if (cur->fkt)
+ rc = cur->fkt(settings, xval);
+ else if (!freerdp_settings_set_string(settings, cur->id, xval))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+
+ handled = TRUE;
+ break;
+ }
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ if (!handled && (count == 1))
+ {
+ /* Legacy path, allow /app:command and /app:||command syntax */
+ rc = parse_app_option_program(settings, val);
+ }
+ else
+#endif
+ if (!handled)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static int parse_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("codec:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ else if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+ }
+ else if (option_starts_with("persist-file:", val))
+ {
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, &val[13]))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ if (option_starts_with("bitmap", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("glyph", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ bval != PARSE_OFF ? GLYPH_SUPPORT_FULL
+ : GLYPH_SUPPORT_NONE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("persist", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("offscreen", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static BOOL parse_gateway_host_option(rdpSettings* settings, const char* host)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(host);
+
+ char* name = NULL;
+ int port = -1;
+ if (!freerdp_parse_hostname(host, &name, &port))
+ return FALSE;
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, name);
+ free(name);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, TRUE))
+ return FALSE;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL parse_gateway_cred_option(rdpSettings* settings, const char* value,
+ FreeRDP_Settings_Keys_String what)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ switch (what)
+ {
+ case FreeRDP_GatewayUsername:
+ if (!freerdp_parse_username_settings(value, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain))
+ return FALSE;
+ break;
+ default:
+ if (!freerdp_settings_set_string(settings, what, value))
+ return FALSE;
+ break;
+ }
+
+ return freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+}
+
+static BOOL parse_gateway_type_option(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "rpc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else
+ {
+ if (option_equals(value, "http"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "auto"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "arm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL parse_gateway_usage_option(rdpSettings* settings, const char* value)
+{
+ UINT32 type = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "none"))
+ type = TSC_PROXY_MODE_NONE_DIRECT;
+ else if (option_equals(value, "direct"))
+ type = TSC_PROXY_MODE_DIRECT;
+ else if (option_equals(value, "detect"))
+ type = TSC_PROXY_MODE_DETECT;
+ else if (option_equals(value, "default"))
+ type = TSC_PROXY_MODE_DEFAULT;
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(value, &val, TSC_PROXY_MODE_NONE_DIRECT, TSC_PROXY_MODE_NONE_DETECT))
+ return FALSE;
+ }
+
+ return freerdp_set_gateway_usage_method(settings, type);
+}
+
+static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return TRUE;
+ WINPR_ASSERT(args);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE))
+ goto fail;
+
+ BOOL allowHttpOpts = FALSE;
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL validOption = FALSE;
+ const char* argval = args[x];
+
+ WINPR_ASSERT(argval);
+
+ const char* gw = option_starts_with("g:", argval);
+ if (gw)
+ {
+ if (!parse_gateway_host_option(settings, gw))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gu = option_starts_with("u:", argval);
+ if (gu)
+ {
+ if (!parse_gateway_cred_option(settings, gu, FreeRDP_GatewayUsername))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gd = option_starts_with("d:", argval);
+ if (gd)
+ {
+ if (!parse_gateway_cred_option(settings, gd, FreeRDP_GatewayDomain))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gp = option_starts_with("p:", argval);
+ if (gp)
+ {
+ if (!parse_gateway_cred_option(settings, gp, FreeRDP_GatewayPassword))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gt = option_starts_with("type:", argval);
+ if (gt)
+ {
+ if (!parse_gateway_type_option(settings, gt))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = freerdp_settings_get_bool(settings, FreeRDP_GatewayHttpTransport);
+ }
+
+ const char* gat = option_starts_with("access-token:", argval);
+ if (gat)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, gat))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* bearer = option_starts_with("bearer:", argval);
+ if (bearer)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, bearer))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gwurl = option_starts_with("url:", argval);
+ if (gwurl)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl))
+ goto fail;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* um = option_starts_with("usage-method:", argval);
+ if (um)
+ {
+ if (!parse_gateway_usage_option(settings, um))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ if (allowHttpOpts)
+ {
+ if (option_equals(argval, "no-websockets"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE))
+ goto fail;
+ validOption = TRUE;
+ }
+ else if (option_equals(argval, "extauth-sspi-ntlm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpExtAuthSspiNtlm, TRUE))
+ goto fail;
+ validOption = TRUE;
+ }
+ }
+
+ if (!validOption)
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ free(args);
+ return rc;
+}
+
+static void fill_credential_string(COMMAND_LINE_ARGUMENT_A* args, const char* value)
+{
+ WINPR_ASSERT(args);
+ WINPR_ASSERT(value);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, value);
+ if (!arg)
+ return;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ FillMemory(arg->Value, strlen(arg->Value), '*');
+}
+
+static void fill_credential_strings(COMMAND_LINE_ARGUMENT_A* args)
+{
+ const char* credentials[] = {
+ "p",
+ "smartcard-logon",
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ "gp",
+ "gat",
+#endif
+ "pth",
+ "reconnect-cookie",
+ "assistance"
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(credentials); x++)
+ {
+ const char* cred = credentials[x];
+ fill_credential_string(args, cred);
+ }
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, "gateway");
+ if (arg && ((arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) != 0))
+ {
+ const char* gwcreds[] = { "p:", "access-token:" };
+ char* tok = strtok(arg->Value, ",");
+ while (tok)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(gwcreds); x++)
+ {
+ const char* opt = gwcreds[x];
+ if (option_starts_with(opt, tok))
+ {
+ char* val = &tok[strlen(opt)];
+ FillMemory(val, strlen(val), '*');
+ }
+ }
+ tok = strtok(NULL, ",");
+ }
+ }
+}
+
+static int freerdp_client_settings_parse_command_line_arguments_int(
+ rdpSettings* settings, int argc, char* argv[], BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* largs, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ char* user = NULL;
+ int status = 0;
+ BOOL ext = FALSE;
+ BOOL assist = FALSE;
+ DWORD flags = 0;
+ BOOL promptForPassword = FALSE;
+ BOOL compatibility = FALSE;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+
+ /* Command line detection fails if only a .rdp or .msrcIncident file
+ * is supplied. Check this case first, only then try to detect
+ * legacy command line syntax. */
+ if (argc > 1)
+ {
+ ext = option_is_rdp_file(argv[1]);
+ assist = option_is_incident_file(argv[1]);
+ }
+
+ if (!ext && !assist)
+ compatibility = freerdp_client_detect_command_line(argc, argv, &flags);
+ else
+ compatibility = freerdp_client_detect_command_line(argc - 1, &argv[1], &flags);
+
+ freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, NULL);
+
+ if (compatibility)
+ {
+ WLog_WARN(TAG, "Unsupported command line syntax!");
+ WLog_WARN(TAG, "FreeRDP 1.0 style syntax was dropped with version 3!");
+ return -1;
+ }
+ else
+ {
+ if (allowUnknown)
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+
+ if (ext)
+ {
+ if (freerdp_client_settings_parse_connection_file(settings, argv[1]))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (assist)
+ {
+ if (freerdp_client_settings_parse_assistance_file(settings, argc, argv) < 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, settings,
+ freerdp_client_command_line_pre_filter,
+ freerdp_client_command_line_post_filter);
+
+ if (status < 0)
+ return status;
+
+ prepare_default_settings(settings, largs, ext);
+ }
+
+ CommandLineFindArgumentA(largs, "v");
+ arg = largs;
+ errno = 0;
+
+ /* Disable unicode input unless requested. */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, FALSE))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ do
+ {
+ BOOL enable = arg->Value ? TRUE : FALSE;
+
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg)
+
+ CommandLineSwitchCase(arg, "v")
+ {
+ const int rc = parse_host_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "spn-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "sspi-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_SspiModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "winscard-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WinSCardModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "redirect-prefer")
+ {
+ const int rc = parse_redirect_prefer_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "credentials-delegation")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableCredentialsDelegation, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "prevent-session-lock")
+ {
+ const int rc = parse_prevent_session_lock_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "vmconnect")
+ {
+ const int rc = parse_vmconnect_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "w")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "h")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "size")
+ {
+ const int rc = parse_size_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "f")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "suppress-output")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multimon")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (option_equals(arg->Value, "force"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ForceMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ CommandLineSwitchCase(arg, "span")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "workarea")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Workarea, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "monitors")
+ {
+ const int rc = parse_monitors_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "t")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "decorations")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Decorations, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dynamic-resolution")
+ {
+ const int rc = parse_dynamic_resolution_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "smart-sizing")
+ {
+ const int rc = parse_smart_sizing_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "bpp")
+ {
+ const int rc = parse_bpp_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "relax-order-checks")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowUnanouncedOrdersFromServer,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "restricted-admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pth")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PasswordHash, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "client-hostname")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd")
+ {
+ int rc = parse_kbd_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "kbd-remap")
+ {
+ WLog_WARN(TAG, "/kbd-remap:<key>=<value>,<key2>=<value2> is deprecated, use "
+ "/kbd:remap:<key>=<value>,remap:<key2>=<value2>,... instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd-lang")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-lang:<value> is deprecated, use /kbd:lang:<value> instead");
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ {
+ WLog_ERR(TAG, "Could not identify keyboard active language %s", arg->Value);
+ WLog_ERR(TAG, "Use /list:kbd-lang to list available layouts");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-type")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-type:<value> is deprecated, use /kbd:type:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-unicode")
+ {
+ WLog_WARN(TAG, "/kbd-unicode is deprecated, use /kbd:unicode[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "kbd-subtype")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-subtype:<value> is deprecated, use /kbd:subtype:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-fn-key")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-fn-key:<value> is deprecated, use /kbd:fn-key:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "u")
+ {
+ WINPR_ASSERT(arg->Value);
+ user = arg->Value;
+ }
+ CommandLineSwitchCase(arg, "d")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "p")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway")
+ {
+ if (!parse_gateway_options(settings, arg))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "proxy")
+ {
+ const int rc = parse_proxy_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "g")
+ {
+ if (!parse_gateway_host_option(settings, arg->Value))
+ return FALSE;
+ }
+ CommandLineSwitchCase(arg, "gu")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayUsername))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gd")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayDomain))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gp")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayPassword))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gt")
+ {
+ if (!parse_gateway_type_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gat")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway-usage-method")
+ {
+ if (!parse_gateway_usage_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#endif
+ CommandLineSwitchCase(arg, "app")
+ {
+ int rc = parse_app_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "load-balance-info")
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, arg->Value,
+ strlen(arg->Value)))
+ return COMMAND_LINE_ERROR;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "app-workdir")
+ {
+ WLog_WARN(
+ TAG,
+ "/app-workdir:<directory> is deprecated, use /app:workdir:<directory> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationWorkingDir,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-name")
+ {
+ WLog_WARN(TAG, "/app-name:<directory> is deprecated, use /app:name:<name> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-icon")
+ {
+ WLog_WARN(TAG, "/app-icon:<filename> is deprecated, use /app:icon:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-cmd")
+ {
+ WLog_WARN(TAG, "/app-cmd:<command> is deprecated, use /app:cmd:<command> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-file")
+ {
+ WLog_WARN(TAG, "/app-file:<filename> is deprecated, use /app:file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-guid")
+ {
+ WLog_WARN(TAG, "/app-guid:<guid> is deprecated, use /app:guid:<guid> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "compression")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "compression-level")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "drives")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dump")
+ {
+ const int rc = parse_dump_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "disable-output")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable);
+ }
+ CommandLineSwitchCase(arg, "home-drive")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectHomeDrive, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ipv6")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PreferIPv6OverIPv4, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "clipboard")
+ {
+ const int rc = parse_clipboard_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "server-name")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_UserSpecifiedServerName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell-dir")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ShellWorkingDirectory, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "audio-mode")
+ {
+ const int rc = parse_audio_mode_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "network")
+ {
+ const int rc = parse_network_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "fonts")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wallpaper")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-drag")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-position")
+ {
+ unsigned long x = 0;
+ unsigned long y = 0;
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+
+ if (!parseSizeValue(arg->Value, &x, &y) || x > UINT16_MAX || y > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "invalid window-position argument");
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosX, (UINT32)x))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosY, (UINT32)y))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "menu-anims")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "themes")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "timeout")
+ {
+ ULONGLONG val = 0;
+ if (!value_to_uint(arg->Value, &val, 1, 600000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TcpAckTimeout, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "aero")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gdi")
+ {
+ if (option_equals(arg->Value, "sw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "hw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gfx")
+ {
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "gfx-thin-client")
+ {
+ WLog_WARN(TAG, "/gfx-thin-client is deprecated, use /gfx:thin-client[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GfxThinClient))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-small-cache")
+ {
+ WLog_WARN(TAG, "/gfx-small-cache is deprecated, use /gfx:small-cache[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-progressive")
+ {
+ WLog_WARN(TAG, "/gfx-progressive is deprecated, use /gfx:progressive[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, !enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#ifdef WITH_GFX_H264
+ CommandLineSwitchCase(arg, "gfx-h264")
+ {
+ WLog_WARN(TAG, "/gfx-h264 is deprecated, use /gfx:avc420 instead");
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+#endif
+ CommandLineSwitchCase(arg, "rfx")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "rfx-mode")
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(arg->Value, "video"))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x00))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "image"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxImageCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x02))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ CommandLineSwitchCase(arg, "frame-ack")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FrameAcknowledge, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "nsc")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, enable);
+ }
+#if defined(WITH_JPEG)
+ CommandLineSwitchCase(arg, "jpeg")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "jpeg-quality")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 100))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "nego")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pcb")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "pcid")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PreconnectionId, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#ifdef _WIN32
+ CommandLineSwitchCase(arg, "connect-child-session")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ "vs-debug") ||
+ !freerdp_settings_set_string(settings, FreeRDP_ServerHostname, "localhost") ||
+ !freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList, "ntlm") ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ConnectChildSession, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel, 0))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "sec")
+ {
+ const int rc = parse_sec_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "encryption-methods")
+ {
+ const int rc = parse_encryption_methods_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "args-from")
+ {
+ WLog_ERR(TAG, "/args-from:%s can not be used in combination with other arguments!",
+ arg->Value);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "from-stdin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CredentialsFromStdin, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ promptForPassword = (option_equals(arg->Value, "force"));
+
+ if (!promptForPassword)
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ 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;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "sec-rdp")
+ {
+ WLog_WARN(TAG, "/sec-rdp is deprecated, use /sec:rdp[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-tls")
+ {
+ WLog_WARN(TAG, "/sec-tls is deprecated, use /sec:tls[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-nla")
+ {
+ WLog_WARN(TAG, "/sec-nla is deprecated, use /sec:nla[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-ext")
+ {
+ WLog_WARN(TAG, "/sec-ext is deprecated, use /sec:ext[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "tls")
+ {
+ int rc = parse_tls_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "/tls-ciphers:<cipher list> is deprecated, use "
+ "/tls:ciphers:<cipher list> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG,
+ "/tls-seclevel:<level> is deprecated, use /tls:sec-level:<level> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "/tls-secrets-file:<filename> is deprecated, use "
+ "/tls:secrets-file:<filename> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "/enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "cert")
+ {
+ const int rc = parse_cert_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "cert-name")
+ {
+ WLog_WARN(TAG, "/cert-name is deprecated, use /cert:name instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "cert-ignore")
+ {
+ WLog_WARN(TAG, "/cert-ignore is deprecated, use /cert:ignore instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-tofu")
+ {
+ WLog_WARN(TAG, "/cert-tofu is deprecated, use /cert:tofu instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-deny")
+ {
+ WLog_WARN(TAG, "/cert-deny is deprecated, use /cert:deny instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "authentication")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Authentication, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "encryption")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-keyboard")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabKeyboard, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-mouse")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse-relative")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse")
+ {
+ const int rc = parse_mouse_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "unmap-buttons")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UnmapButtons, enable);
+ }
+ CommandLineSwitchCase(arg, "toggle-fullscreen")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_ToggleFullscreen, enable);
+ }
+ CommandLineSwitchCase(arg, "force-console-callbacks")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseCommonStdioCallbacks, enable);
+ }
+ CommandLineSwitchCase(arg, "floatbar")
+ {
+ const int rc = parse_floatbar_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "mouse-motion")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseMotion, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "parent-window")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT64_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint64(settings, FreeRDP_ParentWindowId, (UINT64)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "client-build-number")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClientBuild, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "cache")
+ {
+ int rc = parse_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "bitmap-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "persist-cache")
+ {
+ WLog_WARN(TAG, "/persist-cache is deprecated, use /cache:persist[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "persist-cache-file")
+ {
+ WLog_WARN(TAG, "/persist-cache-file:<filename> is deprecated, use "
+ "/cache:persist-file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "offscreen-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ (UINT32)enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "glyph-cache")
+ {
+ WLog_WARN(TAG, "/glyph-cache is deprecated, use /cache:glyph[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ arg->Value ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "codec-cache")
+ {
+ WLog_WARN(TAG,
+ "/codec-cache:<option> is deprecated, use /cache:codec:<option> instead");
+ const int rc = parse_codec_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "max-fast-path-size")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-request-control")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-update")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-channels")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wm-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WmClass, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "play-rfx")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PlayRemoteFxFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PlayRemoteFx, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-only")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AuthenticationOnly, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-pkg-list")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect-max-retries")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 1000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "reconnect-cookie")
+ {
+ const int rc = parse_reconnect_cookie_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "print-reconnect-cookie")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PrintReconnectCookie, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pwidth")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pheight")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "orientation")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint16(settings, FreeRDP_DesktopOrientation, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "old-license")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_OldLicenseBehaviour, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale")
+ {
+ const int rc = parse_scale_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "scale-desktop")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 500))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale-device")
+ {
+ const int rc = parse_scale_device_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "action-script")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ActionScript, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, RDP2TCP_DVC_CHANNEL_NAME)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RDP2TCPArgs, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "fipsmode")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FIPSMode, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "smartcard-logon")
+ {
+ const int rc = parse_smartcard_logon_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tune")
+ {
+ const int rc = parse_tune_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ if (handle_option)
+ {
+ const int rc = handle_option(arg, handle_userdata);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if (user)
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_Domain) && user)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_parse_username_settings(user, settings, FreeRDP_Username, FreeRDP_Domain))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ if (promptForPassword)
+ {
+ freerdp* instance = freerdp_settings_get_pointer_writable(settings, FreeRDP_instance);
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_GatewayPassword))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Gateway Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayPassword, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ freerdp_performance_flags_make(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "port");
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ fill_credential_strings(largs);
+
+ return status;
+}
+
+static void argv_free(int* pargc, char** pargv[])
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+ const int argc = *pargc;
+ char** argv = *pargv;
+ *pargc = 0;
+ *pargv = NULL;
+
+ if (!argv)
+ return;
+ for (int x = 0; x < argc; x++)
+ free(argv[x]);
+ free(argv);
+}
+
+static BOOL argv_append(int* pargc, char** pargv[], const char* what)
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+
+ if (*pargc < 0)
+ return FALSE;
+
+ if (!what)
+ return FALSE;
+
+ int nargc = *pargc + 1;
+ char** tmp = realloc(*pargv, nargc * sizeof(char*));
+ if (!tmp)
+ return FALSE;
+
+ tmp[*pargc] = what;
+ *pargv = tmp;
+ *pargc = nargc;
+ return TRUE;
+}
+
+static BOOL argv_append_dup(int* pargc, char** pargv[], const char* what)
+{
+ char* copy = NULL;
+ if (what)
+ copy = _strdup(what);
+
+ const BOOL rc = argv_append(pargc, pargv, copy);
+ if (!rc)
+ free(copy);
+ return rc;
+}
+
+static BOOL args_from_fp(FILE* fp, int* aargc, char** aargv[], const char* file, const char* cmd)
+{
+ BOOL success = FALSE;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to read command line options from file '%s'", file);
+ return FALSE;
+ }
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto fail;
+ while (!feof(fp))
+ {
+ char* line = NULL;
+ size_t size = 0;
+ INT64 rc = GetLine(&line, &size, fp);
+ if ((rc < 0) || !line)
+ {
+ /* abort if GetLine failed due to reaching EOF */
+ if (feof(fp))
+ break;
+ goto fail;
+ }
+
+ while (rc > 0)
+ {
+ const char cur = (line[rc - 1]);
+ if ((cur == '\n') || (cur == '\r'))
+ {
+ line[rc - 1] = '\0';
+ rc--;
+ }
+ else
+ break;
+ }
+ /* abort on empty lines */
+ if (rc == 0)
+ {
+ free(line);
+ break;
+ }
+ if (!argv_append(aargc, aargv, line))
+ {
+ free(line);
+ goto fail;
+ }
+ }
+
+ success = TRUE;
+fail:
+ fclose(fp);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+static BOOL args_from_env(const char* name, int* aargc, char** aargv[], const char* arg,
+ const char* cmd)
+{
+ BOOL success = FALSE;
+ char* env = NULL;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!name)
+ {
+ WLog_ERR(TAG, "%s - environment variable name empty", arg);
+ goto cleanup;
+ }
+
+ const DWORD size = GetEnvironmentVariableX(name, env, 0);
+ if (size == 0)
+ {
+ WLog_ERR(TAG, "%s - no environment variable '%s'", arg, name);
+ goto cleanup;
+ }
+ env = calloc(size + 1, sizeof(char));
+ if (!env)
+ goto cleanup;
+ const DWORD rc = GetEnvironmentVariableX(name, env, size);
+ if (rc != size - 1)
+ goto cleanup;
+ if (rc == 0)
+ {
+ WLog_ERR(TAG, "%s - environment variable '%s' is empty", arg);
+ goto cleanup;
+ }
+
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto cleanup;
+
+ char* context = NULL;
+ char* tok = strtok_s(env, "\n", &context);
+ while (tok)
+ {
+ if (!argv_append_dup(aargc, aargv, tok))
+ goto cleanup;
+ tok = strtok_s(NULL, "\n", &context);
+ }
+
+ success = TRUE;
+cleanup:
+ free(env);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, int oargc,
+ char* oargv[], BOOL allowUnknown)
+{
+ return freerdp_client_settings_parse_command_line_arguments_ex(
+ settings, oargc, oargv, allowUnknown, NULL, 0, NULL, NULL);
+}
+
+int freerdp_client_settings_parse_command_line_arguments_ex(
+ rdpSettings* settings, int oargc, char** oargv, BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* args, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ int argc = oargc;
+ char** argv = oargv;
+ int res = -1;
+ int aargc = 0;
+ char** aargv = NULL;
+ if ((argc == 2) && option_starts_with("/args-from:", argv[1]))
+ {
+ BOOL success = FALSE;
+ const char* file = strchr(argv[1], ':') + 1;
+ FILE* fp = stdin;
+
+ if (option_starts_with("fd:", file))
+ {
+ ULONGLONG result = 0;
+ const char* val = strchr(file, ':') + 1;
+ if (!value_to_uint(val, &result, 0, INT_MAX))
+ return -1;
+ fp = fdopen((int)result, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else if (strncmp(file, "env:", 4) == 0)
+ {
+ const char* name = strchr(file, ':') + 1;
+ success = args_from_env(name, &aargc, &aargv, oargv[1], oargv[0]);
+ }
+ else if (strcmp(file, "stdin") != 0)
+ {
+ fp = winpr_fopen(file, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+
+ if (!success)
+ return -1;
+ argc = aargc;
+ argv = aargv;
+ }
+
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(args, count, &lcount);
+ if (!largs)
+ goto fail;
+
+ res = freerdp_client_settings_parse_command_line_arguments_int(
+ settings, argc, argv, allowUnknown, largs, lcount, handle_option, handle_userdata);
+fail:
+ free(largs);
+ argv_free(&aargc, &aargv);
+ return res;
+}
+
+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_DBG(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_DBG(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+typedef struct
+{
+ FreeRDP_Settings_Keys_Bool settingId;
+ const char* channelName;
+ void* args;
+} ChannelToLoad;
+
+BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings)
+{
+ ChannelToLoad dynChannels[] = {
+#if defined(CHANNEL_AINPUT_CLIENT)
+ { FreeRDP_BOOL_UNUSED, AINPUT_CHANNEL_NAME, NULL }, /* always loaded */
+#endif
+ { FreeRDP_AudioCapture, AUDIN_CHANNEL_NAME, NULL },
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+#ifdef CHANNEL_RDPEI_CLIENT
+ { FreeRDP_MultiTouchInput, RDPEI_CHANNEL_NAME, NULL },
+#endif
+ { FreeRDP_SupportGraphicsPipeline, RDPGFX_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportEchoChannel, ECHO_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportSSHAgentChannel, "sshagent", NULL },
+ { FreeRDP_SupportDisplayControl, DISP_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL },
+ };
+
+ ChannelToLoad staticChannels[] = {
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+ { FreeRDP_RedirectClipboard, CLIPRDR_SVC_CHANNEL_NAME, NULL },
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ { FreeRDP_EncomspVirtualChannel, ENCOMSP_SVC_CHANNEL_NAME, settings },
+#endif
+ { FreeRDP_RemdeskVirtualChannel, REMDESK_SVC_CHANNEL_NAME, settings },
+ { FreeRDP_RemoteApplicationMode, RAIL_SVC_CHANNEL_NAME, settings }
+ };
+
+ /**
+ * Step 1: first load dynamic channels according to the settings
+ */
+ for (size_t i = 0; i < ARRAYSIZE(dynChannels); i++)
+ {
+ if ((dynChannels[i].settingId == FreeRDP_BOOL_UNUSED) ||
+ freerdp_settings_get_bool(settings, dynChannels[i].settingId))
+ {
+ const char* p[] = { dynChannels[i].channelName };
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+
+ /**
+ * step 2: do various adjustements in the settings, to handle channels and settings dependencies
+ */
+ if ((freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME)) ||
+ (freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+#if defined(CHANNEL_TSMF_CLIENT)
+ || (freerdp_dynamic_channel_collection_find(settings, "tsmf"))
+#endif
+ )
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* rdpsnd requires rdpdr to be registered */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR; /* Both rdpsnd and tsmf require this flag to be set */
+ }
+
+ if (freerdp_dynamic_channel_collection_find(settings, AUDIN_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportHeartbeatPdu) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* these RDP8 features require rdpdr to be registered */
+ }
+
+ const char* DrivesToRedirect = freerdp_settings_get_string(settings, FreeRDP_DrivesToRedirect);
+
+ if (DrivesToRedirect && (strlen(DrivesToRedirect) != 0))
+ {
+ /*
+ * Drives to redirect:
+ *
+ * Very similar to DevicesToRedirect, but can contain a
+ * comma-separated list of drive letters to redirect.
+ */
+ char* value = NULL;
+ char* tok = NULL;
+ char* context = NULL;
+
+ value = _strdup(DrivesToRedirect);
+ if (!value)
+ return FALSE;
+
+ tok = strtok_s(value, ";", &context);
+ if (!tok)
+ {
+ WLog_ERR(TAG, "DrivesToRedirect contains invalid data: '%s'", DrivesToRedirect);
+ free(value);
+ return FALSE;
+ }
+
+ while (tok)
+ {
+ /* Syntax: Comma seperated list of the following entries:
+ * '*' ... Redirect all drives, including hotplug
+ * 'DynamicDrives' ... hotplug
+ * '%' ... user home directory
+ * <label>(<path>) ... One or more paths to redirect.
+ * <path>(<label>) ... One or more paths to redirect.
+ * <path> ... One or more paths to redirect.
+ */
+ /* TODO: Need to properly escape labels and paths */
+ BOOL success = 0;
+ const char* name = NULL;
+ const char* drive = tok;
+ char* subcontext = NULL;
+ char* start = strtok_s(tok, "(", &subcontext);
+ char* end = strtok_s(NULL, ")", &subcontext);
+ if (start && end)
+ name = end;
+
+ if (freerdp_path_valid(name, NULL) && freerdp_path_valid(drive, NULL))
+ {
+ success = freerdp_client_add_drive(settings, name, NULL);
+ if (success)
+ success = freerdp_client_add_drive(settings, drive, NULL);
+ }
+ else
+ success = freerdp_client_add_drive(settings, drive, name);
+
+ if (!success)
+ {
+ free(value);
+ return FALSE;
+ }
+
+ tok = strtok_s(NULL, ";", &context);
+ }
+ free(value);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "media", "*" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* All of these features require rdpdr */
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "home", "%" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DeviceRedirection))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDPDR_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME) &&
+ !freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ RDPDR_DEVICE* smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, 0, NULL);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_PRINT))
+ {
+ RDPDR_DEVICE* printer = freerdp_device_new(RDPDR_DTYP_PRINT, 0, NULL);
+
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_LyncRdpMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ }
+
+ /* step 3: schedule some static channels to load depending on the settings */
+ for (size_t i = 0; i < ARRAYSIZE(staticChannels); i++)
+ {
+ if ((staticChannels[i].settingId == 0) ||
+ freerdp_settings_get_bool(settings, staticChannels[i].settingId))
+ {
+ if (staticChannels[i].args)
+ {
+ if (!freerdp_client_load_static_channel_addin(
+ channels, settings, staticChannels[i].channelName, staticChannels[i].args))
+ return FALSE;
+ }
+ else
+ {
+ const char* p[] = { staticChannels[i].channelName };
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+ }
+
+ char* RDP2TCPArgs = freerdp_settings_get_string_writable(settings, FreeRDP_RDP2TCPArgs);
+ if (RDP2TCPArgs)
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDP2TCP_DVC_CHANNEL_NAME,
+ RDP2TCPArgs))
+ return FALSE;
+ }
+
+ /* step 4: do the static channels loading and init */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount); i++)
+ {
+ ADDIN_ARGV* _args =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_StaticChannelArray, i);
+
+ if (!freerdp_client_load_static_channel_addin(channels, settings, _args->argv[0], _args))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount) > 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDynamicChannels))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, DRDYNVC_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void freerdp_client_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 client 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_client_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 client 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_client_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 client 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/client/common/cmdline.h b/client/common/cmdline.h
new file mode 100644
index 0000000..8186cc6
--- /dev/null
+++ b/client/common/cmdline.h
@@ -0,0 +1,519 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2018 Bernhard Miklautz <bernhard.miklautz@thincast.com>
+ * Copyright 2018 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 CLIENT_COMMON_CMDLINE_H
+#define CLIENT_COMMON_CMDLINE_H
+
+#include <freerdp/config.h>
+
+#include <winpr/cmdline.h>
+
+static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
+ { "a", COMMAND_LINE_VALUE_REQUIRED, "<addin>[,<options>]", NULL, NULL, -1, "addin", "Addin" },
+ { "action-script", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", "~/.config/freerdp/action.sh",
+ NULL, -1, NULL, "Action script" },
+ { "admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "console",
+ "Admin (or console) session" },
+ { "aero", COMMAND_LINE_VALUE_BOOL, NULL, NULL, BoolValueFalse, -1, NULL,
+ "desktop composition" },
+ { "app", COMMAND_LINE_VALUE_REQUIRED,
+ "program:[<path>|<||alias>],cmd:<command>,file:<filename>,guid:<guid>,icon:<filename>,name:<"
+ "name>,workdir:<directory>",
+ NULL, NULL, -1, NULL, "Remote application program" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "app-cmd", COMMAND_LINE_VALUE_REQUIRED, "<parameters>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:cmd:<command>] Remote application command-line parameters" },
+ { "app-file", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:file:<filename>] File to open with remote application" },
+ { "app-guid", COMMAND_LINE_VALUE_REQUIRED, "<app-guid>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:guid:<guid>] Remote application GUID" },
+ { "app-icon", COMMAND_LINE_VALUE_REQUIRED, "<icon-path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:icon:<filename>] Remote application icon for user interface" },
+ { "app-name", COMMAND_LINE_VALUE_REQUIRED, "<app-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:name:<name>] Remote application name for user interface" },
+ { "app-workdir", COMMAND_LINE_VALUE_REQUIRED, "<workspace path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:workdir:<directory>] Remote application workspace path" },
+#endif
+ { "assistance", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "Remote assistance password" },
+ { "auto-request-control", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "Automatically request remote assistance input control" },
+ { "async-channels", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous channels (experimental)" },
+ { "async-update", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous update" },
+ { "audio-mode", COMMAND_LINE_VALUE_REQUIRED, "<mode>", NULL, NULL, -1, NULL,
+ "Audio output mode" },
+ { "auth-only", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Authenticate only" },
+ { "auth-pkg-list", COMMAND_LINE_VALUE_REQUIRED, "<!ntlm,kerberos>", NULL, NULL, -1, NULL,
+ "Authentication package filter (comma-separated list, use '!' to exclude)" },
+ { "authentication", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Authentication (experimental)" },
+ { "auto-reconnect", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Automatic reconnection" },
+ { "auto-reconnect-max-retries", COMMAND_LINE_VALUE_REQUIRED, "<retries>", NULL, NULL, -1, NULL,
+ "Automatic reconnection maximum retries, 0 for unlimited [0,1000]" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "bitmap-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:bitmap[:on|off]] bitmap cache" },
+ { "persist-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist[:on|off]] persistent bitmap cache" },
+ { "persist-cache-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist-file:<filename>] persistent bitmap cache file" },
+#endif
+ { "bpp", COMMAND_LINE_VALUE_REQUIRED, "<depth>", "16", NULL, -1, NULL,
+ "Session bpp (color depth)" },
+ { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1,
+ NULL, "Print the build configuration" },
+ { "cache", COMMAND_LINE_VALUE_REQUIRED,
+ "[bitmap[:on|off],codec[:rfx|nsc],glyph[:on|off],offscreen[:on|off],persist,persist-file:<"
+ "filename>]",
+ NULL, NULL, -1, NULL, "" },
+ { "cert", COMMAND_LINE_VALUE_REQUIRED,
+ "[deny,ignore,name:<name>,tofu,fingerprint:<hash>:<hash as hex>[,fingerprint:<hash>:<another "
+ "hash>]]",
+ NULL, NULL, -1, NULL,
+ "Certificate accept options. Use with care!\n"
+ " * deny ... Automatically abort connection if the certificate does not match, no "
+ "user interaction.\n"
+ " * ignore ... Ignore the certificate checks altogether (overrules all other options)\n"
+ " * name ... Use the alternate <name> instead of the certificate subject to match "
+ "locally stored certificates\n"
+ " * tofu ... Accept certificate unconditionally on first connect and deny on "
+ "subsequent connections if the certificate does not match\n"
+ " * fingerprints ... A list of certificate hashes that are accepted unconditionally for a "
+ "connection" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "cert-deny", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:deny] Automatically abort connection for any certificate that can "
+ "not be validated." },
+ { "cert-ignore", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:ignore] Ignore certificate" },
+ { "cert-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:name:<name>] Certificate name" },
+ { "cert-tofu", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:tofu] Automatically accept certificate on first connect" },
+#endif
+#ifdef _WIN32
+ { "connect-child-session", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, "",
+ "connect to child session (win32)" },
+#endif
+ { "client-build-number", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Client Build Number sent to server (influences smartcard behaviour, see [MS-RDPESC])" },
+ { "client-hostname", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "Client Hostname to send to server" },
+ { "clipboard", COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_OPTIONAL,
+ "[[use-selection:<atom>],[direction-to:[all|local|remote|off]],[files-to[:all|local|remote|"
+ "off]]]",
+ BoolValueTrue, NULL, -1, NULL,
+ "Redirect clipboard:\n"
+ " * use-selection:<atom> ... (X11) Specify which X selection to access. Default is "
+ "CLIPBOARD. PRIMARY is the X-style middle-click selection.\n"
+ " * direction-to:[all|local|remote|off] control enabled clipboard direction\n"
+ " * files-to:[all|local|remote|off] control enabled file clipboard directiont" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "codec-cache", COMMAND_LINE_VALUE_REQUIRED, "[rfx|nsc|jpeg]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:codec:[rfx|nsc|jpeg]] Bitmap codec cache" },
+#endif
+ { "compression", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, "z", "compression" },
+ { "compression-level", COMMAND_LINE_VALUE_REQUIRED, "<level>", NULL, NULL, -1, NULL,
+ "Compression level (0,1,2)" },
+ { "credentials-delegation", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "credentials delegation" },
+ { "d", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL, "Domain" },
+ { "decorations", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Window decorations" },
+ { "disp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Display control" },
+ { "drive", COMMAND_LINE_VALUE_REQUIRED, "<name>,<path>", NULL, NULL, -1, NULL,
+ "Redirect directory <path> as named share <name>. Hotplug support is enabled with "
+ "/drive:hotplug,*. This argument provides the same function as \"Drives that I plug in "
+ "later\" option in MSTSC." },
+ { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect all mount points as shares" },
+ { "dump", COMMAND_LINE_VALUE_REQUIRED, "<record|replay>,<file>", NULL, NULL, -1, NULL,
+ "record or replay dump" },
+ { "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Dynamic virtual channel" },
+ { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Send resolution updates when the window is resized" },
+ { "echo", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "echo", "Echo channel" },
+ { "encryption", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Encryption (experimental)" },
+ { "encryption-methods", COMMAND_LINE_VALUE_REQUIRED, "[40,][56,][128,][FIPS]", NULL, NULL, -1,
+ NULL, "RDP standard security encryption methods" },
+ { "f", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Fullscreen mode (<Ctrl>+<Alt>+<Enter> toggles fullscreen)" },
+ { "fipsmode", COMMAND_LINE_VALUE_BOOL, NULL, NULL, NULL, -1, NULL, "FIPS mode" },
+ { "floatbar", COMMAND_LINE_VALUE_OPTIONAL,
+ "sticky:[on|off],default:[visible|hidden],show:[always|fullscreen|window]", NULL, NULL, -1,
+ NULL,
+ "floatbar is disabled by default (when enabled defaults to sticky in fullscreen mode)" },
+ { "fonts", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "smooth fonts (ClearType)" },
+ { "force-console-callbacks", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use default callbacks (console) for certificate/credential/..." },
+ { "frame-ack", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Number of frame acknowledgement" },
+ { "args-from", COMMAND_LINE_VALUE_REQUIRED, "<file>|stdin|fd:<number>|env:<name>", NULL, NULL,
+ -1, NULL,
+ "Read command line from a file, stdin or file descriptor. This argument can not be combined "
+ "with any other. "
+ "Provide one argument per line." },
+ { "from-stdin", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Read credentials from stdin. With <force> the prompt is done before connection, otherwise "
+ "on server request." },
+ { "gateway", COMMAND_LINE_VALUE_REQUIRED,
+ "g:<gateway>[:<port>],u:<user>,d:<domain>,p:<password>,usage-method:["
+ "direct|detect],access-token:<"
+ "token>,type:[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-"
+ "sspi-ntlm]]|arm,url:<wss://url>,bearer:<oauth2-bearer-token>",
+ NULL, NULL, -1, "gw", "Gateway Hostname" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "g", COMMAND_LINE_VALUE_REQUIRED, "<gateway>[:<port>]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:g:<url>] Gateway Hostname" },
+ { "gateway-usage-method", COMMAND_LINE_VALUE_REQUIRED, "[direct|detect]", NULL, NULL, -1, "gum",
+ "[DEPRECATED, use /gateway:usage-method:<method>] Gateway usage method" },
+ { "gd", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:d:<domain>] Gateway domain" },
+#endif
+ { "gdi", COMMAND_LINE_VALUE_REQUIRED, "sw|hw", NULL, NULL, -1, NULL, "GDI rendering" },
+ { "geometry", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Geometry tracking channel" },
+ { "gestures", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Consume multitouch input locally" },
+#ifdef WITH_GFX_H264
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-"
+ "cache[:on|off],thin-client[:on|off],progressive[:on|"
+ "off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-h264", COMMAND_LINE_VALUE_OPTIONAL, "[[AVC420|AVC444],mask:<value>]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gfx:avc420] RDP8.1 graphics pipeline using H264 codec" },
+#endif
+#else
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-cache["
+ ":on|off],thin-client[:on|off],progressive[:on|off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:progressive] RDP8 graphics pipeline using progressive codec" },
+ { "gfx-small-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:small-cache] RDP8 graphics pipeline using small cache mode" },
+ { "gfx-thin-client", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:thin-client] RDP8 graphics pipeline using thin client mode" },
+ { "glyph-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:glyph[:on|off]] Glyph cache (experimental)" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gp", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:p:<password>] Gateway password" },
+#endif
+ { "grab-keyboard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Grab keyboard" },
+ { "grab-mouse", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Grab mouse" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gt", COMMAND_LINE_VALUE_REQUIRED,
+ "[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-sspi-ntlm]]",
+ NULL, NULL, -1, NULL, "[DEPRECATED, use /gateway:type:<type>] Gateway transport type" },
+ { "gu", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gateway:u:<user>] Gateway username" },
+ { "gat", COMMAND_LINE_VALUE_REQUIRED, "<access token>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:access-token:<token>] Gateway Access Token" },
+#endif
+ { "h", COMMAND_LINE_VALUE_REQUIRED, "<height>", "768", NULL, -1, NULL, "Height" },
+ { "heartbeat", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support heartbeat PDUs" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "Print help" },
+ { "home-drive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect user home as share" },
+ { "ipv6", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "6",
+ "Prefer IPv6 AAA record over IPv4 A record" },
+#if defined(WITH_JPEG)
+ { "jpeg", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "JPEG codec support" },
+ { "jpeg-quality", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", NULL, NULL, -1, NULL,
+ "JPEG quality" },
+#endif
+ { "kbd", COMMAND_LINE_VALUE_REQUIRED,
+ "[layout:[0x<id>|<name>],lang:<0x<id>>,fn-key:<value>,type:<value>,subtype:<value>,unicode[:"
+ "on|off],remap:<key1>=<value1>,remap:<key2>=<value2>,pipe:<filename>]",
+ NULL, NULL, -1, NULL,
+ "Keyboard related options:\n"
+ " * layout: set the keybouard layout announced to the server\n"
+ " * lang: set the keyboard language identifier sent to the server\n"
+ " * fn-key: Function key value\n"
+ " * pipe: Name of a named pipe that can be used to type text into the RDP session\n" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "kbd-lang", COMMAND_LINE_VALUE_REQUIRED, "0x<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use / kbd:lang:<value>] Keyboard active language identifier" },
+ { "kbd-fn-key", COMMAND_LINE_VALUE_REQUIRED, "<value>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:fn-key:<value>] Function key value" },
+ { "kbd-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd] List keyboard layouts" },
+ { "kbd-scancode-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use list:kbd-scancode] List keyboard RDP scancodes" },
+ { "kbd-lang-list", COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd-lang] List keyboard languages" },
+ { "kbd-remap", COMMAND_LINE_VALUE_REQUIRED,
+ "[DEPRECATED, use /kbd:remap] List of <key>=<value>,... pairs to remap scancodes", NULL, NULL,
+ -1, NULL, "Keyboard scancode remapping" },
+ { "kbd-subtype", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:subtype]Keyboard subtype" },
+ { "kbd-type", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:type] Keyboard type" },
+ { "kbd-unicode", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:unicode[:on|off]] Send unicode symbols, e.g. use the local "
+ "keyboard map. ATTENTION: Does not work with every "
+ "RDP server!" },
+#endif
+ { "kerberos", COMMAND_LINE_VALUE_REQUIRED,
+ "[kdc-url:<url>,lifetime:<time>,start-time:<time>,renewable-lifetime:<time>,cache:<path>,"
+ "armor:<path>,pkinit-anchors:<path>,pkcs11-module:<name>]",
+ NULL, NULL, -1, NULL, "Kerberos options" },
+ { "load-balance-info", COMMAND_LINE_VALUE_REQUIRED, "<info-string>", NULL, NULL, -1, NULL,
+ "Load balance info" },
+ { "list", COMMAND_LINE_VALUE_REQUIRED | COMMAND_LINE_PRINT,
+ "[kbd|kbd-scancode|kbd-lang[:<value>]|smartcard[:[pkinit-anchors:<path>][,pkcs11-module:<"
+ "name>]]|"
+ "monitor|tune]",
+ "List available options for subcommand", NULL, -1, NULL,
+ "List available options for subcommand" },
+ { "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" },
+ { "max-fast-path-size", COMMAND_LINE_VALUE_REQUIRED, "<size>", NULL, NULL, -1, NULL,
+ "Specify maximum fast-path update size" },
+ { "max-loop-time", COMMAND_LINE_VALUE_REQUIRED, "<time>", NULL, NULL, -1, NULL,
+ "Specify maximum time in milliseconds spend treating packets" },
+ { "menu-anims", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "menu animations" },
+ { "microphone", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>]", NULL, NULL, -1,
+ "mic", "Audio input (microphone)" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "smartcard-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:smartcard] List smartcard informations" },
+ { "monitor-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:monitor] List detected monitors" },
+#endif
+ { "monitors", COMMAND_LINE_VALUE_REQUIRED, "<id>[,<id>[,...]]", NULL, NULL, -1, NULL,
+ "Select monitors to use" },
+ { "mouse-motion", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Send mouse motion" },
+ { "mouse-relative", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Send mouse motion with relative addressing" },
+ { "mouse", COMMAND_LINE_VALUE_REQUIRED, "[relative:[on|off],grab:[on|off]]", NULL, NULL, -1,
+ NULL,
+ "Mouse related options:\n"
+ " * relative: send relative mouse movements if supported by server\n"
+ " * grab: grab the mouse if within the window" },
+#if defined(CHANNEL_TSMF_CLIENT)
+ { "multimedia", COMMAND_LINE_VALUE_OPTIONAL, "[sys:<sys>,][dev:<dev>,][decoder:<decoder>]",
+ NULL, NULL, -1, "mmr", "[DEPRECATED], use /video] Redirect multimedia (video)" },
+#endif
+ { "multimon", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Use multiple monitors" },
+ { "multitouch", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect multitouch input" },
+ { "multitransport", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support multitransport protocol" },
+ { "nego", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "protocol security negotiation" },
+ { "network", COMMAND_LINE_VALUE_REQUIRED,
+ "[modem|broadband|broadband-low|broadband-high|wan|lan|auto]", NULL, NULL, -1, NULL,
+ "Network connection type" },
+ { "nsc", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "nscodec", "NSCodec support" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "offscreen-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:offscreen[:on|off]] offscreen bitmap cache" },
+#endif
+ { "orientation", COMMAND_LINE_VALUE_REQUIRED, "[0|90|180|270]", NULL, NULL, -1, NULL,
+ "Orientation of display in degrees" },
+ { "old-license", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use the old license workflow (no CAL and hwId set to 0)" },
+ { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL, "Password" },
+ { "parallel", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>]", NULL, NULL, -1, NULL,
+ "Redirect parallel device" },
+ { "parent-window", COMMAND_LINE_VALUE_REQUIRED, "<window-id>", NULL, NULL, -1, NULL,
+ "Parent window id" },
+ { "pcb", COMMAND_LINE_VALUE_REQUIRED, "<blob>", NULL, NULL, -1, NULL, "Preconnection Blob" },
+ { "pcid", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL, "Preconnection Id" },
+ { "pheight", COMMAND_LINE_VALUE_REQUIRED, "<height>", NULL, NULL, -1, NULL,
+ "Physical height of display (in millimeters)" },
+ { "play-rfx", COMMAND_LINE_VALUE_REQUIRED, "<pcap-file>", NULL, NULL, -1, NULL,
+ "Replay rfx pcap file" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" },
+ { "suppress-output", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "suppress output when minimized" },
+ { "print-reconnect-cookie", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Print base64 reconnect cookie after connecting" },
+ { "printer", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<driver>]", NULL, NULL, -1, NULL,
+ "Redirect printer device" },
+ { "proxy", COMMAND_LINE_VALUE_REQUIRED, "[<proto>://][<user>:<password>@]<host>[:<port>]", NULL,
+ NULL, -1, NULL,
+ "Proxy settings: override env. var (see also environment variable below). Protocol "
+ "\"socks5\" should be given explicitly where \"http\" is default." },
+ { "pth", COMMAND_LINE_VALUE_REQUIRED, "<password-hash>", NULL, NULL, -1, "pass-the-hash",
+ "Pass the hash (restricted admin mode)" },
+ { "pwidth", COMMAND_LINE_VALUE_REQUIRED, "<width>", NULL, NULL, -1, NULL,
+ "Physical width of display (in millimeters)" },
+ { "rdp2tcp", COMMAND_LINE_VALUE_REQUIRED, "<executable path[:arg...]>", NULL, NULL, -1, NULL,
+ "TCP redirection" },
+ { "reconnect-cookie", COMMAND_LINE_VALUE_REQUIRED, "<base64-cookie>", NULL, NULL, -1, NULL,
+ "Pass base64 reconnect cookie to the connection" },
+ { "redirect-prefer", COMMAND_LINE_VALUE_REQUIRED, "<FQDN|IP|NETBIOS>,[...]", NULL, NULL, -1,
+ NULL, "Override the preferred redirection order" },
+ { "relax-order-checks", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "relax-order-checks",
+ "Do not check if a RDP order was announced during capability exchange, only use when "
+ "connecting to a buggy server" },
+ { "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin",
+ "Restricted admin mode" },
+ { "rfx", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "RemoteFX" },
+ { "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL,
+ "RemoteFX mode" },
+ { "scale", COMMAND_LINE_VALUE_REQUIRED, "[100|140|180]", "100", NULL, -1, NULL,
+ "Scaling factor of the display" },
+ { "scale-desktop", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", "100", NULL, -1, NULL,
+ "Scaling factor for desktop applications (value between 100 and 500)" },
+ { "scale-device", COMMAND_LINE_VALUE_REQUIRED, "100|140|180", "100", NULL, -1, NULL,
+ "Scaling factor for app store applications" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED,
+ "[rdp[:[on|off]]|tls[:[on|off]]|nla[:[on|off]]|ext[:[on|off]]|aad[:[on|off]]]", NULL, NULL,
+ -1, NULL,
+ "Force specific protocol security. e.g. /sec:nla enables NLA and disables all others, while "
+ "/sec:nla:[on|off] just toggles NLA" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:ext] NLA extended protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:nla] NLA protocol security" },
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:rdp] RDP protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:tls] TLS protocol security" },
+#endif
+ { "serial", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>[,<driver>[,permissive]]]", NULL, NULL,
+ -1, "tty", "Redirect serial device" },
+ { "server-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "User-specified server name to use for validation (TLS, Kerberos)" },
+ { "shell", COMMAND_LINE_VALUE_REQUIRED, "<shell>", NULL, NULL, -1, NULL, "Alternate shell" },
+ { "shell-dir", COMMAND_LINE_VALUE_REQUIRED, "<dir>", NULL, NULL, -1, NULL,
+ "Shell working directory" },
+ { "size", COMMAND_LINE_VALUE_REQUIRED, "<width>x<height> or <percent>%[wh]", "1024x768", NULL,
+ -1, NULL, "Screen size" },
+ { "smart-sizing", COMMAND_LINE_VALUE_OPTIONAL, "<width>x<height>", NULL, NULL, -1, NULL,
+ "Scale remote desktop to window size" },
+ { "smartcard", COMMAND_LINE_VALUE_OPTIONAL, "<str>[,<str>...]", NULL, NULL, -1, NULL,
+ "Redirect the smartcard devices containing any of the <str> in their names." },
+ { "smartcard-logon", COMMAND_LINE_VALUE_OPTIONAL,
+ "[cert:<path>,key:<key>,pin:<pin>,csp:<csp name>,reader:<reader>,card:<card>]", NULL, NULL,
+ -1, NULL, "Activates Smartcard (optional certificate) Logon authentication." },
+ { "sound", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>,][latency:<"
+ "latency>,][quality:<quality>]",
+ NULL, NULL, -1, "audio", "Audio output (sound)" },
+ { "span", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Span screen over multiple monitors" },
+ { "spn-class", COMMAND_LINE_VALUE_REQUIRED, "<service-class>", NULL, NULL, -1, NULL,
+ "SPN authentication service class" },
+ { "ssh-agent", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "ssh-agent",
+ "SSH Agent forwarding channel" },
+ { "sspi-module", COMMAND_LINE_VALUE_REQUIRED, "<SSPI module path>", NULL, NULL, -1, NULL,
+ "SSPI shared library module file path" },
+ { "winscard-module", COMMAND_LINE_VALUE_REQUIRED, "<WinSCard module path>", NULL, NULL, -1,
+ NULL, "WinSCard shared library module file path" },
+ { "disable-output", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Deactivate all graphics decoding in the client session. Useful for load tests with many "
+ "simultaneous connections" },
+ { "t", COMMAND_LINE_VALUE_REQUIRED, "<title>", NULL, NULL, -1, "title", "Window title" },
+ { "themes", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "themes" },
+ { "timeout", COMMAND_LINE_VALUE_REQUIRED, "<time in ms>", "9000", NULL, -1, "timeout",
+ "Advanced setting for high latency links: Adjust connection timeout, use if you encounter "
+ "timeout failures with your connection" },
+ { "tls", COMMAND_LINE_VALUE_REQUIRED, "[ciphers|seclevel|secrets-file|enforce]", NULL, NULL, -1,
+ NULL,
+ "TLS configuration options:"
+ " * ciphers:[netmon|ma|<cipher names>]\n"
+ " * seclevel:<level>, default: 1, range: [0-5] Override the default TLS security level, "
+ "might be required for older target servers\n"
+ " * secrets-file:<filename>\n"
+ " * enforce[:[ssl3|1.0|1.1|1.2|1.3]] Force use of SSL/TLS version for a connection. Some "
+ "servers have a buggy TLS "
+ "version negotiation and might fail without this. Defaults to TLS 1.2 if no argument is "
+ "supplied. Use 1.0 for windows 7" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tls-ciphers", COMMAND_LINE_VALUE_REQUIRED, "[netmon|ma|ciphers]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:ciphers] Allowed TLS ciphers" },
+ { "tls-seclevel", COMMAND_LINE_VALUE_REQUIRED, "<level>", "1", NULL, -1, NULL,
+ "[DEPRECATED, use /tls:seclevel] TLS security level - defaults to 1" },
+ { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:secrets:file] File were TLS secrets will be stored in the "
+ "SSLKEYLOGFILE format" },
+ { "enforce-tlsv1_2", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:enforce:1.2] Force use of TLS1.2 for connection. Some "
+ "servers have a buggy TLS version negotiation and "
+ "might fail without this" },
+#endif
+ { "toggle-fullscreen", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Alt+Ctrl+Enter to toggle fullscreen" },
+ { "tune", COMMAND_LINE_VALUE_REQUIRED, "<setting:value>,<setting:value>", "", NULL, -1, NULL,
+ "[experimental] directly manipulate freerdp settings, use with extreme caution!" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tune-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:tune] Print options allowed for /tune" },
+#endif
+ { "u", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "Username" },
+ { "unmap-buttons", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Let server see real physical pointer button" },
+#ifdef CHANNEL_URBDRC_CLIENT
+ { "usb", COMMAND_LINE_VALUE_REQUIRED,
+ "[dbg,][id:<vid>:<pid>#...,][addr:<bus>:<addr>#...,][auto]", NULL, NULL, -1, NULL,
+ "Redirect USB device" },
+#endif
+ { "v", COMMAND_LINE_VALUE_REQUIRED, "<server>[:port]", NULL, NULL, -1, NULL,
+ "Server hostname" },
+ { "vc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Static virtual channel" },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1, NULL,
+ "Print version" },
+ { "video", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Video optimized remoting channel" },
+ { "prevent-session-lock", COMMAND_LINE_VALUE_OPTIONAL, "<time in sec>", NULL, NULL, -1, NULL,
+ "Prevent session locking by injecting fake mouse motion events to the server "
+ "when the connection is idle (default interval: 180 seconds)" },
+ { "vmconnect", COMMAND_LINE_VALUE_OPTIONAL, "<vmid>", NULL, NULL, -1, NULL,
+ "Hyper-V console (use port 2179, disable negotiation)" },
+ { "w", COMMAND_LINE_VALUE_REQUIRED, "<width>", "1024", NULL, -1, NULL, "Width" },
+ { "wallpaper", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "wallpaper" },
+ { "window-drag", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "full window drag" },
+ { "window-position", COMMAND_LINE_VALUE_REQUIRED, "<xpos>x<ypos>", NULL, NULL, -1, NULL,
+ "window position" },
+ { "wm-class", COMMAND_LINE_VALUE_REQUIRED, "<class-name>", NULL, NULL, -1, NULL,
+ "Set the WM_CLASS hint for the window instance" },
+ { "workarea", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Use available work area" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+};
+#endif /* CLIENT_COMMON_CMDLINE_H */
diff --git a/client/common/file.c b/client/common/file.c
new file mode 100644
index 0000000..760c62e
--- /dev/null
+++ b/client/common/file.c
@@ -0,0 +1,2707 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * .rdp file
+ *
+ * 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 <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <winpr/string.h>
+#include <winpr/file.h>
+
+#include <freerdp/client.h>
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpecam.h>
+#include <freerdp/channels/location.h>
+
+/**
+ * Remote Desktop Plus - Overview of .rdp file settings:
+ * http://www.donkz.nl/files/rdpsettings.html
+ *
+ * RDP Settings for Remote Desktop Services in Windows Server 2008 R2:
+ * http://technet.microsoft.com/en-us/library/ff393699/
+ *
+ * https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/rdp-files
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+/*#define DEBUG_CLIENT_FILE 1*/
+
+static const BYTE BOM_UTF16_LE[2] = { 0xFF, 0xFE };
+
+#define INVALID_INTEGER_VALUE 0xFFFFFFFF
+
+#define RDP_FILE_LINE_FLAG_FORMATTED 0x00000001
+#define RDP_FILE_LINE_FLAG_STANDARD 0x00000002
+#define RDP_FILE_LINE_FLAG_TYPE_STRING 0x00000010
+#define RDP_FILE_LINE_FLAG_TYPE_INTEGER 0x00000020
+#define RDP_FILE_LINE_FLAG_TYPE_BINARY 0x00000040
+
+struct rdp_file_line
+{
+ char* name;
+ LPSTR sValue;
+ PBYTE bValue;
+
+ size_t index;
+
+ long iValue;
+ DWORD flags;
+ int valueLength;
+};
+typedef struct rdp_file_line rdpFileLine;
+
+struct rdp_file
+{
+ DWORD UseMultiMon; /* use multimon */
+ LPSTR SelectedMonitors; /* selectedmonitors */
+ DWORD MaximizeToCurrentDisplays; /* maximizetocurrentdisplays */
+ DWORD SingleMonInWindowedMode; /* singlemoninwindowedmode */
+ DWORD ScreenModeId; /* screen mode id */
+ DWORD SpanMonitors; /* span monitors */
+ DWORD SmartSizing; /* smartsizing */
+ DWORD DynamicResolution; /* dynamic resolution */
+ DWORD EnableSuperSpan; /* enablesuperpan */
+ DWORD SuperSpanAccelerationFactor; /* superpanaccelerationfactor */
+
+ DWORD DesktopWidth; /* desktopwidth */
+ DWORD DesktopHeight; /* desktopheight */
+ DWORD DesktopSizeId; /* desktop size id */
+ DWORD SessionBpp; /* session bpp */
+ DWORD DesktopScaleFactor; /* desktopscalefactor */
+
+ DWORD Compression; /* compression */
+ DWORD KeyboardHook; /* keyboardhook */
+ DWORD DisableCtrlAltDel; /* disable ctrl+alt+del */
+
+ DWORD AudioMode; /* audiomode */
+ DWORD AudioQualityMode; /* audioqualitymode */
+ DWORD AudioCaptureMode; /* audiocapturemode */
+ DWORD EncodeRedirectedVideoCapture; /* encode redirected video capture */
+ DWORD RedirectedVideoCaptureEncodingQuality; /* redirected video capture encoding quality */
+ DWORD VideoPlaybackMode; /* videoplaybackmode */
+
+ DWORD ConnectionType; /* connection type */
+
+ DWORD NetworkAutoDetect; /* networkautodetect */
+ DWORD BandwidthAutoDetect; /* bandwidthautodetect */
+
+ DWORD PinConnectionBar; /* pinconnectionbar */
+ DWORD DisplayConnectionBar; /* displayconnectionbar */
+
+ DWORD WorkspaceId; /* workspaceid */
+ DWORD EnableWorkspaceReconnect; /* enableworkspacereconnect */
+
+ DWORD DisableWallpaper; /* disable wallpaper */
+ DWORD AllowFontSmoothing; /* allow font smoothing */
+ DWORD AllowDesktopComposition; /* allow desktop composition */
+ DWORD DisableFullWindowDrag; /* disable full window drag */
+ DWORD DisableMenuAnims; /* disable menu anims */
+ DWORD DisableThemes; /* disable themes */
+ DWORD DisableCursorSetting; /* disable cursor setting */
+
+ DWORD BitmapCacheSize; /* bitmapcachesize */
+ DWORD BitmapCachePersistEnable; /* bitmapcachepersistenable */
+
+ DWORD ServerPort; /* server port */
+
+ LPSTR Username; /* username */
+ LPSTR Domain; /* domain */
+ LPSTR Password; /*password*/
+ PBYTE Password51; /* password 51 */
+
+ LPSTR FullAddress; /* full address */
+ LPSTR AlternateFullAddress; /* alternate full address */
+
+ LPSTR UsbDevicesToRedirect; /* usbdevicestoredirect */
+ DWORD RedirectDrives; /* redirectdrives */
+ DWORD RedirectPrinters; /* redirectprinters */
+ DWORD RedirectComPorts; /* redirectcomports */
+ DWORD RedirectLocation; /* redirectlocation */
+ DWORD RedirectSmartCards; /* redirectsmartcards */
+ DWORD RedirectWebauthN; /* redirectwebauthn */
+ LPSTR RedirectCameras; /* camerastoredirect */
+ DWORD RedirectClipboard; /* redirectclipboard */
+ DWORD RedirectPosDevices; /* redirectposdevices */
+ DWORD RedirectDirectX; /* redirectdirectx */
+ DWORD DisablePrinterRedirection; /* disableprinterredirection */
+ DWORD DisableClipboardRedirection; /* disableclipboardredirection */
+
+ DWORD ConnectToConsole; /* connect to console */
+ DWORD AdministrativeSession; /* administrative session */
+ DWORD AutoReconnectionEnabled; /* autoreconnection enabled */
+ DWORD AutoReconnectMaxRetries; /* autoreconnect max retries */
+
+ DWORD PublicMode; /* public mode */
+ DWORD AuthenticationLevel; /* authentication level */
+ DWORD PromptCredentialOnce; /* promptcredentialonce */
+ DWORD PromptForCredentials; /* prompt for credentials */
+ DWORD NegotiateSecurityLayer; /* negotiate security layer */
+ DWORD EnableCredSSPSupport; /* enablecredsspsupport */
+ DWORD EnableRdsAadAuth; /* enablerdsaadauth */
+
+ DWORD RemoteApplicationMode; /* remoteapplicationmode */
+ LPSTR LoadBalanceInfo; /* loadbalanceinfo */
+
+ LPSTR RemoteApplicationName; /* remoteapplicationname */
+ LPSTR RemoteApplicationIcon; /* remoteapplicationicon */
+ LPSTR RemoteApplicationProgram; /* remoteapplicationprogram */
+ LPSTR RemoteApplicationFile; /* remoteapplicationfile */
+ LPSTR RemoteApplicationGuid; /* remoteapplicationguid */
+ LPSTR RemoteApplicationCmdLine; /* remoteapplicationcmdline */
+ DWORD RemoteApplicationExpandCmdLine; /* remoteapplicationexpandcmdline */
+ DWORD RemoteApplicationExpandWorkingDir; /* remoteapplicationexpandworkingdir */
+ DWORD DisableConnectionSharing; /* disableconnectionsharing */
+ DWORD DisableRemoteAppCapsCheck; /* disableremoteappcapscheck */
+
+ LPSTR AlternateShell; /* alternate shell */
+ LPSTR ShellWorkingDirectory; /* shell working directory */
+
+ LPSTR GatewayHostname; /* gatewayhostname */
+ DWORD GatewayUsageMethod; /* gatewayusagemethod */
+ DWORD GatewayProfileUsageMethod; /* gatewayprofileusagemethod */
+ DWORD GatewayCredentialsSource; /* gatewaycredentialssource */
+
+ LPSTR ResourceProvider; /* resourceprovider */
+
+ LPSTR WvdEndpointPool; /* wvd endpoint pool */
+ LPSTR geo; /* geo */
+ LPSTR armpath; /* armpath */
+ LPSTR aadtenantid; /* aadtenantid" */
+ LPSTR diagnosticserviceurl; /* diagnosticserviceurl */
+ LPSTR hubdiscoverygeourl; /* hubdiscoverygeourl" */
+ LPSTR activityhint; /* activityhint */
+
+ DWORD UseRedirectionServerName; /* use redirection server name */
+
+ LPSTR GatewayAccessToken; /* gatewayaccesstoken */
+
+ LPSTR DrivesToRedirect; /* drivestoredirect */
+ LPSTR DevicesToRedirect; /* devicestoredirect */
+ LPSTR WinPosStr; /* winposstr */
+
+ LPSTR PreconnectionBlob; /* pcb */
+
+ LPSTR KdcProxyName; /* kdcproxyname */
+ DWORD RdgIsKdcProxy; /* rdgiskdcproxy */
+
+ DWORD align1;
+
+ size_t lineCount;
+ size_t lineSize;
+ rdpFileLine* lines;
+
+ ADDIN_ARGV* args;
+ void* context;
+
+ DWORD flags;
+};
+
+static const char key_str_username[] = "username";
+static const char key_str_domain[] = "domain";
+static const char key_str_password[] = "password";
+static const char key_str_full_address[] = "full address";
+static const char key_str_alternate_full_address[] = "alternate full address";
+static const char key_str_usbdevicestoredirect[] = "usbdevicestoredirect";
+static const char key_str_camerastoredirect[] = "camerastoredirect";
+static const char key_str_loadbalanceinfo[] = "loadbalanceinfo";
+static const char key_str_remoteapplicationname[] = "remoteapplicationname";
+static const char key_str_remoteapplicationicon[] = "remoteapplicationicon";
+static const char key_str_remoteapplicationprogram[] = "remoteapplicationprogram";
+static const char key_str_remoteapplicationfile[] = "remoteapplicationfile";
+static const char key_str_remoteapplicationguid[] = "remoteapplicationguid";
+static const char key_str_remoteapplicationcmdline[] = "remoteapplicationcmdline";
+static const char key_str_alternate_shell[] = "alternate shell";
+static const char key_str_shell_working_directory[] = "shell working directory";
+static const char key_str_gatewayhostname[] = "gatewayhostname";
+static const char key_str_gatewayaccesstoken[] = "gatewayaccesstoken";
+static const char key_str_resourceprovider[] = "resourceprovider";
+static const char str_resourceprovider_arm[] = "arm";
+static const char key_str_kdcproxyname[] = "kdcproxyname";
+static const char key_str_drivestoredirect[] = "drivestoredirect";
+static const char key_str_devicestoredirect[] = "devicestoredirect";
+static const char key_str_winposstr[] = "winposstr";
+static const char key_str_pcb[] = "pcb";
+static const char key_str_selectedmonitors[] = "selectedmonitors";
+
+static const char key_str_wvd[] = "wvd endpoint pool";
+static const char key_str_geo[] = "geo";
+static const char key_str_armpath[] = "armpath";
+static const char key_str_aadtenantid[] = "aadtenantid";
+
+static const char key_str_diagnosticserviceurl[] = "diagnosticserviceurl";
+static const char key_str_hubdiscoverygeourl[] = "hubdiscoverygeourl";
+
+static const char key_str_activityhint[] = "activityhint";
+
+static const char key_int_rdgiskdcproxy[] = "rdgiskdcproxy";
+static const char key_int_use_redirection_server_name[] = "use redirection server name";
+static const char key_int_gatewaycredentialssource[] = "gatewaycredentialssource";
+static const char key_int_gatewayprofileusagemethod[] = "gatewayprofileusagemethod";
+static const char key_int_gatewayusagemethod[] = "gatewayusagemethod";
+static const char key_int_disableremoteappcapscheck[] = "disableremoteappcapscheck";
+static const char key_int_disableconnectionsharing[] = "disableconnectionsharing";
+static const char key_int_remoteapplicationexpandworkingdir[] = "remoteapplicationexpandworkingdir";
+static const char key_int_remoteapplicationexpandcmdline[] = "remoteapplicationexpandcmdline";
+static const char key_int_remoteapplicationmode[] = "remoteapplicationmode";
+static const char key_int_enablecredsspsupport[] = "enablecredsspsupport";
+static const char key_int_enablerdsaadauth[] = "enablerdsaadauth";
+static const char key_int_negotiate_security_layer[] = "negotiate security layer";
+static const char key_int_prompt_for_credentials[] = "prompt for credentials";
+static const char key_int_promptcredentialonce[] = "promptcredentialonce";
+static const char key_int_authentication_level[] = "authentication level";
+static const char key_int_public_mode[] = "public mode";
+static const char key_int_autoreconnect_max_retries[] = "autoreconnect max retries";
+static const char key_int_autoreconnection_enabled[] = "autoreconnection enabled";
+static const char key_int_administrative_session[] = "administrative session";
+static const char key_int_connect_to_console[] = "connect to console";
+static const char key_int_disableclipboardredirection[] = "disableclipboardredirection";
+static const char key_int_disableprinterredirection[] = "disableprinterredirection";
+static const char key_int_redirectdirectx[] = "redirectdirectx";
+static const char key_int_redirectposdevices[] = "redirectposdevices";
+static const char key_int_redirectclipboard[] = "redirectclipboard";
+static const char key_int_redirectsmartcards[] = "redirectsmartcards";
+static const char key_int_redirectcomports[] = "redirectcomports";
+static const char key_int_redirectlocation[] = "redirectlocation";
+static const char key_int_redirectprinters[] = "redirectprinters";
+static const char key_int_redirectdrives[] = "redirectdrives";
+static const char key_int_server_port[] = "server port";
+static const char key_int_bitmapcachepersistenable[] = "bitmapcachepersistenable";
+static const char key_int_bitmapcachesize[] = "bitmapcachesize";
+static const char key_int_disable_cursor_setting[] = "disable cursor setting";
+static const char key_int_disable_themes[] = "disable themes";
+static const char key_int_disable_menu_anims[] = "disable menu anims";
+static const char key_int_disable_full_window_drag[] = "disable full window drag";
+static const char key_int_allow_desktop_composition[] = "allow desktop composition";
+static const char key_int_allow_font_smoothing[] = "allow font smoothing";
+static const char key_int_disable_wallpaper[] = "disable wallpaper";
+static const char key_int_enableworkspacereconnect[] = "enableworkspacereconnect";
+static const char key_int_workspaceid[] = "workspaceid";
+static const char key_int_displayconnectionbar[] = "displayconnectionbar";
+static const char key_int_pinconnectionbar[] = "pinconnectionbar";
+static const char key_int_bandwidthautodetect[] = "bandwidthautodetect";
+static const char key_int_networkautodetect[] = "networkautodetect";
+static const char key_int_connection_type[] = "connection type";
+static const char key_int_videoplaybackmode[] = "videoplaybackmode";
+static const char key_int_redirected_video_capture_encoding_quality[] =
+ "redirected video capture encoding quality";
+static const char key_int_encode_redirected_video_capture[] = "encode redirected video capture";
+static const char key_int_audiocapturemode[] = "audiocapturemode";
+static const char key_int_audioqualitymode[] = "audioqualitymode";
+static const char key_int_audiomode[] = "audiomode";
+static const char key_int_disable_ctrl_alt_del[] = "disable ctrl+alt+del";
+static const char key_int_keyboardhook[] = "keyboardhook";
+static const char key_int_compression[] = "compression";
+static const char key_int_desktopscalefactor[] = "desktopscalefactor";
+static const char key_int_session_bpp[] = "session bpp";
+static const char key_int_desktop_size_id[] = "desktop size id";
+static const char key_int_desktopheight[] = "desktopheight";
+static const char key_int_desktopwidth[] = "desktopwidth";
+static const char key_int_superpanaccelerationfactor[] = "superpanaccelerationfactor";
+static const char key_int_enablesuperpan[] = "enablesuperpan";
+static const char key_int_dynamic_resolution[] = "dynamic resolution";
+static const char key_int_smart_sizing[] = "smart sizing";
+static const char key_int_span_monitors[] = "span monitors";
+static const char key_int_screen_mode_id[] = "screen mode id";
+static const char key_int_singlemoninwindowedmode[] = "singlemoninwindowedmode";
+static const char key_int_maximizetocurrentdisplays[] = "maximizetocurrentdisplays";
+static const char key_int_use_multimon[] = "use multimon";
+static const char key_int_redirectwebauthn[] = "redirectwebauthn";
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file);
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file,
+ const char* name);
+static void freerdp_client_file_string_check_free(LPSTR str);
+
+static BOOL freerdp_client_rdp_file_find_integer_entry(rdpFile* file, const char* name,
+ DWORD** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_int_use_multimon) == 0)
+ *outValue = &file->UseMultiMon;
+ else if (_stricmp(name, key_int_maximizetocurrentdisplays) == 0)
+ *outValue = &file->MaximizeToCurrentDisplays;
+ else if (_stricmp(name, key_int_singlemoninwindowedmode) == 0)
+ *outValue = &file->SingleMonInWindowedMode;
+ else if (_stricmp(name, key_int_screen_mode_id) == 0)
+ *outValue = &file->ScreenModeId;
+ else if (_stricmp(name, key_int_span_monitors) == 0)
+ *outValue = &file->SpanMonitors;
+ else if (_stricmp(name, key_int_smart_sizing) == 0)
+ *outValue = &file->SmartSizing;
+ else if (_stricmp(name, key_int_dynamic_resolution) == 0)
+ *outValue = &file->DynamicResolution;
+ else if (_stricmp(name, key_int_enablesuperpan) == 0)
+ *outValue = &file->EnableSuperSpan;
+ else if (_stricmp(name, key_int_superpanaccelerationfactor) == 0)
+ *outValue = &file->SuperSpanAccelerationFactor;
+ else if (_stricmp(name, key_int_desktopwidth) == 0)
+ *outValue = &file->DesktopWidth;
+ else if (_stricmp(name, key_int_desktopheight) == 0)
+ *outValue = &file->DesktopHeight;
+ else if (_stricmp(name, key_int_desktop_size_id) == 0)
+ *outValue = &file->DesktopSizeId;
+ else if (_stricmp(name, key_int_session_bpp) == 0)
+ *outValue = &file->SessionBpp;
+ else if (_stricmp(name, key_int_desktopscalefactor) == 0)
+ *outValue = &file->DesktopScaleFactor;
+ else if (_stricmp(name, key_int_compression) == 0)
+ *outValue = &file->Compression;
+ else if (_stricmp(name, key_int_keyboardhook) == 0)
+ *outValue = &file->KeyboardHook;
+ else if (_stricmp(name, key_int_disable_ctrl_alt_del) == 0)
+ *outValue = &file->DisableCtrlAltDel;
+ else if (_stricmp(name, key_int_audiomode) == 0)
+ *outValue = &file->AudioMode;
+ else if (_stricmp(name, key_int_audioqualitymode) == 0)
+ *outValue = &file->AudioQualityMode;
+ else if (_stricmp(name, key_int_audiocapturemode) == 0)
+ *outValue = &file->AudioCaptureMode;
+ else if (_stricmp(name, key_int_encode_redirected_video_capture) == 0)
+ *outValue = &file->EncodeRedirectedVideoCapture;
+ else if (_stricmp(name, key_int_redirected_video_capture_encoding_quality) == 0)
+ *outValue = &file->RedirectedVideoCaptureEncodingQuality;
+ else if (_stricmp(name, key_int_videoplaybackmode) == 0)
+ *outValue = &file->VideoPlaybackMode;
+ else if (_stricmp(name, key_int_connection_type) == 0)
+ *outValue = &file->ConnectionType;
+ else if (_stricmp(name, key_int_networkautodetect) == 0)
+ *outValue = &file->NetworkAutoDetect;
+ else if (_stricmp(name, key_int_bandwidthautodetect) == 0)
+ *outValue = &file->BandwidthAutoDetect;
+ else if (_stricmp(name, key_int_pinconnectionbar) == 0)
+ *outValue = &file->PinConnectionBar;
+ else if (_stricmp(name, key_int_displayconnectionbar) == 0)
+ *outValue = &file->DisplayConnectionBar;
+ else if (_stricmp(name, key_int_workspaceid) == 0)
+ *outValue = &file->WorkspaceId;
+ else if (_stricmp(name, key_int_enableworkspacereconnect) == 0)
+ *outValue = &file->EnableWorkspaceReconnect;
+ else if (_stricmp(name, key_int_disable_wallpaper) == 0)
+ *outValue = &file->DisableWallpaper;
+ else if (_stricmp(name, key_int_allow_font_smoothing) == 0)
+ *outValue = &file->AllowFontSmoothing;
+ else if (_stricmp(name, key_int_allow_desktop_composition) == 0)
+ *outValue = &file->AllowDesktopComposition;
+ else if (_stricmp(name, key_int_disable_full_window_drag) == 0)
+ *outValue = &file->DisableFullWindowDrag;
+ else if (_stricmp(name, key_int_disable_menu_anims) == 0)
+ *outValue = &file->DisableMenuAnims;
+ else if (_stricmp(name, key_int_disable_themes) == 0)
+ *outValue = &file->DisableThemes;
+ else if (_stricmp(name, key_int_disable_cursor_setting) == 0)
+ *outValue = &file->DisableCursorSetting;
+ else if (_stricmp(name, key_int_bitmapcachesize) == 0)
+ *outValue = &file->BitmapCacheSize;
+ else if (_stricmp(name, key_int_bitmapcachepersistenable) == 0)
+ *outValue = &file->BitmapCachePersistEnable;
+ else if (_stricmp(name, key_int_server_port) == 0)
+ *outValue = &file->ServerPort;
+ else if (_stricmp(name, key_int_redirectdrives) == 0)
+ *outValue = &file->RedirectDrives;
+ else if (_stricmp(name, key_int_redirectprinters) == 0)
+ *outValue = &file->RedirectPrinters;
+ else if (_stricmp(name, key_int_redirectcomports) == 0)
+ *outValue = &file->RedirectComPorts;
+ else if (_stricmp(name, key_int_redirectlocation) == 0)
+ *outValue = &file->RedirectLocation;
+ else if (_stricmp(name, key_int_redirectsmartcards) == 0)
+ *outValue = &file->RedirectSmartCards;
+ else if (_stricmp(name, key_int_redirectclipboard) == 0)
+ *outValue = &file->RedirectClipboard;
+ else if (_stricmp(name, key_int_redirectposdevices) == 0)
+ *outValue = &file->RedirectPosDevices;
+ else if (_stricmp(name, key_int_redirectdirectx) == 0)
+ *outValue = &file->RedirectDirectX;
+ else if (_stricmp(name, key_int_disableprinterredirection) == 0)
+ *outValue = &file->DisablePrinterRedirection;
+ else if (_stricmp(name, key_int_disableclipboardredirection) == 0)
+ *outValue = &file->DisableClipboardRedirection;
+ else if (_stricmp(name, key_int_connect_to_console) == 0)
+ *outValue = &file->ConnectToConsole;
+ else if (_stricmp(name, key_int_administrative_session) == 0)
+ *outValue = &file->AdministrativeSession;
+ else if (_stricmp(name, key_int_autoreconnection_enabled) == 0)
+ *outValue = &file->AutoReconnectionEnabled;
+ else if (_stricmp(name, key_int_autoreconnect_max_retries) == 0)
+ *outValue = &file->AutoReconnectMaxRetries;
+ else if (_stricmp(name, key_int_public_mode) == 0)
+ *outValue = &file->PublicMode;
+ else if (_stricmp(name, key_int_authentication_level) == 0)
+ *outValue = &file->AuthenticationLevel;
+ else if (_stricmp(name, key_int_promptcredentialonce) == 0)
+ *outValue = &file->PromptCredentialOnce;
+ else if ((_stricmp(name, key_int_prompt_for_credentials) == 0))
+ *outValue = &file->PromptForCredentials;
+ else if (_stricmp(name, key_int_negotiate_security_layer) == 0)
+ *outValue = &file->NegotiateSecurityLayer;
+ else if (_stricmp(name, key_int_enablecredsspsupport) == 0)
+ *outValue = &file->EnableCredSSPSupport;
+ else if (_stricmp(name, key_int_enablerdsaadauth) == 0)
+ *outValue = &file->EnableRdsAadAuth;
+ else if (_stricmp(name, key_int_remoteapplicationmode) == 0)
+ *outValue = &file->RemoteApplicationMode;
+ else if (_stricmp(name, key_int_remoteapplicationexpandcmdline) == 0)
+ *outValue = &file->RemoteApplicationExpandCmdLine;
+ else if (_stricmp(name, key_int_remoteapplicationexpandworkingdir) == 0)
+ *outValue = &file->RemoteApplicationExpandWorkingDir;
+ else if (_stricmp(name, key_int_disableconnectionsharing) == 0)
+ *outValue = &file->DisableConnectionSharing;
+ else if (_stricmp(name, key_int_disableremoteappcapscheck) == 0)
+ *outValue = &file->DisableRemoteAppCapsCheck;
+ else if (_stricmp(name, key_int_gatewayusagemethod) == 0)
+ *outValue = &file->GatewayUsageMethod;
+ else if (_stricmp(name, key_int_gatewayprofileusagemethod) == 0)
+ *outValue = &file->GatewayProfileUsageMethod;
+ else if (_stricmp(name, key_int_gatewaycredentialssource) == 0)
+ *outValue = &file->GatewayCredentialsSource;
+ else if (_stricmp(name, key_int_use_redirection_server_name) == 0)
+ *outValue = &file->UseRedirectionServerName;
+ else if (_stricmp(name, key_int_rdgiskdcproxy) == 0)
+ *outValue = &file->RdgIsKdcProxy;
+ else if (_stricmp(name, key_int_redirectwebauthn) == 0)
+ *outValue = &file->RedirectWebauthN;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_rdp_file_find_string_entry(rdpFile* file, const char* name,
+ LPSTR** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_str_username) == 0)
+ *outValue = &file->Username;
+ else if (_stricmp(name, key_str_domain) == 0)
+ *outValue = &file->Domain;
+ else if (_stricmp(name, key_str_password) == 0)
+ *outValue = &file->Password;
+ else if (_stricmp(name, key_str_full_address) == 0)
+ *outValue = &file->FullAddress;
+ else if (_stricmp(name, key_str_alternate_full_address) == 0)
+ *outValue = &file->AlternateFullAddress;
+ else if (_stricmp(name, key_str_usbdevicestoredirect) == 0)
+ *outValue = &file->UsbDevicesToRedirect;
+ else if (_stricmp(name, key_str_camerastoredirect) == 0)
+ *outValue = &file->RedirectCameras;
+ else if (_stricmp(name, key_str_loadbalanceinfo) == 0)
+ *outValue = &file->LoadBalanceInfo;
+ else if (_stricmp(name, key_str_remoteapplicationname) == 0)
+ *outValue = &file->RemoteApplicationName;
+ else if (_stricmp(name, key_str_remoteapplicationicon) == 0)
+ *outValue = &file->RemoteApplicationIcon;
+ else if (_stricmp(name, key_str_remoteapplicationprogram) == 0)
+ *outValue = &file->RemoteApplicationProgram;
+ else if (_stricmp(name, key_str_remoteapplicationfile) == 0)
+ *outValue = &file->RemoteApplicationFile;
+ else if (_stricmp(name, key_str_remoteapplicationguid) == 0)
+ *outValue = &file->RemoteApplicationGuid;
+ else if (_stricmp(name, key_str_remoteapplicationcmdline) == 0)
+ *outValue = &file->RemoteApplicationCmdLine;
+ else if (_stricmp(name, key_str_alternate_shell) == 0)
+ *outValue = &file->AlternateShell;
+ else if (_stricmp(name, key_str_shell_working_directory) == 0)
+ *outValue = &file->ShellWorkingDirectory;
+ else if (_stricmp(name, key_str_gatewayhostname) == 0)
+ *outValue = &file->GatewayHostname;
+ else if (_stricmp(name, key_str_resourceprovider) == 0)
+ *outValue = &file->ResourceProvider;
+ else if (_stricmp(name, key_str_wvd) == 0)
+ *outValue = &file->WvdEndpointPool;
+ else if (_stricmp(name, key_str_geo) == 0)
+ *outValue = &file->geo;
+ else if (_stricmp(name, key_str_armpath) == 0)
+ *outValue = &file->armpath;
+ else if (_stricmp(name, key_str_aadtenantid) == 0)
+ *outValue = &file->aadtenantid;
+ else if (_stricmp(name, key_str_diagnosticserviceurl) == 0)
+ *outValue = &file->diagnosticserviceurl;
+ else if (_stricmp(name, key_str_hubdiscoverygeourl) == 0)
+ *outValue = &file->hubdiscoverygeourl;
+ else if (_stricmp(name, key_str_activityhint) == 0)
+ *outValue = &file->activityhint;
+ else if (_stricmp(name, key_str_gatewayaccesstoken) == 0)
+ *outValue = &file->GatewayAccessToken;
+ else if (_stricmp(name, key_str_kdcproxyname) == 0)
+ *outValue = &file->KdcProxyName;
+ else if (_stricmp(name, key_str_drivestoredirect) == 0)
+ *outValue = &file->DrivesToRedirect;
+ else if (_stricmp(name, key_str_devicestoredirect) == 0)
+ *outValue = &file->DevicesToRedirect;
+ else if (_stricmp(name, key_str_winposstr) == 0)
+ *outValue = &file->WinPosStr;
+ else if (_stricmp(name, key_str_pcb) == 0)
+ *outValue = &file->PreconnectionBlob;
+ else if (_stricmp(name, key_str_selectedmonitors) == 0)
+ *outValue = &file->SelectedMonitors;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_STRING))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Set an integer in a rdpFile
+ *
+ * @return FALSE if a standard name was set, TRUE for a non-standard name, FALSE on error
+ *
+ */
+static BOOL freerdp_client_rdp_file_set_integer(rdpFile* file, const char* name, long value)
+{
+ DWORD* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:i:%ld", name, value);
+#endif
+
+ if (value < 0)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_integer_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = (DWORD)value;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ line->name = _strdup(name);
+ if (!line->name)
+ {
+ free(line->name);
+ line->name = NULL;
+ return FALSE;
+ }
+
+ line->iValue = value;
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_INTEGER;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_parse_rdp_file_integer(rdpFile* file, const char* name,
+ const char* value)
+{
+ char* endptr = NULL;
+ long ivalue = 0;
+ errno = 0;
+ ivalue = strtol(value, &endptr, 0);
+
+ if ((endptr == NULL) || (errno != 0) || (endptr == value) || (ivalue > INT32_MAX) ||
+ (ivalue < INT32_MIN))
+ {
+ if (file->flags & RDP_FILE_FLAG_PARSE_INT_RELAXED)
+ {
+ WLog_WARN(TAG, "Integer option %s has invalid value %s, using default", name, value);
+ return TRUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file integer option %s [value=%s]", name, value);
+ return FALSE;
+ }
+ }
+
+ return freerdp_client_rdp_file_set_integer(file, name, ivalue);
+}
+
+/** set a string value in the provided rdp file context
+ *
+ * @param file rdpFile
+ * @param name name of the string
+ * @param value value of the string to set
+ * @return 0 on success, 1 if the key wasn't found (not a standard key), -1 on error
+ */
+
+static BOOL freerdp_client_rdp_file_set_string(rdpFile* file, const char* name, const char* value)
+{
+ LPSTR* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:s:%s", name, value);
+#endif
+
+ if (!name || !value)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_string_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = _strdup(value);
+ if (!(*targetValue))
+ return FALSE;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = _strdup(name);
+ line->sValue = _strdup(value);
+ if (!line->name || !line->sValue)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = NULL;
+ line->sValue = NULL;
+ return FALSE;
+ }
+
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_STRING;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_add_option(rdpFile* file, const char* option)
+{
+ return freerdp_addin_argv_add_argument(file->args, option);
+}
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file)
+{
+ SSIZE_T index = (SSIZE_T)file->lineCount;
+
+ while ((file->lineCount + 1) > file->lineSize)
+ {
+ size_t new_size = 0;
+ rdpFileLine* new_line = NULL;
+ new_size = file->lineSize * 2;
+ new_line = (rdpFileLine*)realloc(file->lines, new_size * sizeof(rdpFileLine));
+
+ if (!new_line)
+ return -1;
+
+ file->lines = new_line;
+ file->lineSize = new_size;
+ }
+
+ ZeroMemory(&(file->lines[file->lineCount]), sizeof(rdpFileLine));
+ file->lines[file->lineCount].index = (size_t)index;
+ (file->lineCount)++;
+ return index;
+}
+
+static BOOL freerdp_client_parse_rdp_file_string(rdpFile* file, char* name, char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+static BOOL freerdp_client_parse_rdp_file_option(rdpFile* file, const char* option)
+{
+ return freerdp_client_add_option(file, option);
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer(rdpFile* file, const BYTE* buffer, size_t size)
+{
+ return freerdp_client_parse_rdp_file_buffer_ex(file, buffer, size, NULL);
+}
+
+static BOOL trim(char** strptr)
+{
+ char* start = NULL;
+ char* str = NULL;
+ char* end = NULL;
+
+ start = str = *strptr;
+ if (!str)
+ return TRUE;
+ if (!(~((size_t)str)))
+ return TRUE;
+ end = str + strlen(str) - 1;
+
+ while (isspace(*str))
+ str++;
+
+ while ((end > str) && isspace(*end))
+ end--;
+ end[1] = '\0';
+ if (start == str)
+ *strptr = str;
+ else
+ {
+ *strptr = _strdup(str);
+ free(start);
+ return *strptr != NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL trim_strings(rdpFile* file)
+{
+ if (!trim(&file->Username))
+ return FALSE;
+ if (!trim(&file->Domain))
+ return FALSE;
+ if (!trim(&file->AlternateFullAddress))
+ return FALSE;
+ if (!trim(&file->FullAddress))
+ return FALSE;
+ if (!trim(&file->UsbDevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->RedirectCameras))
+ return FALSE;
+ if (!trim(&file->LoadBalanceInfo))
+ return FALSE;
+ if (!trim(&file->GatewayHostname))
+ return FALSE;
+ if (!trim(&file->GatewayAccessToken))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationName))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationIcon))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationProgram))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationFile))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationGuid))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationCmdLine))
+ return FALSE;
+ if (!trim(&file->AlternateShell))
+ return FALSE;
+ if (!trim(&file->ShellWorkingDirectory))
+ return FALSE;
+ if (!trim(&file->DrivesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->WinPosStr))
+ return FALSE;
+ if (!trim(&file->PreconnectionBlob))
+ return FALSE;
+ if (!trim(&file->KdcProxyName))
+ return FALSE;
+ if (!trim(&file->SelectedMonitors))
+ return FALSE;
+
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ rdpFileLine* curLine = &file->lines[i];
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ {
+ if (!trim(&curLine->sValue))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, size_t size,
+ rdp_file_fkt_parse parse)
+{
+ BOOL rc = FALSE;
+ size_t length = 0;
+ char* line = NULL;
+ char* type = NULL;
+ char* context = NULL;
+ char* d1 = NULL;
+ char* d2 = NULL;
+ char* beg = NULL;
+ char* name = NULL;
+ char* value = NULL;
+ char* copy = NULL;
+
+ if (!file)
+ return FALSE;
+ if (size < 2)
+ return FALSE;
+
+ if ((buffer[0] == BOM_UTF16_LE[0]) && (buffer[1] == BOM_UTF16_LE[1]))
+ {
+ LPCWSTR uc = (LPCWSTR)(&buffer[2]);
+ size = size / sizeof(WCHAR) - 1;
+
+ copy = ConvertWCharNToUtf8Alloc(uc, size, NULL);
+ if (!copy)
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file from UCS2 to UTF8");
+ return FALSE;
+ }
+ }
+ else
+ {
+ copy = calloc(1, size + sizeof(BYTE));
+
+ if (!copy)
+ return FALSE;
+
+ memcpy(copy, buffer, size);
+ }
+
+ line = strtok_s(copy, "\r\n", &context);
+
+ while (line)
+ {
+ length = strnlen(line, size);
+
+ if (length > 1)
+ {
+ beg = line;
+ if (beg[0] == '/')
+ {
+ if (!freerdp_client_parse_rdp_file_option(file, line))
+ goto fail;
+
+ goto next_line; /* FreeRDP option */
+ }
+
+ d1 = strchr(line, ':');
+
+ if (!d1)
+ goto next_line; /* not first delimiter */
+
+ type = &d1[1];
+ d2 = strchr(type, ':');
+
+ if (!d2)
+ goto next_line; /* no second delimiter */
+
+ if ((d2 - d1) != 2)
+ goto next_line; /* improper type length */
+
+ *d1 = 0;
+ *d2 = 0;
+ name = beg;
+ value = &d2[1];
+
+ if (parse && parse(file->context, name, *type, value))
+ {
+ }
+ else if (*type == 'i')
+ {
+ /* integer type */
+ if (!freerdp_client_parse_rdp_file_integer(file, name, value))
+ goto fail;
+ }
+ else if (*type == 's')
+ {
+ /* string type */
+ if (!freerdp_client_parse_rdp_file_string(file, name, value))
+ goto fail;
+ }
+ else if (*type == 'b')
+ {
+ /* binary type */
+ WLog_ERR(TAG, "Unsupported RDP file binary option %s [value=%s]", name, value);
+ }
+ }
+
+ next_line:
+ line = strtok_s(NULL, "\r\n", &context);
+ }
+
+ rc = trim_strings(file);
+fail:
+ free(copy);
+ return rc;
+}
+
+BOOL freerdp_client_parse_rdp_file(rdpFile* file, const char* name)
+{
+ return freerdp_client_parse_rdp_file_ex(file, name, NULL);
+}
+
+BOOL freerdp_client_parse_rdp_file_ex(rdpFile* file, const char* name, rdp_file_fkt_parse parse)
+{
+ BOOL status = 0;
+ BYTE* buffer = NULL;
+ FILE* fp = NULL;
+ size_t read_size = 0;
+ INT64 file_size = 0;
+ const char* fname = name;
+
+ if (!file || !name)
+ return FALSE;
+
+ if (_strnicmp(fname, "file://", 7) == 0)
+ fname = &name[7];
+
+ fp = winpr_fopen(fname, "r");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to open RDP file %s", name);
+ return FALSE;
+ }
+
+ _fseeki64(fp, 0, SEEK_END);
+ file_size = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ if (file_size < 1)
+ {
+ WLog_ERR(TAG, "RDP file %s is empty", name);
+ fclose(fp);
+ return FALSE;
+ }
+
+ buffer = (BYTE*)malloc((size_t)file_size + 2);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return FALSE;
+ }
+
+ read_size = fread(buffer, (size_t)file_size, 1, fp);
+
+ if (!read_size)
+ {
+ if (!ferror(fp))
+ read_size = (size_t)file_size;
+ }
+
+ fclose(fp);
+
+ if (read_size < 1)
+ {
+ WLog_ERR(TAG, "Could not read from RDP file %s", name);
+ free(buffer);
+ return FALSE;
+ }
+
+ buffer[file_size] = '\0';
+ buffer[file_size + 1] = '\0';
+ status = freerdp_client_parse_rdp_file_buffer_ex(file, buffer, (size_t)file_size, parse);
+ free(buffer);
+ return status;
+}
+
+static INLINE BOOL FILE_POPULATE_STRING(char** _target, const rdpSettings* _settings,
+ FreeRDP_Settings_Keys_String _option)
+{
+ WINPR_ASSERT(_target);
+ WINPR_ASSERT(_settings);
+
+ const char* str = freerdp_settings_get_string(_settings, _option);
+ freerdp_client_file_string_check_free(*_target);
+ *_target = (void*)~((size_t)NULL);
+ if (str)
+ {
+ *_target = _strdup(str);
+ if (!_target)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static char* freerdp_client_channel_args_to_string(const rdpSettings* settings, const char* channel,
+ const char* option)
+{
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, channel);
+ const char* filters[] = { option };
+ if (!args || (args->argc < 2))
+ return NULL;
+
+ return CommandLineToCommaSeparatedValuesEx(args->argc - 1, args->argv + 1, filters,
+ ARRAYSIZE(filters));
+}
+
+static BOOL rdp_opt_duplicate(const rdpSettings* _settings, FreeRDP_Settings_Keys_String _id,
+ char** _key)
+{
+ WINPR_ASSERT(_settings);
+ WINPR_ASSERT(_key);
+ const char* tmp = freerdp_settings_get_string(_settings, _id);
+
+ if (tmp)
+ {
+ *_key = _strdup(tmp);
+ if (!*_key)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_populate_rdp_file_from_settings(rdpFile* file, const rdpSettings* settings)
+{
+ FreeRDP_Settings_Keys_String index = FreeRDP_STRING_UNUSED;
+ UINT32 LoadBalanceInfoLength = 0;
+ const char* GatewayHostname = NULL;
+ char* redirectCameras = NULL;
+ char* redirectUsb = NULL;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (!FILE_POPULATE_STRING(&file->Domain, settings, FreeRDP_Domain) ||
+ !FILE_POPULATE_STRING(&file->Username, settings, FreeRDP_Username) ||
+ !FILE_POPULATE_STRING(&file->Password, settings, FreeRDP_Password) ||
+ !FILE_POPULATE_STRING(&file->FullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateFullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateShell, settings, FreeRDP_AlternateShell) ||
+ !FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect))
+
+ return FALSE;
+ file->ServerPort = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
+
+ file->DesktopWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ file->DesktopHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ file->SessionBpp = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ file->DesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ file->DynamicResolution = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
+ file->VideoPlaybackMode = freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized);
+
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+ file->ConnectToConsole = freerdp_settings_get_bool(settings, FreeRDP_ConsoleSession);
+ file->NegotiateSecurityLayer =
+ freerdp_settings_get_bool(settings, FreeRDP_NegotiateSecurityLayer);
+ file->EnableCredSSPSupport = freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity);
+ file->EnableRdsAadAuth = freerdp_settings_get_bool(settings, FreeRDP_AadSecurity);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ index = FreeRDP_RemoteApplicationWorkingDir;
+ else
+ index = FreeRDP_ShellWorkingDirectory;
+ if (!FILE_POPULATE_STRING(&file->ShellWorkingDirectory, settings, index))
+ return FALSE;
+ file->ConnectionType = freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType);
+
+ file->ScreenModeId = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) ? 2 : 1;
+
+ LoadBalanceInfoLength = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if (LoadBalanceInfoLength > 0)
+ {
+ const BYTE* LoadBalanceInfo =
+ freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
+ file->LoadBalanceInfo = calloc(LoadBalanceInfoLength + 1, 1);
+ if (!file->LoadBalanceInfo)
+ return FALSE;
+ memcpy(file->LoadBalanceInfo, LoadBalanceInfo, LoadBalanceInfoLength);
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AudioPlayback))
+ file->AudioMode = AUDIO_MODE_REDIRECT;
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteConsoleAudio))
+ file->AudioMode = AUDIO_MODE_PLAY_ON_SERVER;
+ else
+ file->AudioMode = AUDIO_MODE_NONE;
+
+ /* The gateway hostname should also contain a port specifier unless it is the default port 443
+ */
+ GatewayHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ if (GatewayHostname)
+ {
+ const UINT32 GatewayPort = freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ if (GatewayPort == 443)
+ file->GatewayHostname = _strdup(GatewayHostname);
+ else
+ {
+ int length = _scprintf("%s:%" PRIu32, GatewayHostname, GatewayPort);
+ if (length < 0)
+ return FALSE;
+
+ file->GatewayHostname = (char*)malloc((size_t)length + 1);
+ if (!file->GatewayHostname)
+ return FALSE;
+
+ if (sprintf_s(file->GatewayHostname, (size_t)length + 1, "%s:%" PRIu32, GatewayHostname,
+ GatewayPort) < 0)
+ return FALSE;
+ }
+ if (!file->GatewayHostname)
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayArmTransport))
+ file->ResourceProvider = _strdup(str_resourceprovider_arm);
+
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdWvdEndpointPool, &file->WvdEndpointPool))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdGeo, &file->geo))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdArmpath, &file->armpath))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdAadtenantid, &file->aadtenantid))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ &file->diagnosticserviceurl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ &file->hubdiscoverygeourl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdActivityhint, &file->activityhint))
+ return FALSE;
+
+ file->AudioCaptureMode = freerdp_settings_get_bool(settings, FreeRDP_AudioCapture);
+ file->BitmapCachePersistEnable =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled);
+ file->Compression = freerdp_settings_get_bool(settings, FreeRDP_CompressionEnabled);
+ file->AuthenticationLevel = freerdp_settings_get_uint32(settings, FreeRDP_AuthenticationLevel);
+ file->GatewayUsageMethod = freerdp_settings_get_uint32(settings, FreeRDP_GatewayUsageMethod);
+ file->GatewayCredentialsSource =
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayCredentialsSource);
+ file->PromptCredentialOnce =
+ freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials);
+ file->PromptForCredentials = freerdp_settings_get_bool(settings, FreeRDP_PromptForCredentials);
+ file->RemoteApplicationMode =
+ freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode);
+ if (!FILE_POPULATE_STRING(&file->GatewayAccessToken, settings, FreeRDP_GatewayAccessToken) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationProgram, settings,
+ FreeRDP_RemoteApplicationProgram) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationName, settings,
+ FreeRDP_RemoteApplicationName) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationIcon, settings,
+ FreeRDP_RemoteApplicationIcon) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationFile, settings,
+ FreeRDP_RemoteApplicationFile) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationGuid, settings,
+ FreeRDP_RemoteApplicationGuid) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationCmdLine, settings,
+ FreeRDP_RemoteApplicationCmdLine))
+ return FALSE;
+ file->SpanMonitors = freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors);
+ file->UseMultiMon = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon);
+ file->AllowDesktopComposition =
+ freerdp_settings_get_bool(settings, FreeRDP_AllowDesktopComposition);
+ file->AllowFontSmoothing = freerdp_settings_get_bool(settings, FreeRDP_AllowFontSmoothing);
+ file->DisableWallpaper = freerdp_settings_get_bool(settings, FreeRDP_DisableWallpaper);
+ file->DisableFullWindowDrag =
+ freerdp_settings_get_bool(settings, FreeRDP_DisableFullWindowDrag);
+ file->DisableMenuAnims = freerdp_settings_get_bool(settings, FreeRDP_DisableMenuAnims);
+ file->DisableThemes = freerdp_settings_get_bool(settings, FreeRDP_DisableThemes);
+ file->BandwidthAutoDetect =
+ (freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType) >= 7) ? TRUE : FALSE;
+ file->NetworkAutoDetect =
+ freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ? 1 : 0;
+ file->AutoReconnectionEnabled =
+ freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ file->RedirectSmartCards = freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards);
+ file->RedirectWebauthN = freerdp_settings_get_bool(settings, FreeRDP_RedirectWebAuthN);
+
+ redirectCameras =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "device:");
+ if (redirectCameras)
+ {
+ char* str =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "encode:");
+ file->EncodeRedirectedVideoCapture = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val < UINT32_MAX) && (errno == 0))
+ file->EncodeRedirectedVideoCapture = val;
+ }
+ free(str);
+
+ str = freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "quality:");
+ file->RedirectedVideoCaptureEncodingQuality = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val <= 2) && (errno == 0))
+ {
+ file->RedirectedVideoCaptureEncodingQuality = val;
+ }
+ }
+ free(str);
+
+ file->RedirectCameras = redirectCameras;
+ }
+#ifdef CHANNEL_URBDRC_CLIENT
+ redirectUsb = freerdp_client_channel_args_to_string(settings, URBDRC_CHANNEL_NAME, "device:");
+ if (redirectUsb)
+ file->UsbDevicesToRedirect = redirectUsb;
+
+#endif
+ file->RedirectClipboard =
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) ? 1 : 0;
+ file->RedirectPrinters = freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters) ? 1 : 0;
+ file->RedirectDrives = freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ? 1 : 0;
+ file->RdgIsKdcProxy = freerdp_settings_get_bool(settings, FreeRDP_KerberosRdgIsProxy) ? 1 : 0;
+ file->RedirectComPorts = (freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectParallelPorts));
+ file->RedirectLocation =
+ freerdp_dynamic_channel_collection_find(settings, LOCATION_DVC_CHANNEL_NAME) ? TRUE : FALSE;
+ if (!FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect) ||
+ !FILE_POPULATE_STRING(&file->PreconnectionBlob, settings, FreeRDP_PreconnectionBlob) ||
+ !FILE_POPULATE_STRING(&file->KdcProxyName, settings, FreeRDP_KerberosKdcUrl))
+ return FALSE;
+
+ {
+ size_t offset = 0;
+ UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ const UINT32* MonitorIds = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ /* String size: 10 char UINT32 max string length, 1 char separator, one element NULL */
+ size_t size = count * (10 + 1) + 1;
+
+ char* str = calloc(size, sizeof(char));
+ for (UINT32 x = 0; x < count; x++)
+ {
+ int rc = _snprintf(&str[offset], size - offset, "%" PRIu32 ",", MonitorIds[x]);
+ if (rc <= 0)
+ {
+ free(str);
+ return FALSE;
+ }
+ offset += (size_t)rc;
+ }
+ if (offset > 0)
+ str[offset - 1] = '\0';
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ file->SelectedMonitors = str;
+ }
+
+ file->KeyboardHook = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardHook);
+
+ return TRUE;
+}
+
+BOOL freerdp_client_write_rdp_file(const rdpFile* file, const char* name, BOOL unicode)
+{
+ FILE* fp = NULL;
+ size_t size = 0;
+ char* buffer = NULL;
+ int status = 0;
+ WCHAR* unicodestr = NULL;
+
+ if (!file || !name)
+ return FALSE;
+
+ size = freerdp_client_write_rdp_file_buffer(file, NULL, 0);
+ if (size == 0)
+ return FALSE;
+ buffer = (char*)calloc((size_t)(size + 1), sizeof(char));
+
+ if (freerdp_client_write_rdp_file_buffer(file, buffer, (size_t)size + 1) != size)
+ {
+ WLog_ERR(TAG, "freerdp_client_write_rdp_file: error writing to output buffer");
+ free(buffer);
+ return FALSE;
+ }
+
+ fp = winpr_fopen(name, "w+b");
+
+ if (fp)
+ {
+ if (unicode)
+ {
+ size_t len = 0;
+ unicodestr = ConvertUtf8NToWCharAlloc(buffer, size, &len);
+
+ if (!unicodestr)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+
+ /* Write multi-byte header */
+ if ((fwrite(BOM_UTF16_LE, sizeof(BYTE), 2, fp) != 2) ||
+ (fwrite(unicodestr, sizeof(WCHAR), len, fp) != len))
+ {
+ free(buffer);
+ free(unicodestr);
+ fclose(fp);
+ return FALSE;
+ }
+
+ free(unicodestr);
+ }
+ else
+ {
+ if (fwrite(buffer, 1, (size_t)size, fp) != (size_t)size)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+ }
+
+ fflush(fp);
+ status = fclose(fp);
+ }
+
+ free(buffer);
+ return (status == 0) ? TRUE : FALSE;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+static SSIZE_T freerdp_client_write_setting_to_buffer(char** buffer, size_t* bufferSize,
+ WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ va_list ap;
+ SSIZE_T len = 0;
+ char* buf = NULL;
+ size_t bufSize = 0;
+
+ if (!buffer || !bufferSize || !fmt)
+ return -1;
+
+ buf = *buffer;
+ bufSize = *bufferSize;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf, bufSize, fmt, ap);
+ va_end(ap);
+ if (len < 0)
+ return -1;
+
+ /* _snprintf doesn't add the ending \0 to its return value */
+ ++len;
+
+ /* we just want to know the size - return it */
+ if (!buf && !bufSize)
+ return len;
+
+ if (!buf)
+ return -1;
+
+ /* update buffer size and buffer position and replace \0 with \n */
+ if (bufSize >= (size_t)len)
+ {
+ *bufferSize -= (size_t)len;
+ buf[len - 1] = '\n';
+ *buffer = buf + len;
+ }
+ else
+ return -1;
+
+ return len;
+}
+
+size_t freerdp_client_write_rdp_file_buffer(const rdpFile* file, char* buffer, size_t size)
+{
+ size_t totalSize = 0;
+
+ if (!file)
+ return 0;
+
+ /* either buffer and size are null or non-null */
+ if ((!buffer || !size) && (buffer || size))
+ return 0;
+
+#define WRITE_SETTING_(fmt_, ...) \
+ { \
+ SSIZE_T res = freerdp_client_write_setting_to_buffer(&buffer, &size, fmt_, __VA_ARGS__); \
+ if (res < 0) \
+ return 0; \
+ totalSize += (size_t)res; \
+ }
+
+#define WRITE_SETTING_INT(key_, param_) \
+ do \
+ { \
+ if (~(param_)) \
+ WRITE_SETTING_("%s:i:%" PRIu32, key_, param_) \
+ } while (0)
+
+#define WRITE_SETTING_STR(key_, param_) \
+ do \
+ { \
+ if (~(size_t)(param_)) \
+ WRITE_SETTING_("%s:s:%s", key_, param_) \
+ } while (0)
+
+ /* integer parameters */
+ WRITE_SETTING_INT(key_int_use_multimon, file->UseMultiMon);
+ WRITE_SETTING_INT(key_int_maximizetocurrentdisplays, file->MaximizeToCurrentDisplays);
+ WRITE_SETTING_INT(key_int_singlemoninwindowedmode, file->SingleMonInWindowedMode);
+ WRITE_SETTING_INT(key_int_screen_mode_id, file->ScreenModeId);
+ WRITE_SETTING_INT(key_int_span_monitors, file->SpanMonitors);
+ WRITE_SETTING_INT(key_int_smart_sizing, file->SmartSizing);
+ WRITE_SETTING_INT(key_int_dynamic_resolution, file->DynamicResolution);
+ WRITE_SETTING_INT(key_int_enablesuperpan, file->EnableSuperSpan);
+ WRITE_SETTING_INT(key_int_superpanaccelerationfactor, file->SuperSpanAccelerationFactor);
+ WRITE_SETTING_INT(key_int_desktopwidth, file->DesktopWidth);
+ WRITE_SETTING_INT(key_int_desktopheight, file->DesktopHeight);
+ WRITE_SETTING_INT(key_int_desktop_size_id, file->DesktopSizeId);
+ WRITE_SETTING_INT(key_int_session_bpp, file->SessionBpp);
+ WRITE_SETTING_INT(key_int_desktopscalefactor, file->DesktopScaleFactor);
+ WRITE_SETTING_INT(key_int_compression, file->Compression);
+ WRITE_SETTING_INT(key_int_keyboardhook, file->KeyboardHook);
+ WRITE_SETTING_INT(key_int_disable_ctrl_alt_del, file->DisableCtrlAltDel);
+ WRITE_SETTING_INT(key_int_audiomode, file->AudioMode);
+ WRITE_SETTING_INT(key_int_audioqualitymode, file->AudioQualityMode);
+ WRITE_SETTING_INT(key_int_audiocapturemode, file->AudioCaptureMode);
+ WRITE_SETTING_INT(key_int_encode_redirected_video_capture, file->EncodeRedirectedVideoCapture);
+ WRITE_SETTING_INT(key_int_redirected_video_capture_encoding_quality,
+ file->RedirectedVideoCaptureEncodingQuality);
+ WRITE_SETTING_INT(key_int_videoplaybackmode, file->VideoPlaybackMode);
+ WRITE_SETTING_INT(key_int_connection_type, file->ConnectionType);
+ WRITE_SETTING_INT(key_int_networkautodetect, file->NetworkAutoDetect);
+ WRITE_SETTING_INT(key_int_bandwidthautodetect, file->BandwidthAutoDetect);
+ WRITE_SETTING_INT(key_int_pinconnectionbar, file->PinConnectionBar);
+ WRITE_SETTING_INT(key_int_displayconnectionbar, file->DisplayConnectionBar);
+ WRITE_SETTING_INT(key_int_workspaceid, file->WorkspaceId);
+ WRITE_SETTING_INT(key_int_enableworkspacereconnect, file->EnableWorkspaceReconnect);
+ WRITE_SETTING_INT(key_int_disable_wallpaper, file->DisableWallpaper);
+ WRITE_SETTING_INT(key_int_allow_font_smoothing, file->AllowFontSmoothing);
+ WRITE_SETTING_INT(key_int_allow_desktop_composition, file->AllowDesktopComposition);
+ WRITE_SETTING_INT(key_int_disable_full_window_drag, file->DisableFullWindowDrag);
+ WRITE_SETTING_INT(key_int_disable_menu_anims, file->DisableMenuAnims);
+ WRITE_SETTING_INT(key_int_disable_themes, file->DisableThemes);
+ WRITE_SETTING_INT(key_int_disable_cursor_setting, file->DisableCursorSetting);
+ WRITE_SETTING_INT(key_int_bitmapcachesize, file->BitmapCacheSize);
+ WRITE_SETTING_INT(key_int_bitmapcachepersistenable, file->BitmapCachePersistEnable);
+ WRITE_SETTING_INT(key_int_server_port, file->ServerPort);
+ WRITE_SETTING_INT(key_int_redirectdrives, file->RedirectDrives);
+ WRITE_SETTING_INT(key_int_redirectprinters, file->RedirectPrinters);
+ WRITE_SETTING_INT(key_int_redirectcomports, file->RedirectComPorts);
+ WRITE_SETTING_INT(key_int_redirectlocation, file->RedirectLocation);
+ WRITE_SETTING_INT(key_int_redirectsmartcards, file->RedirectSmartCards);
+ WRITE_SETTING_INT(key_int_redirectclipboard, file->RedirectClipboard);
+ WRITE_SETTING_INT(key_int_redirectposdevices, file->RedirectPosDevices);
+ WRITE_SETTING_INT(key_int_redirectdirectx, file->RedirectDirectX);
+ WRITE_SETTING_INT(key_int_disableprinterredirection, file->DisablePrinterRedirection);
+ WRITE_SETTING_INT(key_int_disableclipboardredirection, file->DisableClipboardRedirection);
+ WRITE_SETTING_INT(key_int_connect_to_console, file->ConnectToConsole);
+ WRITE_SETTING_INT(key_int_administrative_session, file->AdministrativeSession);
+ WRITE_SETTING_INT(key_int_autoreconnection_enabled, file->AutoReconnectionEnabled);
+ WRITE_SETTING_INT(key_int_autoreconnect_max_retries, file->AutoReconnectMaxRetries);
+ WRITE_SETTING_INT(key_int_public_mode, file->PublicMode);
+ WRITE_SETTING_INT(key_int_authentication_level, file->AuthenticationLevel);
+ WRITE_SETTING_INT(key_int_promptcredentialonce, file->PromptCredentialOnce);
+ WRITE_SETTING_INT(key_int_prompt_for_credentials, file->PromptForCredentials);
+ WRITE_SETTING_INT(key_int_negotiate_security_layer, file->NegotiateSecurityLayer);
+ WRITE_SETTING_INT(key_int_enablecredsspsupport, file->EnableCredSSPSupport);
+ WRITE_SETTING_INT(key_int_enablerdsaadauth, file->EnableRdsAadAuth);
+ WRITE_SETTING_INT(key_int_remoteapplicationmode, file->RemoteApplicationMode);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandcmdline, file->RemoteApplicationExpandCmdLine);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandworkingdir,
+ file->RemoteApplicationExpandWorkingDir);
+ WRITE_SETTING_INT(key_int_disableconnectionsharing, file->DisableConnectionSharing);
+ WRITE_SETTING_INT(key_int_disableremoteappcapscheck, file->DisableRemoteAppCapsCheck);
+ WRITE_SETTING_INT(key_int_gatewayusagemethod, file->GatewayUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewayprofileusagemethod, file->GatewayProfileUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewaycredentialssource, file->GatewayCredentialsSource);
+ WRITE_SETTING_INT(key_int_use_redirection_server_name, file->UseRedirectionServerName);
+ WRITE_SETTING_INT(key_int_rdgiskdcproxy, file->RdgIsKdcProxy);
+ WRITE_SETTING_INT(key_int_redirectwebauthn, file->RedirectWebauthN);
+
+ /* string parameters */
+ WRITE_SETTING_STR(key_str_username, file->Username);
+ WRITE_SETTING_STR(key_str_domain, file->Domain);
+ WRITE_SETTING_STR(key_str_password, file->Password);
+ WRITE_SETTING_STR(key_str_full_address, file->FullAddress);
+ WRITE_SETTING_STR(key_str_alternate_full_address, file->AlternateFullAddress);
+ WRITE_SETTING_STR(key_str_usbdevicestoredirect, file->UsbDevicesToRedirect);
+ WRITE_SETTING_STR(key_str_camerastoredirect, file->RedirectCameras);
+ WRITE_SETTING_STR(key_str_loadbalanceinfo, file->LoadBalanceInfo);
+ WRITE_SETTING_STR(key_str_remoteapplicationname, file->RemoteApplicationName);
+ WRITE_SETTING_STR(key_str_remoteapplicationicon, file->RemoteApplicationIcon);
+ WRITE_SETTING_STR(key_str_remoteapplicationprogram, file->RemoteApplicationProgram);
+ WRITE_SETTING_STR(key_str_remoteapplicationfile, file->RemoteApplicationFile);
+ WRITE_SETTING_STR(key_str_remoteapplicationguid, file->RemoteApplicationGuid);
+ WRITE_SETTING_STR(key_str_remoteapplicationcmdline, file->RemoteApplicationCmdLine);
+ WRITE_SETTING_STR(key_str_alternate_shell, file->AlternateShell);
+ WRITE_SETTING_STR(key_str_shell_working_directory, file->ShellWorkingDirectory);
+ WRITE_SETTING_STR(key_str_gatewayhostname, file->GatewayHostname);
+ WRITE_SETTING_STR(key_str_resourceprovider, file->ResourceProvider);
+ WRITE_SETTING_STR(key_str_wvd, file->WvdEndpointPool);
+ WRITE_SETTING_STR(key_str_geo, file->geo);
+ WRITE_SETTING_STR(key_str_armpath, file->armpath);
+ WRITE_SETTING_STR(key_str_aadtenantid, file->aadtenantid);
+ WRITE_SETTING_STR(key_str_diagnosticserviceurl, file->diagnosticserviceurl);
+ WRITE_SETTING_STR(key_str_hubdiscoverygeourl, file->hubdiscoverygeourl);
+ WRITE_SETTING_STR(key_str_activityhint, file->activityhint);
+ WRITE_SETTING_STR(key_str_gatewayaccesstoken, file->GatewayAccessToken);
+ WRITE_SETTING_STR(key_str_kdcproxyname, file->KdcProxyName);
+ WRITE_SETTING_STR(key_str_drivestoredirect, file->DrivesToRedirect);
+ WRITE_SETTING_STR(key_str_devicestoredirect, file->DevicesToRedirect);
+ WRITE_SETTING_STR(key_str_winposstr, file->WinPosStr);
+ WRITE_SETTING_STR(key_str_pcb, file->PreconnectionBlob);
+ WRITE_SETTING_STR(key_str_selectedmonitors, file->SelectedMonitors);
+
+ /* custom parameters */
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ SSIZE_T res = -1;
+ const rdpFileLine* curLine = &file->lines[i];
+
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:i:%" PRIu32,
+ curLine->name, (UINT32)curLine->iValue);
+ else if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:s:%s", curLine->name,
+ curLine->sValue);
+ if (res < 0)
+ return 0;
+
+ totalSize += (size_t)res;
+ }
+
+ return totalSize;
+}
+
+static ADDIN_ARGV* rdp_file_to_args(const char* channel, const char* values)
+{
+ size_t count = 0;
+ char** p = NULL;
+ ADDIN_ARGV* args = freerdp_addin_argv_new(0, NULL);
+ if (!args)
+ return NULL;
+ if (!freerdp_addin_argv_add_argument(args, channel))
+ goto fail;
+
+ p = CommandLineParseCommaSeparatedValues(values, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL rc = 0;
+ const char* val = p[x];
+ const size_t len = strlen(val) + 8;
+ char* str = calloc(len, sizeof(char));
+ if (!str)
+ goto fail;
+
+ _snprintf(str, len, "device:%s", val);
+ rc = freerdp_addin_argv_add_argument(args, str);
+ free(str);
+ if (!rc)
+ goto fail;
+ }
+ free(p);
+ return args;
+
+fail:
+ free(p);
+ freerdp_addin_argv_free(args);
+ return NULL;
+}
+
+BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file, rdpSettings* settings)
+{
+ BOOL setDefaultConnectionType = TRUE;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (~((size_t)file->Domain))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, file->Domain))
+ return FALSE;
+ }
+
+ if (~((size_t)file->Username))
+ {
+ char* user = NULL;
+ char* domain = NULL;
+
+ if (!freerdp_parse_username(file->Username, &user, &domain))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return FALSE;
+
+ if (!(~((size_t)file->Domain)) && domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, domain))
+ return FALSE;
+ }
+
+ free(user);
+ free(domain);
+ }
+
+ if (~((size_t)file->Password))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, file->Password))
+ return FALSE;
+ }
+
+ {
+ const char* address = NULL;
+
+ /* With MSTSC alternate full address always wins,
+ * so mimic this. */
+ if (~((size_t)file->AlternateFullAddress))
+ address = file->AlternateFullAddress;
+ else if (~((size_t)file->FullAddress))
+ address = file->FullAddress;
+
+ if (address)
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(address, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+ }
+
+ if (~file->ServerPort)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, file->ServerPort))
+ return FALSE;
+ }
+
+ if (~file->DesktopSizeId)
+ {
+ switch (file->DesktopSizeId)
+ {
+ case 0:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 640))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 480))
+ return FALSE;
+ break;
+ case 1:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600))
+ return FALSE;
+ break;
+ case 2:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1024))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 768))
+ return FALSE;
+ break;
+ case 3:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1280))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1024))
+ return FALSE;
+ break;
+ case 4:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1600))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1200))
+ return FALSE;
+ break;
+ default:
+ WLog_WARN(TAG, "Unsupported 'desktop size id' value %" PRIu32, file->DesktopSizeId);
+ break;
+ }
+ }
+ if (~file->DesktopWidth)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, file->DesktopWidth))
+ return FALSE;
+ }
+
+ if (~file->DesktopHeight)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, file->DesktopHeight))
+ return FALSE;
+ }
+
+ if (~file->SessionBpp)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, file->SessionBpp))
+ return FALSE;
+ }
+
+ if (~file->ConnectToConsole)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->ConnectToConsole != 0))
+ return FALSE;
+ }
+
+ if (~file->AdministrativeSession)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->AdministrativeSession != 0))
+ return FALSE;
+ }
+
+ if (~file->NegotiateSecurityLayer)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer,
+ file->NegotiateSecurityLayer != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableCredSSPSupport)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity,
+ file->EnableCredSSPSupport != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableRdsAadAuth)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AadSecurity, file->EnableRdsAadAuth != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->AlternateShell))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, file->AlternateShell))
+ return FALSE;
+ }
+
+ if (~((size_t)file->ShellWorkingDirectory))
+ {
+ /* ShellWorkingDir is used for either, shell working dir or remote app working dir */
+ FreeRDP_Settings_Keys_String targetId =
+ (~file->RemoteApplicationMode && file->RemoteApplicationMode != 0)
+ ? FreeRDP_RemoteApplicationWorkingDir
+ : FreeRDP_ShellWorkingDirectory;
+
+ if (!freerdp_settings_set_string(settings, targetId, file->ShellWorkingDirectory))
+ return FALSE;
+ }
+
+ if (~file->ScreenModeId)
+ {
+ /**
+ * Screen Mode Id:
+ * http://technet.microsoft.com/en-us/library/ff393692/
+ *
+ * This setting corresponds to the selection in the Display
+ * configuration slider on the Display tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 1: The remote session will appear in a window.
+ * 2: The remote session will appear full screen.
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen,
+ (file->ScreenModeId == 2) ? TRUE : FALSE))
+ return FALSE;
+ }
+
+ if (~(file->SmartSizing))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing,
+ (file->SmartSizing == 1) ? TRUE : FALSE))
+ return FALSE;
+ /**
+ * SmartSizingWidth and SmartSizingHeight:
+ *
+ * Adding this option to use the DesktopHeight and DesktopWidth as
+ * parameters for the SmartSizingWidth and SmartSizingHeight, as there
+ * are no options for that in standard RDP files.
+ *
+ * Equivalent of doing /smart-sizing:WxH
+ */
+ if (((~(file->DesktopWidth) && ~(file->DesktopHeight)) || ~(file->DesktopSizeId)) &&
+ (file->SmartSizing == 1))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth,
+ file->DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight,
+ file->DesktopHeight))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->LoadBalanceInfo))
+ {
+ const size_t len = strlen(file->LoadBalanceInfo);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo,
+ file->LoadBalanceInfo, len))
+ return FALSE;
+ }
+
+ if (~file->AuthenticationLevel)
+ {
+ /**
+ * Authentication Level:
+ * http://technet.microsoft.com/en-us/library/ff393709/
+ *
+ * This setting corresponds to the selection in the If server authentication
+ * fails drop-down list on the Advanced tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 0: If server authentication fails, connect to the computer without warning (Connect and
+ * don’t warn me). 1: If server authentication fails, do not establish a connection (Do not
+ * connect). 2: If server authentication fails, show a warning and allow me to connect or
+ * refuse the connection (Warn me). 3: No authentication requirement is specified.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel,
+ file->AuthenticationLevel))
+ return FALSE;
+ }
+
+ if (~file->ConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, file->ConnectionType))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+
+ if (~file->AudioMode)
+ {
+ switch (file->AudioMode)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_NONE:
+ default:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ break;
+ }
+ }
+
+ if (~file->AudioCaptureMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, file->AudioCaptureMode != 0))
+ return FALSE;
+ }
+
+ if (~file->Compression)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled,
+ file->Compression != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayHostname))
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(file->GatewayHostname, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GatewayPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->ResourceProvider))
+ {
+ if (_stricmp(file->ResourceProvider, str_resourceprovider_arm) == 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->WvdEndpointPool))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdWvdEndpointPool,
+ file->WvdEndpointPool))
+ return FALSE;
+ }
+
+ if (~((size_t)file->geo))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdGeo, file->geo))
+ return FALSE;
+ }
+
+ if (~((size_t)file->armpath))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdArmpath, file->armpath))
+ return FALSE;
+ }
+
+ if (~((size_t)file->aadtenantid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdAadtenantid,
+ file->aadtenantid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->diagnosticserviceurl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ file->diagnosticserviceurl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->hubdiscoverygeourl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ file->hubdiscoverygeourl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->activityhint))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdActivityhint,
+ file->activityhint))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayAccessToken))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken,
+ file->GatewayAccessToken))
+ return FALSE;
+ }
+
+ if (~file->GatewayUsageMethod)
+ {
+ if (!freerdp_set_gateway_usage_method(settings, file->GatewayUsageMethod))
+ return FALSE;
+ }
+
+ if (~file->PromptCredentialOnce)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials,
+ file->PromptCredentialOnce != 0))
+ return FALSE;
+ }
+
+ if (~file->PromptForCredentials)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PromptForCredentials,
+ file->PromptForCredentials != 0))
+ return FALSE;
+ }
+
+ if (~file->RemoteApplicationMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode,
+ file->RemoteApplicationMode != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationProgram))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram,
+ file->RemoteApplicationProgram))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName,
+ file->RemoteApplicationName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationIcon))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon,
+ file->RemoteApplicationIcon))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationFile))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile,
+ file->RemoteApplicationFile))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationGuid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid,
+ file->RemoteApplicationGuid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationCmdLine))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ file->RemoteApplicationCmdLine))
+ return FALSE;
+ }
+
+ if (~file->SpanMonitors)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, file->SpanMonitors != 0))
+ return FALSE;
+ }
+
+ if (~file->UseMultiMon)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, file->UseMultiMon != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowFontSmoothing)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing,
+ file->AllowFontSmoothing != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableWallpaper)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper,
+ file->DisableWallpaper != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableFullWindowDrag)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag,
+ file->DisableFullWindowDrag != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableMenuAnims)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims,
+ file->DisableMenuAnims != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableThemes)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, file->DisableThemes != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowDesktopComposition)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition,
+ file->AllowDesktopComposition != 0))
+ return FALSE;
+ }
+
+ if (~file->BitmapCachePersistEnable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ file->BitmapCachePersistEnable != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableRemoteAppCapsCheck)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableRemoteAppCapsCheck,
+ file->DisableRemoteAppCapsCheck != 0))
+ return FALSE;
+ }
+
+ if (~file->BandwidthAutoDetect)
+ {
+ if (file->BandwidthAutoDetect != 0)
+ {
+ if ((~file->NetworkAutoDetect) && (file->NetworkAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to networkautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(TAG,
+ "Add networkautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->NetworkAutoDetect)
+ {
+ if (file->NetworkAutoDetect != 0)
+ {
+ if ((~file->BandwidthAutoDetect) && (file->BandwidthAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to bandwidthautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(
+ TAG, "Add bandwidthautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectionEnabled)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled,
+ file->AutoReconnectionEnabled != 0))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectMaxRetries)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ file->AutoReconnectMaxRetries))
+ return FALSE;
+ }
+
+ if (~file->RedirectSmartCards)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards,
+ file->RedirectSmartCards != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectWebauthN)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectWebAuthN,
+ file->RedirectWebauthN != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectClipboard)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ file->RedirectClipboard != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPrinters)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters,
+ file->RedirectPrinters != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectDrives)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, file->RedirectDrives != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPosDevices)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectComPorts)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectLocation)
+ {
+ size_t count = 0;
+ char** str =
+ CommandLineParseCommaSeparatedValuesEx(LOCATION_DVC_CHANNEL_NAME, NULL, &count);
+ const BOOL rc = freerdp_client_add_dynamic_channel(settings, count, str);
+ free(str);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (~file->RedirectDirectX)
+ {
+ /* What is this?! */
+ }
+
+ if (~((size_t)file->DevicesToRedirect))
+ {
+ /**
+ * Devices to redirect:
+ * http://technet.microsoft.com/en-us/library/ff393728/
+ *
+ * This setting corresponds to the selections for Other supported Plug and Play
+ * (PnP) devices under More on the Local Resources tab under Options in RDC.
+ *
+ * Values:
+ *
+ * '*':
+ * Redirect all supported Plug and Play devices.
+ *
+ * 'DynamicDevices':
+ * Redirect any supported Plug and Play devices that are connected later.
+ *
+ * The hardware ID for the supported Plug and Play device:
+ * Redirect the specified supported Plug and Play device.
+ *
+ * Examples:
+ * devicestoredirect:s:*
+ * devicestoredirect:s:DynamicDevices
+ * devicestoredirect:s:USB\VID_04A9&PID_30C1\6&4BD985D&0&2;,DynamicDevices
+ *
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->DrivesToRedirect))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_DrivesToRedirect,
+ file->DrivesToRedirect))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RedirectCameras))
+ {
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(RDPECAM_DVC_CHANNEL_NAME, file->RedirectCameras);
+ if (!args)
+ return FALSE;
+
+ if (~file->EncodeRedirectedVideoCapture)
+ {
+ char encode[64];
+ _snprintf(encode, sizeof(encode), "encode:%" PRIu32,
+ file->EncodeRedirectedVideoCapture);
+ freerdp_addin_argv_add_argument(args, encode);
+ }
+ if (~file->RedirectedVideoCaptureEncodingQuality)
+ {
+ char quality[64];
+ _snprintf(quality, sizeof(quality), "quality:%" PRIu32,
+ file->RedirectedVideoCaptureEncodingQuality);
+ freerdp_addin_argv_add_argument(args, quality);
+ }
+
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(
+ TAG,
+ "This build does not support [MS-RDPECAM] camera redirection channel. Ignoring '%s'",
+ key_str_camerastoredirect);
+#endif
+ }
+
+ if (~((size_t)file->UsbDevicesToRedirect))
+ {
+#ifdef CHANNEL_URBDRC_CLIENT
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(URBDRC_CHANNEL_NAME, file->UsbDevicesToRedirect);
+ if (!args)
+ return FALSE;
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(TAG,
+ "This build does not support [MS-RDPEUSB] usb redirection channel. Ignoring '%s'",
+ key_str_usbdevicestoredirect);
+#endif
+ }
+
+ if (~file->KeyboardHook)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardHook, file->KeyboardHook))
+ return FALSE;
+ }
+
+ if (~(size_t)file->SelectedMonitors)
+ {
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(file->SelectedMonitors, &count);
+ UINT32* list = NULL;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(args);
+ return FALSE;
+ }
+ list = freerdp_settings_get_pointer_writable(settings, FreeRDP_MonitorIds);
+ if (!list && (count > 0))
+ {
+ free(args);
+ return FALSE;
+ }
+ for (size_t x = 0; x < count; x++)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(args[x], NULL, 0);
+ if ((val >= UINT32_MAX) && (errno != 0))
+ {
+ free(args);
+ free(list);
+ return FALSE;
+ }
+ list[x] = val;
+ }
+ free(args);
+ }
+
+ if (~file->DynamicResolution)
+ {
+ const BOOL val = file->DynamicResolution != 0;
+ if (val)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, val))
+ return FALSE;
+ }
+
+ if (~file->DesktopScaleFactor)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor,
+ file->DesktopScaleFactor))
+ return FALSE;
+ }
+
+ if (~file->VideoPlaybackMode)
+ {
+ if (file->VideoPlaybackMode != 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, TRUE))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, FALSE))
+ return FALSE;
+ }
+ }
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+
+ if (~((size_t)file->PreconnectionBlob))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob,
+ file->PreconnectionBlob) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->KdcProxyName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKdcUrl, file->KdcProxyName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RdgIsKdcProxy))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_KerberosRdgIsProxy,
+ file->RdgIsKdcProxy != 0))
+ return FALSE;
+ }
+
+ if (file->args->argc > 1)
+ {
+ WCHAR* ConnectionFile =
+ freerdp_settings_get_string_as_utf16(settings, FreeRDP_ConnectionFile, NULL);
+
+ if (freerdp_client_settings_parse_command_line(settings, file->args->argc, file->args->argv,
+ FALSE) < 0)
+ {
+ free(ConnectionFile);
+ return FALSE;
+ }
+
+ BOOL rc = freerdp_settings_set_string_from_utf16(settings, FreeRDP_ConnectionFile,
+ ConnectionFile);
+ free(ConnectionFile);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (setDefaultConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file, const char* name)
+{
+ BOOL bFound = FALSE;
+ rdpFileLine* line = NULL;
+
+ for (size_t index = 0; index < file->lineCount; index++)
+ {
+ line = &(file->lines[index]);
+
+ if (line->flags & RDP_FILE_LINE_FLAG_FORMATTED)
+ {
+ if (_stricmp(name, line->name) == 0)
+ {
+ bFound = TRUE;
+ break;
+ }
+ }
+ }
+
+ return (bFound) ? line : NULL;
+}
+/**
+ * Set a string option to a rdpFile
+ * @param file rdpFile
+ * @param name name of the option
+ * @param value value of the option
+ * @return 0 on success
+ */
+int freerdp_client_rdp_file_set_string_option(rdpFile* file, const char* name, const char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+const char* freerdp_client_rdp_file_get_string_option(const rdpFile* file, const char* name)
+{
+ LPSTR* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_string_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(size_t)(*value))
+ return *value;
+ if (line)
+ return line->sValue;
+ }
+
+ return NULL;
+}
+
+int freerdp_client_rdp_file_set_integer_option(rdpFile* file, const char* name, int value)
+{
+ return freerdp_client_rdp_file_set_integer(file, name, value);
+}
+
+int freerdp_client_rdp_file_get_integer_option(const rdpFile* file, const char* name)
+{
+ DWORD* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_integer_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(*value))
+ return *value;
+ if (line)
+ return (int)line->iValue;
+ }
+
+ return -1;
+}
+
+static void freerdp_client_file_string_check_free(LPSTR str)
+{
+ if (~((size_t)str))
+ free(str);
+}
+
+rdpFile* freerdp_client_rdp_file_new(void)
+{
+ return freerdp_client_rdp_file_new_ex(0);
+}
+
+rdpFile* freerdp_client_rdp_file_new_ex(DWORD flags)
+{
+ rdpFile* file = (rdpFile*)calloc(1, sizeof(rdpFile));
+
+ if (!file)
+ return NULL;
+
+ file->flags = flags;
+
+ FillMemory(file, sizeof(rdpFile), 0xFF);
+ file->lines = NULL;
+ file->lineCount = 0;
+ file->lineSize = 32;
+ file->GatewayProfileUsageMethod = 1;
+ file->lines = (rdpFileLine*)calloc(file->lineSize, sizeof(rdpFileLine));
+
+ file->args = freerdp_addin_argv_new(0, NULL);
+ if (!file->lines || !file->args)
+ goto fail;
+
+ if (!freerdp_client_add_option(file, "freerdp"))
+ goto fail;
+
+ return file;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_client_rdp_file_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+void freerdp_client_rdp_file_free(rdpFile* file)
+{
+ if (file)
+ {
+ if (file->lineCount)
+ {
+ for (size_t i = 0; i < file->lineCount; i++)
+ {
+ free(file->lines[i].name);
+ free(file->lines[i].sValue);
+ }
+ }
+ free(file->lines);
+
+ freerdp_addin_argv_free(file->args);
+
+ freerdp_client_file_string_check_free(file->Username);
+ freerdp_client_file_string_check_free(file->Domain);
+ freerdp_client_file_string_check_free(file->Password);
+ freerdp_client_file_string_check_free(file->FullAddress);
+ freerdp_client_file_string_check_free(file->AlternateFullAddress);
+ freerdp_client_file_string_check_free(file->UsbDevicesToRedirect);
+ freerdp_client_file_string_check_free(file->RedirectCameras);
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ freerdp_client_file_string_check_free(file->LoadBalanceInfo);
+ freerdp_client_file_string_check_free(file->RemoteApplicationName);
+ freerdp_client_file_string_check_free(file->RemoteApplicationIcon);
+ freerdp_client_file_string_check_free(file->RemoteApplicationProgram);
+ freerdp_client_file_string_check_free(file->RemoteApplicationFile);
+ freerdp_client_file_string_check_free(file->RemoteApplicationGuid);
+ freerdp_client_file_string_check_free(file->RemoteApplicationCmdLine);
+ freerdp_client_file_string_check_free(file->AlternateShell);
+ freerdp_client_file_string_check_free(file->ShellWorkingDirectory);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ freerdp_client_file_string_check_free(file->GatewayAccessToken);
+ freerdp_client_file_string_check_free(file->KdcProxyName);
+ freerdp_client_file_string_check_free(file->DrivesToRedirect);
+ freerdp_client_file_string_check_free(file->DevicesToRedirect);
+ freerdp_client_file_string_check_free(file->WinPosStr);
+ freerdp_client_file_string_check_free(file->ResourceProvider);
+ freerdp_client_file_string_check_free(file->WvdEndpointPool);
+ freerdp_client_file_string_check_free(file->geo);
+ freerdp_client_file_string_check_free(file->armpath);
+ freerdp_client_file_string_check_free(file->aadtenantid);
+ freerdp_client_file_string_check_free(file->diagnosticserviceurl);
+ freerdp_client_file_string_check_free(file->hubdiscoverygeourl);
+ freerdp_client_file_string_check_free(file->activityhint);
+ free(file);
+ }
+}
+
+void freerdp_client_rdp_file_set_callback_context(rdpFile* file, void* context)
+{
+ file->context = context;
+}
diff --git a/client/common/geometry.c b/client/common/geometry.c
new file mode 100644
index 0000000..83347ea
--- /dev/null
+++ b/client/common/geometry.c
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Geometry tracking Virtual Channel Extension
+ *
+ * Copyright 2017 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 <freerdp/config.h>
+
+#include <freerdp/client/geometry.h>
+#include <winpr/interlocked.h>
+
+void mappedGeometryRef(MAPPED_GEOMETRY* g)
+{
+ InterlockedIncrement(&g->refCounter);
+}
+
+void mappedGeometryUnref(MAPPED_GEOMETRY* g)
+{
+ if (!g)
+ return;
+
+ if (InterlockedDecrement(&g->refCounter))
+ return;
+
+ g->MappedGeometryUpdate = NULL;
+ g->MappedGeometryClear = NULL;
+ g->custom = NULL;
+ free(g->geometry.rects);
+ free(g);
+}
diff --git a/client/common/man/CMakeLists.txt b/client/common/man/CMakeLists.txt
new file mode 100644
index 0000000..b601f1d
--- /dev/null
+++ b/client/common/man/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_executable(generate_argument_docbook
+ generate_argument_docbook.c
+)
diff --git a/client/common/man/generate_argument_docbook.c b/client/common/man/generate_argument_docbook.c
new file mode 100644
index 0000000..156d809
--- /dev/null
+++ b/client/common/man/generate_argument_docbook.c
@@ -0,0 +1,210 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "../cmdline.h"
+
+static char* resize(char** buffer, size_t* size, size_t increment)
+{
+ const size_t nsize = *size + increment;
+ char* tmp = realloc(*buffer, nsize);
+ if (!tmp)
+ {
+ fprintf(stderr, "Could not reallocate string buffer from %" PRIuz " to %" PRIuz " bytes.\n",
+ *size, nsize);
+ free(*buffer);
+ }
+ memset(&tmp[*size], '\0', increment);
+ *size = nsize;
+ *buffer = tmp;
+ return tmp;
+}
+
+static char* append(char** buffer, size_t* size, const char* str)
+{
+ const size_t len = strnlen(*buffer, *size);
+ const size_t add = strlen(str);
+ const size_t required = len + add + 1;
+
+ if (required > *size)
+ {
+ if (!resize(buffer, size, required - *size))
+ return NULL;
+ }
+ strcat(*buffer, str);
+ return *buffer;
+}
+
+static LPSTR tr_esc_str(LPCSTR arg, bool format)
+{
+ const char* str = NULL;
+ LPSTR tmp = NULL;
+ size_t ds = 0;
+
+ if (NULL == arg)
+ return NULL;
+
+ const size_t s = strlen(arg) + 1;
+ if (!resize(&tmp, &ds, s))
+ exit(-2);
+
+ for (size_t x = 0; x < s; x++)
+ {
+ char data[2] = { 0 };
+ switch (arg[x])
+ {
+ case '<':
+ if (format)
+ str = "<replaceable>";
+ else
+ str = "&lt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-3);
+ break;
+
+ case '>':
+ if (format)
+ str = "</replaceable>";
+ else
+ str = "&gt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-4);
+ break;
+
+ case '\'':
+ if (!append(&tmp, &ds, "&apos;"))
+ exit(-5);
+ break;
+
+ case '"':
+ if (!append(&tmp, &ds, "&quot;"))
+ exit(-6);
+ break;
+
+ case '&':
+ if (!append(&tmp, &ds, "&amp;"))
+ exit(-6);
+ break;
+
+ case '\r':
+ case '\n':
+ if (!append(&tmp, &ds, "<sbr/>"))
+ exit(-7);
+ break;
+
+ default:
+ data[0] = arg[x];
+ if (!append(&tmp, &ds, data))
+ exit(-8);
+ break;
+ }
+ }
+
+ return tmp;
+}
+
+int main(int argc, char* argv[])
+{
+ size_t elements = sizeof(global_cmd_args) / sizeof(global_cmd_args[0]);
+ const char* fname = "freerdp-argument.1.xml";
+
+ fprintf(stdout, "Generating docbook file '%s'\n", fname);
+ FILE* fp = fopen(fname, "w");
+ if (NULL == fp)
+ {
+ fprintf(stderr, "Could not open '%s' for writing.\n", fname);
+ return -1;
+ }
+
+ /* The tag used as header in the manpage */
+ fprintf(fp, "<refsect1>\n");
+ fprintf(fp, "\t<title>Options</title>\n");
+ fprintf(fp, "\t\t<variablelist>\n");
+
+ /* Iterate over argument struct and write data to docbook 4.5
+ * compatible XML */
+ if (elements < 2)
+ {
+ fprintf(stderr, "The argument array 'args' is empty, writing an empty file.\n");
+ elements = 1;
+ }
+
+ for (size_t x = 0; x < elements - 1; x++)
+ {
+ const COMMAND_LINE_ARGUMENT_A* arg = &global_cmd_args[x];
+ char* name = tr_esc_str(arg->Name, FALSE);
+ char* alias = tr_esc_str(arg->Alias, FALSE);
+ char* format = tr_esc_str(arg->Format, TRUE);
+ char* text = tr_esc_str(arg->Text, FALSE);
+ fprintf(fp, "\t\t\t<varlistentry>\n");
+
+ do
+ {
+ fprintf(fp, "\t\t\t\t<term><option>");
+
+ if (arg->Flags == COMMAND_LINE_VALUE_BOOL)
+ fprintf(fp, "%s", arg->Default ? "-" : "+");
+ else
+ fprintf(fp, "/");
+
+ fprintf(fp, "%s</option>", name);
+
+ if (format)
+ {
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "[");
+
+ fprintf(fp, ":%s", format);
+
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "]");
+ }
+
+ fprintf(fp, "</term>\n");
+
+ if (alias == name)
+ break;
+
+ free(name);
+ name = alias;
+ } while (alias);
+
+ if (text)
+ {
+ fprintf(fp, "\t\t\t\t<listitem>\n");
+ fprintf(fp, "\t\t\t\t\t<para>");
+
+ if (text)
+ fprintf(fp, "%s", text);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL &&
+ (!arg->Default || arg->Default == BoolValueTrue))
+ fprintf(fp, " (default:%s)", arg->Default ? "on" : "off");
+ else if (arg->Default)
+ {
+ char* value = tr_esc_str(arg->Default, FALSE);
+ fprintf(fp, " (default:%s)", value);
+ free(value);
+ }
+
+ fprintf(fp, "</para>\n");
+ fprintf(fp, "\t\t\t\t</listitem>\n");
+ }
+
+ fprintf(fp, "\t\t\t</varlistentry>\n");
+ free(name);
+ free(format);
+ free(text);
+ }
+
+ fprintf(fp, "\t\t</variablelist>\n");
+ fprintf(fp, "\t</refsect1>\n");
+ fclose(fp);
+
+ fprintf(stdout, "successfully generated '%s'\n", fname);
+ return 0;
+}
diff --git a/client/common/smartcard_cli.c b/client/common/smartcard_cli.c
new file mode 100644
index 0000000..2832e92
--- /dev/null
+++ b/client/common/smartcard_cli.c
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard client functions
+ *
+ * Copyright 2021 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/client/utils/smartcard_cli.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+BOOL freerdp_smartcard_list(const rdpSettings* settings)
+{
+ SmartcardCertInfo** certs = NULL;
+ size_t count = 0;
+
+ if (!smartcard_enumerateCerts(settings, &certs, &count, FALSE))
+ return FALSE;
+
+ printf("smartcard reader detected, listing %" PRIuz " certificates:\n", count);
+ for (size_t i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* info = certs[i];
+ char asciiStr[256] = { 0 };
+
+ WINPR_ASSERT(info);
+
+ printf("%" PRIuz ": %s\n", i, info->subject);
+
+ if (ConvertWCharToUtf8(info->csp, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* CSP: %s\n", asciiStr);
+
+ if (ConvertWCharToUtf8(info->reader, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* reader: %s\n", asciiStr);
+#ifndef _WIN32
+ printf("\t* slotId: %" PRIu32 "\n", info->slotId);
+ printf("\t* pkinitArgs: %s\n", info->pkinitArgs);
+#endif
+ if (ConvertWCharToUtf8(info->containerName, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* containerName: %s\n", asciiStr);
+ if (info->upn)
+ printf("\t* UPN: %s\n", info->upn);
+ }
+ smartcardCertList_Free(certs, count);
+
+ return TRUE;
+}
diff --git a/client/common/test/CMakeLists.txt b/client/common/test/CMakeLists.txt
new file mode 100644
index 0000000..1e31f7c
--- /dev/null
+++ b/client/common/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestClient")
+set(MODULE_PREFIX "TEST_CLIENT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestClientRdpFile.c
+ TestClientChannels.c
+ TestClientCmdLine.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/common/test/TestClientChannels.c b/client/common/test/TestClientChannels.c
new file mode 100644
index 0000000..b15a734
--- /dev/null
+++ b/client/common/test/TestClientChannels.c
@@ -0,0 +1,87 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/rdpsnd.h>
+
+int TestClientChannels(int argc, char* argv[])
+{
+ DWORD dwFlags = 0;
+ FREERDP_ADDIN** ppAddins = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ dwFlags = FREERDP_ADDIN_DYNAMIC;
+
+ printf("Enumerate all\n");
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate rdpsnd\n");
+ ppAddins = freerdp_channels_list_addins(RDPSND_CHANNEL_NAME, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+#if defined(CHANNEL_TSMF_CLIENT)
+ printf("Enumerate tsmf video\n");
+ ppAddins = freerdp_channels_list_addins("tsmf", NULL, "video", dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+#endif
+
+ ppAddins = freerdp_channels_list_addins("unknown", NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate static addins\n");
+
+ dwFlags = FREERDP_ADDIN_STATIC;
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ return 0;
+}
diff --git a/client/common/test/TestClientCmdLine.c b/client/common/test/TestClientCmdLine.c
new file mode 100644
index 0000000..2ce0c47
--- /dev/null
+++ b/client/common/test/TestClientCmdLine.c
@@ -0,0 +1,263 @@
+#include <freerdp/client.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/settings.h>
+#include <winpr/cmdline.h>
+#include <winpr/spec.h>
+#include <winpr/strlst.h>
+#include <winpr/collections.h>
+
+typedef BOOL (*validate_settings_pr)(rdpSettings* settings);
+
+#define printref() printf("%s:%d: in function %-40s:", __FILE__, __LINE__, __func__)
+
+#define TEST_ERROR(format, ...) \
+ do \
+ { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ printref(); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+#define TEST_FAILURE(format, ...) \
+ do \
+ { \
+ printref(); \
+ printf(" FAILURE "); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+static void print_test_title(int argc, char** argv)
+{
+ printf("Running test:");
+
+ for (int i = 0; i < argc; i++)
+ {
+ printf(" %s", argv[i]);
+ }
+
+ printf("\n");
+}
+
+static INLINE BOOL testcase(const char* name, char** argv, size_t argc, int expected_return,
+ validate_settings_pr validate_settings)
+{
+ int status = 0;
+ BOOL valid_settings = TRUE;
+ rdpSettings* settings = freerdp_settings_new(0);
+ print_test_title(argc, argv);
+
+ if (!settings)
+ {
+ TEST_ERROR("Test %s could not allocate settings!\n", name);
+ return FALSE;
+ }
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+
+ if (validate_settings)
+ {
+ valid_settings = validate_settings(settings);
+ }
+
+ freerdp_settings_free(settings);
+
+ if (status == expected_return)
+ {
+ if (!valid_settings)
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ TEST_FAILURE("Expected status %d, got status %d\n", expected_return, status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#if defined(_WIN32)
+#define DRIVE_REDIRECT_PATH "c:\\Windows"
+#else
+#define DRIVE_REDIRECT_PATH "/tmp"
+#endif
+
+static BOOL check_settings_smartcard_no_redirection(rdpSettings* settings)
+{
+ BOOL result = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ TEST_FAILURE("Expected RedirectSmartCards = FALSE, but RedirectSmartCards = TRUE!\n");
+ result = FALSE;
+ }
+
+ if (freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ TEST_FAILURE("Expected no SMARTCARD device, but found at least one!\n");
+ result = FALSE;
+ }
+
+ return result;
+}
+
+typedef struct
+{
+ int expected_status;
+ validate_settings_pr validate_settings;
+ const char* command_line[128];
+ struct
+ {
+ int index;
+ const char* expected_value;
+ } modified_arguments[8];
+} test;
+
+static const test tests[] = {
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-version", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media," DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-u", "test", "-p", "test", "-v", "test.freerdp.com", 0 },
+ { { 4, "****" }, { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/u:test", "/p:test", "/v:test.freerdp.com", 0 },
+ { { 2, "/p:****" }, { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-invalid", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--invalid", 0 },
+ { { 0 } } },
+#if defined(WITH_FREERDP_DEPRECATED_CMDLINE)
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/kbd-list", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/monitor-list", 0 },
+ { { 0 } } },
+#endif
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:kbd", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:monitor", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media:" DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media,/foo/bar/blabla", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+
+#if 0
+ {
+ COMMAND_LINE_STATUS_PRINT, check_settings_smartcard_no_redirection,
+ {"testfreerdp", "-z", "--plugin", "cliprdr", "--plugin", "rdpsnd", "--data", "alsa", "latency:100", "--", "--plugin", "rdpdr", "--data", "disk:w7share:/home/w7share", "--", "--plugin", "drdynvc", "--data", "tsmf:decoder:gstreamer", "--", "-u", "test", "host.example.com", 0},
+ {{0}}
+ },
+#endif
+};
+
+static void check_modified_arguments(const test* test, char** command_line, int* rc)
+{
+ const char* expected_argument = NULL;
+
+ for (int k = 0; (expected_argument = test->modified_arguments[k].expected_value); k++)
+ {
+ int index = test->modified_arguments[k].index;
+ char* actual_argument = command_line[index];
+
+ if (0 != strcmp(actual_argument, expected_argument))
+ {
+ printref();
+ printf("Failure: overridden argument %d is %s but it should be %s\n", index,
+ actual_argument, expected_argument);
+ fflush(stdout);
+ *rc = -1;
+ }
+ }
+}
+
+int TestClientCmdLine(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++)
+ {
+ const test* current = &tests[i];
+ int failure = 0;
+ char** command_line = string_list_copy(current->command_line);
+
+ if (!testcase(__func__, command_line, string_list_length((const char* const*)command_line),
+ current->expected_status, current->validate_settings))
+ {
+ TEST_FAILURE("parsing arguments.\n");
+ failure = 1;
+ }
+
+ check_modified_arguments(current, command_line, &failure);
+
+ if (failure)
+ {
+ string_list_print(stdout, (const char* const*)command_line);
+ rc = -1;
+ }
+
+ string_list_free(command_line);
+ }
+
+ return rc;
+}
diff --git a/client/common/test/TestClientRdpFile.c b/client/common/test/TestClientRdpFile.c
new file mode 100644
index 0000000..e631bc8
--- /dev/null
+++ b/client/common/test/TestClientRdpFile.c
@@ -0,0 +1,600 @@
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/path.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/channels/rdpecam.h>
+
+static const BYTE testRdpFileUTF16[] = {
+ 0xff, 0xfe, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00,
+ 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x39, 0x00,
+ 0x32, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00,
+ 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x68, 0x00, 0x65, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00,
+ 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x30, 0x00, 0x38, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x73, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x20, 0x00, 0x62, 0x00, 0x70, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x33, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x70, 0x00,
+ 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x35, 0x00, 0x35, 0x00, 0x33, 0x00, 0x2c, 0x00,
+ 0x32, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x33, 0x00, 0x35, 0x00, 0x33, 0x00,
+ 0x2c, 0x00, 0x38, 0x00, 0x31, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00,
+ 0x65, 0x00, 0x79, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x6f, 0x00, 0x6b, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x70, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x76, 0x00,
+ 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x37, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74, 0x00, 0x77, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x6b, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x72, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x73, 0x00,
+ 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x70, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00,
+ 0x66, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6f, 0x00, 0x74, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00,
+ 0x77, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00,
+ 0x70, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00,
+ 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00,
+ 0x69, 0x00, 0x6d, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00,
+ 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x75, 0x00, 0x72, 0x00,
+ 0x73, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x63, 0x00,
+ 0x61, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x66, 0x00,
+ 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x61, 0x00, 0x64, 0x00, 0x64, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00,
+ 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x37, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x4d, 0x00,
+ 0x2d, 0x00, 0x30, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x62, 0x00, 0x31, 0x00,
+ 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x6c, 0x00,
+ 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x70, 0x00, 0x72, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x74, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x70, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00,
+ 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x64, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x20, 0x00, 0x66, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x63, 0x00, 0x75, 0x00, 0x72, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x73, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x79, 0x00,
+ 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00,
+ 0x41, 0x00, 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x32, 0x00, 0x4b, 0x00, 0x38, 0x00,
+ 0x52, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x57, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00,
+ 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x63, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00,
+ 0x6c, 0x00, 0x73, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x75, 0x00, 0x73, 0x00, 0x61, 0x00, 0x67, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x65, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x67, 0x00, 0x69, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00, 0x70, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x72, 0x00, 0x69, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x2a, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x42, 0x00,
+ 0x31, 0x00, 0x5c, 0x00, 0x4a, 0x00, 0x6f, 0x00, 0x68, 0x00, 0x6e, 0x00, 0x44, 0x00, 0x6f, 0x00,
+ 0x65, 0x00, 0x0d, 0x00, 0x0a, 0x00
+};
+
+#if defined(CHANNEL_RDPECAM_CLIENT)
+static const char* camera_args[] = { RDPECAM_DVC_CHANNEL_NAME,
+ "device:*",
+ "device:\\?\\usb#vid_0bda&pid_58b0&mi",
+ "device:-\\?\\usb#vid_0bdc&pid_58b1&mi",
+ "encode:1",
+ "quality:2" };
+#endif
+
+#if defined(CHANNEL_URBDRC_CLIENT)
+static const char* urbdrc_args[] = { "urbdrc", "device:*", "device:USBInstanceID:someid",
+ "device:{72631e54-78a4-11d0-bcf7-00aa00b7b32a}" };
+#endif
+
+static char testRdpFileUTF8[] =
+ "screen mode id:i:2\n"
+ "use multimon:i:0\n"
+ "desktopwidth:i:1920\n"
+ "desktopheight:i:1080\n"
+ "dynamic resolution:i:1080\n"
+ "desktopscalefactor:i:1080\n"
+ "redirected video capture encoding quality:i:2\n"
+ "encode redirected video capture:i:1\n"
+ "camerastoredirect:s:*,\\?\\usb#vid_0bda&pid_58b0&mi,-\\?\\usb#vid_0bdc&pid_58b1&mi\n"
+ "usbdevicestoredirect:s:*,USBInstanceID:someid,{72631e54-78a4-11d0-bcf7-00aa00b7b32a}\n"
+ "selectedmonitors:s:3,2,42,23"
+ "session bpp:i:32\n"
+ "winposstr:s:0,1,553,211,1353,811\n"
+ "compression:i:1\n"
+ "keyboardhook:i:2\n"
+ "audiocapturemode:i:0\n"
+ "videoplaybackmode:i:2\n"
+ "connection type:i:7\n"
+ "networkautodetect:i:1\n"
+ "bandwidthautodetect:i:1\n"
+ "displayconnectionbar:i:1\n"
+ "enableworkspacereconnect:i:0\n"
+ "disable wallpaper:i:0\n"
+ "allow font smoothing:i:0\n"
+ "allow desktop composition:i:0\n"
+ "disable full window drag:i:1\n"
+ "disable menu anims:i:1\n"
+ "disable themes:i:0\n"
+ "disable cursor setting:i:0\n"
+ "bitmapcachepersistenable:i:1\n"
+ "full address:s:LAB1-W7-DM-01.lab1.awake.local\n"
+ "alternate full address:s:LAB1-W7-DM-01.lab1.awake.global\n"
+ "audiomode:i:0\n"
+ "redirectprinters:i:1\n"
+ "redirectcomports:i:0\n"
+ "redirectsmartcards:i:1\n"
+ "redirectclipboard:i:1\n"
+ "redirectposdevices:i:0\n"
+ "autoreconnection enabled:i:1\n"
+ "authentication level:i:2\n"
+ "prompt for credentials:i:0\n"
+ "negotiate security layer:i:1\n"
+ "remoteapplicationmode:i:0\n"
+ "alternate shell:s:\n"
+ "shell working directory:s:\n"
+ "gatewayhostname:s:LAB1-W2K8R2-GW.lab1.awake.local\n"
+ "gatewayusagemethod:i:1\n"
+ "gatewaycredentialssource:i:0\n"
+ "gatewayprofileusagemethod:i:1\n"
+ "promptcredentialonce:i:1\n"
+ "use redirection server name:i:0\n"
+ "rdgiskdcproxy:i:0\n"
+ "kdcproxyname:s:\n"
+ "drivestoredirect:s:*\n"
+ "username:s:LAB1\\JohnDoe\n"
+ "vendor integer:i:123\n"
+ "vendor string:s:microsoft\n";
+
+static char* append(const char* fmt, ...)
+{
+ int rc = 0;
+ char* dst = NULL;
+ va_list ap;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ return NULL;
+ dst = malloc((size_t)rc + 1);
+ if (!dst)
+ return NULL;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(dst, (size_t)rc + 1, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ {
+ free(dst);
+ return NULL;
+ }
+ return dst;
+}
+
+int TestClientRdpFile(int argc, char* argv[])
+{
+ int rc = -1;
+ int iValue = 0;
+ UINT32 uValue = 0;
+ const UINT32* puValue = NULL;
+ const char* sValue = NULL;
+ char* utfname = NULL;
+ char* uniname = NULL;
+ char* base = NULL;
+ char* tmp = NULL;
+ UINT64 id = 0;
+ rdpFile* file = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ winpr_RAND(&id, sizeof(id));
+
+ /* Unicode */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, testRdpFileUTF16, sizeof(testRdpFileUTF16)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ goto fail;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ goto fail;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local");
+ goto fail;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ /* Ascii */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, (BYTE*)testRdpFileUTF8,
+ sizeof(testRdpFileUTF8)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ return -1;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ return -1;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "dynamic resolution");
+ if (iValue != 1080)
+ {
+ printf("dynamic resolution uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ printf("FreeRDP_DynamicResolutionUpdate has invalid value");
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "desktopscalefactor");
+ if (iValue != 1080)
+ {
+ printf("desktopscalefactor uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if ((INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor) != iValue)
+ {
+ printf("FreeRDP_DesktopScaleFactor has invalid value");
+ goto fail;
+ }
+
+ /* Check [MS-RDPECAM] related options */
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ {
+ ADDIN_ARGV* args = NULL;
+ iValue =
+ freerdp_client_rdp_file_get_integer_option(file, "encode redirected video capture");
+ if (iValue != 1)
+ {
+ printf("encode redirected video capture uses invalid default value %d", iValue);
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(
+ file, "redirected video capture encoding quality");
+ if (iValue != 2)
+ {
+ printf("redirected video capture encoding quality uses invalid default value %d",
+ iValue);
+ goto fail;
+ }
+ args = freerdp_dynamic_channel_collection_find(settings, RDPECAM_DVC_CHANNEL_NAME);
+ if (!args)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 6)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], camera_args[x]) != 0)
+ {
+ printf("rdpecam invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Check [URBDRC] related options */
+#if defined(CHANNEL_URBDRC_CLIENT)
+ {
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, "urbdrc");
+ if (!args)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 4)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], urbdrc_args[x]) != 0)
+ {
+ printf("urbdrc invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Validate selectedmonitors:s:3,2,42,23 */
+ uValue = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (uValue != 4)
+ {
+ printf("FreeRDP_NumMonitorIds has invalid value %" PRIu32, uValue);
+ goto fail;
+ }
+ puValue = (const UINT32*)freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, 0);
+ if (!puValue)
+ {
+ printf("FreeRDP_MonitorIds has invalid value %p", (const void*)puValue);
+ goto fail;
+ }
+ if ((puValue[0] != 3) || (puValue[1] != 2) || (puValue[2] != 42) || (puValue[3] != 23))
+ {
+ printf("FreeRDP_MonitorIds has invalid values: [%" PRIu32 ",%" PRIu32 ",%" PRIu32
+ ",%" PRIu32 "]",
+ puValue[0], puValue[1], puValue[2], puValue[3]);
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "videoplaybackmode");
+ if (iValue != 2)
+ {
+ printf("videoplaybackmode uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized))
+ {
+ printf("FreeRDP_SupportVideoOptimized has invalid value");
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportGeometryTracking))
+ {
+ printf("FreeRDP_SupportGeometryTracking has invalid value");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 123)
+ goto fail;
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "vendor integer", 456) == -1)
+ {
+ printf("failed to set integer: vendor integer");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 456)
+ return -1;
+
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "microsoft", 10) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "vendor string", "apple");
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "apple", 6) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "fruits", "banana,oranges");
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "numbers", 123456789) == -1)
+ {
+ printf("failed to set integer: numbers");
+ return -1;
+ }
+
+ freerdp_client_rdp_file_free(file);
+
+ tmp = GetKnownPath(KNOWN_PATH_TEMP);
+ if (!tmp)
+ goto fail;
+
+ base = append("%s/rdp-file-test-%" PRIx64, tmp, id);
+ if (!base)
+ goto fail;
+ if (!CreateDirectoryA(base, NULL))
+ goto fail;
+ utfname = append("%s/utfname", base);
+ uniname = append("%s/uniname", base);
+ file = freerdp_client_rdp_file_new();
+ if (!file || !utfname || !uniname)
+ goto fail;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, utfname, FALSE))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, uniname, TRUE))
+ goto fail;
+
+ rc = 0;
+fail:
+ if (utfname)
+ winpr_DeleteFile(utfname);
+ if (uniname)
+ winpr_DeleteFile(uniname);
+ if (base)
+ winpr_RemoveDirectory(base);
+ free(utfname);
+ free(uniname);
+ free(base);
+ free(tmp);
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ return rc;
+}
diff --git a/client/freerdp-client.pc.in b/client/freerdp-client.pc.in
new file mode 100644
index 0000000..eca4ab8
--- /dev/null
+++ b/client/freerdp-client.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-client@FREERDP_API_VERSION@
+
+Name: FreeRDP client
+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}