summaryrefslogtreecommitdiffstats
path: root/client/X11
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /client/X11
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'client/X11')
-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
47 files changed, 15917 insertions, 0 deletions
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 */