diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /client/X11 | |
parent | Initial commit. (diff) | |
download | freerdp3-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')
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:<device></command></term> + <listitem> + <para>Activate smartcard redirection for device <replaceable>device</replaceable></para> + </listitem> + </varlistentry> + <varlistentry> + <term><command>/printer:<device>,<driver></command></term> + <listitem> + <para>Activate printer redirection for printer <replaceable>device</replaceable> using driver <replaceable>driver</replaceable></para> + </listitem> + </varlistentry> + <varlistentry> + <term><command>/serial:<device></command></term> + <listitem> + <para>Activate serial port redirection for port <replaceable>device</replaceable></para> + </listitem> + </varlistentry> + <varlistentry> + <term><command>/parallel:<device></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, ¤t); + 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 */ |