From 1fb8d4d67c57d1f2a4b46a2ea6a32fed87f56dc3 Mon Sep 17 00:00:00 2001 From: Packit Date: Aug 19 2020 14:45:02 +0000 Subject: freerdp-2.0.0 base --- diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..77704ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,25 @@ +## Found a bug? - We would like to help you and smash the bug away. +1. __Please don't "report" questions as bugs.__ + * We are reachable via IRC _#freerdp on freenode_ + * We are reachable via mailing list + * Try our mailing list for discussions/questions +1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information. +1. If it's a __new__ bug - create a new issue. +1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting + +## To save time and help us identify the issue a bug report should at least contain the following: + * a useful description of the bug - "It's not working" isn't good enough - you must try harder ;) + * the steps to reproduce the bug + * command line you have used + * to what system did you connect to? (win8, 2008, ..) + * what did you expect to happen? + * what actually happened? + * freerdp version (e.g. xfreerdp --version) or package version or git commit + * freerdp configuration (e.g. xfreerdp --buildconfig) + * operating System, architecture, distribution e.g. linux, amd64, debian + * if you built it yourself add some notes which branch you have used, also your cmake parameters can help + * extra information helping us to find the bug + +## Please remove this text before submitting your issue! + +_Thank you for reporting a bug!_ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4d3c855 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,56 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Found a bug? - We would like to help you and smash the bug away.** +1. __Please don't "report" questions as bugs. For these (questions/build instructions/...) please use one of the following means of contact:__ + * We are reachable via IRC _#freerdp on freenode_ + * We are reachable via mailing list + * Try our mailing list for discussions/questions +1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information. +1. If it's a __new__ bug - create a new issue. +1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting + + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +** Application details +* Version of FreeRDP +* Command line used +* output of `/buildconfig` +* OS version connecting to +* If available the log output from a run with `/log-level:trace` + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. + +** Please remove this text before submitting your issue! + +_Thank you for reporting a bug!_ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f76bc12 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +## This is how are pull requests handled by FreeRDP +1. Every new pull request needs to build and pass the unit tests at https://ci.freerdp.com +1. At least 1 (better two) people need to review and test a pull request and agree to accept + +## Preparations before creating a pull +* Rebase your branch to current master, no merges allowed! +* Try to clean up your commit history, group changes to commits +* Check your formatting! A _astyle_ script can be found at ```./scripts/format_code.sh``` +* Optional (but higly recommended) + * Run a clang scanbuild before and after your changes to avoid introducing new bugs + * Run your compiler at pedantic level to check for new warnings + +## To ease accepting your contribution +* Give the pull request a proper name so people looking at it have an basic idea what it is for +* Add at least a brief description what it does (or should do :) and what it's good for +* Give instructions on how to test your changes +* Ideally add unit tests if adding new features + +## What you should be prepared for +* fix issues found during the review phase +* Joining IRC _#freerdp_ to talk to other developers or help them test your pull might accelerate acceptance +* Joining our mailing list may be helpful too. + +## Please remove this text before submitting your pull! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63a86e --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# CMake +CMakeFiles/ +CMakeScripts/ +CMakeCache.txt +config.h +install_manifest*.txt +CTestTestfile.cmake +*.pc +Makefile +Testing +cmake_install.cmake +CPackConfig.cmake +CPackSourceConfig.cmake +DartConfiguration.tcl +CMakeCPackOptions.cmake +_CPack_Packages +LICENSE.txt +/external/* +!external/README +*Config.cmake +*ConfigVersion.cmake +include/freerdp/version.h +include/freerdp/build-config.h +buildflags.h + +*.a.objlist.cmake +*.a.objlist +*.a.objdir +*_dummy.c +*_dummy.c.base + +# Eclipse +*.project +*.cproject +*.settings + +nbproject/ +compile_commands.json + +# .rdp files +*.rdp +*.RDP + +# Documentation +docs/api +client/X11/xfreerdp.1 +client/X11/xfreerdp.1.xml + +# Mac OS X +.DS_Store +*.xcodeproj/ +DerivedData/ + +# iOS +FreeRDP.build +Debug-* +Release-* + +# Windows +*.vcxproj +*.vcxproj.* +*.vcproj +*.vcproj.* +*.aps +*.sdf +*.sln +*.suo +*.ncb +*.opensdf +Thumbs.db +ipch +Debug +RelWithDebInfo +*.lib +*.exp +*.pdb +*.dll +*.ilk +*.resource.txt +*.embed.manifest* +*.intermediate.manifest* +version.rc + +# Binaries +*.a +*.o +*.so +*.so.* +*.dylib +bin +libs +cunit/test_freerdp +client/X11/xfreerdp +client/Mac/xcode +client/Sample/sfreerdp +client/DirectFB/dfreerdp +client/Wayland/wlfreerdp +server/Sample/sfreerdp-server +server/X11/xfreerdp-server +xcode +libfreerdp/codec/test/TestOpenH264ASM + +# Other +*~ +*.dir +Release +Win32 +build*/ +*.orig +*.msrcIncident + +default.log +*Amplifier XE* +*Inspector XE* + +*.cbp +*.txt.user + +*.autosave + +# etags +TAGS + +# generated packages +*.zip +*.exe +*.sh +*.deb +*.rpm +*.dmg +*.tar.Z +*.tar.gz + +# packaging related files +!packaging/scripts/prepare_deb_freerdp-nightly.sh +packaging/deb/freerdp-nightly/freerdp-nightly +packaging/deb/freerdp-nightly/freerdp-nightly-dev +packaging/deb/freerdp-nightly/freerdp-nightly-dbg +.source_version + +# +.idea + +# VisualStudio Code +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2dba29b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,54 @@ +sudo: required +dist: trusty + +os: linux + +language: c + +compiler: + - gcc + +matrix: + include: + - os: linux + compiler: gcc + - os: linux + compiler: clang + exclude: + - compiler: gcc + +addons: + apt: + packages: + - gdb + - libx11-dev + - libxrandr-dev + - libxi-dev + - libxv-dev + - libcups2-dev + - libxdamage-dev + - libxcursor-dev + - libxext-dev + - libxinerama-dev + - libxkbcommon-dev + - libxkbfile-dev + - libxml2-dev + - libasound2-dev + - libgstreamer1.0-dev + - libgstreamer-plugins-base1.0-dev + - libpulse-dev + - libpcsclite-dev + - libgsm1-dev + - libavcodec-dev + - libavutil-dev + - libx264-dev + - libxext-dev + +before_script: + - ulimit -c unlimited -S + +script: + - sudo hostname travis-ci.local + - cmake -G "Unix Makefiles" -C ci/cmake-preloads/config-linux-all.txt -D CMAKE_BUILD_TYPE=Debug -DWITH_LIBSYSTEMD=OFF -DWITH_WAYLAND=OFF . + - make + - make test diff --git a/CMakeCPack.cmake b/CMakeCPack.cmake new file mode 100644 index 0000000..ca749c0 --- /dev/null +++ b/CMakeCPack.cmake @@ -0,0 +1,102 @@ + +# Generate .txt license file for CPack (PackageMaker requires a file extension) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt @ONLY) + +SET(CPACK_BINARY_ZIP "ON") + +# Workaround to remove c++ compiler macros and defines for Eclipse. +# If c++ macros/defines are set __cplusplus is also set which causes +# problems when compiling freerdp/jni. To prevent this problem we set the macros to "". + +if (ANDROID AND CMAKE_EXTRA_GENERATOR STREQUAL "Eclipse CDT4") + set(CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS "") + message(STATUS "Disabled CXX system defines for eclipse (workaround).") +endif() + +set(CPACK_SOURCE_IGNORE_FILES "/\\\\.git/;/\\\\.gitignore;/CMakeCache.txt") + +if(NOT WIN32) + if(APPLE AND (NOT IOS)) + + if(WITH_SERVER) + set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "mfreerdp-server") + endif() + endif() + + if(WITH_X11) + set(CPACK_PACKAGE_EXECUTABLES "xfreerdp") + + if(WITH_SERVER) + set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "xfreerdp-server") + endif() + endif() +endif() + +set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") +set(CPACK_TOPLEVEL_TAG "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +string(TOLOWER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_lower) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}") + +set(CPACK_PACKAGE_NAME "FreeRDP") +set(CPACK_PACKAGE_VENDOR "FreeRDP") +set(CPACK_PACKAGE_VERSION ${FREERDP_VERSION_FULL}) +set(CPACK_PACKAGE_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${FREERDP_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${FREERDP_VERSION_REVISION}) +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "FreeRDP: A Remote Desktop Protocol Implementation") + +set(CPACK_PACKAGE_CONTACT "Marc-Andre Moreau") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "marcandre.moreau@gmail.com") +set(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "FreeRDP") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt") + +set(CPACK_NSIS_MODIFY_PATH ON) +set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/resources\\\\FreeRDP_Install.bmp") +set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/resources\\\\FreeRDP_Icon_96px.ico") +set(CPACK_NSIS_MUI_UNICON "${CMAKE_SOURCE_DIR}/resource\\\\FreeRDP_Icon_96px.ico") + +set(CPACK_COMPONENTS_ALL client server libraries headers symbols tools) + +if(MSVC) + if(MSVC_RUNTIME STREQUAL "dynamic") + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) + include(InstallRequiredSystemLibraries) + + install(PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT libraries) + endif() +endif() + +set(CPACK_COMPONENT_CLIENT_DISPLAY_NAME "Client") +set(CPACK_COMPONENT_CLIENT_GROUP "Applications") + +set(CPACK_COMPONENT_SERVER_DISPLAY_NAME "Server") +set(CPACK_COMPONENT_SERVER_GROUP "Applications") + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_LIBRARIES_GROUP "Runtime") + +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "Headers") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +set(CPACK_COMPONENT_SYMBOLS_DISPLAY_NAME "Symbols") +set(CPACK_COMPONENT_SYMBOLS_GROUP "Development") + +set(CPACK_COMPONENT_TOOLS_DISPLAY_NAME "Tools") +set(CPACK_COMPONENT_TOOLS_GROUP "Applications") + +set(CPACK_COMPONENT_GROUP_RUNTIME_DESCRIPTION "Runtime") +set(CPACK_COMPONENT_GROUP_APPLICATIONS_DESCRIPTION "Applications") +set(CPACK_COMPONENT_GROUP_DEVELOPMENT_DESCRIPTION "Development") + +configure_file("${CMAKE_SOURCE_DIR}/CMakeCPackOptions.cmake.in" + "${CMAKE_BINARY_DIR}/CMakeCPackOptions.cmake" @ONLY) +set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_BINARY_DIR}/CMakeCPackOptions.cmake") + +include(CPack) diff --git a/CMakeCPackOptions.cmake.in b/CMakeCPackOptions.cmake.in new file mode 100644 index 0000000..826eaa1 --- /dev/null +++ b/CMakeCPackOptions.cmake.in @@ -0,0 +1,10 @@ +# This file is configured at cmake time, and loaded at cpack time. +# To pass variables to cpack from cmake, they must be configured in this file. + +if("${CPACK_GENERATOR}" STREQUAL "PackageMaker") + if(CMAKE_PACKAGE_QTGUI) + set(CPACK_PACKAGE_DEFAULT_LOCATION "/Applications") + else() + set(CPACK_PACKAGE_DEFAULT_LOCATION "/usr") + endif() +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0199269 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,1131 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# 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. + +cmake_minimum_required(VERSION 2.8) + +project(FreeRDP C CXX) + +if(NOT DEFINED VENDOR) + set(VENDOR "FreeRDP" CACHE STRING "FreeRDP package vendor") +endif() + +if(NOT DEFINED PRODUCT) + set(PRODUCT "FreeRDP" CACHE STRING "FreeRDP package name") +endif() + +if(NOT DEFINED FREERDP_VENDOR) + set(FREERDP_VENDOR 1) +endif() + +set(CMAKE_COLOR_MAKEFILE ON) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Include our extra modules +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) + +if((CMAKE_SYSTEM_NAME MATCHES "WindowsStore") AND (CMAKE_SYSTEM_VERSION MATCHES "10.0")) + set(UWP 1) + add_definitions("-D_UWP") + set(CMAKE_WINDOWS_VERSION "WIN10") +endif() + +# Check for cmake compatibility (enable/disable features) +include(CheckCmakeCompat) + +# Include cmake modules +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(CheckStructHasMember) +include(FindPkgConfig) +include(TestBigEndian) + +include(FindFeature) +include(ConfigOptions) +include(ComplexLibrary) +include(FeatureSummary) +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +include(GNUInstallDirsWrapper) +include(CMakePackageConfigHelpers) +include(InstallFreeRDPMan) +include(GetGitRevisionDescription) +include(SetFreeRDPCMakeInstallDir) + +# Soname versioning +set(BUILD_NUMBER 0) +if ($ENV{BUILD_NUMBER}) + set(BUILD_NUMBER $ENV{BUILD_NUMBER}) +endif() +set(WITH_LIBRARY_VERSIONING "ON") + +set(RAW_VERSTION_STRING "2.0.0-rc4") +if(EXISTS "${CMAKE_SOURCE_DIR}/.source_tag") + file(READ ${CMAKE_SOURCE_DIR}/.source_tag RAW_VERSTION_STRING) +elseif(USE_VERSION_FROM_GIT_TAG) + git_get_exact_tag(_GIT_TAG --tags --always) + if (NOT ${_GIT_TAG} STREQUAL "n/a") + set(RAW_VERSTION_STRING ${_GIT_TAG}) + endif() +endif() +string(STRIP ${RAW_VERSTION_STRING} RAW_VERSTION_STRING) + +set(VERSION_REGEX "^.?([0-9]+)\\.([0-9]+)\\.([0-9]+)-?(.*)") +string(REGEX REPLACE "${VERSION_REGEX}" "\\1" FREERDP_VERSION_MAJOR "${RAW_VERSTION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\2" FREERDP_VERSION_MINOR "${RAW_VERSTION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\3" FREERDP_VERSION_REVISION "${RAW_VERSTION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\4" FREERDP_VERSION_SUFFIX "${RAW_VERSTION_STRING}") + +set(FREERDP_API_VERSION "${FREERDP_VERSION_MAJOR}") +set(FREERDP_VERSION "${FREERDP_VERSION_MAJOR}.${FREERDP_VERSION_MINOR}.${FREERDP_VERSION_REVISION}") +if (FREERDP_VERSION_SUFFIX) + set(FREERDP_VERSION_FULL "${FREERDP_VERSION}-${FREERDP_VERSION_SUFFIX}") +else() + set(FREERDP_VERSION_FULL "${FREERDP_VERSION}") +endif() +message("FREERDP_VERSION=${FREERDP_VERSION_FULL}") + +set(FREERDP_INCLUDE_DIR "include/freerdp${FREERDP_VERSION_MAJOR}/") + +# Compatibility options +if(DEFINED STATIC_CHANNELS) + message(WARNING "STATIC_CHANNELS is obsolete, please use BUILTIN_CHANNELS instead") + set(BUILTIN_CHANNELS ${STATIC_CHANNELS} CACHE BOOL "" FORCE) +endif() + +# Make paths absolute +if (CMAKE_INSTALL_PREFIX) + get_filename_component(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ABSOLUTE) +endif() +if (FREERDP_EXTERNAL_PATH) + get_filename_component (FREERDP_EXTERNAL_PATH "${FREERDP_EXTERNAL_PATH}" ABSOLUTE) +endif() + +# Allow to search the host machine for git/ccache +if(CMAKE_CROSSCOMPILING) + SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +endif(CMAKE_CROSSCOMPILING) + +find_program(CCACHE ccache) +if(CCACHE AND WITH_CCACHE) + if(CMAKE_VERSION VERSION_GREATER 3.3.2) + if(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER) + SET(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) + endif(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER) + if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) + SET(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) + endif(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) + else() + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE}) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE}) + endif() +endif(CCACHE AND WITH_CCACHE) + +if(EXISTS "${CMAKE_SOURCE_DIR}/.source_version" ) + file(READ ${CMAKE_SOURCE_DIR}/.source_version GIT_REVISION) + + string(STRIP ${GIT_REVISION} GIT_REVISION) +else() + git_get_exact_tag(GIT_REVISION --tags --always) + + if (${GIT_REVISION} STREQUAL "n/a") + git_rev_parse (GIT_REVISION --short) + endif() +endif() + +if(CMAKE_CROSSCOMPILING) + SET (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +endif(CMAKE_CROSSCOMPILING) +# /Allow to search the host machine for git/ccache + +message(STATUS "Git Revision ${GIT_REVISION}") + +# Turn on solution folders (2.8.4+) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Default to release build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() + +if(NOT DEFINED BUILD_SHARED_LIBS) + if(IOS) + set(BUILD_SHARED_LIBS OFF) + else() + set(BUILD_SHARED_LIBS ON) + endif() +endif() + +if(BUILD_TESTING) + set(EXPORT_ALL_SYMBOLS TRUE) +elseif(NOT DEFINED EXPORT_ALL_SYMBOLS) + set(EXPORT_ALL_SYMBOLS FALSE) +endif() + +if (EXPORT_ALL_SYMBOLS) +# set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_definitions(-DFREERDP_TEST_EXPORTS -DBUILD_TESTING) +endif(EXPORT_ALL_SYMBOLS) + +# BSD +if(${CMAKE_SYSTEM_NAME} MATCHES "BSD") + set(BSD TRUE) + if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + set(FREEBSD TRUE) + endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "kFreeBSD") + set(KFREEBSD TRUE) + endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") + set(OPENBSD TRUE) + endif() +endif() + +if(${CMAKE_SYSTEM_NAME} MATCHES "DragonFly") + set(BSD TRUE) + set(FREEBSD TRUE) +endif() + +if(FREEBSD) + find_path(EPOLLSHIM_INCLUDE_DIR NAMES sys/epoll.h sys/timerfd.h HINTS /usr/local/include/libepoll-shim) + find_library(EPOLLSHIM_LIBS NAMES epoll-shim libepoll-shim HINTS /usr/local/lib) +endif() + +# Configure MSVC Runtime +if(MSVC) + include(MSVCRuntime) + if(NOT DEFINED MSVC_RUNTIME) + set(MSVC_RUNTIME "dynamic") + endif() + if(MSVC_RUNTIME STREQUAL "static") + if(BUILD_SHARED_LIBS) + message(FATAL_ERROR "Static CRT is only supported in a fully static build") + endif() + message(STATUS "Use the MSVC static runtime option carefully!") + message(STATUS "OpenSSL uses /MD by default, and is very picky") + message(STATUS "Random freeing errors are a common sign of runtime issues") + endif() + configure_msvc_runtime() + + if(NOT DEFINED CMAKE_SUPPRESS_REGENERATION) + set(CMAKE_SUPPRESS_REGENERATION ON) + endif() +endif() + +# Enable 64bit file support on linux and FreeBSD. +if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" OR FREEBSD) + add_definitions("-D_FILE_OFFSET_BITS=64") +endif() + +# Use Standard conforming getpwnam_r() on Solaris. +if("${CMAKE_SYSTEM_NAME}" MATCHES "SunOS") + add_definitions("-D_POSIX_PTHREAD_SEMANTICS") +endif() + +# Compiler-specific flags +if(CMAKE_COMPILER_IS_GNUCC) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "i686") + CHECK_SYMBOL_EXISTS(__x86_64__ "" IS_X86_64) + if(IS_X86_64) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=i686") + endif() + else() + if(CMAKE_POSITION_INDEPENDENT_CODE) + if(${CMAKE_VERSION} VERSION_LESS 2.8.9) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + endif() + endif() + endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + + CHECK_C_COMPILER_FLAG (-Wno-unused-result Wno-unused-result) + if(Wno-unused-result) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-result") + endif() + CHECK_C_COMPILER_FLAG (-Wno-unused-but-set-variable Wno-unused-but-set-variable) + if(Wno-unused-but-set-variable) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-but-set-variable") + endif() + CHECK_C_COMPILER_FLAG(-Wno-deprecated-declarations Wno-deprecated-declarations) + if(Wno-deprecated-declarations) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations") + endif() + CHECK_CXX_COMPILER_FLAG(-Wno-deprecated-declarations Wno-deprecated-declarationsCXX) + if(Wno-deprecated-declarationsCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + endif() + + if(NOT EXPORT_ALL_SYMBOLS) + message(STATUS "GCC default symbol visibility: hidden") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") + endif() + if(BUILD_TESTING) + CHECK_C_COMPILER_FLAG(-Wno-format Wno-format) + if(Wno-format) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format") + endif() + endif() + CHECK_C_COMPILER_FLAG (-Wimplicit-function-declaration Wimplicit-function-declaration) + if(Wimplicit-function-declaration) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wimplicit-function-declaration") + endif() + + if (NOT OPENBSD) + CHECK_C_COMPILER_FLAG (-Wredundant-decls Wredundant-decls) + if(Wredundant-decls) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wredundant-decls") + endif() + endif() + if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_definitions(-DNDEBUG) + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + endif() +endif() + +# When building with Unix Makefiles and doing any release builds +# try to set __FILE__ to relative paths via a make specific macro +if (CMAKE_GENERATOR MATCHES "Unix Makefile*") + if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_BUILD_TYPE) + CHECK_C_COMPILER_FLAG (-Wno-builtin-macro-redefined Wno-builtin-macro-redefined) + if(Wno-builtin-macro-redefined) + set(CMAKE_C_FLAGS_${UPPER_BUILD_TYPE} "${CMAKE_C_FLAGS_${UPPER_BUILD_TYPE}} -Wno-builtin-macro-redefined -D__FILE__='\"$(subst ${CMAKE_BINARY_DIR}/,,$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<)))\"'") + endif() + + CHECK_CXX_COMPILER_FLAG (-Wno-builtin-macro-redefined Wno-builtin-macro-redefinedCXX) + if(Wno-builtin-macro-redefinedCXX) + set(CMAKE_CXX_FLAGS_${UPPER_BUILD_TYPE} "${CMAKE_CXX_FLAGS_${UPPER_BUILD_TYPE}} -Wno-builtin-macro-redefined -D__FILE__='\"$(subst ${CMAKE_BINARY_DIR}/,,$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<)))\"'") + endif() + endif() +endif() + +if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-parameter") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-macros -Wno-padded") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-c11-extensions -Wno-gnu") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-command-line-argument") + CHECK_C_COMPILER_FLAG(-Wno-deprecated-declarations Wno-deprecated-declarations) + if(Wno-deprecated-declarations) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations") + endif() + CHECK_CXX_COMPILER_FLAG(-Wno-deprecated-declarations Wno-deprecated-declarationsCXX) + if(Wno-deprecated-declarationsCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + endif() +endif() + +# Enable address sanitizer, where supported and when required +if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_REQUIRED_FLAGS_SAVED ${CMAKE_REQUIRED_FLAGS}) + + CHECK_C_COMPILER_FLAG ("-fno-omit-frame-pointer" fno-omit-frame-pointer) + + file(WRITE ${CMAKE_BINARY_DIR}/foo.txt "") + if(WITH_SANITIZE_ADDRESS) + set(CMAKE_REQUIRED_FLAGS "-fsanitize=address") + CHECK_C_COMPILER_FLAG ("-fsanitize=address" fsanitize-address) + CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${CMAKE_BINARY_DIR}/foo.txt" fsanitize-blacklist) + CHECK_C_COMPILER_FLAG ("-fsanitize-address-use-after-scope" fsanitize-address-use-after-scope) + unset(CMAKE_REQUIRED_FLAGS) + + if(fsanitize-address) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + + if(fsanitize-blacklist) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/scripts/blacklist-address-sanitizer.txt") + endif(fsanitize-blacklist) + + if(fsanitize-address-use-after-scope) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-address-use-after-scope") + endif(fsanitize-address-use-after-scope) + + else(fsanitize-address) + message(WARNING "Missing support for address sanitizer!") + endif(fsanitize-address) + + if(fno-omit-frame-pointer) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer") + endif() + elseif(WITH_SANITIZE_MEMORY) + set(CMAKE_REQUIRED_FLAGS "-fsanitize=memory") + CHECK_C_COMPILER_FLAG ("-fsanitize=memory" fsanitize-memory) + CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${CMAKE_BINARY_DIR}/foo.txt" fsanitize-blacklist) + CHECK_C_COMPILER_FLAG ("-fsanitize-memory-use-after-dtor" fsanitize-memory-use-after-dtor) + CHECK_C_COMPILER_FLAG ("-fsanitize-memory-track-origins" fsanitize-memory-track-origins) + unset(CMAKE_REQUIRED_FLAGS) + + if(fsanitize-memory) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=memory") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=memory") + + if(fsanitize-blacklist) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/scripts/blacklist-memory-sanitizer.txt") + endif(fsanitize-blacklist) + + if (fsanitize-memory-use-after-dtor) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-memory-use-after-dtor") + endif(fsanitize-memory-use-after-dtor) + + if (fsanitize-memory-track-origins) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-memory-track-origins") + endif(fsanitize-memory-track-origins) + + else(fsanitize-memory) + message(WARNING "Missing support for memory sanitizer!") + endif(fsanitize-memory) + + if(fno-omit-frame-pointer) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer") + endif() + elseif(WITH_SANITIZE_THREAD) + CHECK_C_COMPILER_FLAG ("-fsanitize=thread" fsanitize-thread) + CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${CMAKE_BINARY_DIR}/foo.txt" fsanitize-blacklist) + unset(CMAKE_REQUIRED_FLAGS) + if(fsanitize-thread) + set(CMAKE_REQUIRED_FLAGS "-Werror -fsanitize=thread") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") + if(fsanitize-blacklist) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-blacklist=${CMAKE_SOURCE_DIR}/scripts/blacklist-thread-sanitizer.txt") + endif(fsanitize-blacklist) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=thread") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") + else(fsanitize-thread) + message(WARNING "Missing support for thread sanitizer!") + endif(fsanitize-thread) + + if(fno-omit-frame-pointer) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer") + endif() + endif() + + file(REMOVE ${CMAKE_BINARY_DIR}/foo.txt) + + if (WITH_NO_UNDEFINED) + set(CMAKE_REQUIRED_FLAGS "-Wl,--no-undefined") + CHECK_C_COMPILER_FLAG (-Wl,--no-undefined no-undefined) + unset(CMAKE_REQUIRED_FLAGS) + + if(no-undefined) + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() + endif() + + set(CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS_SAVED}) +endif() + +if(MSVC) + # Remove previous warning definitions, + # NMake is otherwise complaining. + foreach (flags_var_to_scrub + CMAKE_C_FLAGS + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS_MINSIZEREL) + string (REGEX REPLACE "(^| )[/-]W[ ]*[1-9]" " " + "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") + endforeach() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gd") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3") + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + add_definitions(-D_AMD64_) + else() + add_definitions(-D_X86_) + endif() + + set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) + set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}) + + if(CMAKE_BUILD_TYPE STREQUAL "Release") + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Zi") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") + endif() + +endif() + +if(WIN32) + add_definitions(-DUNICODE -D_UNICODE) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-DWIN32_LEAN_AND_MEAN) + add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS) + + set(CMAKE_USE_RELATIVE_PATH ON) + if (${CMAKE_GENERATOR} MATCHES "NMake Makefile*" OR ${CMAKE_GENERATOR} MATCHES "Ninja*") + set(CMAKE_PDB_BINARY_DIR ${CMAKE_BINARY_DIR}) + elseif (${CMAKE_GENERATOR} MATCHES "Visual Studio*") + set(CMAKE_PDB_BINARY_DIR "${CMAKE_BINARY_DIR}/\${CMAKE_INSTALL_CONFIG_NAME}") + else() + message(FATAL_ERROR "Unknown generator ${CMAKE_GENERATOR}") + endif() + + # Set product and vendor for dll and exe version information. + set(RC_VERSION_VENDOR ${VENDOR}) + set(RC_VERSION_PRODUCT ${PRODUCT}) + set(RC_VERSION_PATCH ${BUILD_NUMBER}) + set(RC_VERSION_DESCRIPTION ${GIT_REVISION}) + + string(TIMESTAMP RC_VERSION_YEAR "%Y") + + if(NOT DEFINED CMAKE_WINDOWS_VERSION) + set(CMAKE_WINDOWS_VERSION "WINXP") + endif() + + if(CMAKE_WINDOWS_VERSION STREQUAL "WINXP") + add_definitions(-DWINVER=0x0501 -D_WIN32_WINNT=0x0501) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN7") + add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN8") + add_definitions(-DWINVER=0x0602 -D_WIN32_WINNT=0x0602) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN10") + add_definitions(-DWINVER=0x0A00 -D_WIN32_WINNT=0x0A00) + endif() + + if (FREERDP_EXTERNAL_SSL_PATH) + set(OPENSSL_ROOT_DIR ${FREERDP_EXTERNAL_SSL_PATH}) + endif() +endif() + +if(IOS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -isysroot ${CMAKE_IOS_SDK_ROOT} -g") +endif() + +add_definitions(-DWINPR_EXPORTS -DFREERDP_EXPORTS) + +# Include files +if(NOT IOS) + check_include_files(fcntl.h HAVE_FCNTL_H) + check_include_files(unistd.h HAVE_UNISTD_H) + check_include_files(execinfo.h HAVE_EXECINFO_H) + check_include_files(inttypes.h HAVE_INTTYPES_H) + check_include_files(sys/modem.h HAVE_SYS_MODEM_H) + check_include_files(sys/filio.h HAVE_SYS_FILIO_H) + check_include_files(sys/sockio.h HAVE_SYS_SOCKIO_H) + check_include_files(sys/strtio.h HAVE_SYS_STRTIO_H) + check_include_files(sys/select.h HAVE_SYS_SELECT_H) + check_include_files(syslog.h HAVE_SYSLOG_H) +else() + set(HAVE_FCNTL_H 1) + set(HAVE_UNISTD_H 1) + set(HAVE_INTTYPES_H 1) + set(HAVE_SYS_FILIO_H 1) +endif() + +if(NOT IOS) + check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_TM_GMTOFF) +else() + set(HAVE_TM_GMTOFF 1) +endif() + +# Mac OS X +if(APPLE) + if(IOS) + if (NOT FREERDP_IOS_EXTERNAL_SSL_PATH) + message(STATUS "FREERDP_IOS_EXTERNAL_SSL_PATH not set! Required if openssl is not found in the iOS SDK (which usually isn't") + endif() + set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${FREERDP_IOS_EXTERNAL_SSL_PATH}) + set_property(GLOBAL PROPERTY XCODE_ATTRIBUTE_SKIP_INSTALL YES) + else(IOS) + if(NOT DEFINED CMAKE_OSX_ARCHITECTURES) + set(CMAKE_OSX_ARCHITECTURES i386 x86_64) + endif() + endif(IOS) + +# Temporarily disabled, causes the cmake script to be reexecuted, causing the compilation to fail. +# Workaround: specify the parameter in the command-line +# if(WITH_CLANG) +# set(CMAKE_C_COMPILER "clang") +# endif() + + if (WITH_VERBOSE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -v") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -v") + endif() +endif(APPLE) + +# OpenBSD +if(OPENBSD) + set(WITH_MANPAGES "ON") + set(WITH_ALSA "OFF") + set(WITH_PULSE "OFF") + set(WITH_OSS "ON") + set(WITH_WAYLAND "OFF") +endif() + +# Android +if(ANDROID) + set(WITH_LIBRARY_VERSIONING "OFF") + + set_property( GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ${ANDROID_LIBRARY_USE_LIB64_PATHS} ) + + if (${ANDROID_ABI} STREQUAL "armeabi") + set (WITH_NEON OFF) + endif() + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + add_definitions(-DNDK_DEBUG=1) + + # NOTE: Manually add -gdwarf-3, as newer toolchains default to -gdwarf-4, + # which is not supported by the gdbserver binary shipped with + # the android NDK (tested with r9b) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -gdwarf-3") + endif() + set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -llog") + + if (NOT FREERDP_EXTERNAL_PATH) + if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external/") + set (FREERDP_EXTERNAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/") + else() + message(STATUS "FREERDP_EXTERNAL_PATH not set!") + endif() + endif() + + list (APPEND CMAKE_INCLUDE_PATH ${FREERDP_EXTERNAL_PATH}/include) + list (APPEND CMAKE_LIBRARY_PATH ${FREERDP_EXTERNAL_PATH}/${ANDROID_ABI}/ ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH ) + + if (WITH_GPROF) + CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/scripts/gprof_generate.sh.cmake + ${CMAKE_BINARY_DIR}/scripts/gprof_generate.sh @ONLY) + endif(WITH_GPROF) +endif() + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + +if(NOT IOS) + find_package(Threads REQUIRED) +endif() + +if(NOT WIN32) + CHECK_SYMBOL_EXISTS(pthread_mutex_timedlock pthread.h HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL) + if (NOT HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL) + CHECK_LIBRARY_EXISTS(pthread pthread_mutex_timedlock "" HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB) + endif (NOT HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL) + if (NOT HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB) + CHECK_LIBRARY_EXISTS(pthreads pthread_mutex_timedlock "" HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS) + endif (NOT HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB) + + if (HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL OR HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB OR HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS) + set(HAVE_PTHREAD_MUTEX_TIMEDLOCK ON) + endif (HAVE_PTHREAD_MUTEX_TIMEDLOCK_SYMBOL OR HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIB OR HAVE_PTHREAD_MUTEX_TIMEDLOCK_LIBS) +endif() + +if(WITH_VALGRIND_MEMCHECK) + check_include_files(valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H) +else() + unset(HAVE_VALGRIND_MEMCHECK_H CACHE) +endif() + +if(UNIX OR CYGWIN) + check_include_files(aio.h HAVE_AIO_H) + check_include_files(sys/eventfd.h HAVE_SYS_EVENTFD_H) + if (HAVE_SYS_EVENTFD_H) + check_symbol_exists(eventfd_read sys/eventfd.h WITH_EVENTFD_READ_WRITE) + endif() + if (FREEBSD) + list(APPEND CMAKE_REQUIRED_INCLUDES ${EPOLLSHIM_INCLUDE_DIR}) + endif() + check_include_files(sys/timerfd.h HAVE_SYS_TIMERFD_H) + if (FREEBSD) + list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES ${EPOLLSHIM_INCLUDE_DIR}) + endif() + check_include_files(poll.h HAVE_POLL_H) + list(APPEND CMAKE_REQUIRED_LIBRARIES m) + check_symbol_exists(ceill math.h HAVE_MATH_C99_LONG_DOUBLE) + list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES m) + set(X11_FEATURE_TYPE "RECOMMENDED") + set(WAYLAND_FEATURE_TYPE "RECOMMENDED") +else() + set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") +endif() + +if(WITH_PCSC_WINPR) + find_package(PCSCWinPR) +endif() + +set(X11_FEATURE_PURPOSE "X11") +set(X11_FEATURE_DESCRIPTION "X11 client and server") + +set(WAYLAND_FEATURE_PURPOSE "Wayland") +set(WAYLAND_FEATURE_DESCRIPTION "Wayland client") + +set(DIRECTFB_FEATURE_TYPE "OPTIONAL") +set(DIRECTFB_FEATURE_PURPOSE "DirectFB") +set(DIRECTFB_FEATURE_DESCRIPTION "DirectFB client") + +set(ZLIB_FEATURE_TYPE "REQUIRED") +set(ZLIB_FEATURE_PURPOSE "compression") +set(ZLIB_FEATURE_DESCRIPTION "data compression") + +set(OPENSSL_FEATURE_TYPE "REQUIRED") +set(OPENSSL_FEATURE_PURPOSE "cryptography") +set(OPENSSL_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions") + +set(MBEDTLS_FEATURE_TYPE "OPTIONAL") +set(MBEDTLS_FEATURE_PURPOSE "cryptography") +set(MBEDTLS_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions") + +set(OPENSLES_FEATURE_TYPE "OPTIONAL") +set(OPENSLES_FEATURE_PURPOSE "multimedia") +set(OPENSLES_FEATURE_DESCRIPTION "OpenSLES audio / video") + +set(OSS_FEATURE_TYPE "RECOMMENDED") +set(OSS_FEATURE_PURPOSE "sound") +set(OSS_FEATURE_DESCRIPTION "audio input, audio output and multimedia redirection") + +set(ALSA_FEATURE_TYPE "RECOMMENDED") +set(ALSA_FEATURE_PURPOSE "sound") +set(ALSA_FEATURE_DESCRIPTION "audio input, audio output and multimedia redirection") + +set(PULSE_FEATURE_TYPE "OPTIONAL") +set(PULSE_FEATURE_PURPOSE "sound") +set(PULSE_FEATURE_DESCRIPTION "audio input, audio output and multimedia redirection") + +set(CUPS_FEATURE_TYPE "OPTIONAL") +set(CUPS_FEATURE_PURPOSE "printing") +set(CUPS_FEATURE_DESCRIPTION "printer device redirection") + +set(PCSC_FEATURE_TYPE "OPTIONAL") +set(PCSC_FEATURE_PURPOSE "smart card") +set(PCSC_FEATURE_DESCRIPTION "smart card device redirection") + +set(FFMPEG_FEATURE_TYPE "RECOMMENDED") +set(FFMPEG_FEATURE_PURPOSE "multimedia") +set(FFMPEG_FEATURE_DESCRIPTION "multimedia redirection, audio and video playback") + +set(VAAPI_FEATURE_TYPE "OPTIONAL") +set(VAAPI_FEATURE_PURPOSE "multimedia") +set(VAAPI_FEATURE_DESCRIPTION "VA-API hardware acceleration for video playback") + +set(GSTREAMER_0_10_FEATURE_TYPE "OPTIONAL") +set(GSTREAMER_0_10_FEATURE_PURPOSE "multimedia") +set(GSTREAMER_0_10_FEATURE_DESCRIPTION "multimedia redirection, audio and video playback, gstreamer 0.10 version") + +set(GSTREAMER_1_0_FEATURE_TYPE "RECOMMENDED") +set(GSTREAMER_1_0_FEATURE_PURPOSE "multimedia") +set(GSTREAMER_1_0_FEATURE_DESCRIPTION "multimedia redirection, audio and video playback") + +set(IPP_FEATURE_TYPE "OPTIONAL") +set(IPP_FEATURE_PURPOSE "performance") +set(IPP_FEATURE_DESCRIPTION "Intel Integrated Performance Primitives library") + +set(JPEG_FEATURE_TYPE "OPTIONAL") +set(JPEG_FEATURE_PURPOSE "codec") +set(JPEG_FEATURE_DESCRIPTION "use JPEG library") + +set(X264_FEATURE_TYPE "OPTIONAL") +set(X264_FEATURE_PURPOSE "codec") +set(X264_FEATURE_DESCRIPTION "use x264 library") + +set(OPENH264_FEATURE_TYPE "OPTIONAL") +set(OPENH264_FEATURE_PURPOSE "codec") +set(OPENH264_FEATURE_DESCRIPTION "use OpenH264 library") + +set(GSM_FEATURE_TYPE "OPTIONAL") +set(GSM_FEATURE_PURPOSE "codec") +set(GSM_FEATURE_DESCRIPTION "GSM audio codec library") + +set(LAME_FEATURE_TYPE "OPTIONAL") +set(LAME_FEATURE_PURPOSE "codec") +set(LAME_FEATURE_DESCRIPTION "lame MP3 audio codec library") + +set(FAAD2_FEATURE_TYPE "OPTIONAL") +set(FAAD2_FEATURE_PURPOSE "codec") +set(FAAD2_FEATURE_DESCRIPTION "FAAD2 AAC audio codec library") + +set(FAAC_FEATURE_TYPE "OPTIONAL") +set(FAAC_FEATURE_PURPOSE "codec") +set(FAAC_FEATURE_DESCRIPTION "FAAC AAC audio codec library") + +set(SOXR_FEATURE_TYPE "OPTIONAL") +set(SOXR_FEATURE_PURPOSE "codec") +set(SOXR_FEATURE_DESCRIPTION "SOX audio resample library") + +set(GSSAPI_FEATURE_TYPE "OPTIONAL") +set(GSSAPI_FEATURE_PURPOSE "auth") +set(GSSAPI_FEATURE_DESCRIPTION "add kerberos support") + +if(WIN32) + set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(ZLIB_FEATURE_TYPE "DISABLED") + set(DIRECTFB_FEATURE_TYPE "DISABLED") + set(OSS_FEATURE_TYPE "DISABLED") + set(ALSA_FEATURE_TYPE "DISABLED") + set(PULSE_FEATURE_TYPE "DISABLED") + set(CUPS_FEATURE_TYPE "DISABLED") + set(PCSC_FEATURE_TYPE "DISABLED") + set(FFMPEG_FEATURE_TYPE "DISABLED") + set(VAAPI_FEATURE_TYPE "DISABLED") + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "OPTIONAL") + set(OPENSLES_FEATURE_TYPE "DISABLED") +endif() + +if(APPLE) + set(DIRECTFB_FEATURE_TYPE "DISABLED") + set(FFMPEG_FEATURE_TYPE "OPTIONAL") + set(VAAPI_FEATURE_TYPE "DISABLED") + set(GSTREAMER_1_0_FEATURE_TYPE "OPTIONAL") + set(X11_FEATURE_TYPE "OPTIONAL") + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(OSS_FEATURE_TYPE "DISABLED") + set(ALSA_FEATURE_TYPE "DISABLED") + if(IOS) + set(X11_FEATURE_TYPE "DISABLED") + set(PULSE_FEATURE_TYPE "DISABLED") + set(CUPS_FEATURE_TYPE "DISABLED") + set(PCSC_FEATURE_TYPE "DISABLED") + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "DISABLED") + endif() + set(OPENSLES_FEATURE_TYPE "DISABLED") +endif() + +if(UNIX AND NOT ANDROID) + set(WLOG_SYSTEMD_JOURNAL_FEATURE_TYPE "RECOMMENDED") + set(WLOG_SYSTEMD_JOURNAL_FEATURE_PURPOSE "systemd journal appender") + set(WLOG_SYSTEMD_JOURNAL_FEATURE_DESCRIPTION "allows to export wLog to systemd journal") + + #include(Findlibsystemd) + find_feature(libsystemd ${WLOG_SYSTEMD_JOURNAL_FEATURE_TYPE} ${WLOG_SYSTEMD_JOURNAL_FEATURE_PURPOSE} ${WLOG_SYSTEMD_JOURNAL_FEATURE_DESCRIPTION}) + + if(LIBSYSTEMD_FOUND) + set(HAVE_JOURNALD_H TRUE) + else() + unset(HAVE_JOURNALD_H) + endif() +endif(UNIX AND NOT ANDROID) + +if(ANDROID) + set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(DIRECTFB_FEATURE_TYPE "DISABLED") + set(OSS_FEATURE_TYPE "DISABLED") + set(ALSA_FEATURE_TYPE "DISABLED") + set(PULSE_FEATURE_TYPE "DISABLED") + set(CUPS_FEATURE_TYPE "DISABLED") + set(PCSC_FEATURE_TYPE "DISABLED") + set(FFMPEG_FEATURE_TYPE "DISABLED") + set(VAAPI_FEATURE_TYPE "DISABLED") + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "DISABLED") + set(OPENSLES_FEATURE_TYPE "REQUIRED") +endif() + +find_feature(X11 ${X11_FEATURE_TYPE} ${X11_FEATURE_PURPOSE} ${X11_FEATURE_DESCRIPTION}) +find_feature(Wayland ${WAYLAND_FEATURE_TYPE} ${WAYLAND_FEATURE_PURPOSE} ${WAYLAND_FEATURE_DESCRIPTION}) +find_feature(DirectFB ${DIRECTFB_FEATURE_TYPE} ${DIRECTFB_FEATURE_PURPOSE} ${DIRECTFB_FEATURE_DESCRIPTION}) +if (${WITH_DIRECTFB}) + message(WARNING "DIRECTFB is orphaned and not maintained see docs/README.directfb for details") +endif() + +find_feature(ZLIB ${ZLIB_FEATURE_TYPE} ${ZLIB_FEATURE_PURPOSE} ${ZLIB_FEATURE_DESCRIPTION}) +find_feature(OpenSSL ${OPENSSL_FEATURE_TYPE} ${OPENSSL_FEATURE_PURPOSE} ${OPENSSL_FEATURE_DESCRIPTION}) +find_feature(MbedTLS ${MBEDTLS_FEATURE_TYPE} ${MBEDTLS_FEATURE_PURPOSE} ${MBEDTLS_FEATURE_DESCRIPTION}) +find_feature(OpenSLES ${OPENSLES_FEATURE_TYPE} ${OPENSLES_FEATURE_PURPOSE} ${OPENSLES_FEATURE_DESCRIPTION}) + +find_feature(OSS ${OSS_FEATURE_TYPE} ${OSS_FEATURE_PURPOSE} ${OSS_FEATURE_DESCRIPTION}) +find_feature(ALSA ${ALSA_FEATURE_TYPE} ${ALSA_FEATURE_PURPOSE} ${ALSA_FEATURE_DESCRIPTION}) +find_feature(Pulse ${PULSE_FEATURE_TYPE} ${PULSE_FEATURE_PURPOSE} ${PULSE_FEATURE_DESCRIPTION}) + +find_feature(Cups ${CUPS_FEATURE_TYPE} ${CUPS_FEATURE_PURPOSE} ${CUPS_FEATURE_DESCRIPTION}) +find_feature(PCSC ${PCSC_FEATURE_TYPE} ${PCSC_FEATURE_PURPOSE} ${PCSC_FEATURE_DESCRIPTION}) + +find_feature(FFmpeg ${FFMPEG_FEATURE_TYPE} ${FFMPEG_FEATURE_PURPOSE} ${FFMPEG_FEATURE_DESCRIPTION}) + +find_feature(GStreamer_0_10 ${GSTREAMER_0_10_FEATURE_TYPE} ${GSTREAMER_0_10_FEATURE_PURPOSE} ${GSTREAMER_0_10_FEATURE_DESCRIPTION}) +find_feature(GStreamer_1_0 ${GSTREAMER_1_0_FEATURE_TYPE} ${GSTREAMER_1_0_FEATURE_PURPOSE} ${GSTREAMER_1_0_FEATURE_DESCRIPTION}) + +find_feature(JPEG ${JPEG_FEATURE_TYPE} ${JPEG_FEATURE_PURPOSE} ${JPEG_FEATURE_DESCRIPTION}) +find_feature(x264 ${X264_FEATURE_TYPE} ${X264_FEATURE_PURPOSE} ${X264_FEATURE_DESCRIPTION}) +find_feature(OpenH264 ${OPENH264_FEATURE_TYPE} ${OPENH264_FEATURE_PURPOSE} ${OPENH264_FEATURE_DESCRIPTION}) +find_feature(GSM ${GSM_FEATURE_TYPE} ${GSM_FEATURE_PURPOSE} ${GSM_FEATURE_DESCRIPTION}) +find_feature(LAME ${LAME_FEATURE_TYPE} ${LAME_FEATURE_PURPOSE} ${LAME_FEATURE_DESCRIPTION}) +find_feature(FAAD2 ${FAAD2_FEATURE_TYPE} ${FAAD2_FEATURE_PURPOSE} ${FAAD2_FEATURE_DESCRIPTION}) +find_feature(FAAC ${FAAC_FEATURE_TYPE} ${FAAC_FEATURE_PURPOSE} ${FAAC_FEATURE_DESCRIPTION}) +find_feature(soxr ${SOXR_FEATURE_TYPE} ${SOXR_FEATURE_PURPOSE} ${SOXR_FEATURE_DESCRIPTION}) + +find_feature(GSSAPI ${GSSAPI_FEATURE_TYPE} ${GSSAPI_FEATURE_PURPOSE} ${GSSAPI_FEATURE_DESCRIPTION}) + +if ((WITH_FFMPEG OR WITH_DSP_FFMPEG) AND NOT FFMPEG_FOUND) + message(FATAL_ERROR "FFMPEG support requested but not detected") +endif() +set(WITH_FFMPEG ${FFMPEG_FOUND}) + +# Version check, if we have detected FFMPEG but the version is too old +# deactivate it as sound backend. +if (WITH_DSP_FFMPEG) + # Deactivate FFmpeg backend for sound, if the version is too old. + # See libfreerdp/codec/dsp_ffmpeg.h + file(STRINGS "${AVCODEC_INCLUDE_DIR}/libavcodec/version.h" AV_VERSION_FILE REGEX "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+") + FOREACH(item ${AV_VERSION_FILE}) + STRING(REGEX MATCH "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+" litem ${item}) + IF(litem) + string(REGEX REPLACE "[ \t]+" ";" VSPLIT_LINE ${litem}) + list(LENGTH VSPLIT_LINE VSPLIT_LINE_LEN) + if (NOT "${VSPLIT_LINE_LEN}" EQUAL "2") + message(ERROR "invalid entry in libavcodec version header ${item}") + endif(NOT "${VSPLIT_LINE_LEN}" EQUAL "2") + list(GET VSPLIT_LINE 0 VNAME) + list(GET VSPLIT_LINE 1 VVALUE) + set(${VNAME} ${VVALUE}) + ENDIF(litem) + ENDFOREACH(item ${AV_VERSION_FILE}) + + set(AVCODEC_VERSION "${LIBAVCODEC_VERSION_MAJOR}.${LIBAVCODEC_VERSION_MINOR}.${LIBAVCODEC_VERSION_MICRO}") + if (AVCODEC_VERSION VERSION_LESS "57.48.101") + message(WARNING "FFmpeg version detected (${AVCODEC_VERSION}) is too old. (Require at least 57.48.101 for sound). Deactivating") + set(WITH_DSP_FFMPEG OFF) + endif() +endif (WITH_DSP_FFMPEG) + +if (WITH_OPENH264 AND NOT OPENH264_FOUND) + message(FATAL_ERROR "OpenH264 support requested but not detected") +endif() +set(WITH_OPENH264 ${OPENH264_FOUND}) + +if ( (WITH_GSSAPI) AND (NOT GSS_FOUND)) + message(WARNING "-DWITH_GSSAPI=ON is set, but not GSSAPI implementation was found, disabling") +elseif(WITH_GSSAPI) + if(GSS_FLAVOUR STREQUAL "MIT") + add_definitions("-DWITH_GSSAPI -DWITH_GSSAPI_MIT") + if(GSS_VERSION_1_13) + add_definitions("-DHAVE_AT_LEAST_KRB_V1_13") + endif() + include_directories(${_GSS_INCLUDE_DIR}) + elseif(GSS_FLAVOUR STREQUAL "Heimdal") + add_definitions("-DWITH_GSSAPI -DWITH_GSSAPI_HEIMDAL") + include_directories(${_GSS_INCLUDE_DIR}) + else() + message(WARNING "Kerberos version not detected") + endif() +endif() + +if(TARGET_ARCH MATCHES "x86|x64") + if (NOT APPLE) + # Intel Performance Primitives + find_feature(IPP ${IPP_FEATURE_TYPE} ${IPP_FEATURE_PURPOSE} ${IPP_FEATURE_DESCRIPTION}) + endif() +endif() + +if(OPENSSL_FOUND) + add_definitions("-DWITH_OPENSSL") + message(STATUS "Using OpenSSL Version: ${OPENSSL_VERSION}") +endif() + +if(MBEDTLS_FOUND) + add_definitions("-DWITH_MBEDTLS") +endif() + +if (TARGET_ARCH MATCHES "sparc") + set(HAVE_ALIGNED_REQUIRED 1) +endif() + +if (WITH_X264 OR WITH_OPENH264 OR WITH_MEDIA_FOUNDATION OR WITH_FFMPEG) + set(WITH_GFX_H264 ON) +else() + set(WITH_GFX_H264 OFF) +endif() + +# Android expects all libraries to be loadable +# without paths. +if (ANDROID) + set(FREERDP_DATA_PATH "share") + set(FREERDP_INSTALL_PREFIX ".") + set(FREERDP_LIBRARY_PATH ".") + set(FREERDP_PLUGIN_PATH ".") + set(FREERDP_ADDIN_PATH ".") +else (ANDROID) + set(FREERDP_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/freerdp${FREERDP_VERSION_MAJOR}") + set(FREERDP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + set(FREERDP_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}") + set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/freerdp${FREERDP_VERSION_MAJOR}") + set(FREERDP_ADDIN_PATH "${FREERDP_PLUGIN_PATH}") +endif(ANDROID) + +# Path to put extensions +set(FREERDP_EXTENSION_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/freerdp${FREERDP_VERSION_MAJOR}/extensions") + +# Include directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Configure files +add_definitions("-DHAVE_CONFIG_H") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# RPATH configuration +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +if (APPLE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) + set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks") +else (APPLE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + if (NOT FREEBSD) + set(CMAKE_INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:\$ORIGIN/..") + endif() +endif(APPLE) + +if (BUILD_SHARED_LIBS) + set(CMAKE_MACOSX_RPATH ON) +endif() + +# Android profiling +if(ANDROID) + if(WITH_GPROF) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") + set(PROFILER_LIBRARIES + "${FREERDP_EXTERNAL_PROFILER_PATH}/obj/local/${ANDROID_ABI}/libandroid-ndk-profiler.a") + include_directories("${FREERDP_EXTERNAL_PROFILER_PATH}") + endif() +endif() + +# Unit Tests + +include(CTest) + +if(BUILD_TESTING) + enable_testing() + + if(MSVC) + set(TESTING_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + else() + set(TESTING_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Testing") + endif() +endif() + +# WinPR +include_directories("${CMAKE_SOURCE_DIR}/winpr/include") +include_directories("${CMAKE_BINARY_DIR}/winpr/include") + +if (${CMAKE_VERSION} VERSION_LESS 2.8.12) + set(PUBLIC_KEYWORD "") + set(PRIVATE_KEYWORD "") +else() + set(PUBLIC_KEYWORD "PUBLIC") + set(PRIVATE_KEYWORD "PRIVATE") +endif() + +if(BUILD_SHARED_LIBS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWINPR_DLL") +endif() + +add_subdirectory(winpr) + +# Sub-directories + +if(WITH_THIRD_PARTY) + add_subdirectory(third-party) + if (NOT "${THIRD_PARTY_INCLUDES}" STREQUAL "") + include_directories(${THIRD_PARTY_INCLUDES}) + endif() +endif() + +add_subdirectory(include) + +add_subdirectory(libfreerdp) + +if (IOS) + set(CMAKE_OSX_DEPLOYMENT_TARGET "") + if (IOS_PLATFORM MATCHES "SIMULATOR") + set(CMAKE_OSX_SYSROOT "iphonesimulator") + else() + set(CMAKE_OSX_SYSROOT "iphoneos") + endif() +endif() + +# RdTk +include_directories("${CMAKE_SOURCE_DIR}/rdtk/include") +include_directories("${CMAKE_BINARY_DIR}/rdtk/include") + +add_subdirectory(rdtk) + +if(WAYLAND_FOUND) + add_subdirectory(uwac) +endif() + +if(BSD) + if(IS_DIRECTORY /usr/local/include) + include_directories(/usr/local/include) + link_directories(/usr/local/lib) + endif() + if(OPENBSD) + if(IS_DIRECTORY /usr/X11R6/include) + include_directories(/usr/X11R6/include) + endif() + endif() +endif() + +if(WITH_CHANNELS) + add_subdirectory(channels) +endif() + +if(WITH_CLIENT_COMMON OR WITH_CLIENT) +add_subdirectory(client) +endif() + +if(WITH_SERVER) + add_subdirectory(server) +endif() + +# Packaging + +set(CMAKE_CPACK_INCLUDE_FILE "CMakeCPack.cmake") + +if(NOT (VENDOR MATCHES "FreeRDP")) + if(DEFINED CLIENT_VENDOR_PATH) + if(EXISTS "${CMAKE_SOURCE_DIR}/${CLIENT_VENDOR_PATH}/CMakeCPack.cmake") + set(CMAKE_CPACK_INCLUDE_FILE "${CLIENT_VENDOR_PATH}/CMakeCPack.cmake") + endif() + endif() +endif() + +#message("VENDOR: ${VENDOR} CLIENT_VENDOR_PATH: ${CLIENT_VENDOR_PATH} CMAKE_CPACK_INCLUDE_FILE: ${CMAKE_CPACK_INCLUDE_FILE}") + +include(${CMAKE_CPACK_INCLUDE_FILE}) + +set(FREERDP_BUILD_CONFIG_LIST "") +GET_CMAKE_PROPERTY(res VARIABLES) +FOREACH(var ${res}) + IF (var MATCHES "^WITH_*|^BUILD_TESTING|^BUILTIN_CHANNELS|^HAVE_*") + LIST(APPEND FREERDP_BUILD_CONFIG_LIST "${var}=${${var}}") + ENDIF() +ENDFOREACH() +string(REPLACE ";" " " FREERDP_BUILD_CONFIG "${FREERDP_BUILD_CONFIG_LIST}") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/buildflags.h.in ${CMAKE_CURRENT_BINARY_DIR}/buildflags.h) diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..b7f6656 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,270 @@ +# 2018-11-19 Version 2.0.0-rc4 + +FreeRDP 2.0.0-rc4 is the fifth release candidate for 2.0.0. Although it mainly +addresses security and stability there are some new features as well. + +Noteworthy features and improvements: + +* fix multiple reported CVEs (#5031) +* gateway: multiple fixes and improvements (#3600, #4787, #4902, #4964, #4947, + #4952, #4938) +* client/X11: support for rail (remote app) icons was added (#5012) +* licensing: the licensing code was re-worked. Per-device licenses + are now saved on the client and used on re-connect. + WARNING: this is a change in FreeRDP behavior regarding licensing. If the old + behavior is required, or no licenses should be saved use the + new command line option +old-license (#4979) +* improve order handling - only orders that were enabled + during capability exchange are accepted (#4926). + WARNING and NOTE: some servers do improperly send orders that weren't negotiated, + for such cases the new command line option /relax-order-checks was added to + disable the strict order checking. If connecting to xrdp the options + /relax-order-checks *and* +glyph-cache are required. +* /smartcard has now support for substring filters (#4840) + for details see https://github.com/FreeRDP/FreeRDP/wiki/smartcard-logon +* add support to set tls security level (for openssl >= 1.1.0) + - default level is set to 1 + - the new command line option /tls-seclevel:[LEVEL] allows to set + a different level if required +* add new command line option /smartcard-logon to allow + smartcard login (currently only with RDP security) (#4842) +* new command line option: /window-position to allow positioning + the window on startup (#5018) +* client/X11: set window title before mapping (#5023) +* rdpsnd/audin (mostly server side) add support for audio re-sampling using soxr or ffmpeg +* client/Android: add Japanese translation (#4906) +* client/Android: add Korean translation (#5029) + +For a complete and detailed change log since the last release candidate run: +git log 2.0.0-rc3..2.0.0-rc4 + + +# 2018-08-01 Version 2.0.0-rc3 + +FreeRDP 2.0.0-rc3 is the fourth release candidate for 2.0.0. +For a complete and detailed change log since the last release candidate run: +git log 2.0.0-rc2..2.0.0-rc3 + +Noteworthy features and improvements: + +* Updated and improved sound and microphone redirection format support (AAC) +* Improved reliability of reconnect and redirection support +* Fixed memory leaks with +async-update +* Improved connection error reporting +* Improved gateway support (various fixes for HTTP and RDG) +* SOCKS proxy support (all clients) +* More reliable resolution switching with /dynamic-resolution (MS-RDPEVOR) (xfreerdp) + +Fixed github issues (excerpt): + +* #1924, #4132, #4511 Fixed redirection +* #4165 AAC and MP3 codec support for sound and microphone redirection +* #4222 Gateway connections prefer IP to hostname +* #4550 Fixed issues with +async-update +* #4634 Comment support in known_hosts file +* #4684 /drive and +drives don't work togehter +* #4735 Automatically reconnect if connection timed out waiting for user interaction + +See https://github.com/FreeRDP/FreeRDP/milestone/9 for a complete list. + + +# 2017-11-28 Version 2.0.0-rc2 + +FreeRDP 2.0.0-rc2 is the third release candidate for 2.0.0. +For a complete and detailed change log since the last release candidate run: +git log 2.0.0-rc1..2.0.0-rc2 + +Noteworthy features and improvements: + +* IMPORTANT: add support CredSSP v6 - this fixes KB4088776 see #4449, #4488 +* basic support for the "Video Optimized Remoting Virtual Channel Extension" (MS-RDPEVOR) was added +* many smart card related fixes and cleanups #4312 +* fix ccache support +* fix OpenSSL 1.1.0 detection on Windows +* fix IPv6 handling on Windows +* add support for memory and thread sanitizer +* support for dynamic resloution changes was added in xfreerdp #4313 +* support for gateway access token (command line option /gat) was added +* initial support for travis-ci.org was added +* SSE optimization version of RGB to AVC444 frame split was added +* build: -msse2/-msse3 are not enabled globally anymore + +Fixed github issues (excerpt): + +* #4227 Convert settings->Password to binary blob +* #4231 freerdp-2.0.0_rc0: 5 tests failed out of 184 on ppc +* #4276 Big endian fixes +* #4291 xfreerdp “Segmentation fault” when connecting to freerdp-shadow-cli +* #4293 [X11] shadow server memory corruption with /monitors:2 #4293 +* #4296 drive redirection - raise an error if the directory can't be founde +* #4306 Cannot connect to shadow server with NLA auth: SEC_E_OUT_OF_SEQUENCE +* #4447 Apple rpath namespace fixes +* #4457 Fix /size: /w: /h: with /monitors: (Fix custom sizes) +* #4527 pre-connection blob (pcb) support in .rdp files +* #4552 Fix Windows 10 cursors drawing as black +* smartcard related: #3521, #3431, #3474, #3488, #775, #1424 + +See https://github.com/FreeRDP/FreeRDP/milestone/8 for a complete list. + + +# 2017-11-28 Version 2.0.0-rc1 + +FreeRDP 2.0.0-rc1 is the second release candidate for 2.0.0. +For a complete and detailed change log since the last release candidate run: +git log 2.0.0-rc0..master + +Noteworthy features and improvements: + +* support for FIPS mode was added (option +fipsmode) +* initial client side kerberos support (run cmake with WITH_GSSAPI) +* support for ssh-agent redirection (as rdp channel) +* the man page(s) and /help were updated an improved +* build: support for GNU/kFreeBSD +* add support for ICU for unicode conversion (-DWITH_ICU=ON) +* client add option to force password prompt before connection (/from-stdin[:force]) +* add Samsung DeX support +* extend /size to allow width or height percentages (#4146) +* add support for "password is pin" +* clipboard is now enabled per default (use -clipboard to disable) + +Fixed github issues (excerpt): + +* #4281: Added option to prefer IPv6 over IPv4 +* #3890: Point to OpenSSL doc for private CA +* #3378: support 31 static channels as described in the spec +* #1536: fix clipboard on mac +* #4253: Rfx decode tile width. +* #3267: fix parsing of drivestoredirect +* #4257: Proper error checks for /kbd argument +* #4249: Corruption due to recursive parser +* #4111: 15bpp color handling for brush. +* #3509: Added Ctrl+Alt+Enter description +* #3211: Return freerdp error from main. +* #3513: add better description for drive redirection +* #4199: ConvertFindDataAToW string length +* #4135: client/x11: fix colors on big endian +* #4089: fix h264 context leak when DeleteSurface +* #4117: possible segfault +* #4091: fix a regression with remote program + +See https://github.com/FreeRDP/FreeRDP/milestone/7 for a complete list. + + +2012-02-07 Version 1.0.1 + +FreeRDP 1.0.1 is a maintenance release to address a certain number of +issues found in 1.0.0. This release also brings corrective measures +to certificate validation which were required for inclusion in Ubuntu. + +* Certificate Validation + * Improved validation logic and robustness + * Added validation of certificate name against hostname + +* Token-based Server Redirection + * Fixed redirection logic + * HAProxy load-balancer support + +* xfreerdp-server + * better event handling + * capture performance improvements + +* wfreerdp + * Fix RemoteFX support + * Fix mingw64 compilation + +* libfreerdp-core: + * Fix severe TCP sending bug + * Added server-side Standard RDP security + +2012-01-16 Version 1.0.0 + +License: + +FreeRDP 1.0 is the first release of FreeRDP under the Apache License 2.0. +The FreeRDP 1.x series is a rewrite, meaning there is no continuity with +the previous FreeRDP 0.x series which were released under GPLv2. + +New Features: + +* RemoteFX + * Both encoder and decoder + * SSE2 and NEON optimization +* NSCodec +* RemoteApp + * Working, minor glitches +* Multimedia Redirection + * ffmpeg support +* Network Level Authentication (NLA) + * NTLMv2 +* Certificate validation +* FIPS-compliant RDP security +* new build system (cmake) +* added official logo and icon + +New Architecture: + +* libfreerdp-core + * core protocol + * highly portable + * both client and server +* libfreerdp-cache + * caching operations +* libfreerdp-codec + * bitmap decompression + * codec encoding/decoding +* libfreerdp-kbd + * keyboard mapping +* libfreerdp-channels + * virtual channel management + * client and server side support +* libfreerdp-gdi + * extensively unit tested + * portable software GDI implementation +* libfreerdp-rail + * RemoteApp library +* libfreerdp-utils + * shared utility library + +FreeRDP Clients: + +* client/X11 (xfreerdp) + * official client + * RemoteApp support + * X11 GDI implementation +* client/DirectFB (dfreerdp) + * DirectFB support + * software-based GDI (libfreerdp-gdi) +* client/Windows (wfreerdp) + * Native Win32 support + +FreeRDP Servers (experimental): + +* server/X11 (xfreerdp-server) + * RemoteFX-only + * no authentication + * highly experimental + * keyboard and mouse input supported + +Virtual Channels: + +* cliprdr (Clipboard Redirection) +* rail (RemoteApp) +* drdynvc (Dynamic Virtual Channels) + * audin (Audio Input Redirection) + * alsa support + * pulse support + * tsmf (Multimedia Redirection) + * alsa support + * pulse support + * ffmpeg support +* rdpdr (Device Redirection) + * disk (Disk Redirection) + * parallel (Parallel Port Redirection) + * serial (Serial Port Redirection) + * printer (Printer Redirection) + * CUPS support + * smartcard (Smartcard Redirection) +* rdpsnd (Sound Redirection) + * alsa support + * pulse support + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README b/README new file mode 100644 index 0000000..d1ad246 --- /dev/null +++ b/README @@ -0,0 +1,34 @@ +FreeRDP: A Remote Desktop Protocol Implementation +================================================= + +FreeRDP is a free implementation of the Remote Desktop Protocol (RDP), released under the Apache license. +Enjoy the freedom of using your software wherever you want, the way you want it, in a world where +interoperability can finally liberate your computing experience. + +Resources +--------- + +Project website: https://www.freerdp.com/ +Issue tracker: https://github.com/FreeRDP/FreeRDP/issues +Sources: https://github.com/FreeRDP/FreeRDP/ +Downloads: https://pub.freerdp.com/releases/ +Wiki: https://github.com/FreeRDP/FreeRDP/wiki +API documentation: https://pub.freerdp.com/api/ + +IRC channel: #freerdp @ irc.freenode.net +Mailing list: https://lists.sourceforge.net/lists/listinfo/freerdp-devel + +Microsoft Open Specifications +----------------------------- + +Information regarding the Microsoft Open Specifications can be found at: +http://www.microsoft.com/openspecifications/ + +A list of reference documentation is maintained here: +https://github.com/FreeRDP/FreeRDP/wiki/Reference-Documentation + +Compilation +----------- + +Instructions on how to get started compiling FreeRDP can be found on the wiki: +https://github.com/FreeRDP/FreeRDP/wiki/Compilation diff --git a/buildflags.h.in b/buildflags.h.in new file mode 100644 index 0000000..0cc4a64 --- /dev/null +++ b/buildflags.h.in @@ -0,0 +1,11 @@ +#ifndef FREERDP_BUILD_FLAGS_H +#define FREERDP_BUILD_FLAGS_H + +#define CFLAGS "${CMAKE_C_FLAGS}" +#define COMPILER_ID "${CMAKE_C_COMPILER_ID}" +#define COMPILER_VERSION "${CMAKE_C_COMPILER_VERSION}" +#define TARGET_ARCH "${TARGET_ARCH}" +#define BUILD_CONFIG "${FREERDP_BUILD_CONFIG}" +#define BUILD_TYPE "${CMAKE_BUILD_TYPE}" + +#endif /* FREERDP_BUILD_FLAGS_H */ diff --git a/channels/CMakeLists.txt b/channels/CMakeLists.txt new file mode 100644 index 0000000..76a5716 --- /dev/null +++ b/channels/CMakeLists.txt @@ -0,0 +1,300 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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(CMAKE_POSITION_INDEPENDENT_CODE ON) + +include(CMakeParseArguments) +include(CMakeDependentOption) + +macro(define_channel_options) + set(PREFIX "CHANNEL") + + cmake_parse_arguments(${PREFIX} + "" + "NAME;TYPE;DESCRIPTION;SPECIFICATIONS;DEFAULT" + "" + ${ARGN}) + + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION) + + if(${${CHANNEL_CLIENT_OPTION}}) + set(OPTION_CLIENT_DEFAULT ${${CHANNEL_CLIENT_OPTION}}) + endif() + + if(${${CHANNEL_SERVER_OPTION}}) + set(OPTION_SERVER_DEFAULT ${${CHANNEL_SERVER_OPTION}}) + endif() + + if(${${CHANNEL_OPTION}}) + set(OPTION_DEFAULT ${${CHANNEL_OPTION}}) + endif() + + if(${OPTION_CLIENT_DEFAULT} OR ${OPTION_SERVER_DEFAULT}) + set(OPTION_DEFAULT "ON") + endif() + + set(CHANNEL_DEFAULT ${OPTION_DEFAULT}) + + set(CHANNEL_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel") + option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT}) + +endmacro(define_channel_options) + +macro(define_channel_client_options _channel_client_default) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION) + set(CHANNEL_CLIENT_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel client") + option(${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}" ${_channel_client_default}) + cmake_dependent_option(${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}" + ${_channel_client_default} "${CHANNEL_OPTION}" OFF) +endmacro(define_channel_client_options) + +macro(define_channel_server_options _channel_server_default) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION) + set(CHANNEL_SERVER_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel server") + option(${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}" ${_channel_server_default}) + cmake_dependent_option(${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}" + ${_channel_server_default} "${CHANNEL_OPTION}" OFF) +endmacro(define_channel_server_options) + +macro(define_channel _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME ${CHANNEL_NAME}) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" MODULE_PREFIX) +endmacro(define_channel) + +macro(define_channel_client _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME "${CHANNEL_NAME}-client") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" MODULE_PREFIX) +endmacro(define_channel_client) + +macro(define_channel_server _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME "${CHANNEL_NAME}-server") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" MODULE_PREFIX) +endmacro(define_channel_server) + +macro(define_channel_client_subsystem _channel_name _subsystem _type) + set(CHANNEL_NAME ${_channel_name}) + set(CHANNEL_SUBSYSTEM ${_subsystem}) + string(LENGTH "${_type}" _type_length) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_PREFIX) + if(_type_length GREATER 0) + set(SUBSYSTEM_TYPE ${_type}) + set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}-${SUBSYSTEM_TYPE}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}_${SUBSYSTEM_TYPE}" MODULE_PREFIX) + else() + set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX) + endif() +endmacro(define_channel_client_subsystem) + +macro(define_channel_server_subsystem _channel_name _subsystem _type) + set(CHANNEL_NAME ${_channel_name}) + set(CHANNEL_SUBSYSTEM ${_subsystem}) + set(MODULE_NAME "${CHANNEL_NAME}-server-${CHANNEL_SUBSYSTEM}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_server_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX) +endmacro(define_channel_server_subsystem) + +macro(add_channel_client _channel_prefix _channel_name) + add_subdirectory(client) + if(${${_channel_prefix}_CLIENT_STATIC}) + set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} ${_channel_prefix} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_NAME ${${_channel_prefix}_CLIENT_NAME} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_CHANNEL ${${_channel_prefix}_CLIENT_CHANNEL} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_ENTRY ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE) + set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE) + endif() +endmacro(add_channel_client) + +macro(add_channel_server _channel_prefix _channel_name) + add_subdirectory(server) + if(${${_channel_prefix}_SERVER_STATIC}) + set(CHANNEL_STATIC_SERVER_MODULES ${CHANNEL_STATIC_SERVER_MODULES} ${_channel_prefix} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_NAME ${${_channel_prefix}_SERVER_NAME} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_CHANNEL ${${_channel_prefix}_SERVER_CHANNEL} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_ENTRY ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE) + set(CHANNEL_STATIC_SERVER_ENTRIES ${CHANNEL_STATIC_SERVER_ENTRIES} ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE) + endif() +endmacro(add_channel_server) + +macro(add_channel_client_subsystem _channel_prefix _channel_name _subsystem _type) + add_subdirectory(${_subsystem}) + set(_channel_module_name "${_channel_name}-client") + string(LENGTH "${_type}" _type_length) + if(_type_length GREATER 0) + string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}_${_type}" _subsystem_prefix) + else() + string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}" _subsystem_prefix) + endif() + if(${${_subsystem_prefix}_STATIC}) + get_target_property(CHANNEL_SUBSYSTEMS ${_channel_module_name} SUBSYSTEMS) + if(_type_length GREATER 0) + set(SUBSYSTEMS ${SUBSYSTEMS} "${_subsystem}-${_type}") + else() + set(SUBSYSTEMS ${SUBSYSTEMS} ${_subsystem}) + endif() + set_target_properties(${_channel_module_name} PROPERTIES SUBSYSTEMS "${SUBSYSTEMS}") + endif() +endmacro(add_channel_client_subsystem) + +macro(channel_install _targets _destination _export_target) + install(TARGETS ${_targets} DESTINATION ${_destination} EXPORT ${_export_target}) +endmacro(channel_install) + +macro(server_channel_install _targets _destination) + channel_install(${_targets} ${_destination} "FreeRDP-ServerTargets") +endmacro(server_channel_install) + +macro(client_channel_install _targets _destination) + channel_install(${_targets} ${_destination} "FreeRDP-ClientTargets") +endmacro(client_channel_install) + +macro(add_channel_client_library _module_prefix _module_name _channel_name _dynamic _entry) + if(${_dynamic} AND (NOT BUILTIN_CHANNELS)) +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt + if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_PATCH 0) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${_module_name}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${_module_prefix}_SRCS ${${_module_prefix}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + endif() + + add_library(${_module_name} ${${_module_prefix}_SRCS}) + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + else() + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE) + set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE) + add_library(${_module_name} STATIC ${${_module_prefix}_SRCS}) + if (${CMAKE_VERSION} VERSION_LESS 2.8.12 OR NOT BUILD_SHARED_LIBS) + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + endif() + endif() +endmacro(add_channel_client_library) + +macro(add_channel_client_subsystem_library _module_prefix _module_name _channel_name _type _dynamic _entry) + if(${_dynamic} AND (NOT BUILTIN_CHANNELS)) +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt + if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_PATCH 0) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${_module_name}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${_module_prefix}_SRCS ${${_module_prefix}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + endif() + + add_library(${_module_name} ${${_module_prefix}_SRCS}) + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + else() + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_TYPE ${_type} PARENT_SCOPE) + add_library(${_module_name} STATIC ${${_module_prefix}_SRCS}) + if (${CMAKE_VERSION} VERSION_LESS 2.8.12 OR NOT BUILD_SHARED_LIBS) + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + endif() + endif() +endmacro(add_channel_client_subsystem_library) + +macro(add_channel_server_library _module_prefix _module_name _channel_name _dynamic _entry) + if(${_dynamic} AND (NOT BUILTIN_CHANNELS)) +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt + if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${_module_name}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${_module_prefix}_SRCS ${${_module_prefix}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + endif() + + add_library(${_module_name} ${${_module_prefix}_SRCS}) + server_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + else() + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE) + set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE) + add_library(${_module_name} STATIC ${${_module_prefix}_SRCS}) + if (${CMAKE_VERSION} VERSION_LESS 2.8.12 OR NOT BUILD_SHARED_LIBS) + server_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) + endif() + endif() +endmacro(add_channel_server_library) + +set(FILENAME "ChannelOptions.cmake") +file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}") + +foreach(FILEPATH ${FILEPATHS}) + if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}") + string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" DIR ${FILEPATH}) + set(CHANNEL_OPTION) + include(${FILEPATH}) + if(${CHANNEL_OPTION}) + set(CHANNEL_MESSAGE "Adding ${CHANNEL_TYPE} channel") + if(${CHANNEL_CLIENT_OPTION}) + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} client") + endif() + if(${CHANNEL_SERVER_OPTION}) + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} server") + endif() + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} \"${CHANNEL_NAME}\"") + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE}: ${CHANNEL_DESCRIPTION}") + message(STATUS "${CHANNEL_MESSAGE}") + add_subdirectory(${DIR}) + endif() + endif() +endforeach(FILEPATH) + +if(WITH_CLIENT_CHANNELS) + add_subdirectory(client) + set(FREERDP_CHANNELS_CLIENT_SRCS ${FREERDP_CHANNELS_CLIENT_SRCS} PARENT_SCOPE) + set(FREERDP_CHANNELS_CLIENT_LIBS ${FREERDP_CHANNELS_CLIENT_LIBS} PARENT_SCOPE) +endif() + +if(WITH_SERVER_CHANNELS) + add_subdirectory(server) + set(FREERDP_CHANNELS_SERVER_SRCS ${FREERDP_CHANNELS_SERVER_SRCS} PARENT_SCOPE) + set(FREERDP_CHANNELS_SERVER_LIBS ${FREERDP_CHANNELS_SERVER_LIBS} PARENT_SCOPE) +endif() diff --git a/channels/audin/CMakeLists.txt b/channels/audin/CMakeLists.txt new file mode 100644 index 0000000..d72b102 --- /dev/null +++ b/channels/audin/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("audin") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/audin/ChannelOptions.cmake b/channels/audin/ChannelOptions.cmake new file mode 100644 index 0000000..39ca402 --- /dev/null +++ b/channels/audin/ChannelOptions.cmake @@ -0,0 +1,17 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +if(ANDROID) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "audin" TYPE "dynamic" + DESCRIPTION "Audio Input Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEAI]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/audin/client/CMakeLists.txt b/channels/audin/client/CMakeLists.txt new file mode 100644 index 0000000..0c2e393 --- /dev/null +++ b/channels/audin/client/CMakeLists.txt @@ -0,0 +1,58 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("audin") + +set(${MODULE_PREFIX}_SRCS + audin_main.c + audin_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +target_link_libraries(${MODULE_NAME} freerdp winpr) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "") +endif() + +if(WITH_OPENSLES) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "") +endif() + +if(WITH_WINMM) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "") +endif() + +if(WITH_MACAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "") +endif() diff --git a/channels/audin/client/alsa/CMakeLists.txt b/channels/audin/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..c9f4a5f --- /dev/null +++ b/channels/audin/client/alsa/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "alsa" "") + +set(${MODULE_PREFIX}_SRCS + audin_alsa.c) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS freerdp winpr ${ALSA_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/audin/client/alsa/audin_alsa.c b/channels/audin/client/alsa/audin_alsa.c new file mode 100644 index 0000000..169422a --- /dev/null +++ b/channels/audin/client/alsa/audin_alsa.c @@ -0,0 +1,471 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - ALSA implementation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "audin_main.h" + +typedef struct _AudinALSADevice +{ + IAudinDevice iface; + + char* device_name; + UINT32 frames_per_packet; + AUDIO_FORMAT aformat; + + HANDLE thread; + HANDLE stopEvent; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; + wLog* log; + int bytes_per_frame; +} AudinALSADevice; + +static snd_pcm_format_t audin_alsa_format(UINT32 wFormatTag, UINT32 bitPerChannel) +{ + switch (wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (bitPerChannel) + { + case 16: + return SND_PCM_FORMAT_S16_LE; + + case 8: + return SND_PCM_FORMAT_S8; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } + + case WAVE_FORMAT_ALAW: + return SND_PCM_FORMAT_A_LAW; + + case WAVE_FORMAT_MULAW: + return SND_PCM_FORMAT_MU_LAW; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static BOOL audin_alsa_set_params(AudinALSADevice* alsa, + snd_pcm_t* capture_handle) +{ + int error; + UINT32 channels = alsa->aformat.nChannels; + snd_pcm_hw_params_t* hw_params; + snd_pcm_format_t format = audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample); + + if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_hw_params_malloc (%s)", + snd_strerror(error)); + return FALSE; + } + + snd_pcm_hw_params_any(capture_handle, hw_params); + snd_pcm_hw_params_set_access(capture_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(capture_handle, hw_params, format); + snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, + &alsa->aformat.nSamplesPerSec, NULL); + snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, + &channels); + snd_pcm_hw_params(capture_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + snd_pcm_prepare(capture_handle); + alsa->aformat.nChannels = channels; + alsa->bytes_per_frame = snd_pcm_format_size(format, 1) * channels; + return TRUE; +} + +static DWORD WINAPI audin_alsa_thread_func(LPVOID arg) +{ + long error; + BYTE* buffer; + snd_pcm_t* capture_handle = NULL; + AudinALSADevice* alsa = (AudinALSADevice*) arg; + DWORD status; + WLog_Print(alsa->log, WLOG_DEBUG, "in"); + + if ((error = snd_pcm_open(&capture_handle, alsa->device_name, + SND_PCM_STREAM_CAPTURE, 0)) < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(error)); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto out; + } + + if (!audin_alsa_set_params(alsa, capture_handle)) + { + WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_set_params failed"); + goto out; + } + + buffer = (BYTE*) calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame); + + if (!buffer) + { + WLog_Print(alsa->log, WLOG_ERROR, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + while (1) + { + size_t frames = alsa->frames_per_packet; + status = WaitForSingleObject(alsa->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %ld!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + error = snd_pcm_readi(capture_handle, buffer, frames); + + if (error == 0) + continue; + + if (error == -EPIPE) + { + snd_pcm_recover(capture_handle, error, 0); + continue; + } + else if (error < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror(error)); + break; + } + + error = alsa->receive(&alsa->aformat, + buffer, error * alsa->bytes_per_frame, alsa->user_data); + + if (error) + { + WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_thread_receive failed with error %ld", error); + break; + } + } + + free(buffer); + + if (capture_handle) + snd_pcm_close(capture_handle); + +out: + WLog_Print(alsa->log, WLOG_DEBUG, "out"); + + if (error && alsa->rdpcontext) + setChannelError(alsa->rdpcontext, error, + "audin_alsa_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_free(IAudinDevice* device) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + if (alsa) + free(alsa->device_name); + + free(alsa); + return CHANNEL_RC_OK; +} + +static BOOL audin_alsa_format_supported(IAudinDevice* device, + const AUDIO_FORMAT* format) +{ + if (!device || !format) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && + (format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + return TRUE; + + default: + return FALSE; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + if (!alsa || !format) + return ERROR_INVALID_PARAMETER; + + alsa->aformat = *format; + alsa->frames_per_packet = FramesPerPacket; + + if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, + void* user_data) +{ + AudinALSADevice* alsa = (AudinALSADevice*) device; + + if (!device || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + alsa->receive = receive; + alsa->user_data = user_data; + + if (!(alsa->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_Print(alsa->log, WLOG_ERROR, "CreateEvent failed!"); + goto error_out; + } + + if (!(alsa->thread = CreateThread(NULL, 0, + audin_alsa_thread_func, alsa, 0, NULL))) + { + WLog_Print(alsa->log, WLOG_ERROR, "CreateThread failed!"); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + CloseHandle(alsa->stopEvent); + alsa->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_close(IAudinDevice* device) +{ + UINT error = CHANNEL_RC_OK; + AudinALSADevice* alsa = (AudinALSADevice*) device; + + if (!alsa) + return ERROR_INVALID_PARAMETER; + + if (alsa->stopEvent) + { + SetEvent(alsa->stopEvent); + + if (WaitForSingleObject(alsa->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + CloseHandle(alsa->stopEvent); + alsa->stopEvent = NULL; + CloseHandle(alsa->thread); + alsa->thread = NULL; + } + + alsa->receive = NULL; + alsa->user_data = NULL; + return error; +} + +static COMMAND_LINE_ARGUMENT_A audin_alsa_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, + ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinALSADevice* alsa = (AudinALSADevice*) device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | + COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + audin_alsa_args, flags, alsa, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_alsa_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + alsa->device_name = _strdup(arg->Value); + + if (!alsa->device_name) + { + WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry alsa_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS + pEntryPoints) +{ + ADDIN_ARGV* args; + AudinALSADevice* alsa; + UINT error; + alsa = (AudinALSADevice*) calloc(1, sizeof(AudinALSADevice)); + + if (!alsa) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + alsa->log = WLog_Get(TAG); + alsa->iface.Open = audin_alsa_open; + alsa->iface.FormatSupported = audin_alsa_format_supported; + alsa->iface.SetFormat = audin_alsa_set_format; + alsa->iface.Close = audin_alsa_close; + alsa->iface.Free = audin_alsa_free; + alsa->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_alsa_parse_addin_args(alsa, args))) + { + WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_parse_addin_args failed with errorcode %"PRIu32"!", + error); + goto error_out; + } + + if (!alsa->device_name) + { + alsa->device_name = _strdup("default"); + + if (!alsa->device_name) + { + WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + } + + alsa->frames_per_packet = 128; + alsa->aformat.nChannels = 2; + alsa->aformat.wBitsPerSample = 16; + alsa->aformat.wFormatTag = WAVE_FORMAT_PCM; + alsa->aformat.nSamplesPerSec = 44100; + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, + (IAudinDevice*) alsa))) + { + WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(alsa->device_name); + free(alsa); + return error; +} diff --git a/channels/audin/client/audin_main.c b/channels/audin/client/audin_main.c new file mode 100644 index 0000000..b7ce330 --- /dev/null +++ b/channels/audin/client/audin_main.c @@ -0,0 +1,1079 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2015 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include "audin_main.h" + +#define MSG_SNDIN_VERSION 0x01 +#define MSG_SNDIN_FORMATS 0x02 +#define MSG_SNDIN_OPEN 0x03 +#define MSG_SNDIN_OPEN_REPLY 0x04 +#define MSG_SNDIN_DATA_INCOMING 0x05 +#define MSG_SNDIN_DATA 0x06 +#define MSG_SNDIN_FORMATCHANGE 0x07 + +typedef struct _AUDIN_LISTENER_CALLBACK AUDIN_LISTENER_CALLBACK; +struct _AUDIN_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +typedef struct _AUDIN_CHANNEL_CALLBACK AUDIN_CHANNEL_CALLBACK; +struct _AUDIN_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + /** + * The supported format list sent back to the server, which needs to + * be stored as reference when the server sends the format index in + * Open PDU and Format Change PDU + */ + AUDIO_FORMAT* formats; + UINT32 formats_count; +}; + +typedef struct _AUDIN_PLUGIN AUDIN_PLUGIN; +struct _AUDIN_PLUGIN +{ + IWTSPlugin iface; + + AUDIN_LISTENER_CALLBACK* listener_callback; + + /* Parsed plugin data */ + AUDIO_FORMAT* fixed_format; + char* subsystem; + char* device_name; + + /* Device interface */ + IAudinDevice* device; + + rdpContext* rdpcontext; + BOOL attached; + wStream* data; + AUDIO_FORMAT* format; + UINT32 FramesPerPacket; + + FREERDP_DSP_CONTEXT* dsp_context; + wLog* log; +}; + +static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, ADDIN_ARGV* args); + +static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out, + BOOL freeStream) +{ + UINT error; + + if (!callback || !out) + return ERROR_INVALID_PARAMETER; + + if (!callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + Stream_SealLength(out); + error = callback->channel->Write(callback->channel, + Stream_Length(out), + Stream_Buffer(out), NULL); + + if (freeStream) + Stream_Free(out, TRUE); + + return error; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + wStream* out; + const UINT32 ClientVersion = 0x01; + UINT32 ServerVersion; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, ServerVersion); + WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%"PRIu32", ClientVersion=%"PRIu32, ServerVersion, + ClientVersion); + + /* Do not answer server packet, we do not support the channel version. */ + if (ServerVersion != ClientVersion) + { + WLog_Print(audin->log, WLOG_WARN, + "Incompatible channel version server=%"PRIu32", client supports version=%"PRIu32, ServerVersion, + ClientVersion); + return CHANNEL_RC_OK; + } + + out = Stream_New(NULL, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT8(out, MSG_SNDIN_VERSION); + Stream_Write_UINT32(out, ClientVersion); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback) +{ + BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING }; + + if (!callback || !callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + return callback->channel->Write(callback->channel, 1, out_data, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 i; + UINT error; + wStream* out; + UINT32 NumFormats; + UINT32 cbSizeFormatsPacket; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, NumFormats); + WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %"PRIu32"", NumFormats); + + if ((NumFormats < 1) || (NumFormats > 1000)) + { + WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %"PRIu32"", NumFormats); + return ERROR_INVALID_DATA; + } + + Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */ + callback->formats = audio_formats_new(NumFormats); + + if (!callback->formats) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return ERROR_INVALID_DATA; + } + + out = Stream_New(NULL, 9); + + if (!out) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + goto out; + } + + Stream_Seek(out, 9); + + /* SoundFormats (variable) */ + for (i = 0; i < NumFormats; i++) + { + AUDIO_FORMAT format = { 0 }; + + if (!audio_format_read(s, &format)) + { + error = ERROR_INVALID_DATA; + goto out; + } + + audio_format_print(audin->log, WLOG_DEBUG, &format); + + if (!audio_format_compatible(audin->fixed_format, &format)) + { + audio_format_free(&format); + continue; + } + + if (freerdp_dsp_supports_format(&format, TRUE) || + audin->device->FormatSupported(audin->device, &format)) + { + /* Store the agreed format in the corresponding index */ + callback->formats[callback->formats_count++] = format; + + if (!audio_format_write(out, &format)) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + goto out; + } + } + else + { + audio_format_free(&format); + } + } + + if ((error = audin_send_incoming_data_pdu(callback))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!"); + goto out; + } + + cbSizeFormatsPacket = (UINT32) Stream_GetPosition(out); + Stream_SetPosition(out, 0); + Stream_Write_UINT8(out, MSG_SNDIN_FORMATS); /* Header (1 byte) */ + Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */ + Stream_Write_UINT32(out, cbSizeFormatsPacket); /* cbSizeFormatsPacket (4 bytes) */ + Stream_SetPosition(out, cbSizeFormatsPacket); + error = audin_channel_write_and_free(callback, out, FALSE); +out: + + if (error != CHANNEL_RC_OK) + { + audio_formats_free(callback->formats, NumFormats); + callback->formats = NULL; + } + + Stream_Free(out, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + UINT32 NewFormat) +{ + wStream* out = Stream_New(NULL, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_OK; + } + + Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE); + Stream_Write_UINT32(out, NewFormat); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + UINT32 Result) +{ + wStream* out = Stream_New(NULL, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY); + Stream_Write_UINT32(out, Result); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, + const BYTE* data, size_t size, void* user_data) +{ + UINT error; + BOOL compatible; + AUDIN_PLUGIN* audin; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) user_data; + + if (!callback) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + audin = (AUDIN_PLUGIN*)callback->plugin; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!audin->attached) + return CHANNEL_RC_OK; + + Stream_SetPosition(audin->data, 0); + + if (!Stream_EnsureRemainingCapacity(audin->data, 1)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA); + + compatible = audio_format_compatible(format, audin->format); + if (compatible && audin->device->FormatSupported(audin->device, audin->format)) + { + if (!Stream_EnsureRemainingCapacity(audin->data, size)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write(audin->data, data, size); + } + else + { + if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data)) + return ERROR_INTERNAL_ERROR; + } + + /* Did not encode anything, skip this, the codec is not ready for output. */ + if (Stream_GetPosition(audin->data) <= 1) + return CHANNEL_RC_OK; + + audio_format_print(audin->log, WLOG_TRACE, audin->format); + WLog_Print(audin->log, WLOG_TRACE, "[%"PRIdz"/%"PRIdz"]", size, + Stream_GetPosition(audin->data) - 1); + + if ((error = audin_send_incoming_data_pdu(callback))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!"); + return error; + } + + return audin_channel_write_and_free(callback, audin->data, FALSE); +} + +static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback) +{ + UINT error = ERROR_INTERNAL_ERROR; + BOOL supported; + AUDIO_FORMAT format; + + if (!audin || !audin->device) + return FALSE; + + format = *audin->format; + supported = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format); + WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec", + audio_format_get_tag_string(format.wFormatTag)); + + if (!supported) + { + /* Default sample rates supported by most backends. */ + const UINT32 samplerates[] = { + 96000, + 48000, + 44100, + 22050 + }; + BOOL test = FALSE; + + format.wFormatTag = WAVE_FORMAT_PCM; + format.wBitsPerSample = 16; + test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format); + if (!test) + { + size_t x; + for (x=0; xdevice->FormatSupported, audin->device, &format); + if (test) + break; + } + } + if (!test) + return FALSE; + } + + IFCALLRET(audin->device->SetFormat, error, + audin->device, &format, + audin->FramesPerPacket); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "SetFormat failed with errorcode %"PRIu32"", error); + return FALSE; + } + + if (!supported) + { + if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format)) + return FALSE; + } + + IFCALLRET(audin->device->Open, error, audin->device, + audin_receive_wave_data, callback); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Open failed with errorcode %"PRIu32"", error); + return FALSE; + } + + return TRUE; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 initialFormat; + UINT32 FramesPerPacket; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FramesPerPacket); + Stream_Read_UINT32(s, initialFormat); + WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%"PRIu32" initialFormat=%"PRIu32"", + FramesPerPacket, initialFormat); + audin->FramesPerPacket = FramesPerPacket; + + if (initialFormat >= callback->formats_count) + { + WLog_Print(audin->log, WLOG_ERROR, "invalid format index %"PRIu32" (total %d)", + initialFormat, callback->formats_count); + return ERROR_INVALID_DATA; + } + + audin->format = &callback->formats[initialFormat]; + + if (!audin_open_device(audin, callback)) + return ERROR_INTERNAL_ERROR; + + if ((error = audin_send_format_change_pdu(audin, callback, initialFormat))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!"); + return error; + } + + if ((error = audin_send_open_reply_pdu(audin, callback, 0))) + WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_pdu failed!"); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + wStream* s) +{ + UINT32 NewFormat; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, NewFormat); + WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%"PRIu32"", NewFormat); + + if (NewFormat >= callback->formats_count) + { + WLog_Print(audin->log, WLOG_ERROR, "invalid format index %"PRIu32" (total %d)", + NewFormat, callback->formats_count); + return ERROR_INVALID_DATA; + } + + audin->format = &callback->formats[NewFormat]; + + if (audin->device) + { + IFCALLRET(audin->device->Close, error, audin->device); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Close failed with errorcode %"PRIu32"", error); + return error; + } + } + + if (!audin_open_device(audin, callback)) + return ERROR_INTERNAL_ERROR; + + if ((error = audin_send_format_change_pdu(audin, callback, NewFormat))) + WLog_ERR(TAG, "audin_send_format_change_pdu failed!"); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT error; + BYTE MessageId; + AUDIN_PLUGIN* audin; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + + if (!callback || !data) + return ERROR_INVALID_PARAMETER; + + audin = (AUDIN_PLUGIN*) callback->plugin; + + if (!audin) + return ERROR_INTERNAL_ERROR; + + if (Stream_GetRemainingCapacity(data) < 1) + return ERROR_NO_DATA; + + Stream_Read_UINT8(data, MessageId); + WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02"PRIx8"", MessageId); + + switch (MessageId) + { + case MSG_SNDIN_VERSION: + error = audin_process_version(audin, callback, data); + break; + + case MSG_SNDIN_FORMATS: + error = audin_process_formats(audin, callback, data); + break; + + case MSG_SNDIN_OPEN: + error = audin_process_open(audin, callback, data); + break; + + case MSG_SNDIN_FORMATCHANGE: + error = audin_process_format_change(audin, callback, data); + break; + + default: + WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02"PRIx8"", MessageId); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*) pChannelCallback; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) callback->plugin; + UINT error = CHANNEL_RC_OK; + WLog_Print(audin->log, WLOG_TRACE, "..."); + + if (audin->device) + { + IFCALLRET(audin->device->Close, error, audin->device); + + if (error != CHANNEL_RC_OK) + WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %"PRIu32"", error); + } + + audin->format = NULL; + audio_formats_free(callback->formats, callback->formats_count); + free(callback); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + AUDIN_CHANNEL_CALLBACK* callback; + AUDIN_PLUGIN* audin; + AUDIN_LISTENER_CALLBACK* listener_callback = (AUDIN_LISTENER_CALLBACK*) pListenerCallback; + + if (!listener_callback || !listener_callback->plugin) + return ERROR_INTERNAL_ERROR; + + audin = (AUDIN_PLUGIN*) listener_callback->plugin; + WLog_Print(audin->log, WLOG_TRACE, "..."); + callback = (AUDIN_CHANNEL_CALLBACK*) calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = audin_on_data_received; + callback->iface.OnClose = audin_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!pChannelMgr) + return ERROR_INVALID_PARAMETER; + + WLog_Print(audin->log, WLOG_TRACE, "..."); + audin->listener_callback = (AUDIN_LISTENER_CALLBACK*) calloc(1, sizeof(AUDIN_LISTENER_CALLBACK)); + + if (!audin->listener_callback) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection; + audin->listener_callback->plugin = pPlugin; + audin->listener_callback->channel_mgr = pChannelMgr; + return pChannelMgr->CreateListener(pChannelMgr, "AUDIO_INPUT", 0, + (IWTSListenerCallback*) audin->listener_callback, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_plugin_terminated(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(audin->log, WLOG_TRACE, "..."); + audio_format_free(audin->fixed_format); + + if (audin->device) + { + IFCALLRET(audin->device->Free, error, audin->device); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %"PRIu32"", error); + // dont stop on error + } + + audin->device = NULL; + } + + freerdp_dsp_context_free(audin->dsp_context); + Stream_Free(audin->data, TRUE); + free(audin->subsystem); + free(audin->device_name); + free(audin->listener_callback); + free(audin); + return CHANNEL_RC_OK; +} + +static UINT audin_plugin_attached(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + audin->attached = TRUE; + return error; +} + +static UINT audin_plugin_detached(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + audin->attached = FALSE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*) pPlugin; + + if (audin->device) + { + WLog_Print(audin->log, WLOG_ERROR, "existing device, abort."); + return ERROR_ALREADY_EXISTS; + } + + WLog_Print(audin->log, WLOG_DEBUG, "device registered."); + audin->device = device; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, char* name, ADDIN_ARGV* args) +{ + PFREERDP_AUDIN_DEVICE_ENTRY entry; + FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints; + UINT error; + entry = (PFREERDP_AUDIN_DEVICE_ENTRY) freerdp_load_channel_addin_entry("audin", (LPSTR) name, NULL, + 0); + + if (entry == NULL) + { + WLog_Print(audin->log, WLOG_ERROR, + "freerdp_load_channel_addin_entry did not return any function pointers for %s ", + name); + return ERROR_INVALID_FUNCTION; + } + + entryPoints.plugin = (IWTSPlugin*) audin; + entryPoints.pRegisterAudinDevice = audin_register_device_plugin; + entryPoints.args = args; + entryPoints.rdpcontext = audin->rdpcontext; + + if ((error = entry(&entryPoints))) + { + WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %"PRIu32".", name, error); + return error; + } + + WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem) +{ + free(audin->subsystem); + audin->subsystem = _strdup(subsystem); + + if (!audin->subsystem) + { + WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name) +{ + free(audin->device_name); + audin->device_name = _strdup(device_name); + + if (!audin->device_name) + { + WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_args[] = +{ + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { "format", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "format" }, + { "rate", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "rate" }, + { "channel", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "channel" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + UINT error; + + if (!args || args->argc == 1) + return TRUE; + + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + audin_args, flags, audin, NULL, NULL); + + if (status != 0) + return FALSE; + + arg = audin_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "sys") + { + if ((error = audin_set_subsystem(audin, arg->Value))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_set_subsystem failed with error %"PRIu32"!", error); + return FALSE; + } + } + CommandLineSwitchCase(arg, "dev") + { + if ((error = audin_set_device_name(audin, arg->Value))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_set_device_name failed with error %"PRIu32"!", error); + return FALSE; + } + } + CommandLineSwitchCase(arg, "format") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return FALSE; + + audin->fixed_format->wFormatTag = val; + } + CommandLineSwitchCase(arg, "rate") + { + long val = strtol(arg->Value, NULL, 0); + + if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX)) + return FALSE; + + audin->fixed_format->nSamplesPerSec = val; + } + CommandLineSwitchCase(arg, "channel") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + audin->fixed_format->nChannels = val; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return TRUE; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry audin_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + struct SubsystemEntry + { + char* subsystem; + char* device; + }; + UINT error = CHANNEL_RC_INITIALIZATION_ERROR; + ADDIN_ARGV* args; + AUDIN_PLUGIN* audin; + struct SubsystemEntry entries[] = + { +#if defined(WITH_PULSE) + {"pulse", ""}, +#endif +#if defined(WITH_OSS) + {"oss", "default"}, +#endif +#if defined(WITH_ALSA) + {"alsa", "default"}, +#endif +#if defined(WITH_OPENSLES) + {"opensles", "default"}, +#endif +#if defined(WITH_WINMM) + {"winmm", "default"}, +#endif +#if defined(WITH_MACAUDIO) + {"mac", "default"}, +#endif + {NULL, NULL} + }; + struct SubsystemEntry* entry = &entries[0]; + assert(pEntryPoints); + assert(pEntryPoints->GetPlugin); + audin = (AUDIN_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "audin"); + + if (audin != NULL) + return CHANNEL_RC_ALREADY_INITIALIZED; + + audin = (AUDIN_PLUGIN*) calloc(1, sizeof(AUDIN_PLUGIN)); + + if (!audin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + audin->log = WLog_Get(TAG); + audin->data = Stream_New(NULL, 4096); + audin->fixed_format = audio_format_new(); + + if (!audin->fixed_format) + goto out; + + if (!audin->data) + goto out; + + audin->dsp_context = freerdp_dsp_context_new(TRUE); + + if (!audin->dsp_context) + goto out; + + audin->attached = TRUE; + audin->iface.Initialize = audin_plugin_initialize; + audin->iface.Connected = NULL; + audin->iface.Disconnected = NULL; + audin->iface.Terminated = audin_plugin_terminated; + audin->iface.Attached = audin_plugin_attached; + audin->iface.Detached = audin_plugin_detached; + args = pEntryPoints->GetPluginData(pEntryPoints); + audin->rdpcontext = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( + pEntryPoints))->instance)->context; + + if (args) + { + if (!audin_process_addin_args(audin, args)) + goto out; + } + + if (audin->subsystem) + { + if ((error = audin_load_device_plugin(audin, audin->subsystem, args))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_load_device_plugin %s failed with error %"PRIu32"!", + audin->subsystem, error); + goto out; + } + } + else + { + while (entry && entry->subsystem && !audin->device) + { + if ((error = audin_set_subsystem(audin, entry->subsystem))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_set_subsystem for %s failed with error %"PRIu32"!", + entry->subsystem, error); + } + else if ((error = audin_set_device_name(audin, entry->device))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_set_device_name for %s failed with error %"PRIu32"!", + entry->subsystem, error); + } + else if ((error = audin_load_device_plugin(audin, audin->subsystem, args))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_load_device_plugin %s failed with error %"PRIu32"!", + entry->subsystem, error); + } + + entry++; + } + } + + if (audin->device == NULL) + WLog_Print(audin->log, WLOG_ERROR, "no sound device."); + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "audin", (IWTSPlugin*) audin); +out: + + if (error != CHANNEL_RC_OK) + audin_plugin_terminated((IWTSPlugin*)audin); + + return error; +} diff --git a/channels/audin/client/audin_main.h b/channels/audin/client/audin_main.h new file mode 100644 index 0000000..83a75a4 --- /dev/null +++ b/channels/audin/client/audin_main.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * 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_CHANNEL_AUDIN_CLIENT_MAIN_H +#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("audin.client") + +#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */ + diff --git a/channels/audin/client/mac/CMakeLists.txt b/channels/audin/client/mac/CMakeLists.txt new file mode 100644 index 0000000..b4c695f --- /dev/null +++ b/channels/audin/client/mac/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Armin Novak +# Copyright (c) 2015 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. + +define_channel_client_subsystem("audin" "mac" "") +FIND_LIBRARY(CORE_AUDIO CoreAudio) +FIND_LIBRARY(AUDIO_TOOL AudioToolbox) +FIND_LIBRARY(APP_SERVICES ApplicationServices) + +set(${MODULE_PREFIX}_SRCS + audin_mac.c) + +include_directories(..) +include_directories(${MAC_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +set(${MODULE_PREFIX}_LIBS freerdp ${CORE_AUDIO} ${AUDIO_TOOL} ${APP_SERVICES} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/audin/client/mac/audin_mac.c b/channels/audin/client/mac/audin_mac.c new file mode 100644 index 0000000..40ba05d --- /dev/null +++ b/channels/audin/client/mac/audin_mac.c @@ -0,0 +1,415 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - Mac OS X implementation + * + * Copyright (c) 2015 Armin Novak + * Copyright 2015 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define __COREFOUNDATION_CFPLUGINCOM__ 1 +#define IUNKNOWN_C_GUTS void *_reserved; void* QueryInterface; void* AddRef; void* Release + +#include +#include +#include +#include + +#include +#include + +#include "audin_main.h" + +#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100 + +/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10) + * https://developer.apple.com/documentation/coreaudio/audioformatid + */ +#ifndef AudioFormatID +typedef UInt32 AudioFormatID; +#endif + +#ifndef AudioFormatFlags +typedef UInt32 AudioFormatFlags; +#endif + +typedef struct _AudinMacDevice +{ + IAudinDevice iface; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + int dev_unit; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; + + bool isOpen; + AudioQueueRef audioQueue; + AudioStreamBasicDescription audioFormat; + AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS]; +} AudinMacDevice; + +static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatLinearPCM; + + default: + return 0; + } +} + +static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatFlagIsSignedInteger; + + default: + return 0; + } +} + +static BOOL audin_mac_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudioFormatID req_fmt = 0; + + if (device == NULL || format == NULL) + return FALSE; + + req_fmt = audin_mac_get_format(format); + + if (req_fmt == 0) + return FALSE; + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_mac_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinMacDevice* mac = (AudinMacDevice*)device; + + if (device == NULL || format == NULL) + return ERROR_INVALID_PARAMETER; + + mac->FramesPerPacket = FramesPerPacket; + mac->format = *format; + WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]", + audio_format_get_tag_string(format->wFormatTag), + format->nChannels, format->nSamplesPerSec, format->wBitsPerSample); + mac->audioFormat.mBitsPerChannel = format->wBitsPerSample; + + if (format->wBitsPerSample == 0) + mac->audioFormat.mBitsPerChannel = 16; + + mac->audioFormat.mBytesPerFrame = 0; + mac->audioFormat.mBytesPerPacket = 0; + mac->audioFormat.mChannelsPerFrame = mac->format.nChannels; + mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format); + mac->audioFormat.mFormatID = audin_mac_get_format(format); + mac->audioFormat.mFramesPerPacket = 1; + mac->audioFormat.mReserved = 0; + mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec; + return CHANNEL_RC_OK; +} + +static void mac_audio_queue_input_cb(void* aqData, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + const AudioTimeStamp* inStartTime, + UInt32 inNumPackets, + const AudioStreamPacketDescription* inPacketDesc) +{ + AudinMacDevice* mac = (AudinMacDevice*)aqData; + UINT error = CHANNEL_RC_OK; + const BYTE* buffer = inBuffer->mAudioData; + int buffer_size = inBuffer->mAudioDataByteSize; + (void)inAQ; + (void)inStartTime; + (void)inNumPackets; + (void)inPacketDesc; + + if (buffer_size > 0) + error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data); + + AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); + + if (error) + { + WLog_ERR(TAG, "mac->receive failed with error %"PRIu32"", error); + SetLastError(ERROR_INTERNAL_ERROR); + } +} + +static UINT audin_mac_close(IAudinDevice* device) +{ + UINT errCode = CHANNEL_RC_OK; + char errString[1024]; + OSStatus devStat; + AudinMacDevice* mac = (AudinMacDevice*)device; + + if (device == NULL) + return ERROR_INVALID_PARAMETER; + + if (mac->isOpen) + { + devStat = AudioQueueStop(mac->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStop failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + mac->isOpen = false; + } + + if (mac->audioQueue) + { + devStat = AudioQueueDispose(mac->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueDispose failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + mac->audioQueue = NULL; + } + + mac->receive = NULL; + mac->user_data = NULL; + return errCode; +} + +static UINT audin_mac_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinMacDevice* mac = (AudinMacDevice*)device; + DWORD errCode; + char errString[1024]; + OSStatus devStat; + size_t index; + mac->receive = receive; + mac->user_data = user_data; + devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, + mac, NULL, kCFRunLoopCommonModes, 0, &(mac->audioQueue)); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + for (index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++) + { + devStat = AudioQueueAllocateBuffer(mac->audioQueue, + mac->FramesPerPacket * 2 * mac->format.nChannels, + &mac->audioBuffers[index]); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + devStat = AudioQueueEnqueueBuffer(mac->audioQueue, + mac->audioBuffers[index], + 0, + NULL); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + } + + devStat = AudioQueueStart(mac->audioQueue, NULL); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStart failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + mac->isOpen = true; + return CHANNEL_RC_OK; +err_out: + audin_mac_close(device); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + +static UINT audin_mac_free(IAudinDevice* device) +{ + AudinMacDevice* mac = (AudinMacDevice*)device; + int error; + + if (device == NULL) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_mac_close(device))) + { + WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error); + } + + free(mac); + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_mac_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +static UINT audin_mac_parse_addin_args(AudinMacDevice* device, ADDIN_ARGV* args) +{ + DWORD errCode; + char errString[1024]; + int status; + char* str_num, *eptr; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinMacDevice* mac = (AudinMacDevice*)device; + + if (args->argc == 1) + return CHANNEL_RC_OK; + + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, + mac, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_mac_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + { + errCode = GetLastError(); + WLog_ERR(TAG, "_strdup failed with %s [%d]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + return CHANNEL_RC_NO_MEMORY; + } + + mac->dev_unit = strtol(str_num, &eptr, 10); + + if (mac->dev_unit < 0 || *eptr != '\0') + mac->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry mac_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + DWORD errCode; + char errString[1024]; + ADDIN_ARGV* args; + AudinMacDevice* mac; + UINT error; + mac = (AudinMacDevice*)calloc(1, sizeof(AudinMacDevice)); + + if (!mac) + { + errCode = GetLastError(); + WLog_ERR(TAG, "calloc failed with %s [%"PRIu32"]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + return CHANNEL_RC_NO_MEMORY; + } + + mac->iface.Open = audin_mac_open; + mac->iface.FormatSupported = audin_mac_format_supported; + mac->iface.SetFormat = audin_mac_set_format; + mac->iface.Close = audin_mac_close; + mac->iface.Free = audin_mac_free; + mac->rdpcontext = pEntryPoints->rdpcontext; + mac->dev_unit = -1; + args = pEntryPoints->args; + + if ((error = audin_mac_parse_addin_args(mac, args))) + { + WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %"PRIu32"!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) mac))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(mac); + return error; +} diff --git a/channels/audin/client/opensles/CMakeLists.txt b/channels/audin/client/opensles/CMakeLists.txt new file mode 100644 index 0000000..abc6921 --- /dev/null +++ b/channels/audin/client/opensles/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Armin Novak +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "opensles" "") + +set(${MODULE_PREFIX}_SRCS + opensl_io.c + audin_opensl_es.c) + +include_directories(..) +include_directories(${OPENSLES_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS freerdp ${OPENSLES_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/audin/client/opensles/audin_opensl_es.c b/channels/audin/client/opensles/audin_opensl_es.c new file mode 100644 index 0000000..549edd0 --- /dev/null +++ b/channels/audin/client/opensles/audin_opensl_es.c @@ -0,0 +1,367 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - OpenSL ES implementation + * + * Copyright 2013 Armin Novak + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "audin_main.h" +#include "opensl_io.h" + +typedef struct _AudinOpenSLESDevice +{ + IAudinDevice iface; + + char* device_name; + OPENSL_STREAM* stream; + + AUDIO_FORMAT format; + UINT32 frames_per_packet; + + UINT32 bytes_per_channel; + + AudinReceive receive; + + void* user_data; + + rdpContext* rdpcontext; + wLog* log; +} AudinOpenSLESDevice; + +static UINT audin_opensles_close(IAudinDevice* device); + +static void audin_receive(void* context, const void* data, size_t size) +{ + UINT error; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) context; + + if (!opensles || !data) + { + WLog_ERR(TAG, "[%s] Invalid arguments context=%p, data=%p", __FUNCTION__, opensles, data); + return; + } + + error = opensles->receive(&opensles->format, data, size, opensles->user_data); + + if (error && opensles->rdpcontext) + setChannelError(opensles->rdpcontext, error, "audin_receive reported an error"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_free(IAudinDevice* device) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + + if (!opensles) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*) device); + + /* The function may have been called out of order, + * ignore duplicate requests. */ + if (!opensles) + return CHANNEL_RC_OK; + + free(opensles->device_name); + free(opensles); + return CHANNEL_RC_OK; +} + +static BOOL audin_opensles_format_supported(IAudinDevice* device, + const AUDIO_FORMAT* format) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + + if (!opensles || !format) + return FALSE; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p", (void*) opensles, (void*) format); + assert(format); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: /* PCM */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= 2)) + { + return TRUE; + } + + break; + + default: + WLog_Print(opensles->log, WLOG_DEBUG, "Encoding '%s' [0x%04X"PRIX16"] not supported", + audio_format_get_tag_string(format->wFormatTag), + format->wFormatTag); + break; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_set_format(IAudinDevice* device, + const AUDIO_FORMAT* format, UINT32 FramesPerPacket) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + + if (!opensles || !format) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p, FramesPerPacket=%"PRIu32"", + (void*) device, (void*) format, FramesPerPacket); + assert(format); + + /* The function may have been called out of order, ignore + * requests before the device is available. */ + if (!opensles) + return CHANNEL_RC_OK; + + opensles->format = *format; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + opensles->frames_per_packet = FramesPerPacket; + + switch (format->wBitsPerSample) + { + case 4: + opensles->bytes_per_channel = 1; + break; + + case 8: + opensles->bytes_per_channel = 1; + break; + + case 16: + opensles->bytes_per_channel = 2; + break; + + default: + return ERROR_UNSUPPORTED_TYPE; + } + + break; + + default: + WLog_Print(opensles->log, WLOG_ERROR, "Encoding '%"PRIu16"' [%04"PRIX16"] not supported", + format->wFormatTag, + format->wFormatTag); + return ERROR_UNSUPPORTED_TYPE; + } + + WLog_Print(opensles->log, WLOG_DEBUG, "frames_per_packet=%"PRIu32, + opensles->frames_per_packet); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_open(IAudinDevice* device, AudinReceive receive, + void* user_data) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + + if (!opensles || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, receive=%p, user_data=%p", (void*) device, + (void*) receive, + (void*) user_data); + + if (opensles->stream) + goto error_out; + + if (!(opensles->stream = android_OpenRecDevice( + opensles, audin_receive, + opensles->format.nSamplesPerSec, + opensles->format.nChannels, + opensles->frames_per_packet, + opensles->format.wBitsPerSample))) + { + WLog_Print(opensles->log, WLOG_ERROR, "android_OpenRecDevice failed!"); + goto error_out; + } + + opensles->receive = receive; + opensles->user_data = user_data; + return CHANNEL_RC_OK; +error_out: + audin_opensles_close(opensles); + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT audin_opensles_close(IAudinDevice* device) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + + if (!opensles) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*) device); + android_CloseRecDevice(opensles->stream); + opensles->receive = NULL; + opensles->user_data = NULL; + opensles->stream = NULL; + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_opensles_args[] = +{ + { + "dev", COMMAND_LINE_VALUE_REQUIRED, "", + NULL, NULL, -1, NULL, "audio device name" + }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_parse_addin_args(AudinOpenSLESDevice* device, + ADDIN_ARGV* args) +{ + UINT status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*) device; + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, args=%p", (void*) device, (void*) args); + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + audin_opensles_args, flags, opensles, NULL, NULL); + + if (status < 0) + return status; + + arg = audin_opensles_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + opensles->device_name = _strdup(arg->Value); + + if (!opensles->device_name) + { + WLog_Print(opensles->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry \ + opensles_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry \ + FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + AudinOpenSLESDevice* opensles; + UINT error; + opensles = (AudinOpenSLESDevice*) calloc(1, sizeof(AudinOpenSLESDevice)); + + if (!opensles) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + opensles->log = WLog_Get(TAG); + opensles->iface.Open = audin_opensles_open; + opensles->iface.FormatSupported = audin_opensles_format_supported; + opensles->iface.SetFormat = audin_opensles_set_format; + opensles->iface.Close = audin_opensles_close; + opensles->iface.Free = audin_opensles_free; + opensles->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_opensles_parse_addin_args(opensles, args))) + { + WLog_Print(opensles->log, WLOG_ERROR, + "audin_opensles_parse_addin_args failed with errorcode %"PRIu32"!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) opensles))) + { + WLog_Print(opensles->log, WLOG_ERROR, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(opensles); + return error; +} diff --git a/channels/audin/client/opensles/opensl_io.c b/channels/audin/client/opensles/opensl_io.c new file mode 100644 index 0000000..5e7fba6 --- /dev/null +++ b/channels/audin/client/opensles/opensl_io.c @@ -0,0 +1,388 @@ +/* +opensl_io.c: +Android OpenSL input/output module +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "audin_main.h" +#include "opensl_io.h" +#define CONV16BIT 32768 +#define CONVMYFLT (1./32768.) + +typedef struct +{ + size_t size; + void* data; +} queue_element; + +struct opensl_stream +{ + // engine interfaces + SLObjectItf engineObject; + SLEngineItf engineEngine; + + // device interfaces + SLDeviceVolumeItf deviceVolume; + + // recorder interfaces + SLObjectItf recorderObject; + SLRecordItf recorderRecord; + SLAndroidSimpleBufferQueueItf recorderBufferQueue; + + unsigned int inchannels; + unsigned int sr; + unsigned int buffersize; + unsigned int bits_per_sample; + + queue_element* prep; + queue_element* next; + + void* context; + opensl_receive_t receive; +}; + + +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context); + +// creates the OpenSL ES audio engine +static SLresult openSLCreateEngine(OPENSL_STREAM* p) +{ + SLresult result; + // create engine + result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + + // realize the engine + result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + + // get the engine interface, which is needed in order to create other objects + result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, + &(p->engineEngine)); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + + // get the volume interface - important, this is optional! + result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, + &(p->deviceVolume)); + + if (result != SL_RESULT_SUCCESS) + { + p->deviceVolume = NULL; + result = SL_RESULT_SUCCESS; + } + +engine_end: + assert(SL_RESULT_SUCCESS == result); + return result; +} + +// Open the OpenSL ES device for input +static SLresult openSLRecOpen(OPENSL_STREAM* p) +{ + SLresult result; + SLuint32 sr = p->sr; + SLuint32 channels = p->inchannels; + assert(!p->recorderObject); + + if (channels) + { + switch (sr) + { + case 8000: + sr = SL_SAMPLINGRATE_8; + break; + + case 11025: + sr = SL_SAMPLINGRATE_11_025; + break; + + case 16000: + sr = SL_SAMPLINGRATE_16; + break; + + case 22050: + sr = SL_SAMPLINGRATE_22_05; + break; + + case 24000: + sr = SL_SAMPLINGRATE_24; + break; + + case 32000: + sr = SL_SAMPLINGRATE_32; + break; + + case 44100: + sr = SL_SAMPLINGRATE_44_1; + break; + + case 48000: + sr = SL_SAMPLINGRATE_48; + break; + + case 64000: + sr = SL_SAMPLINGRATE_64; + break; + + case 88200: + sr = SL_SAMPLINGRATE_88_2; + break; + + case 96000: + sr = SL_SAMPLINGRATE_96; + break; + + case 192000: + sr = SL_SAMPLINGRATE_192; + break; + + default: + return -1; + } + + // configure audio source + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL + }; + SLDataSource audioSrc = {&loc_dev, NULL}; + // configure audio sink + int speakers; + + if (channels > 1) + speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + else + speakers = SL_SPEAKER_FRONT_CENTER; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = channels; + format_pcm.samplesPerSec = sr; + format_pcm.channelMask = speakers; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + if (16 == p->bits_per_sample) + { + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + } + else if (8 == p->bits_per_sample) + { + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; + format_pcm.containerSize = 8; + } + else + assert(0); + + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + // create audio recorder + // (requires the RECORD_AUDIO permission) + const SLInterfaceID id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[] = {SL_BOOLEAN_TRUE}; + result = (*p->engineEngine)->CreateAudioRecorder(p->engineEngine, + &(p->recorderObject), &audioSrc, &audioSnk, 1, id, req); + assert(!result); + + if (SL_RESULT_SUCCESS != result) goto end_recopen; + + // realize the audio recorder + result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE); + assert(!result); + + if (SL_RESULT_SUCCESS != result) goto end_recopen; + + // get the record interface + result = (*p->recorderObject)->GetInterface(p->recorderObject, + SL_IID_RECORD, &(p->recorderRecord)); + assert(!result); + + if (SL_RESULT_SUCCESS != result) goto end_recopen; + + // get the buffer queue interface + result = (*p->recorderObject)->GetInterface(p->recorderObject, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &(p->recorderBufferQueue)); + assert(!result); + + if (SL_RESULT_SUCCESS != result) goto end_recopen; + + // register callback on the buffer queue + result = (*p->recorderBufferQueue)->RegisterCallback(p->recorderBufferQueue, + bqRecorderCallback, p); + assert(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + end_recopen: + return result; + } + else return SL_RESULT_SUCCESS; +} + +// close the OpenSL IO and destroy the audio engine +static void openSLDestroyEngine(OPENSL_STREAM* p) +{ + // destroy audio recorder object, and invalidate all associated interfaces + if (p->recorderObject != NULL) + { + (*p->recorderObject)->Destroy(p->recorderObject); + p->recorderObject = NULL; + p->recorderRecord = NULL; + p->recorderBufferQueue = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (p->engineObject != NULL) + { + (*p->engineObject)->Destroy(p->engineObject); + p->engineObject = NULL; + p->engineEngine = NULL; + } +} + +static queue_element* opensles_queue_element_new(size_t size) +{ + queue_element* q = calloc(1, sizeof(queue_element)); + + if (!q) + goto fail; + + q->size = size; + q->data = malloc(size); + + if (!q->data) + goto fail; + + return q; +fail: + free(q); + return NULL; +} + +static void opensles_queue_element_free(void* obj) +{ + queue_element* e = (queue_element*)obj; + + if (e) + free(e->data); + + free(e); +} + +// open the android audio device for input +OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, + int sr, + int inchannels, + int bufferframes, int bits_per_sample) +{ + OPENSL_STREAM* p; + + if (!context || !receive) + return NULL; + + p = (OPENSL_STREAM*) calloc(1, sizeof(OPENSL_STREAM)); + + if (!p) + return NULL; + + p->context = context; + p->receive = receive; + p->inchannels = inchannels; + p->sr = sr; + p->buffersize = bufferframes; + p->bits_per_sample = bits_per_sample; + + if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16)) + goto fail; + + if (openSLCreateEngine(p) != SL_RESULT_SUCCESS) + goto fail; + + if (openSLRecOpen(p) != SL_RESULT_SUCCESS) + goto fail; + + /* Create receive buffers, prepare them and start recording */ + p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8); + p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8); + + if (!p->prep || !p->next) + goto fail; + + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, + p->next->data, p->next->size); + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, + p->prep->data, p->prep->size); + (*p->recorderRecord)->SetRecordState(p->recorderRecord, + SL_RECORDSTATE_RECORDING); + return p; +fail: + android_CloseRecDevice(p); + return NULL; +} + +// close the android audio device +void android_CloseRecDevice(OPENSL_STREAM* p) +{ + if (p == NULL) + return; + + opensles_queue_element_free(p->next); + opensles_queue_element_free(p->prep); + openSLDestroyEngine(p); + free(p); +} + +// this callback handler is called every time a buffer finishes recording +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context) +{ + OPENSL_STREAM* p = (OPENSL_STREAM*) context; + queue_element* e; + + if (!p) + return; + + e = p->next; + + if (!e) + return; + + if (!p->context || !p->receive) + WLog_WARN(TAG, "Missing receive callback=%p, context=%p", p->receive, p->context); + else + p->receive(p->context, e->data, e->size); + + p->next = p->prep; + p->prep = e; + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, + e->data, e->size); +} + diff --git a/channels/audin/client/opensles/opensl_io.h b/channels/audin/client/opensles/opensl_io.h new file mode 100644 index 0000000..6a54b7c --- /dev/null +++ b/channels/audin/client/opensles/opensl_io.h @@ -0,0 +1,65 @@ +/* +opensl_io.c: +Android OpenSL input/output module header +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H +#define FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H + +#include +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct opensl_stream OPENSL_STREAM; + +typedef void (*opensl_receive_t)(void* context, const void* data, size_t size); + +/* +Open the audio device with a given sampling rate (sr), input and output channels and IO buffer size +in frames. Returns a handle to the OpenSL stream +*/ +FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, + opensl_receive_t receive, int sr, + int inchannels, + int bufferframes, int bits_per_sample); +/* +Close the audio device +*/ +FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p); + +#ifdef __cplusplus +}; +#endif + +#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H */ diff --git a/channels/audin/client/oss/CMakeLists.txt b/channels/audin/client/oss/CMakeLists.txt new file mode 100644 index 0000000..bf51ce0 --- /dev/null +++ b/channels/audin/client/oss/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "oss" "") + +set(${MODULE_PREFIX}_SRCS + audin_oss.c) + +include_directories(..) +include_directories(${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS freerdp winpr ${OSS_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + diff --git a/channels/audin/client/oss/audin_oss.c b/channels/audin/client/oss/audin_oss.c new file mode 100644 index 0000000..e1d73b2 --- /dev/null +++ b/channels/audin/client/oss/audin_oss.c @@ -0,0 +1,503 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - OSS implementation + * + * Copyright (c) 2015 Rozhuk Ivan + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#if defined(__OpenBSD__) +#include +#else +#include +#endif +#include + +#include +#include + +#include "audin_main.h" + +typedef struct _AudinOSSDevice +{ + IAudinDevice iface; + + HANDLE thread; + HANDLE stopEvent; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + int dev_unit; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; +} AudinOSSDevice; + +#define OSS_LOG_ERR(_text, _error) \ + if (_error != 0) \ + WLog_ERR(TAG, "%s: %i - %s\n", _text, _error, strerror(_error)); + + +static int audin_oss_get_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + return AFMT_S8; + + case 16: + return AFMT_S16_LE; + } + + break; + + case WAVE_FORMAT_ALAW: + return AFMT_A_LAW; + + case WAVE_FORMAT_MULAW: + return AFMT_MU_LAW; + } + + return 0; +} + +static BOOL audin_oss_format_supported(IAudinDevice* device, + const AUDIO_FORMAT* format) +{ + if (device == NULL || format == NULL) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize != 0 || + format->nSamplesPerSec > 48000 || + (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + + break; + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + return TRUE; + + default: + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + + if (device == NULL || format == NULL) + return ERROR_INVALID_PARAMETER; + + oss->FramesPerPacket = FramesPerPacket; + oss->format = *format; + return CHANNEL_RC_OK; +} + +static DWORD WINAPI audin_oss_thread_func(LPVOID arg) +{ + char dev_name[PATH_MAX] = "/dev/dsp"; + char mixer_name[PATH_MAX] = "/dev/mixer"; + int pcm_handle = -1, mixer_handle; + BYTE* buffer = NULL; + int tmp; + size_t buffer_size; + AudinOSSDevice* oss = (AudinOSSDevice*)arg; + UINT error = 0; + DWORD status; + + if (oss == NULL) + { + error = ERROR_INVALID_PARAMETER; + goto err_out; + } + + if (oss->dev_unit != -1) + { + sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit); + sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit); + } + + WLog_INFO(TAG, "open: %s", dev_name); + + if ((pcm_handle = open(dev_name, O_RDONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + + /* Set rec volume to 100%. */ + if ((mixer_handle = open(mixer_name, O_RDWR)) < 0) + { + OSS_LOG_ERR("mixer open failed, not critical", errno); + } + else + { + tmp = (100 | (100 << 8)); + + if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_MIC), &tmp) == -1) + OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_MIC, not critical", errno); + + tmp = (100 | (100 << 8)); + + if (ioctl(mixer_handle, MIXER_WRITE(SOUND_MIXER_RECLEV), &tmp) == -1) + OSS_LOG_ERR("WRITE_MIXER - SOUND_MIXER_RECLEV, not critical", errno); + + close(mixer_handle); + } + +#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_INPUT flag. */ + tmp = 0; + + if (ioctl(pcm_handle, SNDCTL_DSP_GETCAPS, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno); + } + else if ((tmp & PCM_CAP_INPUT) == 0) + { + OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP); + goto err_out; + } + +#endif + /* Set format. */ + tmp = audin_oss_get_format(&oss->format); + + if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + + tmp = oss->format.nChannels; + + if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + + tmp = oss->format.nSamplesPerSec; + + if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + + tmp = oss->format.nBlockAlign; + + if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + + buffer_size = (oss->FramesPerPacket * oss->format.nChannels * + (oss->format.wBitsPerSample / 8)); + buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE)); + + if (NULL == buffer) + { + OSS_LOG_ERR("malloc() fail", errno); + error = ERROR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + while (1) + { + status = WaitForSingleObject(oss->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + goto err_out; + } + + if (status == WAIT_OBJECT_0) + break; + + tmp = read(pcm_handle, buffer, buffer_size); + + /* Error happen. */ + if (tmp < 0) + { + OSS_LOG_ERR("read() error", errno); + continue; + } + + if (tmp < buffer_size) /* Not enouth data. */ + continue; + + if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data))) + { + WLog_ERR(TAG, "oss->receive failed with error %"PRIu32"", error); + break; + } + } + +err_out: + + if (error && oss->rdpcontext) + setChannelError(oss->rdpcontext, error, + "audin_oss_thread_func reported an error"); + + if (pcm_handle != -1) + { + WLog_INFO(TAG, "close: %s", dev_name); + close(pcm_handle); + } + + free(buffer); + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, + void* user_data) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + oss->receive = receive; + oss->user_data = user_data; + + if (!(oss->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(oss->thread = CreateThread(NULL, 0, audin_oss_thread_func, oss, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(oss->stopEvent); + oss->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_close(IAudinDevice* device) +{ + UINT error; + AudinOSSDevice* oss = (AudinOSSDevice*)device; + + if (device == NULL) + return ERROR_INVALID_PARAMETER; + + if (oss->stopEvent != NULL) + { + SetEvent(oss->stopEvent); + + if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + CloseHandle(oss->stopEvent); + oss->stopEvent = NULL; + CloseHandle(oss->thread); + oss->thread = NULL; + } + + oss->receive = NULL; + oss->user_data = NULL; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_free(IAudinDevice* device) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + int error; + + if (device == NULL) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_oss_close(device))) + { + WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error); + } + + free(oss); + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_oss_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, ADDIN_ARGV* args) +{ + int status; + char* str_num, *eptr; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinOSSDevice* oss = (AudinOSSDevice*)device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | + COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + audin_oss_args, flags, oss, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_oss_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + { + long val = strtol(str_num, &eptr, 10); + + if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX)) + { + free(str_num); + return CHANNEL_RC_NULL_DATA; + } + + oss->dev_unit = val; + } + + if (oss->dev_unit < 0 || *eptr != '\0') + oss->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry oss_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS + pEntryPoints) +{ + ADDIN_ARGV* args; + AudinOSSDevice* oss; + UINT error; + oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice)); + + if (!oss) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + oss->iface.Open = audin_oss_open; + oss->iface.FormatSupported = audin_oss_format_supported; + oss->iface.SetFormat = audin_oss_set_format; + oss->iface.Close = audin_oss_close; + oss->iface.Free = audin_oss_free; + oss->rdpcontext = pEntryPoints->rdpcontext; + oss->dev_unit = -1; + args = pEntryPoints->args; + + if ((error = audin_oss_parse_addin_args(oss, args))) + { + WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %"PRIu32"!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, + (IAudinDevice*) oss))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(oss); + return error; +} diff --git a/channels/audin/client/pulse/CMakeLists.txt b/channels/audin/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..e50f79a --- /dev/null +++ b/channels/audin/client/pulse/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "pulse" "") + +set(${MODULE_PREFIX}_SRCS + audin_pulse.c) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + +set(${MODULE_PREFIX}_LIBS freerdp ${PULSE_LIBRARY} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/audin/client/pulse/audin_pulse.c b/channels/audin/client/pulse/audin_pulse.c new file mode 100644 index 0000000..d22de87 --- /dev/null +++ b/channels/audin/client/pulse/audin_pulse.c @@ -0,0 +1,554 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - PulseAudio implementation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "audin_main.h" + +typedef struct _AudinPulseDevice +{ + IAudinDevice iface; + + char* device_name; + UINT32 frames_per_packet; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; + AUDIO_FORMAT format; + + size_t bytes_per_frame; + size_t buffer_frames; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; + wLog* log; +} AudinPulseDevice; + +static void audin_pulse_context_state_callback(pa_context* context, void* userdata) +{ + pa_context_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + WLog_Print(pulse->log, WLOG_DEBUG, "PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + WLog_Print(pulse->log, WLOG_DEBUG, "state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + WLog_Print(pulse->log, WLOG_DEBUG, "state %d", state); + break; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_connect(IAudinDevice* device) +{ + pa_context_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)", + pa_context_errno(pulse->context)); + return ERROR_INTERNAL_ERROR; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)", + pa_context_errno(pulse->context)); + return ERROR_INTERNAL_ERROR; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%d)", + pa_context_errno(pulse->context)); + pa_context_disconnect(pulse->context); + return ERROR_INVALID_STATE; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_DEBUG, "connected"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_free(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse) + return ERROR_INVALID_PARAMETER; + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + + free(pulse); + return CHANNEL_RC_OK; +} + +static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse || !format) + return FALSE; + + if (!pulse->context) + return 0; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + + case WAVE_FORMAT_ALAW: /* A-LAW */ + case WAVE_FORMAT_MULAW: /* U-LAW */ + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + + default: + return FALSE; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + pa_sample_spec sample_spec = { 0 }; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse || !format) + return ERROR_INVALID_PARAMETER; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (FramesPerPacket > 0) + pulse->frames_per_packet = FramesPerPacket; + + sample_spec.rate = format->nSamplesPerSec; + sample_spec.channels = format->nChannels; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + sample_spec.format = PA_SAMPLE_U8; + break; + + case 16: + sample_spec.format = PA_SAMPLE_S16LE; + break; + + default: + return ERROR_INTERNAL_ERROR; + } + + break; + + case WAVE_FORMAT_ALAW: /* A-LAW */ + sample_spec.format = PA_SAMPLE_ALAW; + break; + + case WAVE_FORMAT_MULAW: /* U-LAW */ + sample_spec.format = PA_SAMPLE_ULAW; + break; + + default: + return ERROR_INTERNAL_ERROR; + } + + pulse->sample_spec = sample_spec; + pulse->format = *format; + return CHANNEL_RC_OK; +} + +static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + pa_stream_state_t state; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + WLog_Print(pulse->log, WLOG_DEBUG, "PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + WLog_Print(pulse->log, WLOG_DEBUG, "state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + WLog_Print(pulse->log, WLOG_DEBUG, "state %d", state); + break; + } +} + +static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + const void* data; + AudinPulseDevice* pulse = (AudinPulseDevice*) userdata; + UINT error = CHANNEL_RC_OK; + pa_stream_peek(stream, &data, &length); + error = IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data); + pa_stream_drop(stream); + + if (error && pulse->rdpcontext) + setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error"); +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_close(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse) + return ERROR_INVALID_PARAMETER; + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + } + + pulse->receive = NULL; + pulse->user_data = NULL; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + + if (!pulse || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (!pulse->sample_spec.rate || pulse->stream) + return ERROR_INVALID_PARAMETER; + + pulse->receive = receive; + pulse->user_data = user_data; + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp_audin", + &pulse->sample_spec, NULL); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return pa_context_errno(pulse->context); + } + + pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec); + pa_stream_set_state_callback(pulse->stream, + audin_pulse_stream_state_callback, pulse); + pa_stream_set_read_callback(pulse->stream, + audin_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = (UINT32) - 1; + buffer_attr.tlength = (UINT32) - 1; + buffer_attr.prebuf = (UINT32) - 1; + buffer_attr.minreq = (UINT32) - 1; + /* 500ms latency */ + buffer_attr.fragsize = pulse->bytes_per_frame * pulse->frames_per_packet; + + if (buffer_attr.fragsize % pulse->format.nBlockAlign) + buffer_attr.fragsize += pulse->format.nBlockAlign - buffer_attr.fragsize % + pulse->format.nBlockAlign; + + if (pa_stream_connect_record(pulse->stream, + pulse->device_name, + &buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + return pa_context_errno(pulse->context); + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + audin_pulse_close(device); + WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%d)", + pa_context_errno(pulse->context)); + pa_threaded_mainloop_unlock(pulse->mainloop); + return pa_context_errno(pulse->context); + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + pulse->buffer_frames = 0; + WLog_Print(pulse->log, WLOG_DEBUG, "connected"); + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinPulseDevice* pulse = (AudinPulseDevice*) device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags, + pulse, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_pulse_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + pulse->device_name = _strdup(arg->Value); + + if (!pulse->device_name) + { + WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry pulse_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + AudinPulseDevice* pulse; + UINT error; + pulse = (AudinPulseDevice*) calloc(1, sizeof(AudinPulseDevice)); + + if (!pulse) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + pulse->log = WLog_Get(TAG); + pulse->iface.Open = audin_pulse_open; + pulse->iface.FormatSupported = audin_pulse_format_supported; + pulse->iface.SetFormat = audin_pulse_set_format; + pulse->iface.Close = audin_pulse_close; + pulse->iface.Free = audin_pulse_free; + pulse->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_pulse_parse_addin_args(pulse, args))) + { + WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_parse_addin_args failed with error %"PRIu32"!", + error); + goto error_out; + } + + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + + if (!pulse->context) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse); + + if ((error = audin_pulse_connect((IAudinDevice*) pulse))) + { + WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed"); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) pulse))) + { + WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + audin_pulse_free((IAudinDevice*)pulse); + return error; +} + diff --git a/channels/audin/client/winmm/CMakeLists.txt b/channels/audin/client/winmm/CMakeLists.txt new file mode 100644 index 0000000..2eddb8e --- /dev/null +++ b/channels/audin/client/winmm/CMakeLists.txt @@ -0,0 +1,38 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "winmm" "") + +set(${MODULE_PREFIX}_SRCS + audin_winmm.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS freerdp winpr winmm.lib) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/winmm") diff --git a/channels/audin/client/winmm/audin_winmm.c b/channels/audin/client/winmm/audin_winmm.c new file mode 100644 index 0000000..5e21005 --- /dev/null +++ b/channels/audin/client/winmm/audin_winmm.c @@ -0,0 +1,524 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - WinMM implementation + * + * Copyright 2013 Zhang Zhaolong + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "audin_main.h" + +typedef struct _AudinWinmmDevice +{ + IAudinDevice iface; + + char* device_name; + AudinReceive receive; + void* user_data; + HANDLE thread; + HANDLE stopEvent; + HWAVEIN hWaveIn; + PWAVEFORMATEX* ppwfx; + PWAVEFORMATEX pwfx_cur; + UINT32 ppwfx_size; + UINT32 cFormats; + UINT32 frames_per_packet; + rdpContext* rdpcontext; + wLog* log; +} AudinWinmmDevice; + +static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*) dwInstance; + PWAVEHDR pWaveHdr; + UINT error = CHANNEL_RC_OK; + MMRESULT mmResult; + + switch (uMsg) + { + case WIM_CLOSE: + break; + + case WIM_DATA: + pWaveHdr = (WAVEHDR*)dwParam1; + + if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags)) + { + if (pWaveHdr->dwBytesRecorded + && !(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0)) + { + AUDIO_FORMAT format; + format.cbSize = winmm->pwfx_cur->cbSize; + format.nBlockAlign = winmm->pwfx_cur->nBlockAlign; + format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec; + format.nChannels = winmm->pwfx_cur->nChannels; + format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec; + format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample; + format.wFormatTag = winmm->pwfx_cur->wFormatTag; + + if ((error = winmm->receive(&format, pWaveHdr->lpData, pWaveHdr->dwBytesRecorded, + winmm->user_data))) + break; + + mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR)); + + if (mmResult != MMSYSERR_NOERROR) + error = ERROR_INTERNAL_ERROR; + } + } + + break; + + case WIM_OPEN: + break; + + default: + break; + } + + if (error && winmm->rdpcontext) + setChannelError(winmm->rdpcontext, error, "waveInProc reported an error"); +} + +static DWORD WINAPI audin_winmm_thread_func(LPVOID arg) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*) arg; + char* buffer; + int size, i; + WAVEHDR waveHdr[4]; + DWORD status; + MMRESULT rc; + + if (!winmm->hWaveIn) + { + if (MMSYSERR_NOERROR != waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, + (DWORD_PTR)waveInProc, (DWORD_PTR)winmm, CALLBACK_FUNCTION)) + { + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + + return ERROR_INTERNAL_ERROR; + } + } + + size = (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet + + 7) / 8; + + for (i = 0; i < 4; i++) + { + buffer = (char*) malloc(size); + + if (!buffer) + return CHANNEL_RC_NO_MEMORY; + + waveHdr[i].dwBufferLength = size; + waveHdr[i].dwFlags = 0; + waveHdr[i].lpData = buffer; + rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInPrepareHeader failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInAddBuffer failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + } + + rc = waveInStart(winmm->hWaveIn); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInStart failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + status = WaitForSingleObject(winmm->stopEvent, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed."); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + rc = waveInReset(winmm->hWaveIn); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInReset failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + for (i = 0; i < 4; i++) + { + rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInUnprepareHeader failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + free(waveHdr[i].lpData); + } + + rc = waveInClose(winmm->hWaveIn); + + if (MMSYSERR_NOERROR != rc) + { + WLog_Print(winmm->log, WLOG_DEBUG, "waveInClose failed. %"PRIu32"", rc); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + winmm->hWaveIn = NULL; + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_free(IAudinDevice* device) +{ + UINT32 i; + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + + if (!winmm) + return ERROR_INVALID_PARAMETER; + + for (i = 0; i < winmm->cFormats; i++) + { + free(winmm->ppwfx[i]); + } + + free(winmm->ppwfx); + free(winmm->device_name); + free(winmm); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_close(IAudinDevice* device) +{ + DWORD status; + UINT error = CHANNEL_RC_OK; + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + + if (!winmm) + return ERROR_INVALID_PARAMETER; + + SetEvent(winmm->stopEvent); + status = WaitForSingleObject(winmm->thread, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(winmm->thread); + CloseHandle(winmm->stopEvent); + winmm->thread = NULL; + winmm->stopEvent = NULL; + winmm->receive = NULL; + winmm->user_data = NULL; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + UINT32 i; + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + + if (!winmm || !format) + return ERROR_INVALID_PARAMETER; + + winmm->frames_per_packet = FramesPerPacket; + + for (i = 0; i < winmm->cFormats; i++) + { + if (winmm->ppwfx[i]->wFormatTag == format->wFormatTag + && winmm->ppwfx[i]->nChannels == format->nChannels + && winmm->ppwfx[i]->wBitsPerSample == format->wBitsPerSample) + { + winmm->pwfx_cur = winmm->ppwfx[i]; + break; + } + } + + return CHANNEL_RC_OK; +} + +static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + PWAVEFORMATEX pwfx; + BYTE* data; + + if (!winmm || !format) + return FALSE; + + pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize); + + if (!pwfx) + return FALSE; + + pwfx->cbSize = format->cbSize; + pwfx->wFormatTag = format->wFormatTag; + pwfx->nChannels = format->nChannels; + pwfx->nSamplesPerSec = format->nSamplesPerSec; + pwfx->nBlockAlign = format->nBlockAlign; + pwfx->wBitsPerSample = format->wBitsPerSample; + data = (BYTE*)pwfx + sizeof(WAVEFORMATEX); + memcpy(data, format->data, format->cbSize); + + if (pwfx->wFormatTag == WAVE_FORMAT_PCM) + { + pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; + + if (MMSYSERR_NOERROR == waveInOpen(NULL, WAVE_MAPPER, pwfx, 0, 0, WAVE_FORMAT_QUERY)) + { + if (winmm->cFormats >= winmm->ppwfx_size) + { + PWAVEFORMATEX* tmp_ppwfx; + tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2); + + if (!tmp_ppwfx) + return FALSE; + + winmm->ppwfx_size *= 2; + winmm->ppwfx = tmp_ppwfx; + } + + winmm->ppwfx[winmm->cFormats++] = pwfx; + return TRUE; + } + } + + free(pwfx); + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + + if (!winmm || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + winmm->receive = receive; + winmm->user_data = user_data; + + if (!(winmm->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(winmm->thread = CreateThread(NULL, 0, + audin_winmm_thread_func, winmm, 0, NULL))) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!"); + CloseHandle(winmm->stopEvent); + winmm->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinWinmmDevice* winmm = (AudinWinmmDevice*) device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, + winmm, NULL, NULL); + arg = audin_winmm_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + winmm->device_name = _strdup(arg->Value); + + if (!winmm->device_name) + { + WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_audin_client_subsystem_entry winmm_freerdp_audin_client_subsystem_entry +#else +#define freerdp_audin_client_subsystem_entry FREERDP_API freerdp_audin_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + AudinWinmmDevice* winmm; + UINT error; + winmm = (AudinWinmmDevice*) calloc(1, sizeof(AudinWinmmDevice)); + + if (!winmm) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + winmm->log = WLog_Get(TAG); + winmm->iface.Open = audin_winmm_open; + winmm->iface.FormatSupported = audin_winmm_format_supported; + winmm->iface.SetFormat = audin_winmm_set_format; + winmm->iface.Close = audin_winmm_close; + winmm->iface.Free = audin_winmm_free; + winmm->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_winmm_parse_addin_args(winmm, args))) + { + WLog_Print(winmm->log, WLOG_ERROR, "audin_winmm_parse_addin_args failed with error %"PRIu32"!", + error); + goto error_out; + } + + if (!winmm->device_name) + { + winmm->device_name = _strdup("default"); + + if (!winmm->device_name) + { + WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + } + + winmm->ppwfx_size = 10; + winmm->ppwfx = malloc(sizeof(PWAVEFORMATEX) * winmm->ppwfx_size); + + if (!winmm->ppwfx) + { + WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) winmm))) + { + WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(winmm->ppwfx); + free(winmm->device_name); + free(winmm); + return error; +} diff --git a/channels/audin/server/CMakeLists.txt b/channels/audin/server/CMakeLists.txt new file mode 100644 index 0000000..4d4004f --- /dev/null +++ b/channels/audin/server/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("audin") + +set(${MODULE_PREFIX}_SRCS + audin.c) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/audin/server/audin.c b/channels/audin/server/audin.c new file mode 100644 index 0000000..f521667 --- /dev/null +++ b/channels/audin/server/audin.c @@ -0,0 +1,686 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Input Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("audin.server") +#define MSG_SNDIN_VERSION 0x01 +#define MSG_SNDIN_FORMATS 0x02 +#define MSG_SNDIN_OPEN 0x03 +#define MSG_SNDIN_OPEN_REPLY 0x04 +#define MSG_SNDIN_DATA_INCOMING 0x05 +#define MSG_SNDIN_DATA 0x06 +#define MSG_SNDIN_FORMATCHANGE 0x07 + +typedef struct _audin_server +{ + audin_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* audin_channel; + + DWORD SessionId; + + FREERDP_DSP_CONTEXT* dsp_context; + +} audin_server; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_select_format(audin_server_context* context, + size_t client_format_index) +{ + audin_server* audin = (audin_server*) context; + + if (client_format_index >= context->num_client_formats) + { + WLog_ERR(TAG, + "error in protocol: client_format_index >= context->num_client_formats!"); + return ERROR_INVALID_DATA; + } + + context->selected_client_format = (SSIZE_T)client_format_index; + + if (!freerdp_dsp_context_reset(audin->dsp_context, + &audin->context.client_formats[client_format_index])) + { + WLog_ERR(TAG, "Failed to reset dsp context format!"); + return ERROR_INTERNAL_ERROR; + } + + if (audin->opened) + { + /* TODO: send MSG_SNDIN_FORMATCHANGE */ + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_send_version(audin_server* audin, wStream* s) +{ + ULONG written; + Stream_Write_UINT8(s, MSG_SNDIN_VERSION); + Stream_Write_UINT32(s, 1); /* Version (4 bytes) */ + + if (!WTSVirtualChannelWrite(audin->audin_channel, (PCHAR) Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_recv_version(audin_server* audin, wStream* s, + UINT32 length) +{ + UINT32 Version; + + if (length < 4) + { + WLog_ERR(TAG, "error parsing version info: expected at least 4 bytes, got %"PRIu32"", + length); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, Version); + + if (Version < 1) + { + WLog_ERR(TAG, "expected Version > 0 but got %"PRIu32"", Version); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_send_formats(audin_server* audin, wStream* s) +{ + size_t i; + ULONG written; + Stream_SetPosition(s, 0); + Stream_Write_UINT8(s, MSG_SNDIN_FORMATS); + Stream_Write_UINT32(s, + audin->context.num_server_formats); /* NumFormats (4 bytes) */ + Stream_Write_UINT32(s, + 0); /* cbSizeFormatsPacket (4 bytes), client-to-server only */ + + for (i = 0; i < audin->context.num_server_formats; i++) + { + AUDIO_FORMAT format = audin->context.server_formats[i]; + // TODO: Eliminate this + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nChannels * format.wBitsPerSample / 8; + + if (!audio_format_write(s, &format)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + return WTSVirtualChannelWrite(audin->audin_channel, (PCHAR) Stream_Buffer(s), + Stream_GetPosition(s), &written) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_recv_formats(audin_server* audin, wStream* s, + UINT32 length) +{ + size_t i; + UINT success = CHANNEL_RC_OK; + + if (length < 8) + { + WLog_ERR(TAG, "error parsing rec formats: expected at least 8 bytes, got %"PRIu32"", + length); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, + audin->context.num_client_formats); /* NumFormats (4 bytes) */ + Stream_Seek_UINT32(s); /* cbSizeFormatsPacket (4 bytes) */ + length -= 8; + + if (audin->context.num_client_formats <= 0) + { + WLog_ERR(TAG, "num_client_formats expected > 0 but got %d", + audin->context.num_client_formats); + return ERROR_INVALID_DATA; + } + + audin->context.client_formats = audio_formats_new(audin->context.num_client_formats); + + if (!audin->context.client_formats) + return ERROR_NOT_ENOUGH_MEMORY; + + for (i = 0; i < audin->context.num_client_formats; i++) + { + AUDIO_FORMAT* format = &audin->context.client_formats[i]; + + if (!audio_format_read(s, format)) + { + audio_formats_free(audin->context.client_formats, i); + audin->context.client_formats = NULL; + WLog_ERR(TAG, "expected length at least 18, but got %"PRIu32"", length); + return ERROR_INVALID_DATA; + } + + audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format); + } + + IFCALLRET(audin->context.Opening, success, &audin->context); + + if (success) + WLog_ERR(TAG, "context.Opening failed with error %"PRIu32"", success); + + return success; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_send_open(audin_server* audin, wStream* s) +{ + ULONG written; + + if (audin->context.selected_client_format < 0) + { + WLog_ERR(TAG, "audin->context.selected_client_format = %d", + audin->context.selected_client_format); + return ERROR_INVALID_DATA; + } + + audin->opened = TRUE; + Stream_SetPosition(s, 0); + Stream_Write_UINT8(s, MSG_SNDIN_OPEN); + Stream_Write_UINT32(s, + audin->context.frames_per_packet); /* FramesPerPacket (4 bytes) */ + Stream_Write_UINT32(s, + audin->context.selected_client_format); /* initialFormat (4 bytes) */ + /* + * [MS-RDPEAI] 3.2.5.1.6 + * The second format specify the format that SHOULD be used to capture data from + * the actual audio input device. + */ + Stream_Write_UINT16(s, 1); /* wFormatTag = PCM */ + Stream_Write_UINT16(s, 2); /* nChannels */ + Stream_Write_UINT32(s, 44100); /* nSamplesPerSec */ + Stream_Write_UINT32(s, 44100 * 2 * 2); /* nAvgBytesPerSec */ + Stream_Write_UINT16(s, 4); /* nBlockAlign */ + Stream_Write_UINT16(s, 16); /* wBitsPerSample */ + Stream_Write_UINT16(s, 0); /* cbSize */ + return WTSVirtualChannelWrite(audin->audin_channel, (PCHAR) Stream_Buffer(s), + Stream_GetPosition(s), &written) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_recv_open_reply(audin_server* audin, wStream* s, + UINT32 length) +{ + UINT32 Result; + UINT success = CHANNEL_RC_OK; + + if (length < 4) + { + WLog_ERR(TAG, "error parsing version info: expected at least 4 bytes, got %"PRIu32"", + length); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, Result); + IFCALLRET(audin->context.OpenResult, success, &audin->context, Result); + + if (success) + WLog_ERR(TAG, "context.OpenResult failed with error %"PRIu32"", success); + + return success; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_server_recv_data(audin_server* audin, wStream* s, + UINT32 length) +{ + AUDIO_FORMAT* format; + int sbytes_per_sample; + int sbytes_per_frame; + int frames; + wStream* out; + UINT success = ERROR_INTERNAL_ERROR; + + if (audin->context.selected_client_format < 0) + { + WLog_ERR(TAG, "audin->context.selected_client_format = %d", + audin->context.selected_client_format); + return ERROR_INVALID_DATA; + } + + out = Stream_New(NULL, 4096); + + if (!out) + return ERROR_OUTOFMEMORY; + + format = &audin->context.client_formats[audin->context.selected_client_format]; + + if (freerdp_dsp_decode(audin->dsp_context, format, Stream_Pointer(s), length, out)) + { + AUDIO_FORMAT dformat = *format; + dformat.wFormatTag = WAVE_FORMAT_PCM; + dformat.wBitsPerSample = 16; + Stream_SealLength(out); + Stream_SetPosition(out, 0); + sbytes_per_sample = format->wBitsPerSample / 8; + sbytes_per_frame = format->nChannels * sbytes_per_sample; + frames = Stream_Length(out) / sbytes_per_frame; + IFCALLRET(audin->context.ReceiveSamples, success, &audin->context, &dformat, out, frames); + + if (success) + WLog_ERR(TAG, "context.ReceiveSamples failed with error %"PRIu32"", success); + } + else + WLog_ERR(TAG, "freerdp_dsp_decode failed!"); + + Stream_Free(out, TRUE); + return success; +} + +static DWORD WINAPI audin_server_thread_func(LPVOID arg) +{ + wStream* s; + void* buffer; + DWORD nCount; + BYTE MessageId; + HANDLE events[8]; + BOOL ready = FALSE; + HANDLE ChannelEvent; + DWORD BytesReturned = 0; + audin_server* audin = (audin_server*) arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + + if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + else + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + nCount = 0; + events[nCount++] = audin->stopEvent; + events[nCount++] = ChannelEvent; + + /* Wait for the client to confirm that the Audio Input dynamic channel is ready */ + + while (1) + { + if ((status = WaitForMultipleObjects(nCount, events, FALSE, + 100)) == WAIT_OBJECT_0) + goto out; + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + goto out; + } + + if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, + &buffer, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + ready = *((BOOL*) buffer); + WTSFreeMemory(buffer); + + if (ready) + break; + } + + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (ready) + { + if ((error = audin_server_send_version(audin, s))) + { + WLog_ERR(TAG, "audin_server_send_version failed with error %"PRIu32"!", error); + goto out_capacity; + } + } + + while (ready) + { + if ((status = WaitForMultipleObjects(nCount, events, FALSE, + INFINITE)) == WAIT_OBJECT_0) + break; + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + goto out; + } + + Stream_SetPosition(s, 0); + + if (!WTSVirtualChannelRead(audin->audin_channel, 0, NULL, 0, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + break; + + if (WTSVirtualChannelRead(audin->audin_channel, 0, (PCHAR) Stream_Buffer(s), + Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + Stream_Read_UINT8(s, MessageId); + BytesReturned--; + + switch (MessageId) + { + case MSG_SNDIN_VERSION: + if ((error = audin_server_recv_version(audin, s, BytesReturned))) + { + WLog_ERR(TAG, "audin_server_recv_version failed with error %"PRIu32"!", error); + goto out_capacity; + } + + if ((error = audin_server_send_formats(audin, s))) + { + WLog_ERR(TAG, "audin_server_send_formats failed with error %"PRIu32"!", error); + goto out_capacity; + } + + break; + + case MSG_SNDIN_FORMATS: + if ((error = audin_server_recv_formats(audin, s, BytesReturned))) + { + WLog_ERR(TAG, "audin_server_recv_formats failed with error %"PRIu32"!", error); + goto out_capacity; + } + + if ((error = audin_server_send_open(audin, s))) + { + WLog_ERR(TAG, "audin_server_send_open failed with error %"PRIu32"!", error); + goto out_capacity; + } + + break; + + case MSG_SNDIN_OPEN_REPLY: + if ((error = audin_server_recv_open_reply(audin, s, BytesReturned))) + { + WLog_ERR(TAG, "audin_server_recv_open_reply failed with error %"PRIu32"!", error); + goto out_capacity; + } + + break; + + case MSG_SNDIN_DATA_INCOMING: + break; + + case MSG_SNDIN_DATA: + if ((error = audin_server_recv_data(audin, s, BytesReturned))) + { + WLog_ERR(TAG, "audin_server_recv_data failed with error %"PRIu32"!", error); + goto out_capacity; + }; + + break; + + case MSG_SNDIN_FORMATCHANGE: + break; + + default: + WLog_ERR(TAG, "audin_server_thread_func: unknown MessageId %"PRIu8"", MessageId); + break; + } + } + +out_capacity: + Stream_Free(s, TRUE); +out: + WTSVirtualChannelClose(audin->audin_channel); + audin->audin_channel = NULL; + + if (error && audin->context.rdpcontext) + setChannelError(audin->context.rdpcontext, error, + "audin_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static BOOL audin_server_open(audin_server_context* context) +{ + audin_server* audin = (audin_server*) context; + + if (!audin->thread) + { + PULONG pSessionId = NULL; + DWORD BytesReturned = 0; + audin->SessionId = WTS_CURRENT_SESSION; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, + WTSSessionId, (LPSTR*) &pSessionId, &BytesReturned)) + { + audin->SessionId = (DWORD) * pSessionId; + WTSFreeMemory(pSessionId); + } + + audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, + "AUDIO_INPUT", WTS_CHANNEL_OPTION_DYNAMIC); + + if (!audin->audin_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + return FALSE; + } + + if (!(audin->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return FALSE; + } + + if (!(audin->thread = CreateThread(NULL, 0, audin_server_thread_func, (void*) audin, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(audin->stopEvent); + audin->stopEvent = NULL; + return FALSE; + } + + return TRUE; + } + + WLog_ERR(TAG, "thread already running!"); + return FALSE; +} + +static BOOL audin_server_is_open(audin_server_context* context) +{ + audin_server* audin = (audin_server*) context; + + if (!audin) + return FALSE; + + return audin->thread != NULL; +} + +static BOOL audin_server_close(audin_server_context* context) +{ + audin_server* audin = (audin_server*) context; + + if (audin->thread) + { + SetEvent(audin->stopEvent); + + if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", GetLastError()); + return FALSE; + } + + CloseHandle(audin->thread); + CloseHandle(audin->stopEvent); + audin->thread = NULL; + audin->stopEvent = NULL; + } + + if (audin->audin_channel) + { + WTSVirtualChannelClose(audin->audin_channel); + audin->audin_channel = NULL; + } + + audin->context.selected_client_format = -1; + return TRUE; +} + +audin_server_context* audin_server_context_new(HANDLE vcm) +{ + audin_server* audin; + audin = (audin_server*)calloc(1, sizeof(audin_server)); + + if (!audin) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + audin->context.vcm = vcm; + audin->context.selected_client_format = -1; + audin->context.frames_per_packet = 4096; + audin->context.SelectFormat = audin_server_select_format; + audin->context.Open = audin_server_open; + audin->context.IsOpen = audin_server_is_open; + audin->context.Close = audin_server_close; + audin->dsp_context = freerdp_dsp_context_new(FALSE); + + if (!audin->dsp_context) + { + WLog_ERR(TAG, "freerdp_dsp_context_new failed!"); + free(audin); + return NULL; + } + + return (audin_server_context*) audin; +} + +void audin_server_context_free(audin_server_context* context) +{ + audin_server* audin = (audin_server*) context; + + if (!audin) + return; + + audin_server_close(context); + freerdp_dsp_context_free(audin->dsp_context); + audio_formats_free(audin->context.client_formats, audin->context.num_client_formats); + audio_formats_free(audin->context.server_formats, audin->context.num_server_formats); + free(audin); +} diff --git a/channels/client/.gitignore b/channels/client/.gitignore new file mode 100644 index 0000000..aa03c94 --- /dev/null +++ b/channels/client/.gitignore @@ -0,0 +1,2 @@ +tables.c + diff --git a/channels/client/CMakeLists.txt b/channels/client/CMakeLists.txt new file mode 100644 index 0000000..5f1fb31 --- /dev/null +++ b/channels/client/CMakeLists.txt @@ -0,0 +1,109 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "freerdp-channels-client") +set(MODULE_PREFIX "FREERDP_CHANNELS_CLIENT") + +set(${MODULE_PREFIX}_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/tables.c + ${CMAKE_CURRENT_SOURCE_DIR}/tables.h + ${CMAKE_CURRENT_SOURCE_DIR}/addin.c + ${CMAKE_CURRENT_SOURCE_DIR}/addin.h) + +if(CHANNEL_STATIC_CLIENT_ENTRIES) + list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES) +endif() + +set(CLIENT_STATIC_TYPEDEFS "typedef UINT (*static_entry_fkt)();\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}typedef UINT (*static_addin_fkt)();\n") + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + if(${${STATIC_MODULE}_CLIENT_ENTRY} STREQUAL ${STATIC_ENTRY}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME}) + + set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${${STATIC_MODULE}_CLIENT_ENTRY}") + if(${${STATIC_MODULE}_CLIENT_ENTRY} STREQUAL "VirtualChannelEntry") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS);") + elseif(${${STATIC_MODULE}_CLIENT_ENTRY} STREQUAL "VirtualChannelEntryEx") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS,PVOID);") + else() + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(void);") + endif() + set(${STATIC_ENTRY}_IMPORTS "${${STATIC_ENTRY}_IMPORTS}\n${ENTRY_POINT_IMPORT}") + set(${STATIC_ENTRY}_TABLE "${${STATIC_ENTRY}_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", (static_entry_fkt)${ENTRY_POINT_NAME} },") + endif() + endforeach() +endforeach() + +set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\nconst STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[] =\n{") + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + set(CLIENT_STATIC_ENTRY_IMPORTS "${CLIENT_STATIC_ENTRY_IMPORTS}\n${${STATIC_ENTRY}_IMPORTS}") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\nconst STATIC_ENTRY CLIENT_${STATIC_ENTRY}_TABLE[] =\n{") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n${${STATIC_ENTRY}_TABLE}") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n\t{ NULL, NULL }\n};") + set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ \"${STATIC_ENTRY}\", CLIENT_${STATIC_ENTRY}_TABLE },") +endforeach() + +set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ NULL, NULL }\n};") + +set(CLIENT_STATIC_ADDIN_TABLE "const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[] =\n{") +foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + string(TOUPPER "CLIENT_${STATIC_MODULE_CHANNEL}_SUBSYSTEM_TABLE" SUBSYSTEM_TABLE_NAME) + set(SUBSYSTEM_TABLE "const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{") + get_target_property(CHANNEL_SUBSYSTEMS ${STATIC_MODULE_NAME} SUBSYSTEMS) + if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND") + set(CHANNEL_SUBSYSTEMS "") + endif() + foreach(STATIC_SUBSYSTEM ${CHANNEL_SUBSYSTEMS}) + if(${STATIC_SUBSYSTEM} MATCHES "^([^-]*)-(.*)") + string(REGEX REPLACE "^([^-]*)-(.*)" "\\1" STATIC_SUBSYSTEM_NAME ${STATIC_SUBSYSTEM}) + string(REGEX REPLACE "^([^-]*)-(.*)" "\\2" STATIC_SUBSYSTEM_TYPE ${STATIC_SUBSYSTEM}) + else() + set(STATIC_SUBSYSTEM_NAME "${STATIC_SUBSYSTEM}") + set(STATIC_SUBSYSTEM_TYPE "") + endif() + string(LENGTH "${STATIC_SUBSYSTEM_TYPE}" _type_length) + set(SUBSYSTEM_MODULE_NAME "${STATIC_MODULE_NAME}-${STATIC_SUBSYSTEM}") + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${SUBSYSTEM_MODULE_NAME}) + if(_type_length GREATER 0) + set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_${STATIC_SUBSYSTEM_TYPE}_subsystem_entry") + else() + set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_subsystem_entry") + endif() + set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} },") + set(SUBSYSTEM_IMPORT "extern void ${STATIC_SUBSYSTEM_ENTRY}(void);") + set(CLIENT_STATIC_SUBSYSTEM_IMPORTS "${CLIENT_STATIC_SUBSYSTEM_IMPORTS}\n${SUBSYSTEM_IMPORT}") + endforeach() + set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ NULL, NULL, NULL }\n};") + set(CLIENT_STATIC_SUBSYSTEM_TABLES "${CLIENT_STATIC_SUBSYSTEM_TABLES}\n${SUBSYSTEM_TABLE}") + set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${${STATIC_MODULE}_CLIENT_ENTRY}") + set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", (static_addin_fkt)${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },") +endforeach() +set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, NULL, NULL }\n};") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tables.c.in ${CMAKE_CURRENT_BINARY_DIR}/tables.c) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp winpr) + +set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) diff --git a/channels/client/addin.c b/channels/client/addin.c new file mode 100644 index 0000000..86b9f43 --- /dev/null +++ b/channels/client/addin.c @@ -0,0 +1,392 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tables.h" + +#include "addin.h" + +#include +#define TAG CHANNELS_TAG("addin") + +extern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[]; + +void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table, + const char* identifier) +{ + int index = 0; + STATIC_ENTRY* pEntry; + pEntry = (STATIC_ENTRY*) &table->table[index++]; + + while (pEntry->entry != NULL) + { + if (strcmp(pEntry->name, identifier) == 0) + { + return (void*) pEntry->entry; + } + + pEntry = (STATIC_ENTRY*) &table->table[index++]; + } + + return NULL; +} + +void* freerdp_channels_client_find_static_entry(const char* name, const char* identifier) +{ + int index = 0; + STATIC_ENTRY_TABLE* pEntry; + pEntry = (STATIC_ENTRY_TABLE*) &CLIENT_STATIC_ENTRY_TABLES[index++]; + + while (pEntry->table != NULL) + { + if (strcmp(pEntry->name, name) == 0) + { + return freerdp_channels_find_static_entry_in_table(pEntry, identifier); + } + + pEntry = (STATIC_ENTRY_TABLE*) &CLIENT_STATIC_ENTRY_TABLES[index++]; + } + + return NULL; +} + +extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[]; + +FREERDP_ADDIN** freerdp_channels_list_client_static_addins(LPSTR pszName, LPSTR pszSubsystem, + LPSTR pszType, DWORD dwFlags) +{ + size_t i, j; + DWORD nAddins; + FREERDP_ADDIN** ppAddins = NULL; + STATIC_SUBSYSTEM_ENTRY* subsystems; + nAddins = 0; + ppAddins = (FREERDP_ADDIN**) calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + ppAddins[nAddins] = NULL; + + for (i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++) + { + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*) calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", CLIENT_STATIC_ADDIN_TABLE[i].name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + subsystems = (STATIC_SUBSYSTEM_ENTRY*) CLIENT_STATIC_ADDIN_TABLE[i].table; + + for (j = 0; subsystems[j].name != NULL; j++) + { + pAddin = (FREERDP_ADDIN*) calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", CLIENT_STATIC_ADDIN_TABLE[i].name); + sprintf_s(pAddin->cSubsystem, ARRAYSIZE(pAddin->cSubsystem), "%s", subsystems[j].name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + } + } + + return ppAddins; +error_out: + freerdp_channels_addin_list_free(ppAddins); + return NULL; +} + +FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPSTR pszName, LPSTR pszSubsystem, + LPSTR pszType, DWORD dwFlags) +{ + int index; + int nDashes; + HANDLE hFind; + DWORD nAddins; + LPSTR pszPattern; + size_t cchPattern; + LPCSTR pszAddinPath = FREERDP_ADDIN_PATH; + LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX; + LPCSTR pszExtension; + LPSTR pszSearchPath; + size_t cchSearchPath; + size_t cchAddinPath; + size_t cchInstallPrefix; + FREERDP_ADDIN** ppAddins; + WIN32_FIND_DATAA FindData; + cchAddinPath = strlen(pszAddinPath); + cchInstallPrefix = strlen(pszInstallPrefix); + pszExtension = PathGetSharedLibraryExtensionA(0); + cchPattern = 128 + strlen(pszExtension) + 2; + pszPattern = (LPSTR) malloc(cchPattern + 1); + + if (!pszPattern) + { + WLog_ERR(TAG, "malloc failed!"); + return NULL; + } + + if (pszName && pszSubsystem && pszType) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX"%s-client-%s-%s.%s", + pszName, pszSubsystem, pszType, pszExtension); + } + else if (pszName && pszType) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX"%s-client-?-%s.%s", + pszName, pszType, pszExtension); + } + else if (pszName) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX"%s-client*.%s", + pszName, pszExtension); + } + else + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX"?-client*.%s", + pszExtension); + } + + cchPattern = strlen(pszPattern); + cchSearchPath = cchInstallPrefix + cchAddinPath + cchPattern + 3; + pszSearchPath = (LPSTR) malloc(cchSearchPath + 1); + + if (!pszSearchPath) + { + WLog_ERR(TAG, "malloc failed!"); + free(pszPattern); + return NULL; + } + + CopyMemory(pszSearchPath, pszInstallPrefix, cchInstallPrefix); + pszSearchPath[cchInstallPrefix] = '\0'; + NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath); + NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern); + free(pszPattern); + hFind = FindFirstFileA(pszSearchPath, &FindData); + free(pszSearchPath); + nAddins = 0; + ppAddins = (FREERDP_ADDIN**) calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + FindClose(hFind); + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + if (hFind == INVALID_HANDLE_VALUE) + return ppAddins; + + do + { + char* p[5]; + FREERDP_ADDIN* pAddin; + nDashes = 0; + pAddin = (FREERDP_ADDIN*) calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + for (index = 0; FindData.cFileName[index]; index++) + nDashes += (FindData.cFileName[index] == '-') ? 1 : 0; + + if (nDashes == 1) + { + /* -client. */ + p[0] = FindData.cFileName; + p[1] = strchr(p[0], '-') + 1; + strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + } + else if (nDashes == 2) + { + /* -client-. */ + p[0] = FindData.cFileName; + p[1] = strchr(p[0], '-') + 1; + p[2] = strchr(p[1], '-') + 1; + p[3] = strchr(p[2], '.') + 1; + strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1); + strncpy(pAddin->cSubsystem, p[2], (p[3] - p[2]) - 1); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + } + else if (nDashes == 3) + { + /* -client--. */ + p[0] = FindData.cFileName; + p[1] = strchr(p[0], '-') + 1; + p[2] = strchr(p[1], '-') + 1; + p[3] = strchr(p[2], '-') + 1; + p[4] = strchr(p[3], '.') + 1; + strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1); + strncpy(pAddin->cSubsystem, p[2], (p[3] - p[2]) - 1); + strncpy(pAddin->cType, p[3], (p[4] - p[3]) - 1); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + pAddin->dwFlags |= FREERDP_ADDIN_TYPE; + ppAddins[nAddins++] = pAddin; + } + else + { + free(pAddin); + } + } + while (FindNextFileA(hFind, &FindData)); + + FindClose(hFind); + ppAddins[nAddins] = NULL; + return ppAddins; +error_out: + FindClose(hFind); + freerdp_channels_addin_list_free(ppAddins); + return NULL; +} + +FREERDP_ADDIN** freerdp_channels_list_addins(LPSTR pszName, LPSTR pszSubsystem, LPSTR pszType, + DWORD dwFlags) +{ + if (dwFlags & FREERDP_ADDIN_STATIC) + return freerdp_channels_list_client_static_addins(pszName, pszSubsystem, pszType, dwFlags); + else if (dwFlags & FREERDP_ADDIN_DYNAMIC) + return freerdp_channels_list_dynamic_addins(pszName, pszSubsystem, pszType, dwFlags); + + return NULL; +} + +void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins) +{ + int index; + + if (!ppAddins) + return; + + for (index = 0; ppAddins[index] != NULL; index++) + free(ppAddins[index]); + + free(ppAddins); +} + +extern const STATIC_ENTRY CLIENT_VirtualChannelEntryEx_TABLE[]; + +BOOL freerdp_channels_is_virtual_channel_entry_ex(LPCSTR pszName) +{ + int i; + STATIC_ENTRY* entry; + + for (i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != NULL; i++) + { + entry = (STATIC_ENTRY*) &CLIENT_VirtualChannelEntryEx_TABLE[i]; + + if (!strcmp(entry->name, pszName)) + return TRUE; + } + + return FALSE; +} + +PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPSTR pszSubsystem, + LPSTR pszType, DWORD dwFlags) +{ + int i, j; + STATIC_SUBSYSTEM_ENTRY* subsystems; + + for (i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++) + { + if (strcmp(CLIENT_STATIC_ADDIN_TABLE[i].name, pszName) == 0) + { + if (pszSubsystem != NULL) + { + subsystems = (STATIC_SUBSYSTEM_ENTRY*) CLIENT_STATIC_ADDIN_TABLE[i].table; + + for (j = 0; subsystems[j].name != NULL; j++) + { + if (strcmp(subsystems[j].name, pszSubsystem) == 0) + { + if (pszType) + { + if (strcmp(subsystems[j].type, pszType) == 0) + return (PVIRTUALCHANNELENTRY) subsystems[j].entry; + } + else + { + return (PVIRTUALCHANNELENTRY) subsystems[j].entry; + } + } + } + } + else + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + { + if (!freerdp_channels_is_virtual_channel_entry_ex(pszName)) + return NULL; + } + + return (PVIRTUALCHANNELENTRY) CLIENT_STATIC_ADDIN_TABLE[i].entry; + } + } + } + + return NULL; +} diff --git a/channels/client/addin.h b/channels/client/addin.h new file mode 100644 index 0000000..f3ad970 --- /dev/null +++ b/channels/client/addin.h @@ -0,0 +1,20 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + diff --git a/channels/client/tables.c.in b/channels/client/tables.c.in new file mode 100644 index 0000000..cc789d8 --- /dev/null +++ b/channels/client/tables.c.in @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "tables.h" + +${CLIENT_STATIC_TYPEDEFS} +${CLIENT_STATIC_ENTRY_IMPORTS} +${CLIENT_STATIC_ENTRY_TABLES} +${CLIENT_STATIC_ENTRY_TABLES_LIST} +${CLIENT_STATIC_SUBSYSTEM_IMPORTS} +${CLIENT_STATIC_SUBSYSTEM_TABLES} +${CLIENT_STATIC_ADDIN_TABLE} + diff --git a/channels/client/tables.h b/channels/client/tables.h new file mode 100644 index 0000000..c84298a --- /dev/null +++ b/channels/client/tables.h @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +struct _STATIC_ENTRY +{ + const char* name; + UINT(*entry)(); +}; +typedef struct _STATIC_ENTRY STATIC_ENTRY; + +struct _STATIC_ENTRY_TABLE +{ + const char* name; + const STATIC_ENTRY* table; +}; +typedef struct _STATIC_ENTRY_TABLE STATIC_ENTRY_TABLE; + +struct _STATIC_SUBSYSTEM_ENTRY +{ + const char* name; + const char* type; + void (*entry)(void); +}; +typedef struct _STATIC_SUBSYSTEM_ENTRY STATIC_SUBSYSTEM_ENTRY; + +struct _STATIC_ADDIN_TABLE +{ + const char* name; + UINT(*entry)(); + const STATIC_SUBSYSTEM_ENTRY* table; +}; +typedef struct _STATIC_ADDIN_TABLE STATIC_ADDIN_TABLE; diff --git a/channels/cliprdr/CMakeLists.txt b/channels/cliprdr/CMakeLists.txt new file mode 100644 index 0000000..c5cfd72 --- /dev/null +++ b/channels/cliprdr/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("cliprdr") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/cliprdr/ChannelOptions.cmake b/channels/cliprdr/ChannelOptions.cmake new file mode 100644 index 0000000..f175f3f --- /dev/null +++ b/channels/cliprdr/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "cliprdr" TYPE "static" + DESCRIPTION "Clipboard Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPECLIP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/cliprdr/client/CMakeLists.txt b/channels/cliprdr/client/CMakeLists.txt new file mode 100644 index 0000000..6081bee --- /dev/null +++ b/channels/cliprdr/client/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("cliprdr") + +set(${MODULE_PREFIX}_SRCS + cliprdr_format.c + cliprdr_format.h + cliprdr_main.c + cliprdr_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + +set(${MODULE_PREFIX}_LIBS freerdp winpr) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/cliprdr/client/cliprdr_format.c b/channels/cliprdr/client/cliprdr_format.c new file mode 100644 index 0000000..a97a584 --- /dev/null +++ b/channels/cliprdr/client/cliprdr_format.c @@ -0,0 +1,516 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "cliprdr_main.h" +#include "cliprdr_format.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags) +{ + UINT32 index; + size_t position; + BOOL asciiNames; + int formatNameLength; + char* szFormatName; + WCHAR* wszFormatName; + CLIPRDR_FORMAT* formats = NULL; + CLIPRDR_FORMAT_LIST formatList; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + asciiNames = (msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE; + + formatList.msgType = CB_FORMAT_LIST; + formatList.msgFlags = msgFlags; + formatList.dataLen = dataLen; + + index = 0; + formatList.numFormats = 0; + position = Stream_GetPosition(s); + + if (!formatList.dataLen) + { + /* empty format list */ + formatList.formats = NULL; + formatList.numFormats = 0; + } + else if (!cliprdr->useLongFormatNames) + { + formatList.numFormats = (dataLen / 36); + + if ((formatList.numFormats * 36) != dataLen) + { + WLog_ERR(TAG, "Invalid short format list length: %"PRIu32"", dataLen); + return ERROR_INTERNAL_ERROR; + } + + if (formatList.numFormats) + formats = (CLIPRDR_FORMAT*) calloc(formatList.numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList.formats = formats; + + while (dataLen) + { + Stream_Read_UINT32(s, formats[index].formatId); /* formatId (4 bytes) */ + dataLen -= 4; + + formats[index].formatName = NULL; + + /* According to MS-RDPECLIP 2.2.3.1.1.1 formatName is "a 32-byte block containing + * the *null-terminated* name assigned to the Clipboard Format: (32 ASCII 8 characters + * or 16 Unicode characters)" + * However, both Windows RDSH and mstsc violate this specs as seen in the following + * example of a transferred short format name string: [R.i.c.h. .T.e.x.t. .F.o.r.m.a.t.] + * These are 16 unicode charaters - *without* terminating null ! + */ + + if (asciiNames) + { + szFormatName = (char*) Stream_Pointer(s); + + if (szFormatName[0]) + { + /* ensure null termination */ + formats[index].formatName = (char*) malloc(32 + 1); + if (!formats[index].formatName) + { + WLog_ERR(TAG, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + CopyMemory(formats[index].formatName, szFormatName, 32); + formats[index].formatName[32] = '\0'; + } + } + else + { + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (wszFormatName[0]) + { + /* ConvertFromUnicode always returns a null-terminated + * string on success, even if the source string isn't. + */ + if (ConvertFromUnicode(CP_UTF8, 0, wszFormatName, 16, + &(formats[index].formatName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert short clipboard format name"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + } + + Stream_Seek(s, 32); + dataLen -= 32; + index++; + } + } + else + { + while (dataLen) + { + Stream_Seek(s, 4); /* formatId (4 bytes) */ + dataLen -= 4; + + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (!wszFormatName[0]) + formatNameLength = 0; + else + formatNameLength = _wcslen(wszFormatName); + + Stream_Seek(s, (formatNameLength + 1) * 2); + dataLen -= ((formatNameLength + 1) * 2); + + formatList.numFormats++; + } + + dataLen = formatList.dataLen; + Stream_SetPosition(s, position); + + if (formatList.numFormats) + formats = (CLIPRDR_FORMAT*) calloc(formatList.numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList.formats = formats; + + while (dataLen) + { + Stream_Read_UINT32(s, formats[index].formatId); /* formatId (4 bytes) */ + dataLen -= 4; + + formats[index].formatName = NULL; + + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (!wszFormatName[0]) + formatNameLength = 0; + else + formatNameLength = _wcslen(wszFormatName); + + if (formatNameLength) + { + if (ConvertFromUnicode(CP_UTF8, 0, wszFormatName, -1, + &(formats[index].formatName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert long clipboard format name"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + Stream_Seek(s, (formatNameLength + 1) * 2); + dataLen -= ((formatNameLength + 1) * 2); + + index++; + } + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatList: numFormats: %"PRIu32"", + formatList.numFormats); + + if (context->ServerFormatList) + { + if ((error = context->ServerFormatList(context, &formatList))) + WLog_ERR(TAG, "ServerFormatList failed with error %"PRIu32"", error); + } + +error_out: + if (formats) + { + for (index = 0; index < formatList.numFormats; index++) + { + free(formats[index].formatName); + } + + free(formats); + } + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = msgFlags; + formatListResponse.dataLen = dataLen; + + IFCALLRET(context->ServerFormatListResponse, error, context, &formatListResponse); + if (error) + WLog_ERR(TAG, "ServerFormatListResponse failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.msgFlags = msgFlags; + formatDataRequest.dataLen = dataLen; + + Stream_Read_UINT32(s, formatDataRequest.requestedFormatId); /* requestedFormatId (4 bytes) */ + + + IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest); + if (error) + WLog_ERR(TAG, "ServerFormatDataRequest failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags) +{ + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataResponse"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = msgFlags; + formatDataResponse.dataLen = dataLen; + formatDataResponse.requestedFormatData = NULL; + + if (dataLen) + formatDataResponse.requestedFormatData = (BYTE*) Stream_Pointer(s); + + IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse); + if (error) + WLog_ERR(TAG, "ServerFormatDataResponse failed with error %"PRIu32"!", error); + + return error; +} + +static UINT64 filetime_to_uint64(FILETIME value) +{ + UINT64 converted = 0; + converted |= (UINT32) value.dwHighDateTime; + converted <<= 32; + converted |= (UINT32) value.dwLowDateTime; + return converted; +} + +static FILETIME uint64_to_filetime(UINT64 value) +{ + FILETIME converted; + converted.dwLowDateTime = (UINT32) (value >> 0); + converted.dwHighDateTime = (UINT32) (value >> 32); + return converted; +} + +#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 8 + 520) + +/** + * Parse a packed file list. + * + * The resulting array must be freed with the `free()` function. + * + * @param [in] format_data packed `CLIPRDR_FILELIST` to parse. + * @param [in] format_data_length length of `format_data` in bytes. + * @param [out] file_descriptor_array parsed array of `FILEDESCRIPTOR` structs. + * @param [out] file_descriptor_count number of elements in `file_descriptor_array`. + * + * @returns 0 on success, otherwise a Win32 error code. + */ +UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length, + FILEDESCRIPTOR** file_descriptor_array, UINT32* file_descriptor_count) +{ + UINT result = NO_ERROR; + UINT32 i; + UINT32 count = 0; + wStream* s = NULL; + + if (!format_data || !file_descriptor_array || !file_descriptor_count) + return ERROR_BAD_ARGUMENTS; + + s = Stream_New((BYTE*) format_data, format_data_length); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "invalid packed file list"); + + result = ERROR_INCORRECT_SIZE; + goto out; + } + + Stream_Read_UINT32(s, count); /* cItems (4 bytes) */ + + if (Stream_GetRemainingLength(s) / CLIPRDR_FILEDESCRIPTOR_SIZE < count) + { + WLog_ERR(TAG, "packed file list is too short: expected %"PRIuz", have %"PRIuz, + ((size_t) count) * CLIPRDR_FILEDESCRIPTOR_SIZE, + Stream_GetRemainingLength(s)); + + result = ERROR_INCORRECT_SIZE; + goto out; + } + + *file_descriptor_count = count; + *file_descriptor_array = calloc(count, sizeof(FILEDESCRIPTOR)); + if (!*file_descriptor_array) + { + result = ERROR_NOT_ENOUGH_MEMORY; + goto out; + } + + for (i = 0; i < count; i++) + { + int c; + UINT64 lastWriteTime; + FILEDESCRIPTOR* file = &((*file_descriptor_array)[i]); + + Stream_Read_UINT32(s, file->dwFlags); /* flags (4 bytes) */ + Stream_Seek(s, 32); /* reserved1 (32 bytes) */ + Stream_Read_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */ + Stream_Seek(s, 16); /* reserved2 (16 bytes) */ + Stream_Read_UINT64(s, lastWriteTime); /* lastWriteTime (8 bytes) */ + file->ftLastWriteTime = uint64_to_filetime(lastWriteTime); + Stream_Read_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */ + Stream_Read_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */ + for (c = 0; c < 260; c++) /* cFileName (520 bytes) */ + Stream_Read_UINT16(s, file->cFileName[c]); + } + + if (Stream_GetRemainingLength(s) > 0) + WLog_WARN(TAG, "packed file list has %"PRIuz" excess bytes", + Stream_GetRemainingLength(s)); +out: + Stream_Free(s, FALSE); + + return result; +} + +#define CLIPRDR_MAX_FILE_SIZE (2U * 1024 * 1024 * 1024) + +/** + * Serialize a packed file list. + * + * The resulting format data must be freed with the `free()` function. + * + * @param [in] file_descriptor_array array of `FILEDESCRIPTOR` structs to serialize. + * @param [in] file_descriptor_count number of elements in `file_descriptor_array`. + * @param [out] format_data serialized CLIPRDR_FILELIST. + * @param [out] format_data_length length of `format_data` in bytes. + * + * @returns 0 on success, otherwise a Win32 error code. + */ +UINT cliprdr_serialize_file_list(const FILEDESCRIPTOR* file_descriptor_array, + UINT32 file_descriptor_count, BYTE** format_data, UINT32* format_data_length) +{ + UINT result = NO_ERROR; + UINT32 i; + wStream* s = NULL; + + if (!file_descriptor_array || !format_data || !format_data_length) + return ERROR_BAD_ARGUMENTS; + + s = Stream_New(NULL, 4 + file_descriptor_count * CLIPRDR_FILEDESCRIPTOR_SIZE); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, file_descriptor_count); /* cItems (4 bytes) */ + + for (i = 0; i < file_descriptor_count; i++) + { + int c; + UINT64 lastWriteTime; + const FILEDESCRIPTOR* file = &file_descriptor_array[i]; + + /* + * There is a known issue with Windows server getting stuck in + * an infinite loop when downloading files that are larger than + * 2 gigabytes. Do not allow clients to send such file lists. + * + * https://support.microsoft.com/en-us/help/2258090 + */ + if ((file->nFileSizeHigh > 0) || (file->nFileSizeLow >= CLIPRDR_MAX_FILE_SIZE)) + { + WLog_ERR(TAG, "cliprdr does not support files over 2 GB"); + result = ERROR_FILE_TOO_LARGE; + goto error; + } + + Stream_Write_UINT32(s, file->dwFlags); /* flags (4 bytes) */ + Stream_Zero(s, 32); /* reserved1 (32 bytes) */ + Stream_Write_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */ + Stream_Zero(s, 16); /* reserved2 (16 bytes) */ + lastWriteTime = filetime_to_uint64(file->ftLastWriteTime); + Stream_Write_UINT64(s, lastWriteTime); /* lastWriteTime (8 bytes) */ + Stream_Write_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */ + Stream_Write_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */ + for (c = 0; c < 260; c++) /* cFileName (520 bytes) */ + Stream_Write_UINT16(s, file->cFileName[c]); + } + + Stream_SealLength(s); + + Stream_GetBuffer(s, *format_data); + Stream_GetLength(s, *format_data_length); + + Stream_Free(s, FALSE); + + return result; + +error: + Stream_Free(s, TRUE); + + return result; +} diff --git a/channels/cliprdr/client/cliprdr_format.h b/channels/cliprdr/client/cliprdr_format.h new file mode 100644 index 0000000..dfe6965 --- /dev/null +++ b/channels/cliprdr/client/cliprdr_format.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H +#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H + +UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags); +UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags); +UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags); +UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags); + +#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H */ diff --git a/channels/cliprdr/client/cliprdr_main.c b/channels/cliprdr/client/cliprdr_main.c new file mode 100644 index 0000000..8ab0903 --- /dev/null +++ b/channels/cliprdr/client/cliprdr_main.c @@ -0,0 +1,1317 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "cliprdr_main.h" +#include "cliprdr_format.h" + +#ifdef WITH_DEBUG_CLIPRDR +static const char* const CB_MSG_TYPE_STRINGS[] = +{ + "", + "CB_MONITOR_READY", + "CB_FORMAT_LIST", + "CB_FORMAT_LIST_RESPONSE", + "CB_FORMAT_DATA_REQUEST", + "CB_FORMAT_DATA_RESPONSE", + "CB_TEMP_DIRECTORY", + "CB_CLIP_CAPS", + "CB_FILECONTENTS_REQUEST", + "CB_FILECONTENTS_RESPONSE", + "CB_LOCK_CLIPDATA", + "CB_UNLOCK_CLIPDATA" +}; +#endif + +CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr) +{ + CliprdrClientContext* pInterface; + + if (!cliprdr) + return NULL; + + pInterface = (CliprdrClientContext*) cliprdr->channelEntryPoints.pInterface; + return pInterface; +} + +static wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, + UINT32 dataLen) +{ + wStream* s; + s = Stream_New(NULL, dataLen + 8); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return NULL; + } + + Stream_Write_UINT16(s, msgType); + Stream_Write_UINT16(s, msgFlags); + /* Write actual length after the entire packet has been constructed. */ + Stream_Seek(s, 4); + return s; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_packet_send(cliprdrPlugin* cliprdr, wStream* s) +{ + size_t pos; + UINT32 dataLen; + UINT status = CHANNEL_RC_OK; + pos = Stream_GetPosition(s); + dataLen = pos - 8; + Stream_SetPosition(s, 4); + Stream_Write_UINT32(s, dataLen); + Stream_SetPosition(s, pos); +#ifdef WITH_DEBUG_CLIPRDR + WLog_DBG(TAG, "Cliprdr Sending (%"PRIu32" bytes)", dataLen + 8); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), dataLen + 8); +#endif + + if (!cliprdr) + { + status = CHANNEL_RC_BAD_INIT_HANDLE; + } + else + { + status = cliprdr->channelEntryPoints.pVirtualChannelWriteEx(cliprdr->InitHandle, + cliprdr->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_GetPosition(s), s); + } + + if (status != CHANNEL_RC_OK) + WLog_ERR(TAG, "VirtualChannelWrite failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + + return status; +} + +#ifdef WITH_DEBUG_CLIPRDR +static void cliprdr_print_general_capability_flags(UINT32 flags) +{ + WLog_INFO(TAG, "generalFlags (0x%08"PRIX32") {", flags); + + if (flags & CB_USE_LONG_FORMAT_NAMES) + WLog_INFO(TAG, "\tCB_USE_LONG_FORMAT_NAMES"); + + if (flags & CB_STREAM_FILECLIP_ENABLED) + WLog_INFO(TAG, "\tCB_STREAM_FILECLIP_ENABLED"); + + if (flags & CB_FILECLIP_NO_FILE_PATHS) + WLog_INFO(TAG, "\tCB_FILECLIP_NO_FILE_PATHS"); + + if (flags & CB_CAN_LOCK_CLIPDATA) + WLog_INFO(TAG, "\tCB_CAN_LOCK_CLIPDATA"); + + WLog_INFO(TAG, "}"); +} +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_general_capability(cliprdrPlugin* cliprdr, + wStream* s) +{ + UINT32 version; + UINT32 generalFlags; + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + if (!context) + { + WLog_ERR(TAG, "cliprdr_get_client_interface failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, version); /* version (4 bytes) */ + Stream_Read_UINT32(s, generalFlags); /* generalFlags (4 bytes) */ + DEBUG_CLIPRDR("Version: %"PRIu32"", version); +#ifdef WITH_DEBUG_CLIPRDR + cliprdr_print_general_capability_flags(generalFlags); +#endif + + if (cliprdr->useLongFormatNames) + cliprdr->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : + FALSE; + + if (cliprdr->streamFileClipEnabled) + cliprdr->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED) ? + TRUE : FALSE; + + if (cliprdr->fileClipNoFilePaths) + cliprdr->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? + TRUE : FALSE; + + if (cliprdr->canLockClipData) + cliprdr->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE; + + cliprdr->capabilitiesReceived = TRUE; + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*) & + (generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = version; + generalCapabilitySet.generalFlags = generalFlags; + IFCALLRET(context->ServerCapabilities, error, context, &capabilities); + + if (error) + WLog_ERR(TAG, "ServerCapabilities failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, wStream* s, + UINT16 length, UINT16 flags) +{ + UINT16 index; + UINT16 lengthCapability; + UINT16 cCapabilitiesSets; + UINT16 capabilitySetType; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Seek_UINT16(s); /* pad1 (2 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerCapabilities"); + + for (index = 0; index < cCapabilitiesSets; index++) + { + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Read_UINT16(s, lengthCapability); /* lengthCapability (2 bytes) */ + + if (lengthCapability < 4 || Stream_GetRemainingLength(s) < lengthCapability - 4) + return ERROR_INVALID_DATA; + + switch (capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + if ((error = cliprdr_process_general_capability(cliprdr, s))) + { + WLog_ERR(TAG, "cliprdr_process_general_capability failed with error %"PRIu32"!", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "unknown cliprdr capability set: %"PRIu16"", capabilitySetType); + return CHANNEL_RC_BAD_PROC; + break; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr, wStream* s, + UINT16 length, UINT16 flags) +{ + CLIPRDR_MONITOR_READY monitorReady; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + WLog_Print(cliprdr->log, WLOG_DEBUG, "MonitorReady"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + if (!cliprdr->capabilitiesReceived) + { + /** + * The clipboard capabilities pdu from server to client is optional, + * but a server using it must send it before sending the monitor ready pdu. + * When the server capabilities pdu is not used, default capabilities + * corresponding to a generalFlags field set to zero are assumed. + */ + cliprdr->useLongFormatNames = FALSE; + cliprdr->streamFileClipEnabled = FALSE; + cliprdr->fileClipNoFilePaths = TRUE; + cliprdr->canLockClipData = FALSE; + } + + monitorReady.msgType = CB_MONITOR_READY; + monitorReady.msgFlags = flags; + monitorReady.dataLen = length; + IFCALLRET(context->MonitorReady, error, context, &monitorReady); + + if (error) + WLog_ERR(TAG, "MonitorReady failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr, + wStream* s, UINT32 length, UINT16 flags) +{ + CLIPRDR_FILE_CONTENTS_REQUEST request; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsRequest"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_GetRemainingLength(s) < 24) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + request.msgType = CB_FILECONTENTS_REQUEST; + request.msgFlags = flags; + request.dataLen = length; + request.haveClipDataId = FALSE; + Stream_Read_UINT32(s, request.streamId); /* streamId (4 bytes) */ + Stream_Read_UINT32(s, request.listIndex); /* listIndex (4 bytes) */ + Stream_Read_UINT32(s, request.dwFlags); /* dwFlags (4 bytes) */ + Stream_Read_UINT32(s, request.nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Read_UINT32(s, request.nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Read_UINT32(s, request.cbRequested); /* cbRequested (4 bytes) */ + + if (Stream_GetRemainingLength(s) >= 4) + { + Stream_Read_UINT32(s, request.clipDataId); /* clipDataId (4 bytes) */ + request.haveClipDataId = TRUE; + } + + IFCALLRET(context->ServerFileContentsRequest, error, context, &request); + + if (error) + WLog_ERR(TAG, "ServerFileContentsRequest failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_filecontents_response(cliprdrPlugin* cliprdr, + wStream* s, UINT32 length, UINT16 flags) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsResponse"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + response.msgType = CB_FILECONTENTS_RESPONSE; + response.msgFlags = flags; + response.dataLen = length; + Stream_Read_UINT32(s, response.streamId); /* streamId (4 bytes) */ + response.cbRequested = length - 4; + response.requestedData = Stream_Pointer(s); /* requestedFileContentsData */ + IFCALLRET(context->ServerFileContentsResponse, error, context, &response); + + if (error) + WLog_ERR(TAG, "ServerFileContentsResponse failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_lock_clipdata(cliprdrPlugin* cliprdr, wStream* s, + UINT32 length, UINT16 flags) +{ + CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + WLog_Print(cliprdr->log, WLOG_DEBUG, "LockClipData"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + lockClipboardData.msgType = CB_LOCK_CLIPDATA; + lockClipboardData.msgFlags = flags; + lockClipboardData.dataLen = length; + Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ServerLockClipboardData, error, context, &lockClipboardData); + + if (error) + WLog_ERR(TAG, "ServerLockClipboardData failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_unlock_clipdata(cliprdrPlugin* cliprdr, wStream* s, + UINT32 length, UINT16 flags) +{ + CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + WLog_Print(cliprdr->log, WLOG_DEBUG, "UnlockClipData"); + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + unlockClipboardData.msgType = CB_UNLOCK_CLIPDATA; + unlockClipboardData.msgFlags = flags; + unlockClipboardData.dataLen = length; + Stream_Read_UINT32(s, unlockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ServerUnlockClipboardData, error, context, + &unlockClipboardData); + + if (error) + WLog_ERR(TAG, "ServerUnlockClipboardData failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_order_recv(cliprdrPlugin* cliprdr, wStream* s) +{ + UINT16 msgType; + UINT16 msgFlags; + UINT32 dataLen; + UINT error; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, msgFlags); /* msgFlags (2 bytes) */ + Stream_Read_UINT32(s, dataLen); /* dataLen (4 bytes) */ + + if (Stream_GetRemainingLength(s) < dataLen) + return ERROR_INVALID_DATA; + +#ifdef WITH_DEBUG_CLIPRDR + WLog_DBG(TAG, "msgType: %s (%"PRIu16"), msgFlags: %"PRIu16" dataLen: %"PRIu32"", + CB_MSG_TYPE_STRINGS[msgType], msgType, msgFlags, dataLen); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), dataLen + 8); +#endif + + switch (msgType) + { + case CB_CLIP_CAPS: + if ((error = cliprdr_process_clip_caps(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_clip_caps failed with error %"PRIu32"!", error); + + break; + + case CB_MONITOR_READY: + if ((error = cliprdr_process_monitor_ready(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_monitor_ready failed with error %"PRIu32"!", error); + + break; + + case CB_FORMAT_LIST: + if ((error = cliprdr_process_format_list(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_format_list failed with error %"PRIu32"!", error); + + break; + + case CB_FORMAT_LIST_RESPONSE: + if ((error = cliprdr_process_format_list_response(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_format_list_response failed with error %"PRIu32"!", + error); + + break; + + case CB_FORMAT_DATA_REQUEST: + if ((error = cliprdr_process_format_data_request(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_format_data_request failed with error %"PRIu32"!", + error); + + break; + + case CB_FORMAT_DATA_RESPONSE: + if ((error = cliprdr_process_format_data_response(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_format_data_response failed with error %"PRIu32"!", + error); + + break; + + case CB_FILECONTENTS_REQUEST: + if ((error = cliprdr_process_filecontents_request(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_filecontents_request failed with error %"PRIu32"!", + error); + + break; + + case CB_FILECONTENTS_RESPONSE: + if ((error = cliprdr_process_filecontents_response(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_filecontents_response failed with error %"PRIu32"!", + error); + + break; + + case CB_LOCK_CLIPDATA: + if ((error = cliprdr_process_lock_clipdata(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_lock_clipdata failed with error %"PRIu32"!", error); + + break; + + case CB_UNLOCK_CLIPDATA: + if ((error = cliprdr_process_unlock_clipdata(cliprdr, s, dataLen, msgFlags))) + WLog_ERR(TAG, "cliprdr_process_lock_clipdata failed with error %"PRIu32"!", error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + WLog_ERR(TAG, "unknown msgType %"PRIu16"", msgType); + break; + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * Callback Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_capabilities(CliprdrClientContext* context, + CLIPRDR_CAPABILITIES* capabilities) +{ + wStream* s; + CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT16(s, 1); /* cCapabilitiesSets */ + Stream_Write_UINT16(s, 0); /* pad1 */ + generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET*)capabilities->capabilitySets; + Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetType); /* capabilitySetType */ + Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetLength); /* lengthCapability */ + Stream_Write_UINT32(s, generalCapabilitySet->version); /* version */ + Stream_Write_UINT32(s, generalCapabilitySet->generalFlags); /* generalFlags */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientCapabilities"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_temp_directory(CliprdrClientContext* context, + CLIPRDR_TEMP_DIRECTORY* tempDirectory) +{ + int length; + wStream* s; + WCHAR* wszTempDir = NULL; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_TEMP_DIRECTORY, 0, 520 * 2); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + length = ConvertToUnicode(CP_UTF8, 0, tempDirectory->szTempDir, -1, &wszTempDir, 0); + + if (length < 0) + return ERROR_INTERNAL_ERROR; + + if (length > 520) + length = 520; + + Stream_Write(s, wszTempDir, length * 2); + Stream_Zero(s, (520 - length) * 2); + free(wszTempDir); + WLog_Print(cliprdr->log, WLOG_DEBUG, "TempDirectory: %s", + tempDirectory->szTempDir); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_list(CliprdrClientContext* context, + CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s; + UINT32 index; + int length = 0; + int cchWideChar; + LPWSTR lpWideCharStr; + int formatNameSize; + int formatNameLength; + char* szFormatName; + WCHAR* wszFormatName; + BOOL asciiNames = FALSE; + CLIPRDR_FORMAT* format; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + + if (!cliprdr->useLongFormatNames) + { + length = formatList->numFormats * 36; + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + formatNameSize = 0; + formatNameLength = 0; + szFormatName = format->formatName; + + if (asciiNames) + { + if (szFormatName) + formatNameLength = strlen(szFormatName); + + if (formatNameLength > 31) + formatNameLength = 31; + + Stream_Write(s, szFormatName, formatNameLength); + Stream_Zero(s, 32 - formatNameLength); + } + else + { + wszFormatName = NULL; + + if (szFormatName) + formatNameSize = ConvertToUnicode(CP_UTF8, 0, szFormatName, -1, &wszFormatName, + 0); + + if (formatNameSize > 15) + formatNameSize = 15; + + if (wszFormatName) + Stream_Write(s, wszFormatName, formatNameSize * 2); + + Stream_Zero(s, 32 - (formatNameSize * 2)); + free(wszFormatName); + } + } + } + else + { + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + length += 4; + formatNameSize = 2; + + if (format->formatName) + formatNameSize = MultiByteToWideChar(CP_UTF8, 0, format->formatName, -1, NULL, + 0) * 2; + + length += formatNameSize; + } + + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + + if (format->formatName) + { + lpWideCharStr = (LPWSTR) Stream_Pointer(s); + cchWideChar = (Stream_Capacity(s) - Stream_GetPosition(s)) / 2; + formatNameSize = MultiByteToWideChar(CP_UTF8, 0, + format->formatName, -1, lpWideCharStr, cchWideChar) * 2; + Stream_Seek(s, formatNameSize); + } + else + { + Stream_Write_UINT16(s, 0); + } + } + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatList: numFormats: %"PRIu32"", + formatList->numFormats); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_list_response(CliprdrClientContext* context, + CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + formatListResponse->msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse->dataLen = 0; + s = cliprdr_packet_new(formatListResponse->msgType, + formatListResponse->msgFlags, formatListResponse->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatListResponse"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_lock_clipboard_data(CliprdrClientContext* context, + CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, lockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, + "ClientLockClipboardData: clipDataId: 0x%08"PRIX32"", + lockClipboardData->clipDataId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_unlock_clipboard_data(CliprdrClientContext* context, + CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_UNLOCK_CLIPDATA, 0, 4); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, + "ClientUnlockClipboardData: clipDataId: 0x%08"PRIX32"", + unlockClipboardData->clipDataId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_data_request(CliprdrClientContext* context, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + formatDataRequest->msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest->msgFlags = 0; + formatDataRequest->dataLen = 4; + s = cliprdr_packet_new(formatDataRequest->msgType, formatDataRequest->msgFlags, + formatDataRequest->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataRequest"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_data_response(CliprdrClientContext* context, + CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + formatDataResponse->msgType = CB_FORMAT_DATA_RESPONSE; + s = cliprdr_packet_new(formatDataResponse->msgType, + formatDataResponse->msgFlags, formatDataResponse->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataResponse"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_file_contents_request(CliprdrClientContext* context, + CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_FILECONTENTS_REQUEST, 0, 28); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, fileContentsRequest->streamId); /* streamId (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->listIndex); /* listIndex (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->dwFlags); /* dwFlags (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->cbRequested); /* cbRequested (4 bytes) */ + + if (fileContentsRequest->haveClipDataId) + Stream_Write_UINT32(s, fileContentsRequest->clipDataId); /* clipDataId (4 bytes) */ + + WLog_Print(cliprdr->log, WLOG_DEBUG, + "ClientFileContentsRequest: streamId: 0x%08"PRIX32"", + fileContentsRequest->streamId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_file_contents_response(CliprdrClientContext* context, + CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle; + s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, fileContentsResponse->msgFlags, + 4 + fileContentsResponse->cbRequested); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, fileContentsResponse->streamId); /* streamId (4 bytes) */ + /** + * requestedFileContentsData: + * FILECONTENTS_SIZE: file size as UINT64 + * FILECONTENTS_RANGE: file data from requested range + */ + Stream_Write(s, fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + WLog_Print(cliprdr->log, WLOG_DEBUG, + "ClientFileContentsResponse: streamId: 0x%08"PRIX32"", + fileContentsResponse->streamId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_data_received(cliprdrPlugin* cliprdr, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (cliprdr->data_in) + Stream_Free(cliprdr->data_in, TRUE); + + cliprdr->data_in = Stream_New(NULL, totalLength); + } + + if (!(data_in = cliprdr->data_in)) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, (int) dataLength)) + { + Stream_Free(cliprdr->data_in, TRUE); + cliprdr->data_in = NULL; + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "cliprdr_plugin_process_received: read error"); + return ERROR_INTERNAL_ERROR; + } + + cliprdr->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(cliprdr->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE cliprdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) lpUserParam; + + if (!cliprdr || (cliprdr->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = cliprdr_virtual_channel_event_data_received(cliprdr, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, "failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && cliprdr->context->rdpcontext) + setChannelError(cliprdr->context->rdpcontext, error, + "cliprdr_virtual_channel_open_event_ex reported an error"); +} + +static DWORD WINAPI cliprdr_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) arg; + UINT error = CHANNEL_RC_OK; + + while (1) + { + if (!MessageQueue_Wait(cliprdr->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(cliprdr->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + + if ((error = cliprdr_order_recv(cliprdr, data))) + { + WLog_ERR(TAG, "cliprdr_order_recv failed with error %"PRIu32"!", error); + break; + } + } + } + + if (error && cliprdr->context->rdpcontext) + setChannelError(cliprdr->context->rdpcontext, error, + "cliprdr_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_connected(cliprdrPlugin* cliprdr, + LPVOID pData, UINT32 dataLength) +{ + UINT32 status; + status = cliprdr->channelEntryPoints.pVirtualChannelOpenEx(cliprdr->InitHandle, + &cliprdr->OpenHandle, cliprdr->channelDef.name, + cliprdr_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpen failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + cliprdr->queue = MessageQueue_New(NULL); + + if (!cliprdr->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + if (!(cliprdr->thread = CreateThread(NULL, 0, cliprdr_virtual_channel_client_thread, + (void*) cliprdr, + 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + MessageQueue_Free(cliprdr->queue); + cliprdr->queue = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_disconnected(cliprdrPlugin* cliprdr) +{ + UINT rc; + + if (cliprdr->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (MessageQueue_PostQuit(cliprdr->queue, 0) + && (WaitForSingleObject(cliprdr->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", rc); + return rc; + } + + MessageQueue_Free(cliprdr->queue); + CloseHandle(cliprdr->thread); + rc = cliprdr->channelEntryPoints.pVirtualChannelCloseEx(cliprdr->InitHandle, cliprdr->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + return rc; + } + + cliprdr->OpenHandle = 0; + + if (cliprdr->data_in) + { + Stream_Free(cliprdr->data_in, TRUE); + cliprdr->data_in = NULL; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_terminated(cliprdrPlugin* cliprdr) +{ + cliprdr->InitHandle = 0; + free(cliprdr->context); + free(cliprdr); + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE cliprdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + cliprdrPlugin* cliprdr = (cliprdrPlugin*) lpUserParam; + + if (!cliprdr || (cliprdr->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = cliprdr_virtual_channel_event_connected(cliprdr, pData, dataLength))) + WLog_ERR(TAG, "cliprdr_virtual_channel_event_connected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = cliprdr_virtual_channel_event_disconnected(cliprdr))) + WLog_ERR(TAG, + "cliprdr_virtual_channel_event_disconnected failed with error %"PRIu32"!", error); + + break; + + case CHANNEL_EVENT_TERMINATED: + if ((error = cliprdr_virtual_channel_event_terminated(cliprdr))) + WLog_ERR(TAG, "cliprdr_virtual_channel_event_terminated failed with error %"PRIu32"!", + error); + + break; + } + + if (error && cliprdr->context->rdpcontext) + setChannelError(cliprdr->context->rdpcontext, error, + "cliprdr_virtual_channel_init_event reported an error"); +} + +/* cliprdr is always built-in */ +#define VirtualChannelEntryEx cliprdr_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + cliprdrPlugin* cliprdr; + CliprdrClientContext* context = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + cliprdr = (cliprdrPlugin*) calloc(1, sizeof(cliprdrPlugin)); + + if (!cliprdr) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + cliprdr->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | + CHANNEL_OPTION_SHOW_PROTOCOL; + sprintf_s(cliprdr->channelDef.name, ARRAYSIZE(cliprdr->channelDef.name), "cliprdr"); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (CliprdrClientContext*) calloc(1, sizeof(CliprdrClientContext)); + + if (!context) + { + free(cliprdr); + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + context->handle = (void*) cliprdr; + context->custom = NULL; + context->ClientCapabilities = cliprdr_client_capabilities; + context->TempDirectory = cliprdr_temp_directory; + context->ClientFormatList = cliprdr_client_format_list; + context->ClientFormatListResponse = cliprdr_client_format_list_response; + context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data; + context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data; + context->ClientFormatDataRequest = cliprdr_client_format_data_request; + context->ClientFormatDataResponse = cliprdr_client_format_data_response; + context->ClientFileContentsRequest = cliprdr_client_file_contents_request; + context->ClientFileContentsResponse = cliprdr_client_file_contents_response; + cliprdr->context = context; + context->rdpcontext = pEntryPointsEx->context; + } + + cliprdr->log = WLog_Get("com.freerdp.channels.cliprdr.client"); + cliprdr->useLongFormatNames = TRUE; + cliprdr->streamFileClipEnabled = FALSE; + cliprdr->fileClipNoFilePaths = TRUE; + cliprdr->canLockClipData = FALSE; + WLog_Print(cliprdr->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(cliprdr->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + cliprdr->InitHandle = pInitHandle; + rc = cliprdr->channelEntryPoints.pVirtualChannelInitEx(cliprdr, context, pInitHandle, + &cliprdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + cliprdr_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelInit failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + free(cliprdr->context); + free(cliprdr); + return FALSE; + } + + cliprdr->channelEntryPoints.pInterface = context; + return TRUE; +} diff --git a/channels/cliprdr/client/cliprdr_main.h b/channels/cliprdr/client/cliprdr_main.h new file mode 100644 index 0000000..25bad18 --- /dev/null +++ b/channels/cliprdr/client/cliprdr_main.h @@ -0,0 +1,63 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H +#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H + +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("cliprdr.client") + +struct cliprdr_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + CliprdrClientContext* context; + + wLog* log; + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + BOOL capabilitiesReceived; + BOOL useLongFormatNames; + BOOL streamFileClipEnabled; + BOOL fileClipNoFilePaths; + BOOL canLockClipData; +}; +typedef struct cliprdr_plugin cliprdrPlugin; + +CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr); + +#ifdef WITH_DEBUG_CLIPRDR +#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_CLIPRDR(...) do { } while (0) +#endif + +#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H */ diff --git a/channels/cliprdr/server/CMakeLists.txt b/channels/cliprdr/server/CMakeLists.txt new file mode 100644 index 0000000..911c7a4 --- /dev/null +++ b/channels/cliprdr/server/CMakeLists.txt @@ -0,0 +1,28 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("cliprdr") + +set(${MODULE_PREFIX}_SRCS + cliprdr_main.c + cliprdr_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + +target_link_libraries(${MODULE_NAME} freerdp) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/cliprdr/server/cliprdr_main.c b/channels/cliprdr/server/cliprdr_main.c new file mode 100644 index 0000000..118c2b0 --- /dev/null +++ b/channels/cliprdr/server/cliprdr_main.c @@ -0,0 +1,1607 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include "cliprdr_main.h" + +/** + * Initialization Sequence\n + * Client Server\n + * | |\n + * |<----------------------Server Clipboard Capabilities PDU-----------------|\n + * |<-----------------------------Monitor Ready PDU--------------------------|\n + * |-----------------------Client Clipboard Capabilities PDU---------------->|\n + * |---------------------------Temporary Directory PDU---------------------->|\n + * |-------------------------------Format List PDU-------------------------->|\n + * |<--------------------------Format List Response PDU----------------------|\n + * + */ + +/** + * Data Transfer Sequences\n + * Shared Local\n + * Clipboard Owner Clipboard Owner\n + * | |\n + * |-------------------------------------------------------------------------|\n _ + * |-------------------------------Format List PDU-------------------------->|\n | + * |<--------------------------Format List Response PDU----------------------|\n _| Copy Sequence + * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n + * |-------------------------------------------------------------------------|\n + * |-------------------------------------------------------------------------|\n _ + * |<--------------------------Format Data Request PDU-----------------------|\n | Paste Sequence Palette, + * |---------------------------Format Data Response PDU--------------------->|\n _| Metafile, File List Data + * |-------------------------------------------------------------------------|\n + * |-------------------------------------------------------------------------|\n _ + * |<------------------------Format Contents Request PDU---------------------|\n | Paste Sequence + * |-------------------------Format Contents Response PDU------------------->|\n _| File Stream Data + * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n + * |-------------------------------------------------------------------------|\n + * + */ + +wStream* cliprdr_server_packet_new(UINT16 msgType, UINT16 msgFlags, + UINT32 dataLen) +{ + wStream* s; + s = Stream_New(NULL, dataLen + 8); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return NULL; + } + + Stream_Write_UINT16(s, msgType); + Stream_Write_UINT16(s, msgFlags); + /* Write actual length after the entire packet has been constructed. */ + Stream_Seek(s, 4); + return s; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_server_packet_send(CliprdrServerPrivate* cliprdr, wStream* s) +{ + size_t pos; + BOOL status; + UINT32 dataLen; + UINT32 written; + pos = Stream_GetPosition(s); + dataLen = pos - 8; + Stream_SetPosition(s, 4); + Stream_Write_UINT32(s, dataLen); + Stream_SetPosition(s, pos); + status = WTSVirtualChannelWrite(cliprdr->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_capabilities(CliprdrServerContext* context, + CLIPRDR_CAPABILITIES* capabilities) +{ + wStream* s; + CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + capabilities->msgType = CB_CLIP_CAPS; + capabilities->msgFlags = 0; + s = cliprdr_server_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT16(s, 1); /* cCapabilitiesSets (2 bytes) */ + Stream_Write_UINT16(s, 0); /* pad1 (2 bytes) */ + generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET*) + capabilities->capabilitySets; + Stream_Write_UINT16(s, + generalCapabilitySet->capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Write_UINT16(s, + generalCapabilitySet->capabilitySetLength); /* lengthCapability (2 bytes) */ + Stream_Write_UINT32(s, generalCapabilitySet->version); /* version (4 bytes) */ + Stream_Write_UINT32(s, + generalCapabilitySet->generalFlags); /* generalFlags (4 bytes) */ + WLog_DBG(TAG, "ServerCapabilities"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_monitor_ready(CliprdrServerContext* context, + CLIPRDR_MONITOR_READY* monitorReady) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + monitorReady->msgType = CB_MONITOR_READY; + monitorReady->msgFlags = 0; + monitorReady->dataLen = 0; + s = cliprdr_server_packet_new(monitorReady->msgType, + monitorReady->msgFlags, monitorReady->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerMonitorReady"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_list(CliprdrServerContext* context, + CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s; + UINT32 index; + int length = 0; + int cchWideChar; + LPWSTR lpWideCharStr; + int formatNameSize; + int formatNameLength; + char* szFormatName; + WCHAR* wszFormatName; + BOOL asciiNames = FALSE; + CLIPRDR_FORMAT* format; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + + if (!context->useLongFormatNames) + { + length = formatList->numFormats * 36; + s = cliprdr_server_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + formatNameSize = 0; + formatNameLength = 0; + szFormatName = format->formatName; + + if (asciiNames) + { + if (szFormatName) + formatNameLength = strlen(szFormatName); + + if (formatNameLength > 31) + formatNameLength = 31; + + Stream_Write(s, szFormatName, formatNameLength); + Stream_Zero(s, 32 - formatNameLength); + } + else + { + wszFormatName = NULL; + + if (szFormatName) + formatNameSize = ConvertToUnicode(CP_UTF8, 0, szFormatName, -1, &wszFormatName, + 0); + + if (formatNameSize > 15) + formatNameSize = 15; + + if (wszFormatName) + Stream_Write(s, wszFormatName, formatNameSize * 2); + + Stream_Zero(s, 32 - (formatNameSize * 2)); + free(wszFormatName); + } + } + } + else + { + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + length += 4; + formatNameSize = 2; + + if (format->formatName) + formatNameSize = MultiByteToWideChar(CP_UTF8, 0, format->formatName, -1, NULL, + 0) * 2; + + length += formatNameSize; + } + + s = cliprdr_server_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < formatList->numFormats; index++) + { + format = (CLIPRDR_FORMAT*) & (formatList->formats[index]); + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + + if (format->formatName) + { + lpWideCharStr = (LPWSTR) Stream_Pointer(s); + cchWideChar = (Stream_Capacity(s) - Stream_GetPosition(s)) / 2; + formatNameSize = MultiByteToWideChar(CP_UTF8, 0, + format->formatName, -1, lpWideCharStr, cchWideChar) * 2; + Stream_Seek(s, formatNameSize); + } + else + { + Stream_Write_UINT16(s, 0); + } + } + } + + WLog_DBG(TAG, "ServerFormatList: numFormats: %"PRIu32"", + formatList->numFormats); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_list_response(CliprdrServerContext* context, + CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + formatListResponse->msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse->dataLen = 0; + s = cliprdr_server_packet_new(formatListResponse->msgType, + formatListResponse->msgFlags, formatListResponse->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerFormatListResponse"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_lock_clipboard_data(CliprdrServerContext* context, + CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + s = cliprdr_server_packet_new(CB_LOCK_CLIPDATA, 0, 4); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, + lockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + WLog_DBG(TAG, "ServerLockClipboardData: clipDataId: 0x%08"PRIX32"", + lockClipboardData->clipDataId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_unlock_clipboard_data(CliprdrServerContext* context, + CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + s = cliprdr_server_packet_new(CB_UNLOCK_CLIPDATA, 0, 4); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, + unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + WLog_DBG(TAG, "ServerUnlockClipboardData: clipDataId: 0x%08"PRIX32"", + unlockClipboardData->clipDataId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_data_request(CliprdrServerContext* context, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + formatDataRequest->msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest->msgFlags = 0; + formatDataRequest->dataLen = 4; + s = cliprdr_server_packet_new(formatDataRequest->msgType, + formatDataRequest->msgFlags, formatDataRequest->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, + formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */ + WLog_DBG(TAG, "ClientFormatDataRequest"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_data_response(CliprdrServerContext* context, + CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + formatDataResponse->msgType = CB_FORMAT_DATA_RESPONSE; + s = cliprdr_server_packet_new(formatDataResponse->msgType, + formatDataResponse->msgFlags, formatDataResponse->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + WLog_DBG(TAG, "ServerFormatDataResponse"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_file_contents_request(CliprdrServerContext* context, + CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + s = cliprdr_server_packet_new(CB_FILECONTENTS_REQUEST, 0, 28); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, fileContentsRequest->streamId); /* streamId (4 bytes) */ + Stream_Write_UINT32(s, + fileContentsRequest->listIndex); /* listIndex (4 bytes) */ + Stream_Write_UINT32(s, fileContentsRequest->dwFlags); /* dwFlags (4 bytes) */ + Stream_Write_UINT32(s, + fileContentsRequest->nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Write_UINT32(s, + fileContentsRequest->nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Write_UINT32(s, + fileContentsRequest->cbRequested); /* cbRequested (4 bytes) */ + Stream_Write_UINT32(s, + fileContentsRequest->clipDataId); /* clipDataId (4 bytes) */ + WLog_DBG(TAG, "ServerFileContentsRequest: streamId: 0x%08"PRIX32"", + fileContentsRequest->streamId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_file_contents_response(CliprdrServerContext* context, + CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + + if (fileContentsResponse->dwFlags & FILECONTENTS_SIZE) + fileContentsResponse->cbRequested = sizeof(UINT64); + + s = cliprdr_server_packet_new(CB_FILECONTENTS_RESPONSE, + fileContentsResponse->msgFlags, + 4 + fileContentsResponse->cbRequested); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_server_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, fileContentsResponse->streamId); /* streamId (4 bytes) */ + /** + * requestedFileContentsData: + * FILECONTENTS_SIZE: file size as UINT64 + * FILECONTENTS_RANGE: file data from requested range + */ + Stream_Write(s, fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + WLog_DBG(TAG, "ServerFileContentsResponse: streamId: 0x%08"PRIX32"", + fileContentsResponse->streamId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* + context, wStream* s) +{ + UINT32 version; + UINT32 generalFlags; + Stream_Read_UINT32(s, version); /* version (4 bytes) */ + Stream_Read_UINT32(s, generalFlags); /* generalFlags (4 bytes) */ + + if (context->useLongFormatNames) + context->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : + FALSE; + + if (context->streamFileClipEnabled) + context->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED) ? + TRUE : FALSE; + + if (context->fileClipNoFilePaths) + context->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? + TRUE : FALSE; + + if (context->canLockClipData) + context->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, + wStream* s, CLIPRDR_HEADER* header) +{ + UINT16 index; + UINT16 cCapabilitiesSets; + UINT16 capabilitySetType; + UINT16 lengthCapability; + UINT error; + WLog_DBG(TAG, "CliprdrClientCapabilities"); + Stream_Read_UINT16(s, cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Seek_UINT16(s); /* pad1 (2 bytes) */ + + for (index = 0; index < cCapabilitiesSets; index++) + { + Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Read_UINT16(s, lengthCapability); /* lengthCapability (2 bytes) */ + + switch (capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + if ((error = cliprdr_server_receive_general_capability(context, s))) + { + WLog_ERR(TAG, "cliprdr_server_receive_general_capability failed with error %"PRIu32"", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "unknown cliprdr capability set: %"PRIu16"", capabilitySetType); + return ERROR_INVALID_DATA; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_temporary_directory(CliprdrServerContext* + context, wStream* s, CLIPRDR_HEADER* header) +{ + int length; + WCHAR* wszTempDir; + CLIPRDR_TEMP_DIRECTORY tempDirectory; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + size_t slength; + UINT error = CHANNEL_RC_OK; + + if ((slength = Stream_GetRemainingLength(s)) < 520) + { + WLog_ERR(TAG, + "Stream_GetRemainingLength returned %"PRIuz" but should at least be 520", slength); + return CHANNEL_RC_NO_MEMORY; + } + + wszTempDir = (WCHAR*) Stream_Pointer(s); + + if (wszTempDir[260] != 0) + { + WLog_ERR(TAG, "wszTempDir[260] was not 0"); + return ERROR_INVALID_DATA; + } + + free(cliprdr->temporaryDirectory); + cliprdr->temporaryDirectory = NULL; + + if (ConvertFromUnicode(CP_UTF8, 0, wszTempDir, -1, + &(cliprdr->temporaryDirectory), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert temporary directory name"); + return ERROR_INVALID_DATA; + } + + length = strlen(cliprdr->temporaryDirectory); + + if (length > 519) + length = 519; + + CopyMemory(tempDirectory.szTempDir, cliprdr->temporaryDirectory, length); + tempDirectory.szTempDir[length] = '\0'; + WLog_DBG(TAG, "CliprdrTemporaryDirectory: %s", cliprdr->temporaryDirectory); + IFCALLRET(context->TempDirectory, error, context, &tempDirectory); + + if (error) + WLog_ERR(TAG, "TempDirectory failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_list(CliprdrServerContext* context, + wStream* s, CLIPRDR_HEADER* header) +{ + UINT32 index; + UINT32 dataLen; + size_t position; + BOOL asciiNames; + int formatNameLength; + char* szFormatName; + WCHAR* wszFormatName; + CLIPRDR_FORMAT* formats = NULL; + CLIPRDR_FORMAT_LIST formatList; + UINT error = CHANNEL_RC_OK; + dataLen = header->dataLen; + asciiNames = (header->msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE; + formatList.msgType = CB_FORMAT_LIST; + formatList.msgFlags = header->msgFlags; + formatList.dataLen = header->dataLen; + index = 0; + formatList.numFormats = 0; + position = Stream_GetPosition(s); + + if (!header->dataLen) + { + /* empty format list */ + formatList.formats = NULL; + formatList.numFormats = 0; + } + else if (!context->useLongFormatNames) + { + formatList.numFormats = (dataLen / 36); + + if ((formatList.numFormats * 36) != dataLen) + { + WLog_ERR(TAG, "Invalid short format list length: %"PRIu32"", dataLen); + return ERROR_INVALID_PARAMETER; + } + + if (formatList.numFormats) + formats = (CLIPRDR_FORMAT*) calloc(formatList.numFormats, + sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList.formats = formats; + + while (dataLen) + { + Stream_Read_UINT32(s, formats[index].formatId); /* formatId (4 bytes) */ + dataLen -= 4; + formats[index].formatName = NULL; + + /* According to MS-RDPECLIP 2.2.3.1.1.1 formatName is "a 32-byte block containing + * the *null-terminated* name assigned to the Clipboard Format: (32 ASCII 8 characters + * or 16 Unicode characters)" + * However, both Windows RDSH and mstsc violate this specs as seen in the following + * example of a transferred short format name string: [R.i.c.h. .T.e.x.t. .F.o.r.m.a.t.] + * These are 16 unicode charaters - *without* terminating null ! + */ + + if (asciiNames) + { + szFormatName = (char*) Stream_Pointer(s); + + if (szFormatName[0]) + { + /* ensure null termination */ + formats[index].formatName = (char*) malloc(32 + 1); + CopyMemory(formats[index].formatName, szFormatName, 32); + formats[index].formatName[32] = '\0'; + } + } + else + { + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (wszFormatName[0]) + { + /* ConvertFromUnicode always returns a null-terminated + * string on success, even if the source string isn't. + */ + if (ConvertFromUnicode(CP_UTF8, 0, wszFormatName, 16, + &(formats[index].formatName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert short clipboard format name"); + error = ERROR_INVALID_DATA; + goto out; + } + } + } + + Stream_Seek(s, 32); + dataLen -= 32; + index++; + } + } + else + { + while (dataLen) + { + Stream_Seek(s, 4); /* formatId (4 bytes) */ + dataLen -= 4; + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (!wszFormatName[0]) + formatNameLength = 0; + else + formatNameLength = _wcslen(wszFormatName); + + Stream_Seek(s, (formatNameLength + 1) * 2); + dataLen -= ((formatNameLength + 1) * 2); + formatList.numFormats++; + } + + dataLen = formatList.dataLen; + Stream_SetPosition(s, position); + + if (formatList.numFormats) + formats = (CLIPRDR_FORMAT*) calloc(formatList.numFormats, + sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList.formats = formats; + + while (dataLen) + { + Stream_Read_UINT32(s, formats[index].formatId); /* formatId (4 bytes) */ + dataLen -= 4; + formats[index].formatName = NULL; + wszFormatName = (WCHAR*) Stream_Pointer(s); + + if (!wszFormatName[0]) + formatNameLength = 0; + else + formatNameLength = _wcslen(wszFormatName); + + if (formatNameLength) + { + if (ConvertFromUnicode(CP_UTF8, 0, wszFormatName, -1, + &(formats[index].formatName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert long clipboard format name"); + error = ERROR_INVALID_DATA; + goto out; + } + } + + Stream_Seek(s, (formatNameLength + 1) * 2); + dataLen -= ((formatNameLength + 1) * 2); + index++; + } + } + + WLog_DBG(TAG, "ClientFormatList: numFormats: %"PRIu32"", + formatList.numFormats); + IFCALLRET(context->ClientFormatList, error, context, &formatList); + + if (error) + WLog_ERR(TAG, "ClientFormatList failed with error %"PRIu32"!", error); + +out: + + for (index = 0; index < formatList.numFormats; index++) + { + free(formatList.formats[index].formatName); + } + + free(formatList.formats); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_list_response( + CliprdrServerContext* context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientFormatListResponse"); + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = header->msgFlags; + formatListResponse.dataLen = header->dataLen; + IFCALLRET(context->ClientFormatListResponse, error, context, + &formatListResponse); + + if (error) + WLog_ERR(TAG, "ClientFormatListResponse failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_lock_clipdata(CliprdrServerContext* context, + wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientLockClipData"); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + lockClipboardData.msgType = CB_LOCK_CLIPDATA; + lockClipboardData.msgFlags = header->msgFlags; + lockClipboardData.dataLen = header->dataLen; + Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ClientLockClipboardData, error, context, &lockClipboardData); + + if (error) + WLog_ERR(TAG, "ClientLockClipboardData failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_unlock_clipdata(CliprdrServerContext* + context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientUnlockClipData"); + unlockClipboardData.msgType = CB_UNLOCK_CLIPDATA; + unlockClipboardData.msgFlags = header->msgFlags; + unlockClipboardData.dataLen = header->dataLen; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, + unlockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ClientUnlockClipboardData, error, context, + &unlockClipboardData); + + if (error) + WLog_ERR(TAG, "ClientUnlockClipboardData failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_data_request(CliprdrServerContext* + context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientFormatDataRequest"); + formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.msgFlags = header->msgFlags; + formatDataRequest.dataLen = header->dataLen; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, + formatDataRequest.requestedFormatId); /* requestedFormatId (4 bytes) */ + IFCALLRET(context->ClientFormatDataRequest, error, context, &formatDataRequest); + + if (error) + WLog_ERR(TAG, "ClientFormatDataRequest failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_data_response( + CliprdrServerContext* context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientFormatDataResponse"); + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = header->msgFlags; + formatDataResponse.dataLen = header->dataLen; + formatDataResponse.requestedFormatData = NULL; + + if (Stream_GetRemainingLength(s) < header->dataLen) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + if (header->dataLen) + { + formatDataResponse.requestedFormatData = (BYTE*) malloc(header->dataLen); + Stream_Read(s, formatDataResponse.requestedFormatData, header->dataLen); + } + + IFCALLRET(context->ClientFormatDataResponse, error, context, + &formatDataResponse); + + if (error) + WLog_ERR(TAG, "ClientFormatDataResponse failed with error %"PRIu32"!", error); + + free(formatDataResponse.requestedFormatData); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_filecontents_request( + CliprdrServerContext* context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_FILE_CONTENTS_REQUEST request; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientFileContentsRequest"); + request.msgType = CB_FILECONTENTS_REQUEST; + request.msgFlags = header->msgFlags; + request.dataLen = header->dataLen; + + if (Stream_GetRemainingLength(s) < 24) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, request.streamId); /* streamId (4 bytes) */ + Stream_Read_UINT32(s, request.listIndex); /* listIndex (4 bytes) */ + Stream_Read_UINT32(s, request.dwFlags); /* dwFlags (4 bytes) */ + Stream_Read_UINT32(s, request.nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Read_UINT32(s, request.nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Read_UINT32(s, request.cbRequested); /* cbRequested (4 bytes) */ + + if (Stream_GetRemainingLength(s) < 4) /* clipDataId (4 bytes) optional */ + request.clipDataId = 0; + else + Stream_Read_UINT32(s, request.clipDataId); + + IFCALLRET(context->ClientFileContentsRequest, error, context, &request); + + if (error) + WLog_ERR(TAG, "ClientFileContentsRequest failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_filecontents_response( + CliprdrServerContext* context, wStream* s, CLIPRDR_HEADER* header) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "CliprdrClientFileContentsResponse"); + response.msgType = CB_FILECONTENTS_RESPONSE; + response.msgFlags = header->msgFlags; + response.dataLen = header->dataLen; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, response.streamId); /* streamId (4 bytes) */ + response.cbRequested = header->dataLen - 4; + response.requestedData = Stream_Pointer(s); /* requestedFileContentsData */ + IFCALLRET(context->ClientFileContentsResponse, error, context, &response); + + if (error) + WLog_ERR(TAG, "ClientFileContentsResponse failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_pdu(CliprdrServerContext* context, + wStream* s, CLIPRDR_HEADER* header) +{ + UINT error; + WLog_DBG(TAG, + "CliprdrServerReceivePdu: msgType: %"PRIu16" msgFlags: 0x%04"PRIX16" dataLen: %"PRIu32"", + header->msgType, header->msgFlags, header->dataLen); + + switch (header->msgType) + { + case CB_CLIP_CAPS: + if ((error = cliprdr_server_receive_capabilities(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_capabilities failed with error %"PRIu32"!", + error); + + break; + + case CB_TEMP_DIRECTORY: + if ((error = cliprdr_server_receive_temporary_directory(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_temporary_directory failed with error %"PRIu32"!", error); + + break; + + case CB_FORMAT_LIST: + if ((error = cliprdr_server_receive_format_list(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_format_list failed with error %"PRIu32"!", + error); + + break; + + case CB_FORMAT_LIST_RESPONSE: + if ((error = cliprdr_server_receive_format_list_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_list_response failed with error %"PRIu32"!", error); + + break; + + case CB_LOCK_CLIPDATA: + if ((error = cliprdr_server_receive_lock_clipdata(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_lock_clipdata failed with error %"PRIu32"!", + error); + + break; + + case CB_UNLOCK_CLIPDATA: + if ((error = cliprdr_server_receive_unlock_clipdata(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_unlock_clipdata failed with error %"PRIu32"!", + error); + + break; + + case CB_FORMAT_DATA_REQUEST: + if ((error = cliprdr_server_receive_format_data_request(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_data_request failed with error %"PRIu32"!", error); + + break; + + case CB_FORMAT_DATA_RESPONSE: + if ((error = cliprdr_server_receive_format_data_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_data_response failed with error %"PRIu32"!", error); + + break; + + case CB_FILECONTENTS_REQUEST: + if ((error = cliprdr_server_receive_filecontents_request(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_filecontents_request failed with error %"PRIu32"!", error); + + break; + + case CB_FILECONTENTS_RESPONSE: + if ((error = cliprdr_server_receive_filecontents_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_filecontents_response failed with error %"PRIu32"!", error); + + break; + + default: + error = ERROR_INVALID_DATA; + WLog_DBG(TAG, "Unexpected clipboard PDU type: %"PRIu16"", header->msgType); + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_init(CliprdrServerContext* context) +{ + UINT32 generalFlags; + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_MONITOR_READY monitorReady; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + UINT error; + ZeroMemory(&capabilities, sizeof(capabilities)); + ZeroMemory(&monitorReady, sizeof(monitorReady)); + generalFlags = 0; + + if (context->useLongFormatNames) + generalFlags |= CB_USE_LONG_FORMAT_NAMES; + + if (context->streamFileClipEnabled) + generalFlags |= CB_STREAM_FILECLIP_ENABLED; + + if (context->fileClipNoFilePaths) + generalFlags |= CB_FILECLIP_NO_FILE_PATHS; + + if (context->canLockClipData) + generalFlags |= CB_CAN_LOCK_CLIPDATA; + + capabilities.msgType = CB_CLIP_CAPS; + capabilities.msgFlags = 0; + capabilities.dataLen = 4 + CB_CAPSTYPE_GENERAL_LEN; + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*) &generalCapabilitySet; + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = CB_CAPSTYPE_GENERAL_LEN; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = generalFlags; + + if ((error = context->ServerCapabilities(context, &capabilities))) + { + WLog_ERR(TAG, "ServerCapabilities failed with error %"PRIu32"!", error); + return error; + } + + if ((error = context->MonitorReady(context, &monitorReady))) + { + WLog_ERR(TAG, "MonitorReady failed with error %"PRIu32"!", error); + return error; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_server_read(CliprdrServerContext* context) +{ + wStream* s; + size_t position; + DWORD BytesToRead; + DWORD BytesReturned; + CLIPRDR_HEADER header; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + UINT error; + DWORD status; + s = cliprdr->s; + + if (Stream_GetPosition(s) < CLIPRDR_HEADER_LENGTH) + { + BytesReturned = 0; + BytesToRead = CLIPRDR_HEADER_LENGTH - Stream_GetPosition(s); + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, + (PCHAR) Stream_Pointer(s), BytesToRead, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Seek(s, BytesReturned); + } + + if (Stream_GetPosition(s) >= CLIPRDR_HEADER_LENGTH) + { + position = Stream_GetPosition(s); + Stream_SetPosition(s, 0); + Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */ + Stream_Read_UINT32(s, header.dataLen); /* dataLen (4 bytes) */ + + if (!Stream_EnsureCapacity(s, (header.dataLen + CLIPRDR_HEADER_LENGTH))) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(s, position); + + if (Stream_GetPosition(s) < (header.dataLen + CLIPRDR_HEADER_LENGTH)) + { + BytesReturned = 0; + BytesToRead = (header.dataLen + CLIPRDR_HEADER_LENGTH) - Stream_GetPosition(s); + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, + (PCHAR) Stream_Pointer(s), BytesToRead, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Seek(s, BytesReturned); + } + + if (Stream_GetPosition(s) >= (header.dataLen + CLIPRDR_HEADER_LENGTH)) + { + Stream_SetPosition(s, (header.dataLen + CLIPRDR_HEADER_LENGTH)); + Stream_SealLength(s); + Stream_SetPosition(s, CLIPRDR_HEADER_LENGTH); + + if ((error = cliprdr_server_receive_pdu(context, s, &header))) + { + WLog_ERR(TAG, "cliprdr_server_receive_pdu failed with error code %"PRIu32"!", error); + return error; + } + + Stream_SetPosition(s, 0); + /* check for trailing zero bytes */ + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + BytesReturned = 0; + BytesToRead = 4; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, + (PCHAR) Stream_Pointer(s), BytesToRead, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned == 4) + { + Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */ + + if (!header.msgType) + { + /* ignore trailing bytes */ + Stream_SetPosition(s, 0); + } + } + else + { + Stream_Seek(s, BytesReturned); + } + } + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI cliprdr_server_thread(LPVOID arg) +{ + DWORD status; + DWORD nCount; + HANDLE events[8]; + HANDLE ChannelEvent; + CliprdrServerContext* context = (CliprdrServerContext*) arg; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + UINT error; + + ChannelEvent = context->GetEventHandle(context); + nCount = 0; + events[nCount++] = cliprdr->StopEvent; + events[nCount++] = ChannelEvent; + + if ((error = cliprdr_server_init(context))) + { + WLog_ERR(TAG, "cliprdr_server_init failed with error %"PRIu32"!", error); + goto out; + } + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + goto out; + } + + status = WaitForSingleObject(cliprdr->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + goto out; + } + + if (status == WAIT_OBJECT_0) + break; + + status = WaitForSingleObject(ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + if ((error = context->CheckEventHandle(context))) + { + WLog_ERR(TAG, "CheckEventHandle failed with error %"PRIu32"!", error); + break; + } + } + } + +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "cliprdr_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_open(CliprdrServerContext* context) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + cliprdr->ChannelHandle = WTSVirtualChannelOpen(cliprdr->vcm, + WTS_CURRENT_SESSION, "cliprdr"); + + if (!cliprdr->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen for cliprdr failed!"); + return ERROR_INTERNAL_ERROR; + } + + cliprdr->ChannelEvent = NULL; + + if (WTSVirtualChannelQuery(cliprdr->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned)) + { + if (BytesReturned != sizeof(HANDLE)) + { + WLog_ERR(TAG, "BytesReturned has not size of HANDLE!"); + return ERROR_INTERNAL_ERROR; + } + + CopyMemory(&(cliprdr->ChannelEvent), buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + } + + if (!cliprdr->ChannelEvent) + { + WLog_ERR(TAG, "WTSVirtualChannelQuery for cliprdr failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_close(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + + if (cliprdr->ChannelHandle) + { + WTSVirtualChannelClose(cliprdr->ChannelHandle); + cliprdr->ChannelHandle = NULL; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_start(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + UINT error; + + if (!cliprdr->ChannelHandle) + { + if ((error = context->Open(context))) + { + WLog_ERR(TAG, "Open failed!"); + return error; + } + } + + if (!(cliprdr->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(cliprdr->Thread = CreateThread(NULL, 0, cliprdr_server_thread, (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(cliprdr->StopEvent); + cliprdr->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_stop(CliprdrServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + + if (cliprdr->StopEvent) + { + SetEvent(cliprdr->StopEvent); + + if (WaitForSingleObject(cliprdr->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + CloseHandle(cliprdr->Thread); + CloseHandle(cliprdr->StopEvent); + } + + if (cliprdr->ChannelHandle) + return context->Close(context); + + return error; +} + +static HANDLE cliprdr_server_get_event_handle(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*) context->handle; + return cliprdr->ChannelEvent; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_check_event_handle(CliprdrServerContext* context) +{ + return cliprdr_server_read(context); +} + +CliprdrServerContext* cliprdr_server_context_new(HANDLE vcm) +{ + CliprdrServerContext* context; + CliprdrServerPrivate* cliprdr; + context = (CliprdrServerContext*) calloc(1, sizeof(CliprdrServerContext)); + + if (context) + { + context->Open = cliprdr_server_open; + context->Close = cliprdr_server_close; + context->Start = cliprdr_server_start; + context->Stop = cliprdr_server_stop; + context->GetEventHandle = cliprdr_server_get_event_handle; + context->CheckEventHandle = cliprdr_server_check_event_handle; + context->ServerCapabilities = cliprdr_server_capabilities; + context->MonitorReady = cliprdr_server_monitor_ready; + context->ServerFormatList = cliprdr_server_format_list; + context->ServerFormatListResponse = cliprdr_server_format_list_response; + context->ServerLockClipboardData = cliprdr_server_lock_clipboard_data; + context->ServerUnlockClipboardData = cliprdr_server_unlock_clipboard_data; + context->ServerFormatDataRequest = cliprdr_server_format_data_request; + context->ServerFormatDataResponse = cliprdr_server_format_data_response; + context->ServerFileContentsRequest = cliprdr_server_file_contents_request; + context->ServerFileContentsResponse = cliprdr_server_file_contents_response; + cliprdr = context->handle = (CliprdrServerPrivate*) calloc(1, + sizeof(CliprdrServerPrivate)); + + if (cliprdr) + { + cliprdr->vcm = vcm; + cliprdr->s = Stream_New(NULL, 4096); + + if (!cliprdr->s) + { + WLog_ERR(TAG, "Stream_New failed!"); + free(context->handle); + free(context); + return NULL; + } + } + else + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return NULL; + } + } + + return context; +} + +void cliprdr_server_context_free(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr; + + if (!context) + return; + + cliprdr = (CliprdrServerPrivate*) context->handle; + + if (cliprdr) + { + Stream_Free(cliprdr->s, TRUE); + free(cliprdr->temporaryDirectory); + } + + free(context->handle); + free(context); +} diff --git a/channels/cliprdr/server/cliprdr_main.h b/channels/cliprdr/server/cliprdr_main.h new file mode 100644 index 0000000..248f341 --- /dev/null +++ b/channels/cliprdr/server/cliprdr_main.h @@ -0,0 +1,48 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H +#define FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H + +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("cliprdr.server") + +#define CLIPRDR_HEADER_LENGTH 8 + +struct _cliprdr_server_private +{ + HANDLE vcm; + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + HANDLE ChannelEvent; + + wStream* s; + char* temporaryDirectory; +}; +typedef struct _cliprdr_server_private CliprdrServerPrivate; + +#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H */ diff --git a/channels/disp/CMakeLists.txt b/channels/disp/CMakeLists.txt new file mode 100644 index 0000000..541892d --- /dev/null +++ b/channels/disp/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("disp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/disp/ChannelOptions.cmake b/channels/disp/ChannelOptions.cmake new file mode 100644 index 0000000..0e254ad --- /dev/null +++ b/channels/disp/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "disp" TYPE "dynamic" + DESCRIPTION "Display Update Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEDISP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/disp/client/CMakeLists.txt b/channels/disp/client/CMakeLists.txt new file mode 100644 index 0000000..1ec2c6f --- /dev/null +++ b/channels/disp/client/CMakeLists.txt @@ -0,0 +1,39 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("disp") + +set(${MODULE_PREFIX}_SRCS + disp_main.c + disp_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/disp/client/disp_main.c b/channels/disp/client/disp_main.c new file mode 100755 index 0000000..0555317 --- /dev/null +++ b/channels/disp/client/disp_main.c @@ -0,0 +1,399 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Display Update Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "disp_main.h" + +struct _DISP_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _DISP_CHANNEL_CALLBACK DISP_CHANNEL_CALLBACK; + +struct _DISP_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + DISP_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _DISP_LISTENER_CALLBACK DISP_LISTENER_CALLBACK; + +struct _DISP_PLUGIN +{ + IWTSPlugin iface; + + IWTSListener* listener; + DISP_LISTENER_CALLBACK* listener_callback; + + UINT32 MaxNumMonitors; + UINT32 MaxMonitorAreaFactorA; + UINT32 MaxMonitorAreaFactorB; +}; +typedef struct _DISP_PLUGIN DISP_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callback, UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors) +{ + UINT status; + wStream* s; + UINT32 type; + UINT32 index; + UINT32 length; + DISP_PLUGIN* disp; + UINT32 MonitorLayoutSize; + + disp = (DISP_PLUGIN*) callback->plugin; + + MonitorLayoutSize = 40; + + length = 8 + 8 + (NumMonitors * MonitorLayoutSize); + + type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT; + + s = Stream_New(NULL, length); + if(!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, type); /* Type (4 bytes) */ + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + + if (NumMonitors > disp->MaxNumMonitors) + NumMonitors = disp->MaxNumMonitors; + + Stream_Write_UINT32(s, MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */ + Stream_Write_UINT32(s, NumMonitors); /* NumMonitors (4 bytes) */ + + WLog_DBG(TAG, "disp_send_display_control_monitor_layout_pdu: NumMonitors=%"PRIu32"", NumMonitors); + + for (index = 0; index < NumMonitors; index++) + { + Monitors[index].Width -= (Monitors[index].Width % 2); + + if (Monitors[index].Width < 200) + Monitors[index].Width = 200; + + if (Monitors[index].Width > 8192) + Monitors[index].Width = 8192; + + if (Monitors[index].Width % 2) + Monitors[index].Width++; + + if (Monitors[index].Height < 200) + Monitors[index].Height = 200; + + if (Monitors[index].Height > 8192) + Monitors[index].Height = 8192; + + Stream_Write_UINT32(s, Monitors[index].Flags); /* Flags (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].Left); /* Left (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].Top); /* Top (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].Width); /* Width (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].Height); /* Height (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].PhysicalWidth); /* PhysicalWidth (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].PhysicalHeight); /* PhysicalHeight (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].Orientation); /* Orientation (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */ + Stream_Write_UINT32(s, Monitors[index].DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */ + + WLog_DBG(TAG, "\t%d : Flags: 0x%08"PRIX32" Left/Top: (%"PRId32",%"PRId32") W/H=%"PRIu32"x%"PRIu32")", index, + Monitors[index].Flags, Monitors[index].Left, Monitors[index].Top, Monitors[index].Width, + Monitors[index].Height); + WLog_DBG(TAG, "\t PhysicalWidth: %"PRIu32" PhysicalHeight: %"PRIu32" Orientation: %"PRIu32"", + Monitors[index].PhysicalWidth, Monitors[index].PhysicalHeight, Monitors[index].Orientation); + } + + Stream_SealLength(s); + + status = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), Stream_Buffer(s), NULL); + + Stream_Free(s, TRUE); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_recv_display_control_caps_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s) +{ + DISP_PLUGIN* disp; + DispClientContext *context; + UINT ret = CHANNEL_RC_OK; + + disp = (DISP_PLUGIN*) callback->plugin; + context = (DispClientContext *)disp->iface.pInterface; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, disp->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */ + Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */ + Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */ + + if (context->DisplayControlCaps) + ret = context->DisplayControlCaps(context, disp->MaxNumMonitors, disp->MaxMonitorAreaFactorA, disp->MaxMonitorAreaFactorB); + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_recv_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 type; + UINT32 length; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, type); /* Type (4 bytes) */ + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + //WLog_ERR(TAG, "Type: %"PRIu32" Length: %"PRIu32"", type, length); + + switch (type) + { + case DISPLAY_CONTROL_PDU_TYPE_CAPS: + return disp_recv_display_control_caps_pdu(callback, s); + + default: + WLog_ERR(TAG, "Type %"PRIu32" not recognized!", type); + return ERROR_INTERNAL_ERROR; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *data) +{ + DISP_CHANNEL_CALLBACK* callback = (DISP_CHANNEL_CALLBACK*) pChannelCallback; + + return disp_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + DISP_CHANNEL_CALLBACK* callback; + DISP_LISTENER_CALLBACK* listener_callback = (DISP_LISTENER_CALLBACK*) pListenerCallback; + + callback = (DISP_CHANNEL_CALLBACK*) calloc(1, sizeof(DISP_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = disp_on_data_received; + callback->iface.OnClose = disp_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + DISP_PLUGIN* disp = (DISP_PLUGIN*) pPlugin; + + disp->listener_callback = (DISP_LISTENER_CALLBACK*) calloc(1, sizeof(DISP_LISTENER_CALLBACK)); + + if (!disp->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + disp->listener_callback->iface.OnNewChannelConnection = disp_on_new_channel_connection; + disp->listener_callback->plugin = pPlugin; + disp->listener_callback->channel_mgr = pChannelMgr; + + status = pChannelMgr->CreateListener(pChannelMgr, DISP_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*) disp->listener_callback, &(disp->listener)); + + disp->listener->pInterface = disp->iface.pInterface; + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_plugin_terminated(IWTSPlugin* pPlugin) +{ + DISP_PLUGIN* disp = (DISP_PLUGIN*) pPlugin; + free(disp->listener_callback); + free(disp->iface.pInterface); + free(pPlugin); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_send_monitor_layout(DispClientContext* context, UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors) +{ + DISP_PLUGIN* disp = (DISP_PLUGIN*) context->handle; + DISP_CHANNEL_CALLBACK* callback = disp->listener_callback->channel_callback; + + return disp_send_display_control_monitor_layout_pdu(callback, NumMonitors, Monitors); +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry disp_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + DISP_PLUGIN* disp; + DispClientContext* context; + + disp = (DISP_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "disp"); + if (!disp) + { + disp = (DISP_PLUGIN*) calloc(1, sizeof(DISP_PLUGIN)); + if (!disp) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + disp->iface.Initialize = disp_plugin_initialize; + disp->iface.Connected = NULL; + disp->iface.Disconnected = NULL; + disp->iface.Terminated = disp_plugin_terminated; + disp->MaxNumMonitors = 16; + disp->MaxMonitorAreaFactorA = 8192; + disp->MaxMonitorAreaFactorB = 8192; + + context = (DispClientContext*) calloc(1, sizeof(DispClientContext)); + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + free(disp); + return CHANNEL_RC_NO_MEMORY; + } + + context->handle = (void*) disp; + context->SendMonitorLayout = disp_send_monitor_layout; + + disp->iface.pInterface = (void*) context; + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "disp", (IWTSPlugin*) disp); + } + else + { + WLog_ERR(TAG, "could not get disp Plugin."); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/channels/disp/client/disp_main.h b/channels/disp/client/disp_main.h new file mode 100644 index 0000000..c2aabae --- /dev/null +++ b/channels/disp/client/disp_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Display Update Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DISP_CLIENT_MAIN_H +#define FREERDP_CHANNEL_DISP_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#define DISPLAY_CONTROL_PDU_TYPE_CAPS 0x00000005 +#define DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT 0x00000002 + +#define TAG CHANNELS_TAG("disp.client") + +#endif /* FREERDP_CHANNEL_DISP_CLIENT_MAIN_H */ + diff --git a/channels/drdynvc/CMakeLists.txt b/channels/drdynvc/CMakeLists.txt new file mode 100644 index 0000000..9a6ee1f --- /dev/null +++ b/channels/drdynvc/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("drdynvc") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/drdynvc/ChannelOptions.cmake b/channels/drdynvc/ChannelOptions.cmake new file mode 100644 index 0000000..76376b6 --- /dev/null +++ b/channels/drdynvc/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "drdynvc" TYPE "static" + DESCRIPTION "Dynamic Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEDYC]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/drdynvc/client/CMakeLists.txt b/channels/drdynvc/client/CMakeLists.txt new file mode 100644 index 0000000..abf9185 --- /dev/null +++ b/channels/drdynvc/client/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("drdynvc") + +set(${MODULE_PREFIX}_SRCS + drdynvc_main.c + drdynvc_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + diff --git a/channels/drdynvc/client/drdynvc_main.c b/channels/drdynvc/client/drdynvc_main.c new file mode 100644 index 0000000..f27381b --- /dev/null +++ b/channels/drdynvc/client/drdynvc_main.c @@ -0,0 +1,1705 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "drdynvc_main.h" + +#define TAG CHANNELS_TAG("drdynvc.client") + +static void dvcman_channel_free(void* channel); +static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, + const BYTE* data, UINT32 dataSize); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_get_configuration(IWTSListener* pListener, + void** ppPropertyBag) +{ + *ppPropertyBag = NULL; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_create_listener(IWTSVirtualChannelManager* pChannelMgr, + const char* pszChannelName, ULONG ulFlags, + IWTSListenerCallback* pListenerCallback, IWTSListener** ppListener) +{ + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + DVCMAN_LISTENER* listener; + + if (dvcman->num_listeners < MAX_PLUGINS) + { + WLog_DBG(TAG, "create_listener: %d.%s.", dvcman->num_listeners, pszChannelName); + listener = (DVCMAN_LISTENER*) calloc(1, sizeof(DVCMAN_LISTENER)); + + if (!listener) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + listener->iface.GetConfiguration = dvcman_get_configuration; + listener->iface.pInterface = NULL; + listener->dvcman = dvcman; + listener->channel_name = _strdup(pszChannelName); + + if (!listener->channel_name) + { + WLog_ERR(TAG, "_strdup failed!"); + free(listener); + return CHANNEL_RC_NO_MEMORY; + } + + listener->flags = ulFlags; + listener->listener_callback = pListenerCallback; + + if (ppListener) + *ppListener = (IWTSListener*) listener; + + dvcman->listeners[dvcman->num_listeners++] = (IWTSListener*) listener; + return CHANNEL_RC_OK; + } + else + { + WLog_ERR(TAG, "create_listener: Maximum DVC listener number reached."); + return ERROR_INTERNAL_ERROR; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_register_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name, IWTSPlugin* pPlugin) +{ + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*) pEntryPoints)->dvcman; + + if (dvcman->num_plugins < MAX_PLUGINS) + { + dvcman->plugin_names[dvcman->num_plugins] = name; + dvcman->plugins[dvcman->num_plugins++] = pPlugin; + WLog_DBG(TAG, "register_plugin: num_plugins %d", dvcman->num_plugins); + return CHANNEL_RC_OK; + } + else + { + WLog_ERR(TAG, "register_plugin: Maximum DVC plugin number %u reached.", + MAX_PLUGINS); + return ERROR_INTERNAL_ERROR; + } +} + +static IWTSPlugin* dvcman_get_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name) +{ + int i; + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*) pEntryPoints)->dvcman; + + for (i = 0; i < dvcman->num_plugins; i++) + { + if (dvcman->plugin_names[i] == name || + strcmp(dvcman->plugin_names[i], name) == 0) + { + return dvcman->plugins[i]; + } + } + + return NULL; +} + +static ADDIN_ARGV* dvcman_get_plugin_data(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + return ((DVCMAN_ENTRY_POINTS*) pEntryPoints)->args; +} + +static void* dvcman_get_rdp_settings(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + return (void*)((DVCMAN_ENTRY_POINTS*) pEntryPoints)->settings; +} + +static UINT32 dvcman_get_channel_id(IWTSVirtualChannel* channel) +{ + return ((DVCMAN_CHANNEL*) channel)->channel_id; +} + +static IWTSVirtualChannel* dvcman_find_channel_by_id(IWTSVirtualChannelManager* + pChannelMgr, + UINT32 ChannelId) +{ + int index; + BOOL found = FALSE; + DVCMAN_CHANNEL* channel; + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + ArrayList_Lock(dvcman->channels); + index = 0; + channel = (DVCMAN_CHANNEL*) ArrayList_GetItem(dvcman->channels, index++); + + while (channel) + { + if (channel->channel_id == ChannelId) + { + found = TRUE; + break; + } + + channel = (DVCMAN_CHANNEL*) ArrayList_GetItem(dvcman->channels, index++); + } + + ArrayList_Unlock(dvcman->channels); + return (found) ? ((IWTSVirtualChannel*) channel) : NULL; +} + +static IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin) +{ + DVCMAN* dvcman; + dvcman = (DVCMAN*) calloc(1, sizeof(DVCMAN)); + + if (!dvcman) + { + WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); + return NULL; + } + + dvcman->iface.CreateListener = dvcman_create_listener; + dvcman->iface.FindChannelById = dvcman_find_channel_by_id; + dvcman->iface.GetChannelId = dvcman_get_channel_id; + dvcman->drdynvc = plugin; + dvcman->channels = ArrayList_New(TRUE); + + if (!dvcman->channels) + { + WLog_Print(plugin->log, WLOG_ERROR, "ArrayList_New failed!"); + free(dvcman); + return NULL; + } + + dvcman->channels->object.fnObjectFree = dvcman_channel_free; + dvcman->pool = StreamPool_New(TRUE, 10); + + if (!dvcman->pool) + { + WLog_Print(plugin->log, WLOG_ERROR, "StreamPool_New failed!"); + ArrayList_Free(dvcman->channels); + free(dvcman); + return NULL; + } + + return (IWTSVirtualChannelManager*) dvcman; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_load_addin(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + ADDIN_ARGV* args, + rdpSettings* settings) +{ + DVCMAN_ENTRY_POINTS entryPoints; + PDVC_PLUGIN_ENTRY pDVCPluginEntry = NULL; + WLog_Print(drdynvc->log, WLOG_INFO, "Loading Dynamic Virtual Channel %s", args->argv[0]); + pDVCPluginEntry = (PDVC_PLUGIN_ENTRY) freerdp_load_channel_addin_entry( + args->argv[0], + NULL, NULL, FREERDP_ADDIN_CHANNEL_DYNAMIC); + + if (pDVCPluginEntry) + { + entryPoints.iface.RegisterPlugin = dvcman_register_plugin; + entryPoints.iface.GetPlugin = dvcman_get_plugin; + entryPoints.iface.GetPluginData = dvcman_get_plugin_data; + entryPoints.iface.GetRdpSettings = dvcman_get_rdp_settings; + entryPoints.dvcman = (DVCMAN*) pChannelMgr; + entryPoints.args = args; + entryPoints.settings = settings; + return pDVCPluginEntry((IDRDYNVC_ENTRY_POINTS*) &entryPoints); + } + + return ERROR_INVALID_FUNCTION; +} + +static DVCMAN_CHANNEL* dvcman_channel_new(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, const char* ChannelName) +{ + DVCMAN_CHANNEL* channel; + + if (dvcman_find_channel_by_id(pChannelMgr, ChannelId)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Protocol error: Duplicated ChannelId %"PRIu32" (%s)!", + ChannelId, + ChannelName); + return NULL; + } + + channel = (DVCMAN_CHANNEL*) calloc(1, sizeof(DVCMAN_CHANNEL)); + + if (!channel) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "calloc failed!"); + return NULL; + } + + channel->dvcman = (DVCMAN*) pChannelMgr; + channel->channel_id = ChannelId; + channel->channel_name = _strdup(ChannelName); + + if (!channel->channel_name) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "_strdup failed!"); + free(channel); + return NULL; + } + + if (!InitializeCriticalSectionEx(&(channel->lock), 0, 0)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "InitializeCriticalSectionEx failed!"); + free(channel->channel_name); + free(channel); + return NULL; + } + + return channel; +} + +static void dvcman_channel_free(void* arg) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*) arg; + UINT error = CHANNEL_RC_OK; + + if (channel) + { + if (channel->channel_callback) + { + IFCALL(channel->channel_callback->OnClose, + channel->channel_callback); + } + + if (channel->status == CHANNEL_RC_OK) + { + IWTSVirtualChannel* ichannel = (IWTSVirtualChannel*) channel; + + if (channel->dvcman && channel->dvcman->drdynvc) + { + DrdynvcClientContext* context = channel->dvcman->drdynvc->context; + + if (context) + { + IFCALLRET(context->OnChannelDisconnected, error, + context, channel->channel_name, + channel->pInterface); + } + } + + error = IFCALLRESULT(CHANNEL_RC_OK, ichannel->Close, ichannel); + + if (error != CHANNEL_RC_OK) + WLog_ERR(TAG, "Close failed with error %"PRIu32"!", error); + } + + if (channel->dvc_data) + Stream_Release(channel->dvc_data); + + DeleteCriticalSection(&(channel->lock)); + free(channel->channel_name); + } + + free(channel); +} + +static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + int i; + IWTSPlugin* pPlugin; + DVCMAN_LISTENER* listener; + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + UINT error; + ArrayList_Free(dvcman->channels); + + for (i = 0; i < dvcman->num_listeners; i++) + { + listener = (DVCMAN_LISTENER*) dvcman->listeners[i]; + free(listener->channel_name); + free(listener); + } + + dvcman->num_listeners = 0; + + for (i = 0; i < dvcman->num_plugins; i++) + { + pPlugin = dvcman->plugins[i]; + + if (pPlugin->Terminated) + if ((error = pPlugin->Terminated(pPlugin))) + WLog_Print(drdynvc->log, WLOG_ERROR, "Terminated failed with error %"PRIu32"!", error); + } + + dvcman->num_plugins = 0; + StreamPool_Free(dvcman->pool); + free(dvcman); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_init(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + int i; + IWTSPlugin* pPlugin; + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + UINT error; + + for (i = 0; i < dvcman->num_plugins; i++) + { + pPlugin = dvcman->plugins[i]; + + if (pPlugin->Initialize) + if ((error = pPlugin->Initialize(pPlugin, pChannelMgr))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Initialize failed with error %"PRIu32"!", error); + return error; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_write_channel(IWTSVirtualChannel* pChannel, ULONG cbSize, + const BYTE* pBuffer, void* pReserved) +{ + UINT status; + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*) pChannel; + + if (!channel || !channel->dvcman) + return CHANNEL_RC_BAD_CHANNEL; + + EnterCriticalSection(&(channel->lock)); + status = drdynvc_write_data(channel->dvcman->drdynvc, + channel->channel_id, pBuffer, cbSize); + LeaveCriticalSection(&(channel->lock)); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_close_channel_iface(IWTSVirtualChannel* pChannel) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*) pChannel; + + if (!channel) + return CHANNEL_RC_BAD_CHANNEL; + + WLog_DBG(TAG, "close_channel_iface: id=%"PRIu32"", channel->channel_id); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_create_channel(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, const char* ChannelName) +{ + int i; + BOOL bAccept; + DVCMAN_LISTENER* listener; + DVCMAN_CHANNEL* channel; + DrdynvcClientContext* context; + IWTSVirtualChannelCallback* pCallback; + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + UINT error; + + if (!(channel = dvcman_channel_new(drdynvc, pChannelMgr, ChannelId, ChannelName))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_channel_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + channel->status = ERROR_NOT_CONNECTED; + ArrayList_Add(dvcman->channels, channel); + + for (i = 0; i < dvcman->num_listeners; i++) + { + listener = (DVCMAN_LISTENER*) dvcman->listeners[i]; + + if (strcmp(listener->channel_name, ChannelName) == 0) + { + channel->iface.Write = dvcman_write_channel; + channel->iface.Close = dvcman_close_channel_iface; + bAccept = TRUE; + pCallback = NULL; + + if ((error = listener->listener_callback->OnNewChannelConnection( + listener->listener_callback, + (IWTSVirtualChannel*) channel, NULL, &bAccept, &pCallback)) == CHANNEL_RC_OK + && bAccept) + { + WLog_Print(drdynvc->log, WLOG_DEBUG, "listener %s created new channel %"PRIu32"", + listener->channel_name, channel->channel_id); + channel->status = CHANNEL_RC_OK; + channel->channel_callback = pCallback; + channel->pInterface = listener->iface.pInterface; + context = dvcman->drdynvc->context; + IFCALLRET(context->OnChannelConnected, error, context, ChannelName, + listener->iface.pInterface); + + if (error) + WLog_Print(drdynvc->log, WLOG_ERROR, "context.OnChannelConnected failed with error %"PRIu32"", + error); + + return error; + } + else + { + if (error) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "OnNewChannelConnection failed with error %"PRIu32"!", error); + return error; + } + else + { + WLog_Print(drdynvc->log, WLOG_ERROR, "OnNewChannelConnection returned with bAccept FALSE!"); + return ERROR_INTERNAL_ERROR; + } + } + } + } + + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_open_channel(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId) +{ + DVCMAN_CHANNEL* channel; + IWTSVirtualChannelCallback* pCallback; + UINT error; + channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId); + + if (!channel) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %"PRIu32" not found!", ChannelId); + return ERROR_INTERNAL_ERROR; + } + + if (channel->status == CHANNEL_RC_OK) + { + pCallback = channel->channel_callback; + + if ((pCallback->OnOpen) && (error = pCallback->OnOpen(pCallback))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "OnOpen failed with error %"PRIu32"!", error); + return error; + } + + WLog_Print(drdynvc->log, WLOG_DEBUG, "open_channel: ChannelId %"PRIu32"", ChannelId); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId) +{ + DVCMAN_CHANNEL* channel; + UINT error = CHANNEL_RC_OK; + DVCMAN* dvcman = (DVCMAN*) pChannelMgr; + channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId); + + if (!channel) + { + //WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %"PRIu32" not found!", ChannelId); + /** + * Windows 8 / Windows Server 2012 send close requests for channels that failed to be created. + * Do not warn, simply return success here. + */ + return CHANNEL_RC_OK; + } + + ArrayList_Remove(dvcman->channels, channel); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_receive_channel_data_first(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, UINT32 length) +{ + DVCMAN_CHANNEL* channel; + channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId); + + if (!channel) + { + /** + * Windows Server 2012 R2 can send some messages over Microsoft::Windows::RDS::Geometry::v08.01 + * even if the dynamic virtual channel wasn't registered on our side. Ignoring it works. + */ + WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %"PRIu32" not found!", ChannelId); + return CHANNEL_RC_OK; + } + + if (channel->dvc_data) + Stream_Release(channel->dvc_data); + + channel->dvc_data = StreamPool_Take(channel->dvcman->pool, length); + + if (!channel->dvc_data) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + channel->dvc_data_length = length; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_receive_channel_data(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, wStream* data) +{ + UINT status = CHANNEL_RC_OK; + DVCMAN_CHANNEL* channel; + size_t dataSize = Stream_GetRemainingLength(data); + channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId); + + if (!channel) + { + /* Windows 8.1 tries to open channels not created. + * Ignore cases like this. */ + WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %"PRIu32" not found!", ChannelId); + return CHANNEL_RC_OK; + } + + if (channel->dvc_data) + { + /* Fragmented data */ + if (Stream_GetPosition(channel->dvc_data) + dataSize > Stream_Capacity(channel->dvc_data)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "data exceeding declared length!"); + Stream_Release(channel->dvc_data); + channel->dvc_data = NULL; + return ERROR_INVALID_DATA; + } + + Stream_Copy(data, channel->dvc_data, dataSize); + + if (Stream_GetPosition(channel->dvc_data) >= channel->dvc_data_length) + { + Stream_SealLength(channel->dvc_data); + Stream_SetPosition(channel->dvc_data, 0); + status = channel->channel_callback->OnDataReceived(channel->channel_callback, + channel->dvc_data); + Stream_Release(channel->dvc_data); + channel->dvc_data = NULL; + } + } + else + { + status = channel->channel_callback->OnDataReceived(channel->channel_callback, + data); + } + + return status; +} + +static UINT drdynvc_write_variable_uint(wStream* s, UINT32 val) +{ + UINT cb; + + if (val <= 0xFF) + { + cb = 0; + Stream_Write_UINT8(s, val); + } + else if (val <= 0xFFFF) + { + cb = 1; + Stream_Write_UINT16(s, val); + } + else + { + cb = 2; + Stream_Write_UINT32(s, val); + } + + return cb; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s) +{ + UINT status; + + if (!drdynvc) + status = CHANNEL_RC_BAD_CHANNEL_HANDLE; + else + { + status = drdynvc->channelEntryPoints.pVirtualChannelWriteEx(drdynvc->InitHandle, + drdynvc->OpenHandle, Stream_Buffer(s), (UINT32) Stream_GetPosition(s), s); + } + + switch (status) + { + case CHANNEL_RC_OK: + return CHANNEL_RC_OK; + + case CHANNEL_RC_NOT_CONNECTED: + Stream_Free(s, TRUE); + return CHANNEL_RC_OK; + + case CHANNEL_RC_BAD_CHANNEL_HANDLE: + Stream_Free(s, TRUE); + WLog_ERR(TAG, "VirtualChannelWriteEx failed with CHANNEL_RC_BAD_CHANNEL_HANDLE"); + return status; + + default: + Stream_Free(s, TRUE); + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), + status); + return status; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, + const BYTE* data, UINT32 dataSize) +{ + wStream* data_out; + size_t pos; + UINT32 cbChId; + UINT32 cbLen; + unsigned long chunkLength; + UINT status; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(drdynvc->log, WLOG_DEBUG, "write_data: ChannelId=%"PRIu32" size=%"PRIu32"", ChannelId, + dataSize); + data_out = Stream_New(NULL, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + pos = Stream_GetPosition(data_out); + + if (dataSize == 0) + { + Stream_SetPosition(data_out, 0); + Stream_Write_UINT8(data_out, 0x40 | cbChId); + Stream_SetPosition(data_out, pos); + status = drdynvc_send(drdynvc, data_out); + } + else if (dataSize <= CHANNEL_CHUNK_LENGTH - pos) + { + Stream_SetPosition(data_out, 0); + Stream_Write_UINT8(data_out, 0x30 | cbChId); + Stream_SetPosition(data_out, pos); + Stream_Write(data_out, data, dataSize); + status = drdynvc_send(drdynvc, data_out); + } + else + { + /* Fragment the data */ + cbLen = drdynvc_write_variable_uint(data_out, dataSize); + pos = Stream_GetPosition(data_out); + Stream_SetPosition(data_out, 0); + Stream_Write_UINT8(data_out, 0x20 | cbChId | (cbLen << 2)); + Stream_SetPosition(data_out, pos); + chunkLength = CHANNEL_CHUNK_LENGTH - pos; + Stream_Write(data_out, data, chunkLength); + data += chunkLength; + dataSize -= chunkLength; + status = drdynvc_send(drdynvc, data_out); + + while (status == CHANNEL_RC_OK && dataSize > 0) + { + data_out = Stream_New(NULL, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + pos = Stream_GetPosition(data_out); + Stream_SetPosition(data_out, 0); + Stream_Write_UINT8(data_out, 0x30 | cbChId); + Stream_SetPosition(data_out, pos); + chunkLength = dataSize; + + if (chunkLength > CHANNEL_CHUNK_LENGTH - pos) + chunkLength = CHANNEL_CHUNK_LENGTH - pos; + + Stream_Write(data_out, data, chunkLength); + data += chunkLength; + dataSize -= chunkLength; + status = drdynvc_send(drdynvc, data_out); + } + } + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_send_capability_response(drdynvcPlugin* drdynvc) +{ + UINT status; + wStream* s; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(drdynvc->log, WLOG_TRACE, "capability_response"); + s = Stream_New(NULL, 4); + + if (!s) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_Ndrdynvc_write_variable_uintew failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, + 0x0050); /* Cmd+Sp+cbChId+Pad. Note: MSTSC sends 0x005c */ + Stream_Write_UINT16(s, drdynvc->version); + status = drdynvc_send(drdynvc, s); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, + int cbChId, wStream* s) +{ + UINT status; + + if (!drdynvc) + return CHANNEL_RC_BAD_INIT_HANDLE; + + if (Stream_GetRemainingLength(s) < 3) + return ERROR_INVALID_DATA; + + WLog_Print(drdynvc->log, WLOG_TRACE, "capability_request Sp=%d cbChId=%d", Sp, cbChId); + Stream_Seek(s, 1); /* pad */ + Stream_Read_UINT16(s, drdynvc->version); + + /* RDP8 servers offer version 3, though Microsoft forgot to document it + * in their early documents. It behaves the same as version 2. + */ + if ((drdynvc->version == 2) || (drdynvc->version == 3)) + { + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, drdynvc->PriorityCharge0); + Stream_Read_UINT16(s, drdynvc->PriorityCharge1); + Stream_Read_UINT16(s, drdynvc->PriorityCharge2); + Stream_Read_UINT16(s, drdynvc->PriorityCharge3); + } + + status = drdynvc_send_capability_response(drdynvc); + drdynvc->state = DRDYNVC_STATE_READY; + return status; +} + +static UINT32 drdynvc_cblen_to_bytes(int cbLen) +{ + switch (cbLen) + { + case 0: + return 1; + + case 1: + return 2; + + default: + return 4; + } +} + +static UINT32 drdynvc_read_variable_uint(wStream* s, int cbLen) +{ + UINT32 val; + + switch (cbLen) + { + case 0: + Stream_Read_UINT8(s, val); + break; + + case 1: + Stream_Read_UINT16(s, val); + break; + + default: + Stream_Read_UINT32(s, val); + break; + } + + return val; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, + int cbChId, wStream* s) +{ + size_t pos; + UINT status; + UINT32 ChannelId; + wStream* data_out; + UINT channel_status; + char* name; + size_t length; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (drdynvc->state == DRDYNVC_STATE_CAPABILITIES) + { + /** + * For some reason the server does not always send the + * capabilities pdu as it should. When this happens, + * send a capabilities response. + */ + drdynvc->version = 3; + + if ((status = drdynvc_send_capability_response(drdynvc))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_send_capability_response failed!"); + return status; + } + + drdynvc->state = DRDYNVC_STATE_READY; + } + + if (Stream_GetRemainingLength(s) < drdynvc_cblen_to_bytes(cbChId)) + return ERROR_INVALID_DATA; + + ChannelId = drdynvc_read_variable_uint(s, cbChId); + pos = Stream_GetPosition(s); + name = (char*)Stream_Pointer(s); + length = Stream_GetRemainingLength(s); + + if (strnlen(name, length) >= length) + return ERROR_INVALID_DATA; + + WLog_Print(drdynvc->log, WLOG_DEBUG, "process_create_request: ChannelId=%"PRIu32" ChannelName=%s", + ChannelId, name); + channel_status = dvcman_create_channel(drdynvc, drdynvc->channel_mgr, ChannelId, name); + data_out = Stream_New(NULL, pos + 4); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(data_out, 0x10 | cbChId); + Stream_SetPosition(s, 1); + Stream_Copy(s, data_out, pos - 1); + + if (channel_status == CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_DEBUG, "channel created"); + Stream_Write_UINT32(data_out, 0); + } + else + { + WLog_Print(drdynvc->log, WLOG_DEBUG, "no listener"); + Stream_Write_UINT32(data_out, (UINT32)0xC0000001); /* same code used by mstsc */ + } + + status = drdynvc_send(drdynvc, data_out); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + if (channel_status == CHANNEL_RC_OK) + { + if ((status = dvcman_open_channel(drdynvc, drdynvc->channel_mgr, ChannelId))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_open_channel failed with error %"PRIu32"!", status); + return status; + } + } + else + { + if ((status = dvcman_close_channel(drdynvc->channel_mgr, ChannelId))) + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_close_channel failed with error %"PRIu32"!", status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, + int cbChId, wStream* s) +{ + UINT status; + UINT32 Length; + UINT32 ChannelId; + + if (Stream_GetRemainingLength(s) < drdynvc_cblen_to_bytes(cbChId) + drdynvc_cblen_to_bytes(Sp)) + return ERROR_INVALID_DATA; + + ChannelId = drdynvc_read_variable_uint(s, cbChId); + Length = drdynvc_read_variable_uint(s, Sp); + WLog_Print(drdynvc->log, WLOG_DEBUG, + "process_data_first: Sp=%d cbChId=%d, ChannelId=%"PRIu32" Length=%"PRIu32"", Sp, + cbChId, ChannelId, Length); + status = dvcman_receive_channel_data_first(drdynvc, drdynvc->channel_mgr, ChannelId, + Length); + + if (status) + return status; + + return dvcman_receive_channel_data(drdynvc, drdynvc->channel_mgr, ChannelId, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, + wStream* s) +{ + UINT32 ChannelId; + + if (Stream_GetRemainingLength(s) < drdynvc_cblen_to_bytes(cbChId)) + return ERROR_INVALID_DATA; + + ChannelId = drdynvc_read_variable_uint(s, cbChId); + WLog_Print(drdynvc->log, WLOG_TRACE, "process_data: Sp=%d cbChId=%d, ChannelId=%"PRIu32"", Sp, + cbChId, + ChannelId); + return dvcman_receive_channel_data(drdynvc, drdynvc->channel_mgr, ChannelId, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, + int cbChId, wStream* s) +{ + int value; + UINT error; + UINT32 ChannelId; + wStream* data_out; + + if (Stream_GetRemainingLength(s) < drdynvc_cblen_to_bytes(cbChId)) + return ERROR_INVALID_DATA; + + ChannelId = drdynvc_read_variable_uint(s, cbChId); + WLog_Print(drdynvc->log, WLOG_DEBUG, "process_close_request: Sp=%d cbChId=%d, ChannelId=%"PRIu32"", + Sp, + cbChId, ChannelId); + + if ((error = dvcman_close_channel(drdynvc->channel_mgr, ChannelId))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_close_channel failed with error %"PRIu32"!", error); + return error; + } + + data_out = Stream_New(NULL, 4); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + value = (CLOSE_REQUEST_PDU << 4) | (cbChId & 0x03); + Stream_Write_UINT8(data_out, value); + drdynvc_write_variable_uint(data_out, ChannelId); + error = drdynvc_send(drdynvc, data_out); + + if (error) + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(error), error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_order_recv(drdynvcPlugin* drdynvc, wStream* s) +{ + int value; + int Cmd; + int Sp; + int cbChId; + + if (Stream_GetRemainingLength(s) < 1) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, value); + Cmd = (value & 0xf0) >> 4; + Sp = (value & 0x0c) >> 2; + cbChId = (value & 0x03) >> 0; + WLog_Print(drdynvc->log, WLOG_DEBUG, "order_recv: Cmd=0x%x, Sp=%d cbChId=%d", Cmd, Sp, cbChId); + + switch (Cmd) + { + case CAPABILITY_REQUEST_PDU: + return drdynvc_process_capability_request(drdynvc, Sp, cbChId, s); + + case CREATE_REQUEST_PDU: + return drdynvc_process_create_request(drdynvc, Sp, cbChId, s); + + case DATA_FIRST_PDU: + return drdynvc_process_data_first(drdynvc, Sp, cbChId, s); + + case DATA_PDU: + return drdynvc_process_data(drdynvc, Sp, cbChId, s); + + case CLOSE_REQUEST_PDU: + return drdynvc_process_close_request(drdynvc, Sp, cbChId, s); + + default: + WLog_Print(drdynvc->log, WLOG_ERROR, "unknown drdynvc cmd 0x%x", Cmd); + return ERROR_INTERNAL_ERROR; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_data_received(drdynvcPlugin* drdynvc, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (drdynvc->data_in) + Stream_Free(drdynvc->data_in, TRUE); + + drdynvc->data_in = Stream_New(NULL, totalLength); + } + + if (!(data_in = drdynvc->data_in)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + Stream_Free(drdynvc->data_in, TRUE); + drdynvc->data_in = NULL; + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_plugin_process_received: read error"); + return ERROR_INVALID_DATA; + } + + drdynvc->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(drdynvc->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static void VCAPITYPE drdynvc_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*) lpUserParam; + + if (!drdynvc || (drdynvc->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "drdynvc_virtual_channel_open_event: error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = drdynvc_virtual_channel_event_data_received(drdynvc, pData, dataLength, totalLength, + dataFlags))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_data_received failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, "drdynvc_virtual_channel_open_event reported an error"); +} + +static DWORD WINAPI drdynvc_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*) arg; + + if (!drdynvc) + { + ExitThread((DWORD) CHANNEL_RC_BAD_CHANNEL_HANDLE); + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + } + + while (1) + { + if (!MessageQueue_Wait(drdynvc->queue)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(drdynvc->queue, &message, TRUE)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + + if ((error = drdynvc_order_recv(drdynvc, data))) + { + Stream_Free(data, TRUE); + WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_order_recv failed with error %"PRIu32"!", error); + break; + } + + Stream_Free(data, TRUE); + } + } + + { + /* Disconnect remaining dynamic channels that the server did not. + * This is required to properly shut down channels by calling the appropriate + * event handlers. */ + DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr; + + while (ArrayList_Count(drdynvcMgr->channels) > 0) + { + IWTSVirtualChannel* channel = (IWTSVirtualChannel*) + ArrayList_GetItem(drdynvcMgr->channels, 0); + const UINT32 ChannelId = drdynvc->channel_mgr->GetChannelId(channel); + dvcman_close_channel(drdynvc->channel_mgr, ChannelId); + } + } + + if (error && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, + "drdynvc_virtual_channel_client_thread reported an error"); + + ExitThread((DWORD) error); + return error; +} + +static void drdynvc_queue_object_free(void* obj) +{ + wStream* s; + wMessage* msg = (wMessage*)obj; + + if (!msg || (msg->id != 0)) + return; + + s = (wStream*)msg->wParam; + + if (s) + Stream_Free(s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_connected(drdynvcPlugin* drdynvc, LPVOID pData, + UINT32 dataLength) +{ + UINT error; + UINT32 status; + UINT32 index; + ADDIN_ARGV* args; + rdpSettings* settings; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + status = drdynvc->channelEntryPoints.pVirtualChannelOpenEx(drdynvc->InitHandle, + &drdynvc->OpenHandle, drdynvc->channelDef.name, drdynvc_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelOpen failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + drdynvc->queue = MessageQueue_New(NULL); + + if (!drdynvc->queue) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_New failed!"); + goto error; + } + + drdynvc->queue->object.fnObjectFree = drdynvc_queue_object_free; + drdynvc->channel_mgr = dvcman_new(drdynvc); + + if (!drdynvc->channel_mgr) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_new failed!"); + goto error; + } + + settings = (rdpSettings*) drdynvc->channelEntryPoints.pExtendedData; + + for (index = 0; index < settings->DynamicChannelCount; index++) + { + args = settings->DynamicChannelArray[index]; + error = dvcman_load_addin(drdynvc, drdynvc->channel_mgr, args, settings); + + if (CHANNEL_RC_OK != error) + goto error; + } + + if ((error = dvcman_init(drdynvc, drdynvc->channel_mgr))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_init failed with error %"PRIu32"!", error); + goto error; + } + + drdynvc->state = DRDYNVC_STATE_CAPABILITIES; + + if (!(drdynvc->thread = CreateThread(NULL, 0, drdynvc_virtual_channel_client_thread, + (void*) drdynvc, + 0, NULL))) + { + error = ERROR_INTERNAL_ERROR; + WLog_Print(drdynvc->log, WLOG_ERROR, "CreateThread failed!"); + goto error; + } + +error: + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_disconnected(drdynvcPlugin* drdynvc) +{ + UINT status; + + if (drdynvc->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!MessageQueue_PostQuit(drdynvc->queue, 0)) + { + status = GetLastError(); + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_PostQuit failed with error %"PRIu32"", status); + return status; + } + + if (WaitForSingleObject(drdynvc->thread, INFINITE) != WAIT_OBJECT_0) + { + status = GetLastError(); + WLog_Print(drdynvc->log, WLOG_ERROR, "WaitForSingleObject failed with error %"PRIu32"", status); + return status; + } + + MessageQueue_Free(drdynvc->queue); + CloseHandle(drdynvc->thread); + drdynvc->queue = NULL; + drdynvc->thread = NULL; + status = drdynvc->channelEntryPoints.pVirtualChannelCloseEx(drdynvc->InitHandle, + drdynvc->OpenHandle); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelClose failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + } + + drdynvc->OpenHandle = 0; + + if (drdynvc->data_in) + { + Stream_Free(drdynvc->data_in, TRUE); + drdynvc->data_in = NULL; + } + + if (drdynvc->channel_mgr) + { + dvcman_free(drdynvc, drdynvc->channel_mgr); + drdynvc->channel_mgr = NULL; + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_terminated(drdynvcPlugin* drdynvc) +{ + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + drdynvc->InitHandle = 0; + free(drdynvc->context); + free(drdynvc); + return CHANNEL_RC_OK; +} + +static UINT drdynvc_virtual_channel_event_attached(drdynvcPlugin* drdynvc) +{ + int i; + DVCMAN* dvcman; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*) drdynvc->channel_mgr; + + if (!dvcman) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + for (i = 0; i < dvcman->num_plugins; i++) + { + UINT error; + IWTSPlugin* pPlugin = dvcman->plugins[i]; + + if (pPlugin->Attached) + if ((error = pPlugin->Attached(pPlugin))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Attach failed with error %"PRIu32"!", error); + return error; + } + } + + return CHANNEL_RC_OK; +} + +static UINT drdynvc_virtual_channel_event_detached(drdynvcPlugin* drdynvc) +{ + int i; + DVCMAN* dvcman; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*) drdynvc->channel_mgr; + + if (!dvcman) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + for (i = 0; i < dvcman->num_plugins; i++) + { + UINT error; + IWTSPlugin* pPlugin = dvcman->plugins[i]; + + if (pPlugin->Detached) + if ((error = pPlugin->Detached(pPlugin))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Detach failed with error %"PRIu32"!", error); + return error; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE drdynvc_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*) lpUserParam; + + if (!drdynvc || (drdynvc->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "drdynvc_virtual_channel_init_event: error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = drdynvc_virtual_channel_event_connected(drdynvc, pData, dataLength))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_connected failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = drdynvc_virtual_channel_event_disconnected(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_disconnected failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_TERMINATED: + if ((error = drdynvc_virtual_channel_event_terminated(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_terminated failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_ATTACHED: + if ((error = drdynvc_virtual_channel_event_attached(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_attached failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_DETACHED: + if ((error = drdynvc_virtual_channel_event_detached(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_detached failed with error %"PRIu32"", error); + + break; + + default: + break; + } + + if (error && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, + "drdynvc_virtual_channel_init_event_ex reported an error"); +} + +/** + * Channel Client Interface + */ + +static int drdynvc_get_version(DrdynvcClientContext* context) +{ + drdynvcPlugin* drdynvc = (drdynvcPlugin*) context->handle; + return drdynvc->version; +} + +/* drdynvc is always built-in */ +#define VirtualChannelEntryEx drdynvc_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + drdynvcPlugin* drdynvc; + DrdynvcClientContext* context = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + drdynvc = (drdynvcPlugin*) calloc(1, sizeof(drdynvcPlugin)); + + if (!drdynvc) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + drdynvc->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP; + sprintf_s(drdynvc->channelDef.name, ARRAYSIZE(drdynvc->channelDef.name), "drdynvc"); + drdynvc->state = DRDYNVC_STATE_INITIAL; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (DrdynvcClientContext*) calloc(1, sizeof(DrdynvcClientContext)); + + if (!context) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "calloc failed!"); + free(drdynvc); + return FALSE; + } + + context->handle = (void*) drdynvc; + context->custom = NULL; + drdynvc->context = context; + context->GetVersion = drdynvc_get_version; + drdynvc->rdpcontext = pEntryPointsEx->context; + } + + drdynvc->log = WLog_Get(TAG); + WLog_Print(drdynvc->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(drdynvc->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + drdynvc->InitHandle = pInitHandle; + rc = drdynvc->channelEntryPoints.pVirtualChannelInitEx(drdynvc, context, pInitHandle, + &drdynvc->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, drdynvc_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelInit failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + free(drdynvc->context); + free(drdynvc); + return FALSE; + } + + drdynvc->channelEntryPoints.pInterface = context; + return TRUE; +} + diff --git a/channels/drdynvc/client/drdynvc_main.h b/channels/drdynvc/client/drdynvc_main.h new file mode 100644 index 0000000..1d83582 --- /dev/null +++ b/channels/drdynvc/client/drdynvc_main.h @@ -0,0 +1,140 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H +#define FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef struct drdynvc_plugin drdynvcPlugin; + +#define MAX_PLUGINS 32 + +struct _DVCMAN +{ + IWTSVirtualChannelManager iface; + + drdynvcPlugin* drdynvc; + + int num_plugins; + const char* plugin_names[MAX_PLUGINS]; + IWTSPlugin* plugins[MAX_PLUGINS]; + + int num_listeners; + IWTSListener* listeners[MAX_PLUGINS]; + + wArrayList* channels; + wStreamPool* pool; +}; +typedef struct _DVCMAN DVCMAN; + +struct _DVCMAN_LISTENER +{ + IWTSListener iface; + + DVCMAN* dvcman; + char* channel_name; + UINT32 flags; + IWTSListenerCallback* listener_callback; +}; +typedef struct _DVCMAN_LISTENER DVCMAN_LISTENER; + +struct _DVCMAN_ENTRY_POINTS +{ + IDRDYNVC_ENTRY_POINTS iface; + + DVCMAN* dvcman; + ADDIN_ARGV* args; + rdpSettings* settings; +}; +typedef struct _DVCMAN_ENTRY_POINTS DVCMAN_ENTRY_POINTS; + +struct _DVCMAN_CHANNEL +{ + IWTSVirtualChannel iface; + + int status; + DVCMAN* dvcman; + void* pInterface; + UINT32 channel_id; + char* channel_name; + IWTSVirtualChannelCallback* channel_callback; + + wStream* dvc_data; + UINT32 dvc_data_length; + CRITICAL_SECTION lock; +}; +typedef struct _DVCMAN_CHANNEL DVCMAN_CHANNEL; + +enum _DRDYNVC_STATE +{ + DRDYNVC_STATE_INITIAL, + DRDYNVC_STATE_CAPABILITIES, + DRDYNVC_STATE_READY, + DRDYNVC_STATE_OPENING_CHANNEL, + DRDYNVC_STATE_SEND_RECEIVE, + DRDYNVC_STATE_FINAL +}; +typedef enum _DRDYNVC_STATE DRDYNVC_STATE; + +#define CREATE_REQUEST_PDU 0x01 +#define DATA_FIRST_PDU 0x02 +#define DATA_PDU 0x03 +#define CLOSE_REQUEST_PDU 0x04 +#define CAPABILITY_REQUEST_PDU 0x05 + +struct drdynvc_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + wLog* log; + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + DRDYNVC_STATE state; + DrdynvcClientContext* context; + + int version; + int PriorityCharge0; + int PriorityCharge1; + int PriorityCharge2; + int PriorityCharge3; + rdpContext* rdpcontext; + + IWTSVirtualChannelManager* channel_mgr; +}; + +#endif /* FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H */ diff --git a/channels/drdynvc/server/CMakeLists.txt b/channels/drdynvc/server/CMakeLists.txt new file mode 100644 index 0000000..fe2bd61 --- /dev/null +++ b/channels/drdynvc/server/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("drdynvc") + +set(${MODULE_PREFIX}_SRCS + drdynvc_main.c + drdynvc_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/drdynvc/server/drdynvc_main.c b/channels/drdynvc/server/drdynvc_main.c new file mode 100644 index 0000000..b963970 --- /dev/null +++ b/channels/drdynvc/server/drdynvc_main.c @@ -0,0 +1,205 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "drdynvc_main.h" + +#define TAG CHANNELS_TAG("drdynvc.server") + + +static DWORD WINAPI drdynvc_server_thread(LPVOID arg) +{ +#if 0 + wStream* s; + DWORD status; + DWORD nCount; + void* buffer; + HANDLE events[8]; + HANDLE ChannelEvent; + DWORD BytesReturned; + DrdynvcServerContext* context; + UINT error = ERROR_INTERNAL_ERROR; + context = (DrdynvcServerContext*) arg; + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + ExitThread((DWORD) CHANNEL_RC_NO_MEMORY); + return CHANNEL_RC_NO_MEMORY; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (WaitForSingleObject(context->priv->StopEvent, 0) == WAIT_OBJECT_0) + { + error = CHANNEL_RC_OK; + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0, + &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, + (PCHAR) Stream_Buffer(s), Stream_Capacity(s), &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + break; + } + } + + Stream_Free(s, TRUE); + ExitThread((DWORD) error); +#endif + // WTF ... this code only reads data into the stream until there is no more memory + ExitThread(0); + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_server_start(DrdynvcServerContext* context) +{ + context->priv->ChannelHandle = WTSVirtualChannelOpen(context->vcm, + WTS_CURRENT_SESSION, "drdynvc"); + + if (!context->priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = CreateThread(NULL, 0, drdynvc_server_thread, (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_server_stop(DrdynvcServerContext* context) +{ + UINT error; + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(context->priv->Thread); + return CHANNEL_RC_OK; +} + +DrdynvcServerContext* drdynvc_server_context_new(HANDLE vcm) +{ + DrdynvcServerContext* context; + context = (DrdynvcServerContext*) calloc(1, sizeof(DrdynvcServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = drdynvc_server_start; + context->Stop = drdynvc_server_stop; + context->priv = (DrdynvcServerPrivate*) calloc(1, sizeof(DrdynvcServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return NULL; + } + } + else + { + WLog_ERR(TAG, "calloc failed!"); + } + + return context; +} + +void drdynvc_server_context_free(DrdynvcServerContext* context) +{ + if (context) + { + free(context->priv); + free(context); + } +} diff --git a/channels/drdynvc/server/drdynvc_main.h b/channels/drdynvc/server/drdynvc_main.h new file mode 100644 index 0000000..8e17f89 --- /dev/null +++ b/channels/drdynvc/server/drdynvc_main.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H +#define FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H + +#include +#include +#include + +#include +#include + +struct _drdynvc_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; +}; + +#endif /* FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H */ diff --git a/channels/drive/CMakeLists.txt b/channels/drive/CMakeLists.txt new file mode 100644 index 0000000..f2d2c5a --- /dev/null +++ b/channels/drive/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("drive") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + diff --git a/channels/drive/ChannelOptions.cmake b/channels/drive/ChannelOptions.cmake new file mode 100644 index 0000000..0792c1e --- /dev/null +++ b/channels/drive/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "drive" TYPE "device" + DESCRIPTION "Drive Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEFS]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/drive/client/CMakeLists.txt b/channels/drive/client/CMakeLists.txt new file mode 100644 index 0000000..2c2be39 --- /dev/null +++ b/channels/drive/client/CMakeLists.txt @@ -0,0 +1,36 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("drive") + +set(${MODULE_PREFIX}_SRCS + drive_file.c + drive_file.h + drive_main.c) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/drive/client/drive_file.c b/channels/drive/client/drive_file.c new file mode 100644 index 0000000..f816695 --- /dev/null +++ b/channels/drive/client/drive_file.c @@ -0,0 +1,858 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * Copyright 2017 Armin Novak + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "drive_file.h" + +#ifdef WITH_DEBUG_RDPDR +#define DEBUG_WSTR(msg, wstr) do { LPSTR lpstr; ConvertFromUnicode(CP_UTF8, 0, wstr, -1, &lpstr, 0, NULL, NULL); WLog_DBG(TAG, msg, lpstr); free(lpstr); } while (0) +#else +#define DEBUG_WSTR(msg, wstr) do { } while (0) +#endif + +static void drive_file_fix_path(WCHAR* path) +{ + size_t i; + size_t length; + length = (int) _wcslen(path); + + for (i = 0; i < length; i++) + { + if (path[i] == L'\\') + path[i] = L'/'; + } + +#ifdef WIN32 + + if ((length == 3) && (path[1] == L':') && (path[2] == L'/')) + return; + +#else + + if ((length == 1) && (path[0] == L'/')) + return; + +#endif + + if ((length > 0) && (path[length - 1] == L'/')) + path[length - 1] = L'\0'; +} + +static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path, + UINT32 PathLength) +{ + WCHAR* fullpath; + UINT32 base_path_length; + + if (!base_path || !path) + return NULL; + + base_path_length = _wcslen(base_path) * 2; + fullpath = (WCHAR*)calloc(1, base_path_length + PathLength + sizeof(WCHAR)); + + if (!fullpath) + { + WLog_ERR(TAG, "malloc failed!"); + return NULL; + } + + CopyMemory(fullpath, base_path, base_path_length); + CopyMemory((char*)fullpath + base_path_length, path, PathLength); + drive_file_fix_path(fullpath); + return fullpath; +} + +static BOOL drive_file_remove_dir(const WCHAR* path) +{ + WIN32_FIND_DATAW findFileData; + BOOL ret = TRUE; + INT len; + HANDLE dir; + WCHAR* fullpath; + WCHAR* path_slash; + UINT32 base_path_length; + + if (!path) + return FALSE; + + base_path_length = _wcslen(path) * 2; + path_slash = (WCHAR*)calloc(1, base_path_length + sizeof(WCHAR) * 3); + + if (!path_slash) + { + WLog_ERR(TAG, "malloc failed!"); + return FALSE; + } + + CopyMemory(path_slash, path, base_path_length); + path_slash[base_path_length / 2] = L'/'; + path_slash[base_path_length / 2 + 1] = L'*'; + DEBUG_WSTR("Search in %s", path_slash); + dir = FindFirstFileW(path_slash, &findFileData); + path_slash[base_path_length / 2 + 1] = 0; + + if (dir == INVALID_HANDLE_VALUE) + { + free(path_slash); + return FALSE; + } + + do + { + len = _wcslen(findFileData.cFileName); + + if ((len == 1 && findFileData.cFileName[0] == L'.') || (len == 2 && + findFileData.cFileName[0] == L'.' && findFileData.cFileName[1] == L'.')) + { + continue; + } + + fullpath = drive_file_combine_fullpath(path_slash, findFileData.cFileName, len * 2); + DEBUG_WSTR("Delete %s", fullpath); + + if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + ret = drive_file_remove_dir(fullpath); + } + else + { + ret = DeleteFileW(fullpath); + } + + free(fullpath); + + if (!ret) + break; + } + while (ret && FindNextFileW(dir, &findFileData) != 0); + + FindClose(dir); + + if (ret) + { + if (!RemoveDirectoryW(path)) + { + ret = FALSE; + } + } + + free(path_slash); + return ret; +} + +static BOOL drive_file_set_fullpath(DRIVE_FILE* file, WCHAR* fullpath) +{ + if (!file || !fullpath) + return FALSE; + + free(file->fullpath); + file->fullpath = fullpath; + file->filename = _wcsrchr(file->fullpath, L'/'); + + if (file->filename == NULL) + file->filename = file->fullpath; + else + file->filename += 1; + + return TRUE; +} + +static BOOL drive_file_init(DRIVE_FILE* file) +{ + UINT CreateDisposition = 0; + DWORD dwAttr = GetFileAttributesW(file->fullpath); + + if (dwAttr != INVALID_FILE_ATTRIBUTES) + { + /* The file exists */ + file->is_dir = (dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0; + + if (file->is_dir) + { + if (file->CreateDisposition == FILE_CREATE) + { + SetLastError(ERROR_ALREADY_EXISTS); + return FALSE; + } + + if (file->CreateOptions & FILE_NON_DIRECTORY_FILE) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + + return TRUE; + } + else + { + if (file->CreateOptions & FILE_DIRECTORY_FILE) + { + SetLastError(ERROR_DIRECTORY); + return FALSE; + } + } + } + else + { + file->is_dir = ((file->CreateOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE); + + if (file->is_dir) + { + /* Should only create the directory if the disposition allows for it */ + if ((file->CreateDisposition == FILE_OPEN_IF) || (file->CreateDisposition == FILE_CREATE)) + { + if (CreateDirectoryW(file->fullpath, NULL) != 0) + { + return TRUE; + } + } + + SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + } + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + switch (file->CreateDisposition) + { + case FILE_SUPERSEDE: /* If the file already exists, replace it with the given file. If it does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + case FILE_OPEN: /* If the file already exists, open it instead of creating a new file. If it does not, fail the request and do not create a new file. */ + CreateDisposition = OPEN_EXISTING; + break; + + case FILE_CREATE: /* If the file already exists, fail the request and do not create or open the given file. If it does not, create the given file. */ + CreateDisposition = CREATE_NEW; + break; + + case FILE_OPEN_IF: /* If the file already exists, open it. If it does not, create the given file. */ + CreateDisposition = OPEN_ALWAYS; + break; + + case FILE_OVERWRITE: /* If the file already exists, open it and overwrite it. If it does not, fail the request. */ + CreateDisposition = TRUNCATE_EXISTING; + break; + + case FILE_OVERWRITE_IF: /* If the file already exists, open it and overwrite it. If it does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + default: + break; + } + +#ifndef WIN32 + file->SharedAccess = 0; +#endif + file->file_handle = CreateFileW(file->fullpath, file->DesiredAccess, + file->SharedAccess, NULL, CreateDisposition, + file->FileAttributes, NULL); + } + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + /* Get the error message, if any. */ + DWORD errorMessageID = GetLastError(); + + if (errorMessageID != 0) + { +#ifdef WIN32 + LPSTR messageBuffer = NULL; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + WLog_ERR(TAG, "Error in drive_file_init: %s %s", messageBuffer, file->fullpath); + /* Free the buffer. */ + LocalFree(messageBuffer); +#endif + } + } + + return file->file_handle != INVALID_HANDLE_VALUE; +} + +DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathLength, UINT32 id, + UINT32 DesiredAccess, UINT32 CreateDisposition, + UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess) +{ + DRIVE_FILE* file; + + if (!base_path || !path) + return NULL; + + file = (DRIVE_FILE*) calloc(1, sizeof(DRIVE_FILE)); + + if (!file) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + file->file_handle = INVALID_HANDLE_VALUE; + file->find_handle = INVALID_HANDLE_VALUE; + file->id = id; + file->basepath = (WCHAR*) base_path; + file->FileAttributes = FileAttributes; + file->DesiredAccess = DesiredAccess; + file->CreateDisposition = CreateDisposition; + file->CreateOptions = CreateOptions; + file->SharedAccess = SharedAccess; + drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathLength)); + + if (!drive_file_init(file)) + { + drive_file_free(file); + return NULL; + } + + return file; +} + +BOOL drive_file_free(DRIVE_FILE* file) +{ + BOOL rc = FALSE; + + if (!file) + return FALSE; + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + + if (file->find_handle != INVALID_HANDLE_VALUE) + { + FindClose(file->find_handle); + file->find_handle = INVALID_HANDLE_VALUE; + } + + if (file->delete_pending) + { + if (file->is_dir) + { + if (!drive_file_remove_dir(file->fullpath)) + goto fail; + } + else if (!DeleteFileW(file->fullpath)) + goto fail; + } + + rc = TRUE; +fail: + DEBUG_WSTR("Free %s", file->fullpath); + free(file->fullpath); + free(file); + return rc; +} + +BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset) +{ + LARGE_INTEGER loffset; + + if (!file) + return FALSE; + + loffset.QuadPart = Offset; + return SetFilePointerEx(file->file_handle, loffset, NULL, FILE_BEGIN); +} + +BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length) +{ + UINT32 read; + + if (!file || !buffer || !Length) + return FALSE; + + DEBUG_WSTR("Read file %s", file->fullpath); + + if (ReadFile(file->file_handle, buffer, *Length, &read, NULL)) + { + *Length = read; + return TRUE; + } + + return FALSE; +} + +BOOL drive_file_write(DRIVE_FILE* file, BYTE* buffer, UINT32 Length) +{ + UINT32 written; + + if (!file || !buffer) + return FALSE; + + DEBUG_WSTR("Write file %s", file->fullpath); + + while (Length > 0) + { + if (!WriteFile(file->file_handle, buffer, Length, &written, NULL)) + return FALSE; + + Length -= written; + buffer += written; + } + + return TRUE; +} + +BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output) +{ + WIN32_FILE_ATTRIBUTE_DATA fileAttributes; + DEBUG_WSTR("FindFirstFile %s", file->fullpath); + + if (!file || !output) + return FALSE; + + if (!GetFileAttributesExW(file->fullpath, GetFileExInfoStandard, &fileAttributes)) + goto out_fail; + + switch (FsInformationClass) + { + case FileBasicInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 36)) + goto out_fail; + + Stream_Write_UINT32(output, 36); /* Length */ + Stream_Write_UINT32(output, fileAttributes.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, fileAttributes.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, fileAttributes.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, fileAttributes.dwFileAttributes); /* FileAttributes */ + /* Reserved(4), MUST NOT be added! */ + break; + + case FileStandardInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 22)) + goto out_fail; + + Stream_Write_UINT32(output, 22); /* Length */ + Stream_Write_UINT32(output, fileAttributes.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, fileAttributes.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, fileAttributes.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, fileAttributes.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, 0); /* NumberOfLinks */ + Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */ + Stream_Write_UINT8(output, fileAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? TRUE : + FALSE); /* Directory */ + /* Reserved(2), MUST NOT be added! */ + break; + + case FileAttributeTagInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 8)) + goto out_fail; + + Stream_Write_UINT32(output, 8); /* Length */ + Stream_Write_UINT32(output, fileAttributes.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, 0); /* ReparseTag */ + break; + + default: + /* Unhandled FsInformationClass */ + goto out_fail; + } + + return TRUE; +out_fail: + Stream_Write_UINT32(output, 0); /* Length */ + return FALSE; +} + +BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, + wStream* input) +{ + INT64 size; + WCHAR* fullpath; + ULARGE_INTEGER liCreationTime; + ULARGE_INTEGER liLastAccessTime; + ULARGE_INTEGER liLastWriteTime; + ULARGE_INTEGER liChangeTime; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + FILETIME* pftCreationTime = NULL; + FILETIME* pftLastAccessTime = NULL; + FILETIME* pftLastWriteTime = NULL; + UINT32 FileAttributes; + UINT32 FileNameLength; + LARGE_INTEGER liSize; + UINT8 delete_pending; + UINT8 ReplaceIfExists; + DWORD attr; + + if (!file || !input) + return FALSE; + + switch (FsInformationClass) + { + case FileBasicInformation: + if (Stream_GetRemainingLength(input) < 36) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + Stream_Read_UINT64(input, liCreationTime.QuadPart); + Stream_Read_UINT64(input, liLastAccessTime.QuadPart); + Stream_Read_UINT64(input, liLastWriteTime.QuadPart); + Stream_Read_UINT64(input, liChangeTime.QuadPart); + Stream_Read_UINT32(input, FileAttributes); + + if (!PathFileExistsW(file->fullpath)) + return FALSE; + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + WLog_ERR(TAG, "Unable to set file time %s (%"PRId32")", file->fullpath, GetLastError()); + return FALSE; + } + + if (liCreationTime.QuadPart != 0) + { + ftCreationTime.dwHighDateTime = liCreationTime.HighPart; + ftCreationTime.dwLowDateTime = liCreationTime.LowPart; + pftCreationTime = &ftCreationTime; + } + + if (liLastAccessTime.QuadPart != 0) + { + ftLastAccessTime.dwHighDateTime = liLastAccessTime.HighPart; + ftLastAccessTime.dwLowDateTime = liLastAccessTime.LowPart; + pftLastAccessTime = &ftLastAccessTime; + } + + if (liLastWriteTime.QuadPart != 0) + { + ftLastWriteTime.dwHighDateTime = liLastWriteTime.HighPart; + ftLastWriteTime.dwLowDateTime = liLastWriteTime.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart) + { + ftLastWriteTime.dwHighDateTime = liChangeTime.HighPart; + ftLastWriteTime.dwLowDateTime = liChangeTime.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + DEBUG_WSTR("SetFileTime %s", file->fullpath); + + if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime, pftLastWriteTime)) + { + WLog_ERR(TAG, "Unable to set file time to %s", file->fullpath); + return FALSE; + } + + SetFileAttributesW(file->fullpath, FileAttributes); + break; + + case FileEndOfFileInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232067.aspx */ + case FileAllocationInformation: + if (Stream_GetRemainingLength(input) < 8) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232076.aspx */ + Stream_Read_INT64(input, size); + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + WLog_ERR(TAG, "Unable to truncate %s to %"PRId64" (%"PRId32")", file->fullpath, size, + GetLastError()); + return FALSE; + } + + if (!SetFilePointerEx(file->file_handle, liSize, NULL, FILE_BEGIN)) + { + WLog_ERR(TAG, "Unable to truncate %s to %d (%"PRId32")", file->fullpath, size, GetLastError()); + return FALSE; + } + + DEBUG_WSTR("Truncate %s", file->fullpath); + + if (SetEndOfFile(file->file_handle) == 0) + { + WLog_ERR(TAG, "Unable to truncate %s to %d (%"PRId32")", file->fullpath, size, GetLastError()); + return FALSE; + } + + break; + + case FileDispositionInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232098.aspx */ + /* http://msdn.microsoft.com/en-us/library/cc241371.aspx */ + if (file->is_dir && !PathIsDirectoryEmptyW(file->fullpath)) + break; /* TODO: SetLastError ??? */ + + if (Length) + { + if (Stream_GetRemainingLength(input) < 1) + return FALSE; + + Stream_Read_UINT8(input, delete_pending); + } + else + delete_pending = 1; + + if (delete_pending) + { + DEBUG_WSTR("SetDeletePending %s", file->fullpath); + attr = GetFileAttributesW(file->fullpath); + + if (attr & FILE_ATTRIBUTE_READONLY) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + } + + file->delete_pending = delete_pending; + break; + + case FileRenameInformation: + if (Stream_GetRemainingLength(input) < 6) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232085.aspx */ + Stream_Read_UINT8(input, ReplaceIfExists); + Stream_Seek_UINT8(input); /* RootDirectory */ + Stream_Read_UINT32(input, FileNameLength); + + if (Stream_GetRemainingLength(input) < FileNameLength) + return FALSE; + + fullpath = drive_file_combine_fullpath(file->basepath, (WCHAR*)Stream_Pointer(input), + FileNameLength); + + if (!fullpath) + { + WLog_ERR(TAG, "drive_file_combine_fullpath failed!"); + return FALSE; + } + +#ifdef _WIN32 + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + +#endif + DEBUG_WSTR("MoveFileExW %s", file->fullpath); + + if (MoveFileExW(file->fullpath, fullpath, + MOVEFILE_COPY_ALLOWED | (ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0))) + { + if (!drive_file_set_fullpath(file, fullpath)) + return FALSE; + } + else + { + free(fullpath); + return FALSE; + } + +#ifdef _WIN32 + drive_file_init(file); +#endif + break; + + default: + return FALSE; + } + + return TRUE; +} + +BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathLength, wStream* output) +{ + size_t length; + WCHAR* ent_path; + + if (!file || !path || !output) + return FALSE; + + if (InitialQuery != 0) + { + /* release search handle */ + if (file->find_handle != INVALID_HANDLE_VALUE) + FindClose(file->find_handle); + + ent_path = drive_file_combine_fullpath(file->basepath, path, PathLength); + /* open new search handle and retrieve the first entry */ + file->find_handle = FindFirstFileW(ent_path, &file->find_data); + free(ent_path); + + if (file->find_handle == INVALID_HANDLE_VALUE) + goto out_fail; + } + else if (!FindNextFileW(file->find_handle, &file->find_data)) + goto out_fail; + + length = _wcslen(file->find_data.cFileName) * 2; + + switch (FsInformationClass) + { + case FileDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232097.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 64 + length)) + goto out_fail; + + Stream_Write_UINT32(output, 64 + length); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileFullDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232068.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 68 + length)) + goto out_fail; + + Stream_Write_UINT32(output, 68 + length); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileBothDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232095.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 93 + length)) + goto out_fail; + + Stream_Write_UINT32(output, 93 + length); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write_UINT8(output, 0); /* ShortNameLength */ + /* Reserved(1), MUST NOT be added! */ + Stream_Zero(output, 24); /* ShortName */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileNamesInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232077.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 12 + length)) + goto out_fail; + + Stream_Write_UINT32(output, 12 + length); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + default: + /* Unhandled FsInformationClass */ + goto out_fail; + } + + return TRUE; +out_fail: + Stream_Write_UINT32(output, 0); /* Length */ + Stream_Write_UINT8(output, 0); /* Padding */ + return FALSE; +} diff --git a/channels/drive/client/drive_file.h b/channels/drive/client/drive_file.h new file mode 100644 index 0000000..b928187 --- /dev/null +++ b/channels/drive/client/drive_file.h @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H +#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H + +#include +#include + +#define TAG CHANNELS_TAG("drive.client") + +typedef struct _DRIVE_FILE DRIVE_FILE; + +struct _DRIVE_FILE +{ + UINT32 id; + BOOL is_dir; + HANDLE file_handle; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + WCHAR* basepath; + WCHAR* fullpath; + WCHAR* filename; + BOOL delete_pending; + UINT32 FileAttributes; + UINT32 SharedAccess; + UINT32 DesiredAccess; + UINT32 CreateDisposition; + UINT32 CreateOptions; +}; + +DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathLength, UINT32 id, + UINT32 DesiredAccess, UINT32 CreateDisposition, + UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess); +BOOL drive_file_free(DRIVE_FILE* file); + +BOOL drive_file_open(DRIVE_FILE* file); +BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset); +BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length); +BOOL drive_file_write(DRIVE_FILE* file, BYTE* buffer, UINT32 Length); +BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output); +BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, + wStream* input); +BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathLength, wStream* output); + +#endif /* FREERDP_CHANNEL_DRIVE_FILE_H */ diff --git a/channels/drive/client/drive_main.c b/channels/drive/client/drive_main.c new file mode 100644 index 0000000..df361c0 --- /dev/null +++ b/channels/drive/client/drive_main.c @@ -0,0 +1,1083 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "drive_file.h" + +typedef struct _DRIVE_DEVICE DRIVE_DEVICE; + +struct _DRIVE_DEVICE +{ + DEVICE device; + + WCHAR* path; + BOOL automount; + UINT32 PathLength; + wListDictionary* files; + + HANDLE thread; + wMessageQueue* IrpQueue; + + DEVMAN* devman; + + rdpContext* rdpcontext; +}; + +static UINT sys_code_page = 0; + +static DWORD drive_map_windows_err(DWORD fs_errno) +{ + DWORD rc; + + /* try to return NTSTATUS version of error code */ + + switch (fs_errno) + { + case STATUS_SUCCESS: + rc = STATUS_SUCCESS; + break; + + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + rc = STATUS_ACCESS_DENIED; + break; + + case ERROR_FILE_NOT_FOUND: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_BUSY_DRIVE: + rc = STATUS_DEVICE_BUSY; + break; + + case ERROR_INVALID_DRIVE: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_NOT_READY: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + rc = STATUS_OBJECT_NAME_COLLISION; + break; + + case ERROR_INVALID_NAME: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_INVALID_HANDLE: + rc = STATUS_INVALID_HANDLE; + break; + + case ERROR_NO_MORE_FILES: + rc = STATUS_NO_MORE_FILES; + break; + + case ERROR_DIRECTORY: + rc = STATUS_NOT_A_DIRECTORY; + break; + + case ERROR_PATH_NOT_FOUND: + rc = STATUS_OBJECT_PATH_NOT_FOUND; + break; + + default: + rc = STATUS_UNSUCCESSFUL; + WLog_ERR(TAG, "Error code not found: %"PRIu32"", fs_errno); + break; + } + + return rc; +} + +static DRIVE_FILE* drive_get_file_by_id(DRIVE_DEVICE* drive, UINT32 id) +{ + DRIVE_FILE* file = NULL; + void* key = (void*)(size_t) id; + + if (!drive) + return NULL; + + file = (DRIVE_FILE*) ListDictionary_GetItemValue(drive->files, key); + return file; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FileId; + DRIVE_FILE* file; + BYTE Information; + UINT32 FileAttributes; + UINT32 SharedAccess; + UINT32 DesiredAccess; + UINT32 CreateDisposition; + UINT32 CreateOptions; + UINT32 PathLength; + UINT64 allocationSize; + const WCHAR* path; + + if (!drive || !irp || !irp->devman || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 6 * 4 + 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, DesiredAccess); + Stream_Read_UINT64(irp->input, allocationSize); + Stream_Read_UINT32(irp->input, FileAttributes); + Stream_Read_UINT32(irp->input, SharedAccess); + Stream_Read_UINT32(irp->input, CreateDisposition); + Stream_Read_UINT32(irp->input, CreateOptions); + Stream_Read_UINT32(irp->input, PathLength); + + if (Stream_GetRemainingLength(irp->input) < PathLength) + return ERROR_INVALID_DATA; + + path = (WCHAR*) Stream_Pointer(irp->input); + FileId = irp->devman->id_sequence++; + file = drive_file_new(drive->path, path, PathLength, FileId, DesiredAccess, CreateDisposition, + CreateOptions, FileAttributes, SharedAccess); + + if (!file) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + FileId = 0; + Information = 0; + } + else + { + void* key = (void*)(size_t) file->id; + + if (!ListDictionary_Add(drive->files, key, file)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + switch (CreateDisposition) + { + case FILE_SUPERSEDE: + case FILE_OPEN: + case FILE_CREATE: + case FILE_OVERWRITE: + Information = FILE_SUPERSEDED; + break; + + case FILE_OPEN_IF: + Information = FILE_OPENED; + break; + + case FILE_OVERWRITE_IF: + Information = FILE_OVERWRITTEN; + break; + + default: + Information = 0; + break; + } + } + + Stream_Write_UINT32(irp->output, FileId); + Stream_Write_UINT8(irp->output, Information); + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp) +{ + void* key; + DRIVE_FILE* file; + + if (!drive || !irp || !irp->Complete || !irp->output) + return ERROR_INVALID_PARAMETER; + + file = drive_get_file_by_id(drive, irp->FileId); + key = (void*)(size_t) irp->FileId; + + if (!file) + irp->IoStatus = STATUS_UNSUCCESSFUL; + else + { + ListDictionary_Remove(drive->files, key); + + if (drive_file_free(file)) + irp->IoStatus = STATUS_SUCCESS; + else + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + Stream_Zero(irp->output, 5); /* Padding(5) */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file; + UINT32 Length; + UINT64 Offset; + + if (!drive || !irp || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + if (!Stream_EnsureRemainingCapacity(irp->output, Length + 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INTERNAL_ERROR; + } + else if (Length == 0) + Stream_Write_UINT32(irp->output, 0); + else + { + BYTE* buffer = Stream_Pointer(irp->output) + sizeof(UINT32); + + if (!drive_file_read(file, buffer, &Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Stream_Write_UINT32(irp->output, 0); + } + else + { + Stream_Write_UINT32(irp->output, Length); + Stream_Seek(irp->output, Length); + } + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file; + UINT32 Length; + UINT64 Offset; + + if (!drive || !irp || !irp->input || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + Stream_Seek(irp->input, 20); /* Padding */ + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + else if (!drive_file_write(file, Stream_Pointer(irp->input), Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file; + UINT32 FsInformationClass; + + if (!drive || !irp || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_query_information(file, FsInformationClass, irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_set_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file; + UINT32 FsInformationClass; + UINT32 Length; + + if (!drive || !irp || !irp->Complete || !irp->input || !irp->output) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT32(irp->input, Length); + Stream_Seek(irp->input, 24); /* Padding */ + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_set_information(file, FsInformationClass, Length, + irp->input)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + if (file && file->is_dir && !PathIsDirectoryEmptyW(file->fullpath)) + irp->IoStatus = STATUS_DIRECTORY_NOT_EMPTY; + + Stream_Write_UINT32(irp->output, Length); + return irp->Complete(irp); +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_volume_information(DRIVE_DEVICE* drive, + IRP* irp) +{ + UINT32 FsInformationClass; + wStream* output = NULL; + char* volumeLabel = {"FREERDP"}; + char* diskType = {"FAT32"}; + WCHAR* outStr = NULL; + int length; + DWORD lpSectorsPerCluster; + DWORD lpBytesPerSector; + DWORD lpNumberOfFreeClusters; + DWORD lpTotalNumberOfClusters; + WIN32_FILE_ATTRIBUTE_DATA wfad; + + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + output = irp->output; + + if (Stream_GetRemainingLength(irp->input) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + GetDiskFreeSpaceW(drive->path, &lpSectorsPerCluster, &lpBytesPerSector, &lpNumberOfFreeClusters, + &lpTotalNumberOfClusters); + + switch (FsInformationClass) + { + case FileFsVolumeInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232108.aspx */ + if ((length = ConvertToUnicode(sys_code_page, 0, volumeLabel, -1, &outStr, 0) * 2) <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, 17 + length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 17 + length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + free(outStr); + return CHANNEL_RC_NO_MEMORY; + } + + GetFileAttributesExW(drive->path, GetFileExInfoStandard, &wfad); + Stream_Write_UINT32(output, wfad.ftCreationTime.dwLowDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, wfad.ftCreationTime.dwHighDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, lpNumberOfFreeClusters & 0xffff); /* VolumeSerialNumber */ + Stream_Write_UINT32(output, length); /* VolumeLabelLength */ + Stream_Write_UINT8(output, 0); /* SupportsObjects */ + /* Reserved(1), MUST NOT be added! */ + Stream_Write(output, outStr, length); /* VolumeLabel (Unicode) */ + free(outStr); + break; + + case FileFsSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232107.aspx */ + Stream_Write_UINT32(output, 24); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 24)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsAttributeInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232101.aspx */ + if ((length = ConvertToUnicode(sys_code_page, 0, diskType, -1, &outStr, 0) * 2) <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, 12 + length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 12 + length)) + { + free(outStr); + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, + FILE_CASE_SENSITIVE_SEARCH | + FILE_CASE_PRESERVED_NAMES | + FILE_UNICODE_ON_DISK); /* FileSystemAttributes */ + Stream_Write_UINT32(output, MAX_PATH); /* MaximumComponentNameLength */ + Stream_Write_UINT32(output, length); /* FileSystemNameLength */ + Stream_Write(output, outStr, length); /* FileSystemName (Unicode) */ + free(outStr); + break; + + case FileFsFullSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232104.aspx */ + Stream_Write_UINT32(output, 32); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* CallerAvailableAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsDeviceInformation: + /* http://msdn.microsoft.com/en-us/library/cc232109.aspx */ + Stream_Write_UINT32(output, 8); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 8)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, FILE_DEVICE_DISK); /* DeviceType */ + Stream_Write_UINT32(output, 0); /* Characteristics */ + break; + + default: + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(output, 0); /* Length */ + break; + } + + return irp->Complete(irp); +} + +/* http://msdn.microsoft.com/en-us/library/cc241518.aspx */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_silent_ignore(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FsInformationClass; + + if (!drive || !irp || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Write_UINT32(irp->output, 0); /* Length */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp) +{ + const WCHAR* path; + DRIVE_FILE* file; + BYTE InitialQuery; + UINT32 PathLength; + UINT32 FsInformationClass; + + if (!drive || !irp || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT8(irp->input, InitialQuery); + Stream_Read_UINT32(irp->input, PathLength); + Stream_Seek(irp->input, 23); /* Padding */ + path = (WCHAR*) Stream_Pointer(irp->input); + file = drive_get_file_by_id(drive, irp->FileId); + + if (file == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(irp->output, 0); /* Length */ + } + else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path, PathLength, + irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_directory_control(DRIVE_DEVICE* drive, IRP* irp) +{ + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + switch (irp->MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + return drive_process_irp_query_directory(drive, irp); + + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* TODO */ + return irp->Discard(irp); + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + Stream_Write_UINT32(irp->output, 0); /* Length */ + return irp->Complete(irp); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_device_control(DRIVE_DEVICE* drive, IRP* irp) +{ + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT error; + + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + irp->IoStatus = STATUS_SUCCESS; + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = drive_process_irp_create(drive, irp); + break; + + case IRP_MJ_CLOSE: + error = drive_process_irp_close(drive, irp); + break; + + case IRP_MJ_READ: + error = drive_process_irp_read(drive, irp); + break; + + case IRP_MJ_WRITE: + error = drive_process_irp_write(drive, irp); + break; + + case IRP_MJ_QUERY_INFORMATION: + error = drive_process_irp_query_information(drive, irp); + break; + + case IRP_MJ_SET_INFORMATION: + error = drive_process_irp_set_information(drive, irp); + break; + + case IRP_MJ_QUERY_VOLUME_INFORMATION: + error = drive_process_irp_query_volume_information(drive, irp); + break; + + case IRP_MJ_LOCK_CONTROL: + error = drive_process_irp_silent_ignore(drive, irp); + break; + + case IRP_MJ_DIRECTORY_CONTROL: + error = drive_process_irp_directory_control(drive, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = drive_process_irp_device_control(drive, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + error = irp->Complete(irp); + break; + } + + return error; +} + +static DWORD WINAPI drive_thread_func(LPVOID arg) +{ + IRP* irp; + wMessage message; + DRIVE_DEVICE* drive = (DRIVE_DEVICE*) arg; + UINT error = CHANNEL_RC_OK; + + if (!drive) + { + error = ERROR_INVALID_PARAMETER; + goto fail; + } + + while (1) + { + if (!MessageQueue_Wait(drive->IrpQueue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + irp = (IRP*) message.wParam; + + if (irp) + { + if ((error = drive_process_irp(drive, irp))) + { + WLog_ERR(TAG, "drive_process_irp failed with error %"PRIu32"!", error); + break; + } + } + } + +fail: + + if (error && drive && drive->rdpcontext) + setChannelError(drive->rdpcontext, error, "drive_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_irp_request(DEVICE* device, IRP* irp) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*) device; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (!MessageQueue_Post(drive->IrpQueue, NULL, 0, (void*) irp, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT drive_free_int(DRIVE_DEVICE* drive) +{ + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + CloseHandle(drive->thread); + ListDictionary_Free(drive->files); + MessageQueue_Free(drive->IrpQueue); + Stream_Free(drive->device.data, TRUE); + free(drive->path); + free(drive); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_free(DEVICE* device) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*) device; + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (MessageQueue_PostQuit(drive->IrpQueue, 0) + && (WaitForSingleObject(drive->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + return drive_free_int(drive); +} + +/** + * Helper function used for freeing list dictionary value object + */ +static void drive_file_objfree(void* obj) +{ + drive_file_free((DRIVE_FILE*) obj); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, + const char* name, const char* path, BOOL automount) +{ + size_t i, length; + DRIVE_DEVICE* drive; + UINT error; + + if (name[0] && path[0]) + { + size_t pathLength = strnlen(path, MAX_PATH); + drive = (DRIVE_DEVICE*) calloc(1, sizeof(DRIVE_DEVICE)); + + if (!drive) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + drive->device.type = RDPDR_DTYP_FILESYSTEM; + drive->device.name = name; + drive->device.IRPRequest = drive_irp_request; + drive->device.Free = drive_free; + drive->rdpcontext = pEntryPoints->rdpcontext; + drive->automount = automount; + length = strlen(name); + drive->device.data = Stream_New(NULL, length + 1); + + if (!drive->device.data) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + for (i = 0; i <= length; i++) + Stream_Write_UINT8(drive->device.data, name[i] < 0 ? '_' : name[i]); + + if ((pathLength > 1) && (path[pathLength - 1] == '/')) + pathLength --; + + if (ConvertToUnicode(sys_code_page, 0, path, pathLength, &drive->path, 0) <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + drive->files = ListDictionary_New(TRUE); + + if (!drive->files) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + ListDictionary_ValueObject(drive->files)->fnObjectFree = drive_file_objfree; + drive->IrpQueue = MessageQueue_New(NULL); + + if (!drive->IrpQueue) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, + (DEVICE*) drive))) + { + WLog_ERR(TAG, "RegisterDevice failed with error %"PRIu32"!", error); + goto out_error; + } + + if (!(drive->thread = CreateThread(NULL, 0, drive_thread_func, drive, + CREATE_SUSPENDED, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_error; + } + + ResumeThread(drive->thread); + } + + return CHANNEL_RC_OK; +out_error: + drive_free_int(drive); + return error; +} + +#ifdef BUILTIN_CHANNELS +#define DeviceServiceEntry drive_DeviceServiceEntry +#else +#define DeviceServiceEntry FREERDP_API DeviceServiceEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + RDPDR_DRIVE* drive; + UINT error; +#ifdef WIN32 + char* dev; + int len; + char devlist[512], buf[512]; + char* bufdup; + char* devdup; +#endif + drive = (RDPDR_DRIVE*) pEntryPoints->device; +#ifndef WIN32 + sys_code_page = CP_UTF8; + + if (strcmp(drive->Path, "*") == 0) + { + /* all drives */ + free(drive->Path); + drive->Path = _strdup("/"); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + else if (strcmp(drive->Path, "%") == 0) + { + free(drive->Path); + drive->Path = GetKnownPath(KNOWN_PATH_HOME); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + error = drive_register_drive_path(pEntryPoints, drive->Name, drive->Path, drive->automount); +#else + sys_code_page = GetACP(); + + /* Special case: path[0] == '*' -> export all drives */ + /* Special case: path[0] == '%' -> user home dir */ + if (strcmp(drive->Path, "%") == 0) + { + GetEnvironmentVariableA("USERPROFILE", buf, sizeof(buf)); + PathCchAddBackslashA(buf, sizeof(buf)); + free(drive->Path); + drive->Path = _strdup(buf); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = drive_register_drive_path(pEntryPoints, drive->Name, drive->Path, drive->automount); + } + else if (strcmp(drive->Path, "*") == 0) + { + int i; + /* Enumerate all devices: */ + GetLogicalDriveStringsA(sizeof(devlist) - 1, devlist); + + for (dev = devlist, i = 0; *dev; dev += 4, i++) + { + if (*dev > 'B') + { + /* Suppress disk drives A and B to avoid pesty messages */ + len = sprintf_s(buf, sizeof(buf) - 4, "%s", drive->Name); + buf[len] = '_'; + buf[len + 1] = dev[0]; + buf[len + 2] = 0; + buf[len + 3] = 0; + + if (!(bufdup = _strdup(buf))) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(devdup = _strdup(dev))) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = drive_register_drive_path(pEntryPoints, bufdup, devdup, TRUE))) + { + break; + } + } + } + } + else + { + error = drive_register_drive_path(pEntryPoints, drive->Name, drive->Path, drive->automount); + } + +#endif + return error; +} diff --git a/channels/echo/CMakeLists.txt b/channels/echo/CMakeLists.txt new file mode 100644 index 0000000..bfa8297 --- /dev/null +++ b/channels/echo/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("echo") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/echo/ChannelOptions.cmake b/channels/echo/ChannelOptions.cmake new file mode 100644 index 0000000..eb4950a --- /dev/null +++ b/channels/echo/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "echo" TYPE "dynamic" + DESCRIPTION "Echo Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEECO]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/echo/client/CMakeLists.txt b/channels/echo/client/CMakeLists.txt new file mode 100644 index 0000000..149fbbf --- /dev/null +++ b/channels/echo/client/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("echo") + +set(${MODULE_PREFIX}_SRCS + echo_main.c + echo_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +target_link_libraries(${MODULE_NAME} winpr) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/echo/client/echo_main.c b/channels/echo/client/echo_main.c new file mode 100644 index 0000000..a16089f --- /dev/null +++ b/channels/echo/client/echo_main.c @@ -0,0 +1,200 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "echo_main.h" +#include + +#define TAG CHANNELS_TAG("echo.client") + +typedef struct _ECHO_LISTENER_CALLBACK ECHO_LISTENER_CALLBACK; +struct _ECHO_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +typedef struct _ECHO_CHANNEL_CALLBACK ECHO_CHANNEL_CALLBACK; +struct _ECHO_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; + +typedef struct _ECHO_PLUGIN ECHO_PLUGIN; +struct _ECHO_PLUGIN +{ + IWTSPlugin iface; + + ECHO_LISTENER_CALLBACK* listener_callback; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *data) +{ + ECHO_CHANNEL_CALLBACK* callback = (ECHO_CHANNEL_CALLBACK*) pChannelCallback; + BYTE* pBuffer = Stream_Pointer(data); + UINT32 cbSize = Stream_GetRemainingLength(data); + + /* echo back what we have received. ECHO does not have any message IDs. */ + return callback->channel->Write(callback->channel, cbSize, pBuffer, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + ECHO_CHANNEL_CALLBACK* callback = (ECHO_CHANNEL_CALLBACK*) pChannelCallback; + + free(callback); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + ECHO_CHANNEL_CALLBACK* callback; + ECHO_LISTENER_CALLBACK* listener_callback = (ECHO_LISTENER_CALLBACK*) pListenerCallback; + + callback = (ECHO_CHANNEL_CALLBACK*) calloc(1, sizeof(ECHO_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = echo_on_data_received; + callback->iface.OnClose = echo_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + ECHO_PLUGIN* echo = (ECHO_PLUGIN*) pPlugin; + + echo->listener_callback = (ECHO_LISTENER_CALLBACK*) calloc(1, sizeof(ECHO_LISTENER_CALLBACK)); + + if (!echo->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + echo->listener_callback->iface.OnNewChannelConnection = echo_on_new_channel_connection; + echo->listener_callback->plugin = pPlugin; + echo->listener_callback->channel_mgr = pChannelMgr; + + return pChannelMgr->CreateListener(pChannelMgr, "ECHO", 0, + (IWTSListenerCallback*) echo->listener_callback, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_plugin_terminated(IWTSPlugin* pPlugin) +{ + ECHO_PLUGIN* echo = (ECHO_PLUGIN*) pPlugin; + + free(echo); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry echo_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = CHANNEL_RC_OK; + ECHO_PLUGIN* echo; + + echo = (ECHO_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "echo"); + + if (!echo) + { + echo = (ECHO_PLUGIN*) calloc(1, sizeof(ECHO_PLUGIN)); + + if (!echo) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + echo->iface.Initialize = echo_plugin_initialize; + echo->iface.Connected = NULL; + echo->iface.Disconnected = NULL; + echo->iface.Terminated = echo_plugin_terminated; + + status = pEntryPoints->RegisterPlugin(pEntryPoints, "echo", (IWTSPlugin*) echo); + } + + return status; +} diff --git a/channels/echo/client/echo_main.h b/channels/echo/client/echo_main.h new file mode 100644 index 0000000..04cda41 --- /dev/null +++ b/channels/echo/client/echo_main.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H +#define FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("echo.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) do { } while (0) +#endif + +#endif /* FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H */ + diff --git a/channels/echo/server/CMakeLists.txt b/channels/echo/server/CMakeLists.txt new file mode 100644 index 0000000..e69b555 --- /dev/null +++ b/channels/echo/server/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("echo") + +set(${MODULE_PREFIX}_SRCS + echo_main.c) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/echo/server/echo_main.c b/channels/echo/server/echo_main.c new file mode 100644 index 0000000..b3f8eb0 --- /dev/null +++ b/channels/echo/server/echo_main.c @@ -0,0 +1,360 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2014 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("echo.server") + +typedef struct _echo_server +{ + echo_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* echo_channel; + + DWORD SessionId; + +} echo_server; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_open_channel(echo_server* echo) +{ + DWORD Error; + HANDLE hEvent; + DWORD StartTick; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + + if (WTSQuerySessionInformationA(echo->context.vcm, WTS_CURRENT_SESSION, + WTSSessionId, (LPSTR*) &pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + echo->SessionId = (DWORD) * pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(echo->context.vcm); + StartTick = GetTickCount(); + + while (echo->echo_channel == NULL) + { + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", Error); + return Error; + } + + echo->echo_channel = WTSVirtualChannelOpenEx(echo->SessionId, + "ECHO", WTS_CHANNEL_OPTION_DYNAMIC); + + if (echo->echo_channel) + break; + + Error = GetLastError(); + + if (Error == ERROR_NOT_FOUND) + break; + + if (GetTickCount() - StartTick > 5000) + break; + } + + return echo->echo_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static DWORD WINAPI echo_server_thread_func(LPVOID arg) +{ + wStream* s; + void* buffer; + DWORD nCount; + HANDLE events[8]; + BOOL ready = FALSE; + HANDLE ChannelEvent; + DWORD BytesReturned = 0; + echo_server* echo = (echo_server*) arg; + UINT error; + DWORD status; + + if ((error = echo_server_open_channel(echo))) + { + UINT error2 = 0; + WLog_ERR(TAG, "echo_server_open_channel failed with error %"PRIu32"!", error); + IFCALLRET(echo->context.OpenResult, error2, &echo->context, + ECHO_SERVER_OPEN_RESULT_NOTSUPPORTED); + + if (error2) + WLog_ERR(TAG, "echo server's OpenResult callback failed with error %"PRIu32"", + error2); + + goto out; + } + + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + + if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = echo->stopEvent; + events[nCount++] = ChannelEvent; + + /* Wait for the client to confirm that the Graphics Pipeline dynamic channel is ready */ + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, + ECHO_SERVER_OPEN_RESULT_CLOSED); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %"PRIu32"!", error); + + break; + } + + if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, + ECHO_SERVER_OPEN_RESULT_ERROR); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %"PRIu32"!", error); + + break; + } + + ready = *((BOOL*) buffer); + WTSFreeMemory(buffer); + + if (ready) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, + ECHO_SERVER_OPEN_RESULT_OK); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %"PRIu32"!", error); + + break; + } + } + + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + WTSVirtualChannelClose(echo->echo_channel); + ExitThread(ERROR_NOT_ENOUGH_MEMORY); + return ERROR_NOT_ENOUGH_MEMORY; + } + + while (ready) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + Stream_SetPosition(s, 0); + WTSVirtualChannelRead(echo->echo_channel, 0, NULL, 0, &BytesReturned); + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + + if (WTSVirtualChannelRead(echo->echo_channel, 0, (PCHAR) Stream_Buffer(s), + (ULONG) Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + IFCALLRET(echo->context.Response, error, &echo->context, + (BYTE*) Stream_Buffer(s), BytesReturned); + + if (error) + { + WLog_ERR(TAG, "Response failed with error %"PRIu32"!", error); + break; + } + } + + Stream_Free(s, TRUE); + WTSVirtualChannelClose(echo->echo_channel); + echo->echo_channel = NULL; +out: + + if (error && echo->context.rdpcontext) + setChannelError(echo->context.rdpcontext, error, + "echo_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_open(echo_server_context* context) +{ + echo_server* echo = (echo_server*) context; + + if (echo->thread == NULL) + { + if (!(echo->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(echo->thread = CreateThread(NULL, 0, echo_server_thread_func, (void*) echo, 0, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + CloseHandle(echo->stopEvent); + echo->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_close(echo_server_context* context) +{ + UINT error = CHANNEL_RC_OK; + echo_server* echo = (echo_server*) context; + + if (echo->thread) + { + SetEvent(echo->stopEvent); + + if (WaitForSingleObject(echo->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + CloseHandle(echo->thread); + CloseHandle(echo->stopEvent); + echo->thread = NULL; + echo->stopEvent = NULL; + } + + return error; +} + +static BOOL echo_server_request(echo_server_context* context, + const BYTE* buffer, UINT32 length) +{ + echo_server* echo = (echo_server*) context; + return WTSVirtualChannelWrite(echo->echo_channel, (PCHAR) buffer, length, NULL); +} + +echo_server_context* echo_server_context_new(HANDLE vcm) +{ + echo_server* echo; + echo = (echo_server*) calloc(1, sizeof(echo_server)); + + if (echo) + { + echo->context.vcm = vcm; + echo->context.Open = echo_server_open; + echo->context.Close = echo_server_close; + echo->context.Request = echo_server_request; + } + else + WLog_ERR(TAG, "calloc failed!"); + + return (echo_server_context*) echo; +} + +void echo_server_context_free(echo_server_context* context) +{ + echo_server* echo = (echo_server*) context; + echo_server_close(context); + free(echo); +} diff --git a/channels/encomsp/CMakeLists.txt b/channels/encomsp/CMakeLists.txt new file mode 100644 index 0000000..be2d374 --- /dev/null +++ b/channels/encomsp/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("encomsp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/encomsp/ChannelOptions.cmake b/channels/encomsp/ChannelOptions.cmake new file mode 100644 index 0000000..82ef07e --- /dev/null +++ b/channels/encomsp/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "encomsp" TYPE "static" + DESCRIPTION "Multiparty Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEMC]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/encomsp/client/CMakeLists.txt b/channels/encomsp/client/CMakeLists.txt new file mode 100644 index 0000000..92ee1f8 --- /dev/null +++ b/channels/encomsp/client/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("encomsp") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS + encomsp_main.c + encomsp_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/encomsp/client/encomsp_main.c b/channels/encomsp/client/encomsp_main.c new file mode 100644 index 0000000..d595fef --- /dev/null +++ b/channels/encomsp/client/encomsp_main.c @@ -0,0 +1,1277 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "encomsp_main.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + if (Stream_GetRemainingLength(s) < ENCOMSP_ORDER_HEADER_SIZE) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_write_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + Stream_Write_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Write_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_unicode_string(wStream* s, ENCOMSP_UNICODE_STRING* str) +{ + ZeroMemory(str, sizeof(ENCOMSP_UNICODE_STRING)); + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, str->cchString); /* cchString (2 bytes) */ + + if (str->cchString > 1024) + { + WLog_ERR(TAG, "cchString was %"PRIu16" but has to be < 1025!", str->cchString); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < (size_t)(str->cchString * 2)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read(s, &(str->wString), (str->cchString * 2)); /* String (variable) */ + return CHANNEL_RC_OK; +} + +static EncomspClientContext* encomsp_get_client_interface( + encomspPlugin* encomsp) +{ + EncomspClientContext* pInterface; + pInterface = (EncomspClientContext*) encomsp->channelEntryPoints.pInterface; + return pInterface; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_write(encomspPlugin* encomsp, wStream* s) +{ + UINT status; + + if (!encomsp) + return ERROR_INVALID_HANDLE; + +#if 0 + WLog_INFO(TAG, "EncomspWrite (%"PRIuz")", Stream_Length(s)); + winpr_HexDump(Stream_Buffer(s), Stream_Length(s)); +#endif + status = encomsp->channelEntryPoints.pVirtualChannelWriteEx(encomsp->InitHandle, + encomsp->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_Length(s), s); + + if (status != CHANNEL_RC_OK) + WLog_ERR(TAG, "VirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_filter_updated_pdu(encomspPlugin* encomsp, wStream* s, + ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_FILTER_UPDATED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 1) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, pdu.Flags); /* Flags (1 byte) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->FilterUpdated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->FilterUpdated failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_application_created_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_APPLICATION_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + + if ((error = encomsp_read_unicode_string(s, &(pdu.Name)))) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %"PRIu32"", error); + return error; + } + + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ApplicationCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ApplicationCreated failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_application_removed_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_APPLICATION_REMOVED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ApplicationRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ApplicationRemoved failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_window_created_pdu(encomspPlugin* encomsp, wStream* s, + ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_WINDOW_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 10) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + + if ((error = encomsp_read_unicode_string(s, &(pdu.Name)))) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %"PRIu32"", error); + return error; + } + + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->WindowCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->WindowCreated failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_window_removed_pdu(encomspPlugin* encomsp, wStream* s, + ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_WINDOW_REMOVED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->WindowRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->WindowRemoved failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_show_window_pdu(encomspPlugin* encomsp, wStream* s, + ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_SHOW_WINDOW_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ShowWindow, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ShowWindow failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_participant_created_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_PARTICIPANT_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 10) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + Stream_Read_UINT32(s, pdu.GroupId); /* GroupId (4 bytes) */ + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + + if ((error = encomsp_read_unicode_string(s, &(pdu.FriendlyName)))) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %"PRIu32"", error); + return error; + } + + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ParticipantCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ParticipantCreated failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_participant_removed_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_PARTICIPANT_REMOVED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + Stream_Read_UINT32(s, pdu.DiscType); /* DiscType (4 bytes) */ + Stream_Read_UINT32(s, pdu.DiscCode); /* DiscCode (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ParticipantRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ParticipantRemoved failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_change_participant_control_level_pdu( + encomspPlugin* encomsp, wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %"PRIu32"", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_send_change_participant_control_level_pdu( + EncomspClientContext* context, + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu) +{ + wStream* s; + encomspPlugin* encomsp; + UINT error; + encomsp = (encomspPlugin*) context->handle; + pdu->Type = ODTYPE_PARTICIPANT_CTRL_CHANGED; + pdu->Length = ENCOMSP_ORDER_HEADER_SIZE + 6; + s = Stream_New(NULL, pdu->Length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = encomsp_write_header(s, (ENCOMSP_ORDER_HEADER*) pdu))) + { + WLog_ERR(TAG, "encomsp_write_header failed with error %"PRIu32"!", error); + return error; + } + + Stream_Write_UINT16(s, pdu->Flags); /* Flags (2 bytes) */ + Stream_Write_UINT32(s, pdu->ParticipantId); /* ParticipantId (4 bytes) */ + Stream_SealLength(s); + return encomsp_virtual_channel_write(encomsp, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_graphics_stream_paused_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->GraphicsStreamPaused, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->GraphicsStreamPaused failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_graphics_stream_resumed_pdu(encomspPlugin* encomsp, + wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + EncomspClientContext* context; + ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < (size_t)((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->GraphicsStreamResumed, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->GraphicsStreamResumed failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_process_receive(encomspPlugin* encomsp, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + ENCOMSP_ORDER_HEADER header; + + while (Stream_GetRemainingLength(s) > 0) + { + if ((error = encomsp_read_header(s, &header))) + { + WLog_ERR(TAG, "encomsp_read_header failed with error %"PRIu32"!", error); + return error; + } + + //WLog_DBG(TAG, "EncomspReceive: Type: %"PRIu16" Length: %"PRIu16"", header.Type, header.Length); + + switch (header.Type) + { + case ODTYPE_FILTER_STATE_UPDATED: + if ((error = encomsp_recv_filter_updated_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_filter_updated_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case ODTYPE_APP_REMOVED: + if ((error = encomsp_recv_application_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_application_removed_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_APP_CREATED: + if ((error = encomsp_recv_application_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_application_removed_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_WND_REMOVED: + if ((error = encomsp_recv_window_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_window_removed_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case ODTYPE_WND_CREATED: + if ((error = encomsp_recv_window_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_window_created_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case ODTYPE_WND_SHOW: + if ((error = encomsp_recv_show_window_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_show_window_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_REMOVED: + if ((error = encomsp_recv_participant_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_participant_removed_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_CREATED: + if ((error = encomsp_recv_participant_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_participant_created_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_CTRL_CHANGED: + if ((error = encomsp_recv_change_participant_control_level_pdu(encomsp, s, + &header))) + { + WLog_ERR(TAG, + "encomsp_recv_change_participant_control_level_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_GRAPHICS_STREAM_PAUSED: + if ((error = encomsp_recv_graphics_stream_paused_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_graphics_stream_paused_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case ODTYPE_GRAPHICS_STREAM_RESUMED: + if ((error = encomsp_recv_graphics_stream_resumed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_graphics_stream_resumed_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "header.Type %"PRIu16" not found", header.Type); + return ERROR_INVALID_DATA; + break; + } + } + + return error; +} + +static void encomsp_process_connect(encomspPlugin* encomsp) +{ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_data_received(encomspPlugin* encomsp, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + return CHANNEL_RC_OK; + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (encomsp->data_in) + Stream_Free(encomsp->data_in, TRUE); + + encomsp->data_in = Stream_New(NULL, totalLength); + + if (!encomsp->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = encomsp->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, (int) dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "encomsp_plugin_process_received: read error"); + return ERROR_INVALID_DATA; + } + + encomsp->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(encomsp->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE encomsp_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + encomspPlugin* encomsp = (encomspPlugin*) lpUserParam; + + if (!encomsp || (encomsp->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = encomsp_virtual_channel_event_data_received(encomsp, pData, + dataLength, totalLength, dataFlags))) + WLog_ERR(TAG, "encomsp_virtual_channel_event_data_received failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, "encomsp_virtual_channel_open_event reported an error"); + + return; +} + +static DWORD WINAPI encomsp_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + encomspPlugin* encomsp = (encomspPlugin*) arg; + UINT error = CHANNEL_RC_OK; + encomsp_process_connect(encomsp); + + while (1) + { + if (!MessageQueue_Wait(encomsp->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(encomsp->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + + if ((error = encomsp_process_receive(encomsp, data))) + { + WLog_ERR(TAG, "encomsp_process_receive failed with error %"PRIu32"!", error); + break; + } + } + } + + if (error && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, + "encomsp_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_connected(encomspPlugin* encomsp, + LPVOID pData, UINT32 dataLength) +{ + UINT32 status; + status = encomsp->channelEntryPoints.pVirtualChannelOpenEx(encomsp->InitHandle, + &encomsp->OpenHandle, encomsp->channelDef.name, + encomsp_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpen failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + encomsp->queue = MessageQueue_New(NULL); + + if (!encomsp->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(encomsp->thread = CreateThread(NULL, 0, + encomsp_virtual_channel_client_thread, (void*) encomsp, + 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + MessageQueue_Free(encomsp->queue); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_disconnected(encomspPlugin* encomsp) +{ + UINT rc; + + if (encomsp->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (MessageQueue_PostQuit(encomsp->queue, 0) + && (WaitForSingleObject(encomsp->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", rc); + return rc; + } + + MessageQueue_Free(encomsp->queue); + CloseHandle(encomsp->thread); + encomsp->queue = NULL; + encomsp->thread = NULL; + rc = encomsp->channelEntryPoints.pVirtualChannelCloseEx(encomsp->InitHandle, encomsp->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + return rc; + } + + encomsp->OpenHandle = 0; + + if (encomsp->data_in) + { + Stream_Free(encomsp->data_in, TRUE); + encomsp->data_in = NULL; + } + + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_terminated(encomspPlugin* encomsp) +{ + encomsp->InitHandle = 0; + free(encomsp->context); + free(encomsp); + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE encomsp_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + encomspPlugin* encomsp = (encomspPlugin*) lpUserParam; + + if (!encomsp || (encomsp->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = encomsp_virtual_channel_event_connected(encomsp, pData, + dataLength))) + WLog_ERR(TAG, "encomsp_virtual_channel_event_connected failed with error %"PRIu32"", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = encomsp_virtual_channel_event_disconnected(encomsp))) + WLog_ERR(TAG, + "encomsp_virtual_channel_event_disconnected failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_TERMINATED: + encomsp_virtual_channel_event_terminated(encomsp); + break; + + default: + WLog_ERR(TAG, "Unhandled event type %"PRIu32"", event); + } + + if (error && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, "encomsp_virtual_channel_init_event reported an error"); +} + +/* encomsp is always built-in */ +#define VirtualChannelEntryEx encomsp_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + encomspPlugin* encomsp; + EncomspClientContext* context = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + BOOL isFreerdp = FALSE; + encomsp = (encomspPlugin*) calloc(1, sizeof(encomspPlugin)); + + if (!encomsp) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + encomsp->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | + CHANNEL_OPTION_SHOW_PROTOCOL; + sprintf_s(encomsp->channelDef.name, ARRAYSIZE(encomsp->channelDef.name), "encomsp"); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (EncomspClientContext*) calloc(1, sizeof(EncomspClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + context->handle = (void*) encomsp; + context->FilterUpdated = NULL; + context->ApplicationCreated = NULL; + context->ApplicationRemoved = NULL; + context->WindowCreated = NULL; + context->WindowRemoved = NULL; + context->ShowWindow = NULL; + context->ParticipantCreated = NULL; + context->ParticipantRemoved = NULL; + context->ChangeParticipantControlLevel = + encomsp_send_change_participant_control_level_pdu; + context->GraphicsStreamPaused = NULL; + context->GraphicsStreamResumed = NULL; + encomsp->context = context; + encomsp->rdpcontext = pEntryPointsEx->context; + isFreerdp = TRUE; + } + + CopyMemory(&(encomsp->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + encomsp->InitHandle = pInitHandle; + rc = encomsp->channelEntryPoints.pVirtualChannelInitEx(encomsp, context, pInitHandle, + &encomsp->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + encomsp_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + goto error_out; + } + + encomsp->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + + if (isFreerdp) + free(encomsp->context); + + free(encomsp); + return FALSE; +} diff --git a/channels/encomsp/client/encomsp_main.h b/channels/encomsp/client/encomsp_main.h new file mode 100644 index 0000000..7b23ed1 --- /dev/null +++ b/channels/encomsp/client/encomsp_main.h @@ -0,0 +1,56 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H +#define FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define TAG CHANNELS_TAG("encomsp.client") + +struct encomsp_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + EncomspClientContext* context; + + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + rdpContext* rdpcontext; +}; +typedef struct encomsp_plugin encomspPlugin; + +#endif /* FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H */ diff --git a/channels/encomsp/server/CMakeLists.txt b/channels/encomsp/server/CMakeLists.txt new file mode 100644 index 0000000..10ac0c6 --- /dev/null +++ b/channels/encomsp/server/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("encomsp") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS + encomsp_main.c + encomsp_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/encomsp/server/encomsp_main.c b/channels/encomsp/server/encomsp_main.c new file mode 100644 index 0000000..e1b2248 --- /dev/null +++ b/channels/encomsp/server/encomsp_main.c @@ -0,0 +1,378 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "encomsp_main.h" + +#define TAG CHANNELS_TAG("encomsp.server") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + if (Stream_GetRemainingLength(s) < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +#if 0 + +static int encomsp_write_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + Stream_Write_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Write_UINT16(s, header->Length); /* Length (2 bytes) */ + return 1; +} + +static int encomsp_read_unicode_string(wStream* s, ENCOMSP_UNICODE_STRING* str) +{ + ZeroMemory(str, sizeof(ENCOMSP_UNICODE_STRING)); + + if (Stream_GetRemainingLength(s) < 2) + return -1; + + Stream_Read_UINT16(s, str->cchString); /* cchString (2 bytes) */ + + if (str->cchString > 1024) + return -1; + + if (Stream_GetRemainingLength(s) < (str->cchString * 2)) + return -1; + + Stream_Read(s, &(str->wString), (str->cchString * 2)); /* String (variable) */ + return 1; +} + +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_change_participant_control_level_pdu( + EncomspServerContext* context, wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + int beg, end; + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu; + UINT error = CHANNEL_RC_OK; + beg = ((int) Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + end = (int) Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (Stream_GetRemainingLength(s) < ((beg + header->Length) - end)) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %"PRIu32"", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_receive_pdu(EncomspServerContext* context, + wStream* s) +{ + UINT error = CHANNEL_RC_OK; + ENCOMSP_ORDER_HEADER header; + + while (Stream_GetRemainingLength(s) > 0) + { + if ((error = encomsp_read_header(s, &header))) + { + WLog_ERR(TAG, "encomsp_read_header failed with error %"PRIu32"!", error); + return error; + } + + WLog_INFO(TAG, "EncomspReceive: Type: %"PRIu16" Length: %"PRIu16"", header.Type, + header.Length); + + switch (header.Type) + { + case ODTYPE_PARTICIPANT_CTRL_CHANGED: + if ((error = encomsp_recv_change_participant_control_level_pdu(context, s, + &header))) + { + WLog_ERR(TAG, + "encomsp_recv_change_participant_control_level_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "header.Type unknown %"PRIu16"!", header.Type); + return ERROR_INVALID_DATA; + break; + } + } + + return error; +} + +static DWORD WINAPI encomsp_server_thread(LPVOID arg) +{ + wStream* s; + DWORD nCount; + void* buffer; + HANDLE events[8]; + HANDLE ChannelEvent; + DWORD BytesReturned; + ENCOMSP_ORDER_HEADER* header; + EncomspServerContext* context; + UINT error = CHANNEL_RC_OK; + DWORD status; + context = (EncomspServerContext*) arg; + + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + break; + } + + WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0, &BytesReturned); + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, + (PCHAR) Stream_Buffer(s), Stream_Capacity(s), &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (Stream_GetPosition(s) >= ENCOMSP_ORDER_HEADER_SIZE) + { + header = (ENCOMSP_ORDER_HEADER*) Stream_Buffer(s); + + if (header->Length >= Stream_GetPosition(s)) + { + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if ((error = encomsp_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, "encomsp_server_receive_pdu failed with error %"PRIu32"!", error); + break; + } + + Stream_SetPosition(s, 0); + } + } + } + + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "encomsp_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_start(EncomspServerContext* context) +{ + context->priv->ChannelHandle = WTSVirtualChannelOpen(context->vcm, + WTS_CURRENT_SESSION, "encomsp"); + + if (!context->priv->ChannelHandle) + return CHANNEL_RC_BAD_CHANNEL; + + if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = CreateThread(NULL, 0, + encomsp_server_thread, (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_stop(EncomspServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + CloseHandle(context->priv->Thread); + CloseHandle(context->priv->StopEvent); + return error; +} + +EncomspServerContext* encomsp_server_context_new(HANDLE vcm) +{ + EncomspServerContext* context; + context = (EncomspServerContext*) calloc(1, sizeof(EncomspServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = encomsp_server_start; + context->Stop = encomsp_server_stop; + context->priv = (EncomspServerPrivate*) calloc(1, sizeof(EncomspServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return NULL; + } + } + + return context; +} + +void encomsp_server_context_free(EncomspServerContext* context) +{ + if (context) + { + if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE) + WTSVirtualChannelClose(context->priv->ChannelHandle); + + free(context->priv); + free(context); + } +} diff --git a/channels/encomsp/server/encomsp_main.h b/channels/encomsp/server/encomsp_main.h new file mode 100644 index 0000000..18daf72 --- /dev/null +++ b/channels/encomsp/server/encomsp_main.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H +#define FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H + +#include +#include +#include + +#include + +struct _encomsp_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; +}; + +#endif /* FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H */ diff --git a/channels/geometry/CMakeLists.txt b/channels/geometry/CMakeLists.txt new file mode 100644 index 0000000..7ddea6d --- /dev/null +++ b/channels/geometry/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("geometry") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/geometry/ChannelOptions.cmake b/channels/geometry/ChannelOptions.cmake new file mode 100644 index 0000000..8e8163b --- /dev/null +++ b/channels/geometry/ChannelOptions.cmake @@ -0,0 +1,11 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "geometry" TYPE "dynamic" + DESCRIPTION "Geometry tracking Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEGT]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) diff --git a/channels/geometry/client/CMakeLists.txt b/channels/geometry/client/CMakeLists.txt new file mode 100644 index 0000000..ea28bff --- /dev/null +++ b/channels/geometry/client/CMakeLists.txt @@ -0,0 +1,39 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("geometry") + +set(${MODULE_PREFIX}_SRCS + geometry_main.c + geometry_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/geometry/client/geometry_main.c b/channels/geometry/client/geometry_main.c new file mode 100644 index 0000000..5ef7def --- /dev/null +++ b/channels/geometry/client/geometry_main.c @@ -0,0 +1,481 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Geometry tracking Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("geometry.client") + +#include "geometry_main.h" + +struct _GEOMETRY_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _GEOMETRY_CHANNEL_CALLBACK GEOMETRY_CHANNEL_CALLBACK; + +struct _GEOMETRY_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + GEOMETRY_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _GEOMETRY_LISTENER_CALLBACK GEOMETRY_LISTENER_CALLBACK; + +struct _GEOMETRY_PLUGIN +{ + IWTSPlugin iface; + + IWTSListener* listener; + GEOMETRY_LISTENER_CALLBACK* listener_callback; + + GeometryClientContext* context; +}; +typedef struct _GEOMETRY_PLUGIN GEOMETRY_PLUGIN; + + +static UINT32 mappedGeometryHash(UINT64 *g) +{ + return (UINT32)((*g >> 32) + (*g & 0xffffffff)); +} + +static BOOL mappedGeometryKeyCompare(UINT64 *g1, UINT64 *g2) +{ + return *g1 == *g2; +} + +void mappedGeometryRef(MAPPED_GEOMETRY *g) +{ + InterlockedIncrement(&g->refCounter); +} + +void mappedGeometryUnref(MAPPED_GEOMETRY *g) +{ + if (InterlockedDecrement(&g->refCounter)) + return; + + g->MappedGeometryUpdate = NULL; + g->MappedGeometryClear = NULL; + g->custom = NULL; + free(g->geometry.rects); + free(g); +} + + +void freerdp_rgndata_reset(FREERDP_RGNDATA *data) +{ + data->nRectCount = 0; +} + +static UINT32 geometry_read_RGNDATA(wStream *s, UINT32 len, FREERDP_RGNDATA *rgndata) +{ + UINT32 dwSize, iType; + INT32 right, bottom; + + if (len < 32) + { + WLog_ERR(TAG, "invalid RGNDATA"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, dwSize); + + if (dwSize != 32) + { + WLog_ERR(TAG, "invalid RGNDATA dwSize"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, iType); + + if (iType != RDH_RECTANGLE) + { + WLog_ERR(TAG, "iType %"PRIu32" for RGNDATA is not supported", iType); + return ERROR_UNSUPPORTED_TYPE; + } + + Stream_Read_UINT32(s, rgndata->nRectCount); + Stream_Seek_UINT32(s); /* nRgnSize IGNORED */ + Stream_Read_INT32(s, rgndata->boundingRect.x); + Stream_Read_INT32(s, rgndata->boundingRect.y); + Stream_Read_INT32(s, right); + Stream_Read_INT32(s, bottom); + rgndata->boundingRect.width = right - rgndata->boundingRect.x; + rgndata->boundingRect.height = bottom - rgndata->boundingRect.y; + len -= 32; + + if (len / (4 * 4) < rgndata->nRectCount) + { + WLog_ERR(TAG, "not enough data for region rectangles"); + } + + if (rgndata->nRectCount) + { + int i; + RDP_RECT *tmp = realloc(rgndata->rects, rgndata->nRectCount * sizeof(RDP_RECT)); + + if (!tmp) + { + WLog_ERR(TAG, "unable to allocate memory for %"PRIu32" RECTs", rgndata->nRectCount); + return CHANNEL_RC_NO_MEMORY; + } + rgndata->rects = tmp; + + for (i = 0; i < rgndata->nRectCount; i++) + { + Stream_Read_INT32(s, rgndata->rects[i].x); + Stream_Read_INT32(s, rgndata->rects[i].y); + Stream_Read_INT32(s, right); + Stream_Read_INT32(s, bottom); + rgndata->rects[i].width = right - rgndata->rects[i].x; + rgndata->rects[i].height = bottom - rgndata->rects[i].y; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_recv_pdu(GEOMETRY_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 length, cbGeometryBuffer; + MAPPED_GEOMETRY *mappedGeometry; + GEOMETRY_PLUGIN* geometry; + GeometryClientContext *context; + UINT ret = CHANNEL_RC_OK; + UINT32 version, updateType, geometryType; + UINT64 id; + + geometry = (GEOMETRY_PLUGIN*) callback->plugin; + context = (GeometryClientContext*)geometry->iface.pInterface; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length < 73 || Stream_GetRemainingLength(s) < (length - 4)) + { + WLog_ERR(TAG, "invalid packet length"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, version); + Stream_Read_UINT64(s, id); + Stream_Read_UINT32(s, updateType); + Stream_Seek_UINT32(s); /* flags */ + + mappedGeometry = HashTable_GetItemValue(context->geometries, &id); + + if (updateType == GEOMETRY_CLEAR ) + { + if (!mappedGeometry) + { + WLog_ERR(TAG, "geometry 0x%"PRIx64" not found here, ignoring clear command", id); + return CHANNEL_RC_OK; + } + + WLog_DBG(TAG, "clearing geometry 0x%"PRIx64"", id); + + if (mappedGeometry->MappedGeometryClear && !mappedGeometry->MappedGeometryClear(mappedGeometry)) + return ERROR_INTERNAL_ERROR; + + if (!HashTable_Remove(context->geometries, &id)) + WLog_ERR(TAG, "geometry not removed from geometries"); + } + else if (updateType == GEOMETRY_UPDATE) + { + BOOL newOne = FALSE; + + if (!mappedGeometry) + { + newOne = TRUE; + WLog_DBG(TAG, "creating geometry 0x%"PRIx64"", id); + mappedGeometry = calloc(1, sizeof(MAPPED_GEOMETRY)); + if (!mappedGeometry) + return CHANNEL_RC_NO_MEMORY; + + mappedGeometry->refCounter = 1; + mappedGeometry->mappingId = id; + + if (HashTable_Add(context->geometries, &(mappedGeometry->mappingId), mappedGeometry) < 0) + { + WLog_ERR(TAG, "unable to register geometry 0x%"PRIx64" in the table", id); + free(mappedGeometry); + return CHANNEL_RC_NO_MEMORY; + } + } + else + { + WLog_DBG(TAG, "updating geometry 0x%"PRIx64"", id); + } + + Stream_Read_UINT64(s, mappedGeometry->topLevelId); + + Stream_Read_INT32(s, mappedGeometry->left); + Stream_Read_INT32(s, mappedGeometry->top); + Stream_Read_INT32(s, mappedGeometry->right); + Stream_Read_INT32(s, mappedGeometry->bottom); + + Stream_Read_INT32(s, mappedGeometry->topLevelLeft); + Stream_Read_INT32(s, mappedGeometry->topLevelTop); + Stream_Read_INT32(s, mappedGeometry->topLevelRight); + Stream_Read_INT32(s, mappedGeometry->topLevelBottom); + + Stream_Read_UINT32(s, geometryType); + + Stream_Read_UINT32(s, cbGeometryBuffer); + if (Stream_GetRemainingLength(s) < cbGeometryBuffer) + { + WLog_ERR(TAG, "invalid packet length"); + return ERROR_INVALID_DATA; + } + + if (cbGeometryBuffer) + { + ret = geometry_read_RGNDATA(s, cbGeometryBuffer, &mappedGeometry->geometry); + if (ret != CHANNEL_RC_OK) + return ret; + } + else + { + freerdp_rgndata_reset(&mappedGeometry->geometry); + } + + if (newOne) + { + if (context->MappedGeometryAdded && !context->MappedGeometryAdded(context, mappedGeometry)) + { + WLog_ERR(TAG, "geometry added callback failed"); + ret = ERROR_INTERNAL_ERROR; + } + } + else + { + if (mappedGeometry->MappedGeometryUpdate && !mappedGeometry->MappedGeometryUpdate(mappedGeometry)) + { + WLog_ERR(TAG, "geometry update callback failed"); + ret = ERROR_INTERNAL_ERROR; + } + } + } + else + { + WLog_ERR(TAG, "unknown updateType=%"PRIu32"", updateType); + ret = CHANNEL_RC_OK; + } + + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GEOMETRY_CHANNEL_CALLBACK* callback = (GEOMETRY_CHANNEL_CALLBACK*) pChannelCallback; + return geometry_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GEOMETRY_CHANNEL_CALLBACK* callback; + GEOMETRY_LISTENER_CALLBACK* listener_callback = (GEOMETRY_LISTENER_CALLBACK*) pListenerCallback; + callback = (GEOMETRY_CHANNEL_CALLBACK*) calloc(1, sizeof(GEOMETRY_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = geometry_on_data_received; + callback->iface.OnClose = geometry_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*) pPlugin; + geometry->listener_callback = (GEOMETRY_LISTENER_CALLBACK*) calloc(1, + sizeof(GEOMETRY_LISTENER_CALLBACK)); + + if (!geometry->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + geometry->listener_callback->iface.OnNewChannelConnection = geometry_on_new_channel_connection; + geometry->listener_callback->plugin = pPlugin; + geometry->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener(pChannelMgr, GEOMETRY_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*) geometry->listener_callback, &(geometry->listener)); + geometry->listener->pInterface = geometry->iface.pInterface; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_plugin_terminated(IWTSPlugin* pPlugin) +{ + GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*) pPlugin; + GeometryClientContext* context = (GeometryClientContext *)geometry->iface.pInterface; + + if (context) + HashTable_Free(context->geometries); + + free(geometry->listener_callback); + free(geometry->iface.pInterface); + free(pPlugin); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry geometry_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + GEOMETRY_PLUGIN* geometry; + GeometryClientContext* context; + geometry = (GEOMETRY_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "geometry"); + + if (!geometry) + { + geometry = (GEOMETRY_PLUGIN*) calloc(1, sizeof(GEOMETRY_PLUGIN)); + + if (!geometry) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + geometry->iface.Initialize = geometry_plugin_initialize; + geometry->iface.Connected = NULL; + geometry->iface.Disconnected = NULL; + geometry->iface.Terminated = geometry_plugin_terminated; + context = (GeometryClientContext*) calloc(1, sizeof(GeometryClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_context; + } + + context->geometries = HashTable_New(FALSE); + context->geometries->hash = (HASH_TABLE_HASH_FN)mappedGeometryHash; + context->geometries->keyCompare = (HASH_TABLE_KEY_COMPARE_FN)mappedGeometryKeyCompare; + context->geometries->valueFree = (HASH_TABLE_VALUE_FREE_FN)mappedGeometryUnref; + + context->handle = (void*) geometry; + geometry->iface.pInterface = (void*) context; + geometry->context = context; + error = pEntryPoints->RegisterPlugin(pEntryPoints, "geometry", (IWTSPlugin*) geometry); + } + else + { + WLog_ERR(TAG, "could not get geometry Plugin."); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; + +error_context: + free(geometry); + return CHANNEL_RC_NO_MEMORY; + +} diff --git a/channels/geometry/client/geometry_main.h b/channels/geometry/client/geometry_main.h new file mode 100644 index 0000000..1fb5321 --- /dev/null +++ b/channels/geometry/client/geometry_main.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Geometry tracking virtual channel extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H +#define FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + + +#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */ + diff --git a/channels/parallel/CMakeLists.txt b/channels/parallel/CMakeLists.txt new file mode 100644 index 0000000..0faabb5 --- /dev/null +++ b/channels/parallel/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("parallel") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/parallel/ChannelOptions.cmake b/channels/parallel/ChannelOptions.cmake new file mode 100644 index 0000000..42a8669 --- /dev/null +++ b/channels/parallel/ChannelOptions.cmake @@ -0,0 +1,23 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "parallel" TYPE "device" + DESCRIPTION "Parallel Port Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPESP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/parallel/client/CMakeLists.txt b/channels/parallel/client/CMakeLists.txt new file mode 100644 index 0000000..255435b --- /dev/null +++ b/channels/parallel/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("parallel") + +set(${MODULE_PREFIX}_SRCS + parallel_main.c) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp winpr) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/parallel/client/parallel_main.c b/channels/parallel/client/parallel_main.c new file mode 100644 index 0000000..19ebce3 --- /dev/null +++ b/channels/parallel/client/parallel_main.c @@ -0,0 +1,483 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Redirected Parallel Port Device Service + * + * Copyright 2010 O.S. Systems Software Ltda. + * Copyright 2010 Eduardo Fiss Beloni + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +#ifdef __LINUX__ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("drive.client") + +struct _PARALLEL_DEVICE +{ + DEVICE device; + + int file; + char* path; + UINT32 id; + + HANDLE thread; + wMessageQueue* queue; + rdpContext* rdpcontext; +}; +typedef struct _PARALLEL_DEVICE PARALLEL_DEVICE; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_create(PARALLEL_DEVICE* parallel, IRP* irp) +{ + char* path = NULL; + int status; + UINT32 PathLength; + Stream_Seek(irp->input, 28); + /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */ + /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */ + Stream_Read_UINT32(irp->input, PathLength); + status = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(irp->input), + PathLength / 2, &path, 0, NULL, NULL); + + if (status < 1) + if (!(path = (char*) calloc(1, 1))) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + parallel->id = irp->devman->id_sequence++; + parallel->file = open(parallel->path, O_RDWR); + + if (parallel->file < 0) + { + irp->IoStatus = STATUS_ACCESS_DENIED; + parallel->id = 0; + } + else + { + /* all read and write operations should be non-blocking */ + if (fcntl(parallel->file, F_SETFL, O_NONBLOCK) == -1) + { + } + } + + Stream_Write_UINT32(irp->output, parallel->id); + Stream_Write_UINT8(irp->output, 0); + free(path); + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_close(PARALLEL_DEVICE* parallel, IRP* irp) +{ + if (close(parallel->file) < 0) + { + } + else + { + } + + Stream_Zero(irp->output, 5); /* Padding(5) */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_read(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT32 Length; + UINT64 Offset; + ssize_t status; + BYTE* buffer = NULL; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + buffer = (BYTE*) malloc(Length); + + if (!buffer) + { + WLog_ERR(TAG, "malloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + status = read(parallel->file, buffer, Length); + + if (status < 0) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + free(buffer); + buffer = NULL; + Length = 0; + } + else + { + } + + Stream_Write_UINT32(irp->output, Length); + + if (Length > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, Length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + free(buffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, buffer, Length); + } + + free(buffer); + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_write(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT32 len; + UINT32 Length; + UINT64 Offset; + ssize_t status; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + Stream_Seek(irp->input, 20); /* Padding */ + len = Length; + + while (len > 0) + { + status = write(parallel->file, Stream_Pointer(irp->input), len); + + if (status < 0) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + break; + } + + Stream_Seek(irp->input, status); + len -= status; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_device_control(PARALLEL_DEVICE* parallel, + IRP* irp) +{ + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT error; + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + if ((error = parallel_process_irp_create(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp_create failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_CLOSE: + if ((error = parallel_process_irp_close(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp_close failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_READ: + if ((error = parallel_process_irp_read(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp_read failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_WRITE: + if ((error = parallel_process_irp_write(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp_write failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_DEVICE_CONTROL: + if ((error = parallel_process_irp_device_control(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp_device_control failed with error %"PRIu32"!", + error); + return error; + } + + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + return irp->Complete(irp); + break; + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI parallel_thread_func(LPVOID arg) +{ + IRP* irp; + wMessage message; + PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*) arg; + UINT error = CHANNEL_RC_OK; + + while (1) + { + if (!MessageQueue_Wait(parallel->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(parallel->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + irp = (IRP*) message.wParam; + + if ((error = parallel_process_irp(parallel, irp))) + { + WLog_ERR(TAG, "parallel_process_irp failed with error %"PRIu32"!", error); + break; + } + } + + if (error && parallel->rdpcontext) + setChannelError(parallel->rdpcontext, error, + "parallel_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_irp_request(DEVICE* device, IRP* irp) +{ + PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*) device; + + if (!MessageQueue_Post(parallel->queue, NULL, 0, (void*) irp, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_free(DEVICE* device) +{ + UINT error; + PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*) device; + + if (!MessageQueue_PostQuit(parallel->queue, 0) + || (WaitForSingleObject(parallel->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(parallel->thread); + Stream_Free(parallel->device.data, TRUE); + MessageQueue_Free(parallel->queue); + free(parallel); + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DeviceServiceEntry parallel_DeviceServiceEntry +#else +#define DeviceServiceEntry FREERDP_API DeviceServiceEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + char* name; + char* path; + size_t i; + size_t length; + RDPDR_PARALLEL* device; + PARALLEL_DEVICE* parallel; + UINT error; + device = (RDPDR_PARALLEL*) pEntryPoints->device; + name = device->Name; + path = device->Path; + + if (!name || (name[0] == '*') || !path) + { + /* TODO: implement auto detection of parallel ports */ + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if (name[0] && path[0]) + { + parallel = (PARALLEL_DEVICE*) calloc(1, sizeof(PARALLEL_DEVICE)); + + if (!parallel) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + parallel->device.type = RDPDR_DTYP_PARALLEL; + parallel->device.name = name; + parallel->device.IRPRequest = parallel_irp_request; + parallel->device.Free = parallel_free; + parallel->rdpcontext = pEntryPoints->rdpcontext; + length = strlen(name); + parallel->device.data = Stream_New(NULL, length + 1); + + if (!parallel->device.data) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + for (i = 0; i <= length; i++) + Stream_Write_UINT8(parallel->device.data, name[i] < 0 ? '_' : name[i]); + + parallel->path = path; + parallel->queue = MessageQueue_New(NULL); + + if (!parallel->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, + (DEVICE*) parallel))) + { + WLog_ERR(TAG, "RegisterDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + if (!(parallel->thread = CreateThread(NULL, 0, + parallel_thread_func, (void*) parallel, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + return CHANNEL_RC_OK; +error_out: + MessageQueue_Free(parallel->queue); + Stream_Free(parallel->device.data, TRUE); + free(parallel); + return error; +} diff --git a/channels/printer/CMakeLists.txt b/channels/printer/CMakeLists.txt new file mode 100644 index 0000000..73cb415 --- /dev/null +++ b/channels/printer/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("printer") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() \ No newline at end of file diff --git a/channels/printer/ChannelOptions.cmake b/channels/printer/ChannelOptions.cmake new file mode 100644 index 0000000..86dad03 --- /dev/null +++ b/channels/printer/ChannelOptions.cmake @@ -0,0 +1,24 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) +elseif(WITH_CUPS) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) +else() + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "printer" TYPE "device" + DESCRIPTION "Print Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEPC]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/printer/client/CMakeLists.txt b/channels/printer/client/CMakeLists.txt new file mode 100644 index 0000000..ea2f0df --- /dev/null +++ b/channels/printer/client/CMakeLists.txt @@ -0,0 +1,56 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("printer") + +set(${MODULE_PREFIX}_SRCS + printer_main.c + printer_main.h) + +if(WITH_CUPS) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + printer_cups.c + printer_cups.h) + + include_directories(${CUPS_INCLUDE_DIR}) + add_definitions(-DWITH_CUPS) +endif() + +if(WIN32 AND NOT UWP) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + printer_win.c + printer_win.h) +endif() + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +if(WITH_CUPS) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${CUPS_LIBRARIES}) +endif() + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/printer/client/printer_cups.c b/channels/printer/client/printer_cups.c new file mode 100644 index 0000000..d6f0705 --- /dev/null +++ b/channels/printer/client/printer_cups.c @@ -0,0 +1,331 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - CUPS driver + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include "printer_main.h" + +#include "printer_cups.h" + +typedef struct rdp_cups_printer_driver rdpCupsPrinterDriver; +typedef struct rdp_cups_printer rdpCupsPrinter; +typedef struct rdp_cups_print_job rdpCupsPrintJob; + +struct rdp_cups_printer_driver +{ + rdpPrinterDriver driver; + + int id_sequence; +}; + +struct rdp_cups_printer +{ + rdpPrinter printer; + + rdpCupsPrintJob* printjob; +}; + +struct rdp_cups_print_job +{ + rdpPrintJob printjob; + + void* printjob_object; + int printjob_id; +}; + +static void printer_cups_get_printjob_name(char* buf, int size) +{ + time_t tt; + struct tm* t; + + tt = time(NULL); + t = localtime(&tt); + sprintf_s(buf, size - 1, "FreeRDP Print Job %d%02d%02d%02d%02d%02d", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_cups_write_printjob(rdpPrintJob* printjob, BYTE* data, int size) +{ + rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*) printjob; + +#ifndef _CUPS_API_1_4 + + { + FILE* fp; + + fp = fopen((const char*) cups_printjob->printjob_object, "a+b"); + + if (!fp) + return ERROR_INTERNAL_ERROR; + + if (fwrite(data, 1, size, fp) < size) + { + fclose(fp); + return ERROR_INTERNAL_ERROR; + // FIXME once this function doesn't return void anymore! + } + + fclose(fp); + } + +#else + + cupsWriteRequestData((http_t*) cups_printjob->printjob_object, (const char*) data, size); + +#endif + + return CHANNEL_RC_OK; +} + +static void printer_cups_close_printjob(rdpPrintJob* printjob) +{ + rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*) printjob; + +#ifndef _CUPS_API_1_4 + + { + char buf[100]; + + printer_cups_get_printjob_name(buf, sizeof(buf)); + + if (cupsPrintFile(printjob->printer->name, (const char*) cups_printjob->printjob_object, buf, 0, NULL) == 0) + { + + } + + unlink(cups_printjob->printjob_object); + free(cups_printjob->printjob_object); + } + +#else + + cupsFinishDocument((http_t*) cups_printjob->printjob_object, printjob->printer->name); + cups_printjob->printjob_id = 0; + httpClose((http_t*) cups_printjob->printjob_object); + +#endif + + ((rdpCupsPrinter*) printjob->printer)->printjob = NULL; + free(cups_printjob) ; +} + +static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*) printer; + rdpCupsPrintJob* cups_printjob; + + if (cups_printer->printjob != NULL) + return NULL; + + cups_printjob = (rdpCupsPrintJob*) calloc(1, sizeof(rdpCupsPrintJob)); + if (!cups_printjob) + return NULL; + + cups_printjob->printjob.id = id; + cups_printjob->printjob.printer = printer; + + cups_printjob->printjob.Write = printer_cups_write_printjob; + cups_printjob->printjob.Close = printer_cups_close_printjob; + +#ifndef _CUPS_API_1_4 + + cups_printjob->printjob_object = _strdup(tmpnam(NULL)); + if (!cups_printjob->printjob_object) + { + free(cups_printjob); + return NULL; + } + +#else + { + char buf[100]; + + cups_printjob->printjob_object = httpConnectEncrypt(cupsServer(), ippPort(), HTTP_ENCRYPT_IF_REQUESTED); + + if (!cups_printjob->printjob_object) + { + free(cups_printjob); + return NULL; + } + + printer_cups_get_printjob_name(buf, sizeof(buf)); + + cups_printjob->printjob_id = cupsCreateJob((http_t*) cups_printjob->printjob_object, + printer->name, buf, 0, NULL); + + if (!cups_printjob->printjob_id) + { + httpClose((http_t*) cups_printjob->printjob_object); + free(cups_printjob); + return NULL; + } + + cupsStartDocument((http_t*) cups_printjob->printjob_object, + printer->name, cups_printjob->printjob_id, buf, CUPS_FORMAT_AUTO, 1); + } + +#endif + + cups_printer->printjob = cups_printjob; + + return (rdpPrintJob*)cups_printjob; +} + +static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer; + + if (cups_printer->printjob == NULL) + return NULL; + if (cups_printer->printjob->printjob.id != id) + return NULL; + + return (rdpPrintJob*)cups_printer->printjob; +} + +static void printer_cups_free_printer(rdpPrinter* printer) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*) printer; + + if (cups_printer->printjob) + cups_printer->printjob->printjob.Close((rdpPrintJob*) cups_printer->printjob); + + free(printer->name); + free(printer->driver); + free(printer); +} + +static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver, + const char* name, const char* driverName, BOOL is_default) +{ + rdpCupsPrinter* cups_printer; + + cups_printer = (rdpCupsPrinter*) calloc(1, sizeof(rdpCupsPrinter)); + if (!cups_printer) + return NULL; + + cups_printer->printer.id = cups_driver->id_sequence++; + cups_printer->printer.name = _strdup(name); + if (!cups_printer->printer.name) + { + free(cups_printer); + return NULL; + } + + if (driverName) + cups_printer->printer.driver = _strdup(driverName); + else + cups_printer->printer.driver = _strdup("MS Publisher Imagesetter"); + if (!cups_printer->printer.driver) + { + free(cups_printer->printer.name); + free(cups_printer); + return NULL; + } + cups_printer->printer.is_default = is_default; + + cups_printer->printer.CreatePrintJob = printer_cups_create_printjob; + cups_printer->printer.FindPrintJob = printer_cups_find_printjob; + cups_printer->printer.Free = printer_cups_free_printer; + + return (rdpPrinter*) cups_printer; +} + +static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver) +{ + rdpPrinter** printers; + int num_printers; + cups_dest_t *dests; + cups_dest_t *dest; + int num_dests; + int i; + + num_dests = cupsGetDests(&dests); + printers = (rdpPrinter**) calloc(num_dests + 1, sizeof(rdpPrinter*)); + if (!printers) + return NULL; + + num_printers = 0; + + for (i = 0, dest = dests; i < num_dests; i++, dest++) + { + if (dest->instance == NULL) + { + printers[num_printers++] = printer_cups_new_printer((rdpCupsPrinterDriver*) driver, + dest->name, NULL, dest->is_default); + } + } + cupsFreeDests(num_dests, dests); + + return printers; +} + +static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, + const char* name, const char* driverName) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*) driver; + + return printer_cups_new_printer(cups_driver, name, driverName, + cups_driver->id_sequence == 1 ? TRUE : FALSE); +} + +static rdpCupsPrinterDriver* cups_driver = NULL; + +rdpPrinterDriver* printer_cups_get_driver(void) +{ + if (cups_driver == NULL) + { + cups_driver = (rdpCupsPrinterDriver*) calloc(1, sizeof(rdpCupsPrinterDriver)); + + if (!cups_driver) + return NULL; + + cups_driver->driver.EnumPrinters = printer_cups_enum_printers; + cups_driver->driver.GetPrinter = printer_cups_get_printer; + + cups_driver->id_sequence = 1; + } + + return (rdpPrinterDriver*) cups_driver; +} + diff --git a/channels/printer/client/printer_cups.h b/channels/printer/client/printer_cups.h new file mode 100644 index 0000000..876cb47 --- /dev/null +++ b/channels/printer/client/printer_cups.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - CUPS driver + * + * 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_CHANNEL_PRINTER_CLIENT_CUPS_H +#define FREERDP_CHANNEL_PRINTER_CLIENT_CUPS_H + +#include "printer_main.h" + +rdpPrinterDriver* printer_cups_get_driver(void); + +#endif /* FREERDP_CHANNEL_PRINTER_CLIENT_CUPS_H */ + diff --git a/channels/printer/client/printer_main.c b/channels/printer/client/printer_main.c new file mode 100644 index 0000000..630c0c0 --- /dev/null +++ b/channels/printer/client/printer_main.c @@ -0,0 +1,526 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "../printer.h" + +#ifdef WITH_CUPS +#include "printer_cups.h" +#endif + +#include "printer_main.h" + +#if defined(_WIN32) && !defined(_UWP) +#include "printer_win.h" +#endif + +#include + +#define TAG CHANNELS_TAG("printer.client") + +typedef struct _PRINTER_DEVICE PRINTER_DEVICE; +struct _PRINTER_DEVICE +{ + DEVICE device; + + rdpPrinter* printer; + + WINPR_PSLIST_HEADER pIrpList; + + HANDLE event; + HANDLE stopEvent; + + HANDLE thread; + rdpContext* rdpcontext; + char port[64]; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_create(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + rdpPrintJob* printjob = NULL; + + if (printer_dev->printer) + printjob = printer_dev->printer->CreatePrintJob(printer_dev->printer, + irp->devman->id_sequence++); + + if (printjob) + { + Stream_Write_UINT32(irp->output, printjob->id); /* FileId */ + } + else + { + Stream_Write_UINT32(irp->output, 0); /* FileId */ + irp->IoStatus = STATUS_PRINT_QUEUE_FULL; + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_close(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + rdpPrintJob* printjob = NULL; + + if (printer_dev->printer) + printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, + irp->FileId); + + if (!printjob) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else + { + printjob->Close(printjob); + } + + Stream_Zero(irp->output, 4); /* Padding(4) */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_write(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + UINT32 Length; + UINT64 Offset; + rdpPrintJob* printjob = NULL; + UINT error = CHANNEL_RC_OK; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + Stream_Seek(irp->input, 20); /* Padding */ + + if (printer_dev->printer) + printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, + irp->FileId); + + if (!printjob) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else + { + error = printjob->Write(printjob, Stream_Pointer(irp->input), Length); + } + + if (error) + { + WLog_ERR(TAG, "printjob->Write failed with error %"PRIu32"!", error); + return error; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_device_control(PRINTER_DEVICE* printer_dev, + IRP* irp) +{ + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + UINT error; + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + if ((error = printer_process_irp_create(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp_create failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_CLOSE: + if ((error = printer_process_irp_close(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp_close failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_WRITE: + if ((error = printer_process_irp_write(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp_write failed with error %"PRIu32"!", error); + return error; + } + + break; + + case IRP_MJ_DEVICE_CONTROL: + if ((error = printer_process_irp_device_control(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp_device_control failed with error %"PRIu32"!", + error); + return error; + } + + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + return irp->Complete(irp); + break; + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI printer_thread_func(LPVOID arg) +{ + IRP* irp; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*) arg; + HANDLE obj[] = {printer_dev->event, printer_dev->stopEvent}; + UINT error = CHANNEL_RC_OK; + + while (1) + { + DWORD rc = WaitForMultipleObjects(2, obj, FALSE, INFINITE); + + if (rc == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + if (rc == WAIT_OBJECT_0 + 1) + break; + else if (rc != WAIT_OBJECT_0) + continue; + + ResetEvent(printer_dev->event); + irp = (IRP*) InterlockedPopEntrySList(printer_dev->pIrpList); + + if (irp == NULL) + { + WLog_ERR(TAG, "InterlockedPopEntrySList failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if ((error = printer_process_irp(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp failed with error %"PRIu32"!", error); + break; + } + } + + if (error && printer_dev->rdpcontext) + setChannelError(printer_dev->rdpcontext, error, + "printer_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_irp_request(DEVICE* device, IRP* irp) +{ + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*) device; + InterlockedPushEntrySList(printer_dev->pIrpList, &(irp->ItemEntry)); + SetEvent(printer_dev->event); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_free(DEVICE* device) +{ + IRP* irp; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*) device; + UINT error; + SetEvent(printer_dev->stopEvent); + + if (WaitForSingleObject(printer_dev->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + return error; + } + + while ((irp = (IRP*) InterlockedPopEntrySList(printer_dev->pIrpList)) != NULL) + irp->Discard(irp); + + CloseHandle(printer_dev->thread); + CloseHandle(printer_dev->stopEvent); + CloseHandle(printer_dev->event); + _aligned_free(printer_dev->pIrpList); + + if (printer_dev->printer) + printer_dev->printer->Free(printer_dev->printer); + + Stream_Free(printer_dev->device.data, TRUE); + free(printer_dev); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT printer_register(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, + rdpPrinter* printer) +{ + UINT32 Flags; + int DriverNameLen; + WCHAR* DriverName = NULL; + int PrintNameLen; + WCHAR* PrintName = NULL; + UINT32 CachedFieldsLen; + BYTE* CachedPrinterConfigData; + PRINTER_DEVICE* printer_dev; + UINT error; + printer_dev = (PRINTER_DEVICE*) calloc(1, sizeof(PRINTER_DEVICE)); + + if (!printer_dev) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sprintf_s(printer_dev->port, sizeof(printer_dev->port), "PRN%d", printer->id); + printer_dev->device.type = RDPDR_DTYP_PRINT; + printer_dev->device.name = printer_dev->port; + printer_dev->device.IRPRequest = printer_irp_request; + printer_dev->device.Free = printer_free; + printer_dev->rdpcontext = pEntryPoints->rdpcontext; + printer_dev->printer = printer; + CachedFieldsLen = 0; + CachedPrinterConfigData = NULL; + Flags = 0; + + if (printer->is_default) + Flags |= RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER; + + DriverNameLen = ConvertToUnicode(CP_UTF8, 0, printer->driver, -1, &DriverName, + 0) * 2; + PrintNameLen = ConvertToUnicode(CP_UTF8, 0, printer->name, -1, &PrintName, + 0) * 2; + printer_dev->device.data = Stream_New(NULL, + 28 + DriverNameLen + PrintNameLen + CachedFieldsLen); + + if (!printer_dev->device.data) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + free(DriverName); + free(PrintName); + goto error_out; + } + + Stream_Write_UINT32(printer_dev->device.data, Flags); + Stream_Write_UINT32(printer_dev->device.data, 0); /* CodePage, reserved */ + Stream_Write_UINT32(printer_dev->device.data, 0); /* PnPNameLen */ + Stream_Write_UINT32(printer_dev->device.data, DriverNameLen + 2); + Stream_Write_UINT32(printer_dev->device.data, PrintNameLen + 2); + Stream_Write_UINT32(printer_dev->device.data, CachedFieldsLen); + Stream_Write(printer_dev->device.data, DriverName, DriverNameLen); + Stream_Write_UINT16(printer_dev->device.data, 0); + Stream_Write(printer_dev->device.data, PrintName, PrintNameLen); + Stream_Write_UINT16(printer_dev->device.data, 0); + + if (CachedFieldsLen > 0) + { + Stream_Write(printer_dev->device.data, CachedPrinterConfigData, + CachedFieldsLen); + } + + free(DriverName); + free(PrintName); + printer_dev->pIrpList = (WINPR_PSLIST_HEADER) _aligned_malloc(sizeof( + WINPR_SLIST_HEADER), MEMORY_ALLOCATION_ALIGNMENT); + + if (!printer_dev->pIrpList) + { + WLog_ERR(TAG, "_aligned_malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + InitializeSListHead(printer_dev->pIrpList); + + if (!(printer_dev->event = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + if (!(printer_dev->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, + (DEVICE*) printer_dev))) + { + WLog_ERR(TAG, "RegisterDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + if (!(printer_dev->thread = CreateThread(NULL, 0, printer_thread_func, (void*) printer_dev, 0, + NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + printer_free(&printer_dev->device); + return error; +} + +#ifdef BUILTIN_CHANNELS +#define DeviceServiceEntry printer_DeviceServiceEntry +#else +#define DeviceServiceEntry FREERDP_API DeviceServiceEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + int i; + char* name; + char* driver_name; + rdpPrinter* printer; + rdpPrinter** printers; + RDPDR_PRINTER* device; + rdpPrinterDriver* driver = NULL; + UINT error; +#ifdef WITH_CUPS + driver = printer_cups_get_driver(); +#endif +#if defined(_WIN32) && !defined(_UWP) + driver = printer_win_get_driver(); +#endif + + if (!driver) + { + WLog_ERR(TAG, "Could not get a printer driver!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + device = (RDPDR_PRINTER*) pEntryPoints->device; + name = device->Name; + driver_name = device->DriverName; + + if (name && name[0]) + { + printer = driver->GetPrinter(driver, name, driver_name); + + if (!printer) + { + WLog_ERR(TAG, "Could not get printer %s!", name); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if ((error = printer_register(pEntryPoints, printer))) + { + WLog_ERR(TAG, "printer_register failed with error %"PRIu32"!", error); + return error; + } + } + else + { + printers = driver->EnumPrinters(driver); + + for (i = 0; printers[i]; i++) + { + printer = printers[i]; + + if ((error = printer_register(pEntryPoints, printer))) + { + WLog_ERR(TAG, "printer_register failed with error %"PRIu32"!", error); + free(printers); + return error; + } + } + + free(printers); + } + + return CHANNEL_RC_OK; +} diff --git a/channels/printer/client/printer_main.h b/channels/printer/client/printer_main.h new file mode 100644 index 0000000..dfb56d8 --- /dev/null +++ b/channels/printer/client/printer_main.h @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_PRINTER_CLIENT_MAIN_H +#define FREERDP_CHANNEL_PRINTER_CLIENT_MAIN_H + +#include + +typedef struct rdp_printer_driver rdpPrinterDriver; +typedef struct rdp_printer rdpPrinter; +typedef struct rdp_print_job rdpPrintJob; + +typedef rdpPrinter** (*pcEnumPrinters) (rdpPrinterDriver* driver); +typedef rdpPrinter* (*pcGetPrinter) (rdpPrinterDriver* driver, const char* name, const char* driverName); + +struct rdp_printer_driver +{ + pcEnumPrinters EnumPrinters; + pcGetPrinter GetPrinter; +}; + +typedef rdpPrintJob* (*pcCreatePrintJob) (rdpPrinter* printer, UINT32 id); +typedef rdpPrintJob* (*pcFindPrintJob) (rdpPrinter* printer, UINT32 id); +typedef void (*pcFreePrinter) (rdpPrinter* printer); + +struct rdp_printer +{ + int id; + char* name; + char* driver; + BOOL is_default; + + pcCreatePrintJob CreatePrintJob; + pcFindPrintJob FindPrintJob; + pcFreePrinter Free; +}; + +typedef UINT (*pcWritePrintJob) (rdpPrintJob* printjob, BYTE* data, int size); +typedef void (*pcClosePrintJob) (rdpPrintJob* printjob); + +struct rdp_print_job +{ + UINT32 id; + rdpPrinter* printer; + + pcWritePrintJob Write; + pcClosePrintJob Close; +}; + +#endif /* FREERDP_CHANNEL_PRINTER_CLIENT_MAIN_H */ diff --git a/channels/printer/client/printer_win.c b/channels/printer/client/printer_win.c new file mode 100644 index 0000000..665a851 --- /dev/null +++ b/channels/printer/client/printer_win.c @@ -0,0 +1,341 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - WIN driver + * + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "printer_main.h" + +#include "printer_win.h" + +typedef struct rdp_win_printer_driver rdpWinPrinterDriver; +typedef struct rdp_win_printer rdpWinPrinter; +typedef struct rdp_win_print_job rdpWinPrintJob; + +struct rdp_win_printer_driver +{ + rdpPrinterDriver driver; + + int id_sequence; +}; + +struct rdp_win_printer +{ + rdpPrinter printer; + HANDLE hPrinter; + rdpWinPrintJob* printjob; +}; + +struct rdp_win_print_job +{ + rdpPrintJob printjob; + DOC_INFO_1 di; + DWORD handle; + + void* printjob_object; + int printjob_id; +}; + +static void printer_win_get_printjob_name(char* buf, int size) +{ + time_t tt; + struct tm* t; + + tt = time(NULL); + t = localtime(&tt); + sprintf_s(buf, size - 1, "FreeRDP Print Job %d%02d%02d%02d%02d%02d", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_win_write_printjob(rdpPrintJob* printjob, BYTE* data, int size) +{ + rdpWinPrintJob* win_printjob = (rdpWinPrintJob*) printjob; + + LPVOID pBuf = data; + DWORD cbBuf = size; + DWORD pcWritten; + + if(!WritePrinter(((rdpWinPrinter*)printjob->printer)->hPrinter, pBuf, cbBuf, &pcWritten)) + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +static void printer_win_close_printjob(rdpPrintJob* printjob) +{ + rdpWinPrintJob* win_printjob = (rdpWinPrintJob*) printjob; + + if (!EndPagePrinter(((rdpWinPrinter*) printjob->printer)->hPrinter)) + { + + } + + if (!ClosePrinter(((rdpWinPrinter*) printjob->printer)->hPrinter)) + { + + } + + ((rdpWinPrinter*) printjob->printer)->printjob = NULL; + + free(win_printjob); +} + +static rdpPrintJob* printer_win_create_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; + rdpWinPrintJob* win_printjob; + + if (win_printer->printjob != NULL) + return NULL; + + win_printjob = (rdpWinPrintJob*) calloc(1, sizeof(rdpWinPrintJob)); + if (!win_printjob) + return NULL; + + win_printjob->printjob.id = id; + win_printjob->printjob.printer = printer; + win_printjob->di.pDocName = L"FREERDPjob"; + win_printjob->di.pDatatype= NULL; + win_printjob->di.pOutputFile = NULL; + + win_printjob->handle = StartDocPrinter(win_printer->hPrinter, 1, (LPBYTE) &(win_printjob->di)); + + if (!win_printjob->handle) + { + free(win_printjob); + return NULL; + } + + if (!StartPagePrinter(win_printer->hPrinter)) + { + free(win_printjob); + return NULL; + } + + win_printjob->printjob.Write = printer_win_write_printjob; + win_printjob->printjob.Close = printer_win_close_printjob; + + win_printer->printjob = win_printjob; + + return (rdpPrintJob*) win_printjob; +} + +static rdpPrintJob* printer_win_find_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*) printer; + + if (!win_printer->printjob) + return NULL; + + if (win_printer->printjob->printjob.id != id) + return NULL; + + return (rdpPrintJob*) win_printer->printjob; +} + +static void printer_win_free_printer(rdpPrinter* printer) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*) printer; + + if (win_printer->printjob) + win_printer->printjob->printjob.Close((rdpPrintJob*) win_printer->printjob); + + free(printer->name); + free(printer->driver); + free(printer); +} + +static rdpPrinter* printer_win_new_printer(rdpWinPrinterDriver* win_driver, + const WCHAR* name, const WCHAR* drivername, BOOL is_default) +{ + rdpWinPrinter* win_printer; + DWORD needed = 0; + int status; + PRINTER_INFO_2 *prninfo=NULL; + + win_printer = (rdpWinPrinter*) calloc(1, sizeof(rdpWinPrinter)); + if (!win_printer) + return NULL; + + win_printer->printer.id = win_driver->id_sequence++; + if (ConvertFromUnicode(CP_UTF8, 0, name, -1, &win_printer->printer.name, 0, NULL, NULL) < 1) + { + free(win_printer); + return NULL; + } + + if (!win_printer->printer.name) + { + free(win_printer); + return NULL; + } + win_printer->printer.is_default = is_default; + + win_printer->printer.CreatePrintJob = printer_win_create_printjob; + win_printer->printer.FindPrintJob = printer_win_find_printjob; + win_printer->printer.Free = printer_win_free_printer; + + if (!OpenPrinter(name, &(win_printer->hPrinter), NULL)) + { + free(win_printer->printer.name); + free(win_printer); + return NULL; + } + + /* How many memory should be allocated for printer data */ + GetPrinter(win_printer->hPrinter, 2, (LPBYTE) prninfo, 0, &needed); + if (needed == 0) + { + free(win_printer->printer.name); + free(win_printer); + return NULL; + } + + prninfo = (PRINTER_INFO_2*) GlobalAlloc(GPTR,needed); + if (!prninfo) + { + free(win_printer->printer.name); + free(win_printer); + return NULL; + } + + if (!GetPrinter(win_printer->hPrinter, 2, (LPBYTE) prninfo, needed, &needed)) + { + GlobalFree(prninfo); + free(win_printer->printer.name); + free(win_printer); + return NULL; + } + + if (drivername) + status = ConvertFromUnicode(CP_UTF8, 0, drivername, -1, &win_printer->printer.driver, 0, NULL, NULL); + else + status = ConvertFromUnicode(CP_UTF8, 0, prninfo->pDriverName, -1, &win_printer->printer.driver, 0, NULL, NULL); + if (!win_printer->printer.driver || (status <= 0)) + { + GlobalFree(prninfo); + free(win_printer->printer.name); + free(win_printer); + return NULL; + } + + return (rdpPrinter*)win_printer; +} + +static rdpPrinter** printer_win_enum_printers(rdpPrinterDriver* driver) +{ + rdpPrinter** printers; + int num_printers; + int i; + PRINTER_INFO_2* prninfo = NULL; + DWORD needed, returned; + + /* find required size for the buffer */ + EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, NULL, 0, &needed, &returned); + + + /* allocate array of PRINTER_INFO structures */ + prninfo = (PRINTER_INFO_2*) GlobalAlloc(GPTR,needed); + if (!prninfo) + return NULL; + + /* call again */ + if (!EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, (LPBYTE) prninfo, needed, &needed, &returned)) + { + + } + + printers = (rdpPrinter**) calloc((returned + 1), sizeof(rdpPrinter*)); + if (!printers) + { + GlobalFree(prninfo); + return NULL; + } + + num_printers = 0; + + for (i = 0; i < (int) returned; i++) + { + printers[num_printers++] = printer_win_new_printer((rdpWinPrinterDriver*)driver, + prninfo[i].pPrinterName, prninfo[i].pDriverName, 0); + } + + GlobalFree(prninfo); + return printers; +} + +static rdpPrinter* printer_win_get_printer(rdpPrinterDriver* driver, + const char* name, const char* driverName) +{ + WCHAR* driverNameW = NULL; + rdpWinPrinterDriver* win_driver = (rdpWinPrinterDriver*)driver; + rdpPrinter *myPrinter = NULL; + + if (driverName) + { + ConvertToUnicode(CP_UTF8, 0, driverName, -1, &driverNameW, 0); + if (!driverNameW) + return NULL; + } + + myPrinter = printer_win_new_printer(win_driver, name, driverNameW, + win_driver->id_sequence == 1 ? TRUE : FALSE); + free(driverNameW); + + return myPrinter; +} + +static rdpWinPrinterDriver* win_driver = NULL; + +rdpPrinterDriver* printer_win_get_driver(void) +{ + if (!win_driver) + { + win_driver = (rdpWinPrinterDriver*) calloc(1, sizeof(rdpWinPrinterDriver)); + if (!win_driver) + return NULL; + + win_driver->driver.EnumPrinters = printer_win_enum_printers; + win_driver->driver.GetPrinter = printer_win_get_printer; + + win_driver->id_sequence = 1; + } + + return (rdpPrinterDriver*) win_driver; +} + diff --git a/channels/printer/client/printer_win.h b/channels/printer/client/printer_win.h new file mode 100644 index 0000000..22777d0 --- /dev/null +++ b/channels/printer/client/printer_win.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - win driver + * + * Copyright 2012 Gerald Richter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_PRINTER_CLIENT_WIN_H +#define FREERDP_CHANNEL_PRINTER_CLIENT_WIN_H + +#include + +rdpPrinterDriver* printer_win_get_driver(void); + +#define PRINTER_TAG CHANNELS_TAG("printer.client") +#ifdef WITH_DEBUG_WINPR +#define DEBUG_WINPR(...) WLog_DBG(PRINTER_TAG, __VA_ARGS__) +#else +#define DEBUG_WINPR(...) do { } while (0) +#endif + +#endif /* FREERDP_CHANNEL_PRINTER_CLIENT_WIN_H */ + diff --git a/channels/printer/printer.h b/channels/printer/printer.h new file mode 100644 index 0000000..5ce8f5e --- /dev/null +++ b/channels/printer/printer.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Definition for the printer channel + * + * Copyright 2016 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_PRINTER_PRINTER_H +#define FREERDP_CHANNEL_PRINTER_PRINTER_H + +/* SERVER_PRINTER_CACHE_EVENT.cachedata */ +#define RDPDR_ADD_PRINTER_EVENT 0x00000001 +#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002 +#define RDPDR_DELETE_PRINTER_EVENT 0x00000003 +#define RDPDR_RENAME_PRINTER_EVENT 0x00000004 + +/* DR_PRN_DEVICE_ANNOUNCE.Flags */ +#define RDPDR_PRINTER_ANNOUNCE_FLAG_ASCII 0x00000001 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER 0x00000002 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER 0x00000004 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER 0x00000008 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_XPSFORMAT 0x00000010 + + +#endif /* FREERDP_CHANNEL_PRINTER_PRINTER_H */ diff --git a/channels/rail/CMakeLists.txt b/channels/rail/CMakeLists.txt new file mode 100644 index 0000000..84100dc --- /dev/null +++ b/channels/rail/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rail") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rail/ChannelOptions.cmake b/channels/rail/ChannelOptions.cmake new file mode 100644 index 0000000..76f8571 --- /dev/null +++ b/channels/rail/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "rail" TYPE "static" + DESCRIPTION "Remote Programs Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPERP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rail/client/CMakeLists.txt b/channels/rail/client/CMakeLists.txt new file mode 100644 index 0000000..c87fd2f --- /dev/null +++ b/channels/rail/client/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rail") + +set(${MODULE_PREFIX}_SRCS + ../rail_common.h + ../rail_common.c + rail_main.c + rail_main.h + rail_orders.c + rail_orders.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/rail/client/rail_main.c b/channels/rail/client/rail_main.c new file mode 100644 index 0000000..ea772dd --- /dev/null +++ b/channels/rail/client/rail_main.c @@ -0,0 +1,963 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Armin Novak + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "rail_orders.h" +#include "rail_main.h" + +RailClientContext* rail_get_client_interface(railPlugin* rail) +{ + RailClientContext* pInterface; + + if (!rail) + return NULL; + + pInterface = (RailClientContext*) rail->channelEntryPoints.pInterface; + return pInterface; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send(railPlugin* rail, wStream* s) +{ + UINT status; + + if (!rail) + return CHANNEL_RC_BAD_INIT_HANDLE; + + status = rail->channelEntryPoints.pVirtualChannelWriteEx(rail->InitHandle, rail->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_GetPosition(s), s); + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_channel_data(railPlugin* rail, void* data, size_t length) +{ + wStream* s = NULL; + + if (!rail || !data) + return ERROR_INVALID_PARAMETER; + + s = Stream_New(NULL, length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(s, data, length); + return rail_send(rail, s); +} + +/** + * Callback Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_execute(RailClientContext* context, + const RAIL_EXEC_ORDER* exec) +{ + char* exeOrFile; + UINT error; + railPlugin* rail; + UINT16 flags; + RAIL_UNICODE_STRING ruExeOrFile = { 0 }; + RAIL_UNICODE_STRING ruWorkingDir = { 0 }; + RAIL_UNICODE_STRING ruArguments = { 0 }; + + if (!context || !exec) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + exeOrFile = exec->RemoteApplicationProgram; + flags = exec->flags; + + if (!exeOrFile) + return ERROR_INVALID_PARAMETER; + + if (strnlen(exeOrFile, MAX_PATH) >= 2) + { + if (strncmp(exeOrFile, "||", 2) != 0) + flags |= RAIL_EXEC_FLAG_FILE; + } + + if (!rail_string_to_unicode_string(exec->RemoteApplicationProgram, + &ruExeOrFile) || /* RemoteApplicationProgram */ + !rail_string_to_unicode_string(exec->RemoteApplicationWorkingDir, + &ruWorkingDir) || /* ShellWorkingDirectory */ + !rail_string_to_unicode_string(exec->RemoteApplicationArguments, + &ruArguments)) /* RemoteApplicationCmdLine */ + error = ERROR_INTERNAL_ERROR; + else + error = rail_send_client_exec_order(rail, flags, &ruExeOrFile, &ruWorkingDir, &ruArguments); + + free(ruExeOrFile.string); + free(ruWorkingDir.string); + free(ruArguments.string); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_activate(RailClientContext* context, + const RAIL_ACTIVATE_ORDER* activate) +{ + railPlugin* rail; + + if (!context || !activate) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_activate_order(rail, activate); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_client_sysparam(RailClientContext* context, + RAIL_SYSPARAM_ORDER* sysparam) +{ + wStream* s; + size_t length = RAIL_SYSPARAM_ORDER_LENGTH; + railPlugin* rail; + UINT error; + + if (!context || !sysparam) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + + switch (sysparam->param) + { + case SPI_SET_DRAG_FULL_WINDOWS: + case SPI_SET_KEYBOARD_CUES: + case SPI_SET_KEYBOARD_PREF: + case SPI_SET_MOUSE_BUTTON_SWAP: + length += 1; + break; + + case SPI_SET_WORK_AREA: + case SPI_DISPLAY_CHANGE: + case SPI_TASKBAR_POS: + length += 8; + break; + + case SPI_SET_HIGH_CONTRAST: + length += sysparam->highContrast.colorSchemeLength + 10; + break; + + default: + length += 8; + break; + } + + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rail_write_client_sysparam_order(s, sysparam))) + { + WLog_ERR(TAG, "rail_write_client_sysparam_order failed with error %"PRIu32"!", error); + Stream_Free(s, TRUE); + return error; + } + + if ((error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_SYSPARAM))) + { + WLog_ERR(TAG, "rail_send_pdu failed with error %"PRIu32"!", error); + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_param(RailClientContext* context, + const RAIL_SYSPARAM_ORDER* sysInParam) +{ + UINT error = CHANNEL_RC_OK; + RAIL_SYSPARAM_ORDER sysparam; + + if (!context || !sysInParam) + return ERROR_INVALID_PARAMETER; + + sysparam = *sysInParam; + + if (sysparam.params & SPI_MASK_SET_HIGH_CONTRAST) + { + sysparam.param = SPI_SET_HIGH_CONTRAST; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_TASKBAR_POS) + { + sysparam.param = SPI_TASKBAR_POS; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) + { + sysparam.param = SPI_SET_MOUSE_BUTTON_SWAP; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_KEYBOARD_PREF) + { + sysparam.param = SPI_SET_KEYBOARD_PREF; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_DRAG_FULL_WINDOWS) + { + sysparam.param = SPI_SET_DRAG_FULL_WINDOWS; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_KEYBOARD_CUES) + { + sysparam.param = SPI_SET_KEYBOARD_CUES; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_WORK_AREA) + { + sysparam.param = SPI_SET_WORK_AREA; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %"PRIu32"!", error); + return error; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_system_param(RailClientContext* context, + const RAIL_SYSPARAM_ORDER* sysparam) +{ + if (!context || !sysparam) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_command(RailClientContext* context, + const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + railPlugin* rail; + + if (!context || !syscommand) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_syscommand_order(rail, syscommand); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_handshake(RailClientContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + railPlugin* rail; + + if (!context || !handshake) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_handshake_order(rail, handshake); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_handshake(RailClientContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + if (!context || !handshake) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_handshake_ex(RailClientContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + railPlugin* rail; + + if (!context || !handshakeEx) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_handshake_ex_order(rail, handshakeEx); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_handshake_ex(RailClientContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + if (!context || !handshakeEx) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_notify_event(RailClientContext* context, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + railPlugin* rail; + + if (!context || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_notify_event_order(rail, notifyEvent); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_window_move(RailClientContext* context, + const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + railPlugin* rail; + + if (!context || !windowMove) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_window_move_order(rail, windowMove); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_local_move_size(RailClientContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + if (!context || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_min_max_info(RailClientContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + if (!context || !minMaxInfo) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_information(RailClientContext* context, + const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + railPlugin* rail; + + if (!context || !clientStatus) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_status_order(rail, clientStatus); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_menu(RailClientContext* context, + const RAIL_SYSMENU_ORDER* sysmenu) +{ + railPlugin* rail; + + if (!context || !sysmenu) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_sysmenu_order(rail, sysmenu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_language_bar_info(RailClientContext* context, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + railPlugin* rail; + + if (!context || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_langbar_info_order(rail, langBarInfo); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_language_bar_info(RailClientContext* context, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + if (!context || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_execute_result(RailClientContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + if (!context || !execResult) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_get_appid_request(RailClientContext* context, + const RAIL_GET_APPID_REQ_ORDER* getAppIdReq) +{ + railPlugin* rail; + + if (!context || !getAppIdReq || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*) context->handle; + return rail_send_client_get_appid_req_order(rail, getAppIdReq); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_get_appid_response(RailClientContext* context, + const RAIL_GET_APPID_RESP_ORDER* getAppIdResp) +{ + if (!context || !getAppIdResp) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; /* stub - should be registered by client */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_virtual_channel_event_data_received(railPlugin* rail, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (rail->data_in) + Stream_Free(rail->data_in, TRUE); + + rail->data_in = Stream_New(NULL, totalLength); + + if (!rail->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = rail->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, (int) dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "rail_plugin_process_received: read error"); + return ERROR_INTERNAL_ERROR; + } + + rail->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(rail->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rail_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + railPlugin* rail = (railPlugin*) lpUserParam; + + if (!rail || (rail->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = rail_virtual_channel_event_data_received(rail, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, "rail_virtual_channel_event_data_received failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rail->rdpcontext) + setChannelError(rail->rdpcontext, error, + "rail_virtual_channel_open_event reported an error"); + + return; +} + +static DWORD WINAPI rail_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + railPlugin* rail = (railPlugin*) arg; + UINT error = CHANNEL_RC_OK; + + while (1) + { + if (!MessageQueue_Wait(rail->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(rail->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + error = rail_order_recv(rail, data); + Stream_Free(data, TRUE); + + if (error) + { + WLog_ERR(TAG, "rail_order_recv failed with error %"PRIu32"!", error); + break; + } + } + } + + if (error && rail->rdpcontext) + setChannelError(rail->rdpcontext, error, + "rail_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_virtual_channel_event_connected(railPlugin* rail, LPVOID pData, + UINT32 dataLength) +{ + UINT status; + status = rail->channelEntryPoints.pVirtualChannelOpenEx(rail->InitHandle, + &rail->OpenHandle, rail->channelDef.name, rail_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpen failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + rail->queue = MessageQueue_New(NULL); + + if (!rail->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(rail->thread = CreateThread(NULL, 0, + rail_virtual_channel_client_thread, (void*) rail, 0, + NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + MessageQueue_Free(rail->queue); + rail->queue = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_virtual_channel_event_disconnected(railPlugin* rail) +{ + UINT rc; + + if (rail->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (MessageQueue_PostQuit(rail->queue, 0) + && (WaitForSingleObject(rail->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", rc); + return rc; + } + + MessageQueue_Free(rail->queue); + CloseHandle(rail->thread); + rail->queue = NULL; + rail->thread = NULL; + rc = rail->channelEntryPoints.pVirtualChannelCloseEx(rail->InitHandle, rail->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + return rc; + } + + rail->OpenHandle = 0; + + if (rail->data_in) + { + Stream_Free(rail->data_in, TRUE); + rail->data_in = NULL; + } + + return CHANNEL_RC_OK; +} + +static void rail_virtual_channel_event_terminated(railPlugin* rail) +{ + rail->InitHandle = 0; + free(rail->context); + free(rail); +} + +static VOID VCAPITYPE rail_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + railPlugin* rail = (railPlugin*) lpUserParam; + + if (!rail || (rail->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = rail_virtual_channel_event_connected(rail, pData, dataLength))) + WLog_ERR(TAG, "rail_virtual_channel_event_connected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rail_virtual_channel_event_disconnected(rail))) + WLog_ERR(TAG, "rail_virtual_channel_event_disconnected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rail_virtual_channel_event_terminated(rail); + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + break; + } + + if (error && rail->rdpcontext) + setChannelError(rail->rdpcontext, error, "rail_virtual_channel_init_event_ex reported an error"); +} + +/* rail is always built-in */ +#define VirtualChannelEntryEx rail_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + railPlugin* rail; + RailClientContext* context = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + BOOL isFreerdp = FALSE; + rail = (railPlugin*) calloc(1, sizeof(railPlugin)); + + if (!rail) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + rail->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | + CHANNEL_OPTION_SHOW_PROTOCOL; + sprintf_s(rail->channelDef.name, ARRAYSIZE(rail->channelDef.name), "rail"); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (RailClientContext*) calloc(1, sizeof(RailClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + free(rail); + return FALSE; + } + + context->handle = (void*) rail; + context->custom = NULL; + context->ClientExecute = rail_client_execute; + context->ClientActivate = rail_client_activate; + context->ClientSystemParam = rail_client_system_param; + context->ServerSystemParam = rail_server_system_param; + context->ClientSystemCommand = rail_client_system_command; + context->ClientHandshake = rail_client_handshake; + context->ServerHandshake = rail_server_handshake; + context->ClientHandshakeEx = rail_client_handshake_ex; + context->ServerHandshakeEx = rail_server_handshake_ex; + context->ClientNotifyEvent = rail_client_notify_event; + context->ClientWindowMove = rail_client_window_move; + context->ServerLocalMoveSize = rail_server_local_move_size; + context->ServerMinMaxInfo = rail_server_min_max_info; + context->ClientInformation = rail_client_information; + context->ClientSystemMenu = rail_client_system_menu; + context->ClientLanguageBarInfo = rail_client_language_bar_info; + context->ServerLanguageBarInfo = rail_server_language_bar_info; + context->ServerExecuteResult = rail_server_execute_result; + context->ClientGetAppIdRequest = rail_client_get_appid_request; + context->ServerGetAppIdResponse = rail_server_get_appid_response; + rail->rdpcontext = pEntryPointsEx->context; + rail->context = context; + isFreerdp = TRUE; + } + + rail->log = WLog_Get("com.freerdp.channels.rail.client"); + WLog_Print(rail->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(rail->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rail->InitHandle = pInitHandle; + rc = rail->channelEntryPoints.pVirtualChannelInitEx(rail, context, pInitHandle, + &rail->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rail_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + goto error_out; + } + + rail->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + + if (isFreerdp) + free(rail->context); + + free(rail); + return FALSE; +} diff --git a/channels/rail/client/rail_main.h b/channels/rail/client/rail_main.h new file mode 100644 index 0000000..86e752f --- /dev/null +++ b/channels/rail/client/rail_main.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../rail_common.h" + +struct rail_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + RailClientContext* context; + + wLog* log; + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + rdpContext* rdpcontext; +}; +typedef struct rail_plugin railPlugin; + +RailClientContext* rail_get_client_interface(railPlugin* rail); +UINT rail_send_channel_data(railPlugin* rail, void* data, size_t length); + +#endif /* FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H */ diff --git a/channels/rail/client/rail_orders.c b/channels/rail/client/rail_orders.c new file mode 100644 index 0000000..7e6cf42 --- /dev/null +++ b/channels/rail/client/rail_orders.c @@ -0,0 +1,1317 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Applications Integrated Locally (RAIL) Orders + * + * Copyright 2009 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Armin Novak + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "rail_orders.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string) +{ + if (!s || !unicode_string) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 2 + unicode_string->length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, unicode_string->length); /* cbString (2 bytes) */ + Stream_Write(s, unicode_string->string, unicode_string->length); /* string */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string) +{ + size_t length; + + if (!s || !unicode_string) + return ERROR_INVALID_PARAMETER; + + length = unicode_string->length; + + if (length > 0) + { + if (!Stream_EnsureRemainingCapacity(s, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(s, unicode_string->string, length); /* string */ + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType) +{ + UINT16 orderLength; + + if (!rail || !s) + return ERROR_INVALID_PARAMETER; + + orderLength = (UINT16) Stream_GetPosition(s); + Stream_SetPosition(s, 0); + rail_write_pdu_header(s, orderType, orderLength); + Stream_SetPosition(s, orderLength); + WLog_Print(rail->log, WLOG_DEBUG, "Sending %s PDU, length: %"PRIu16"", + RAIL_ORDER_TYPE_STRINGS[((orderType & 0xF0) >> 3) + (orderType & 0x0F)], orderLength); + return rail_send_channel_data(rail, Stream_Buffer(s), orderLength); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_high_contrast(wStream* s, const RAIL_HIGH_CONTRAST* highContrast) +{ + UINT32 colorSchemeLength; + + if (!s || !highContrast) + return ERROR_INVALID_PARAMETER; + + colorSchemeLength = highContrast->colorScheme.length + 2; + Stream_Write_UINT32(s, highContrast->flags); /* flags (4 bytes) */ + Stream_Write_UINT32(s, colorSchemeLength); /* colorSchemeLength (4 bytes) */ + return rail_write_unicode_string(s, &highContrast->colorScheme); /* colorScheme */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_exec_result_order(wStream* s, RAIL_EXEC_RESULT_ORDER* execResult) +{ + if (!s || !execResult) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, execResult->flags); /* flags (2 bytes) */ + Stream_Read_UINT16(s, execResult->execResult); /* execResult (2 bytes) */ + Stream_Read_UINT32(s, execResult->rawResult); /* rawResult (4 bytes) */ + Stream_Seek_UINT16(s); /* padding (2 bytes) */ + return rail_read_unicode_string(s, + &execResult->exeOrFile) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; /* exeOrFile */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam) +{ + BYTE body; + + if (!s || !sysparam) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, sysparam->param); /* systemParam (4 bytes) */ + Stream_Read_UINT8(s, body); /* body (1 byte) */ + + switch (sysparam->param) + { + case SPI_SET_SCREEN_SAVE_ACTIVE: + sysparam->setScreenSaveActive = (body != 0) ? TRUE : FALSE; + break; + + case SPI_SET_SCREEN_SAVE_SECURE: + sysparam->setScreenSaveSecure = (body != 0) ? TRUE : FALSE; + break; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_minmaxinfo_order(wStream* s, RAIL_MINMAXINFO_ORDER* minmaxinfo) +{ + if (!s || !minmaxinfo) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, minmaxinfo->windowId); /* windowId (4 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxWidth); /* maxWidth (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxHeight); /* maxHeight (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxPosX); /* maxPosX (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxPosY); /* maxPosY (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->minTrackWidth); /* minTrackWidth (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->minTrackHeight); /* minTrackHeight (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxTrackWidth); /* maxTrackWidth (2 bytes) */ + Stream_Read_UINT16(s, minmaxinfo->maxTrackHeight); /* maxTrackHeight (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_localmovesize_order(wStream* s, + RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + UINT16 isMoveSizeStart; + + if (!s || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, localMoveSize->windowId); /* windowId (4 bytes) */ + Stream_Read_UINT16(s, isMoveSizeStart); /* isMoveSizeStart (2 bytes) */ + localMoveSize->isMoveSizeStart = (isMoveSizeStart != 0) ? TRUE : FALSE; + Stream_Read_UINT16(s, localMoveSize->moveSizeType); /* moveSizeType (2 bytes) */ + Stream_Read_UINT16(s, localMoveSize->posX); /* posX (2 bytes) */ + Stream_Read_UINT16(s, localMoveSize->posY); /* posY (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_get_appid_resp_order(wStream* s, + RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + if (!s || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 516) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, getAppidResp->windowId); /* windowId (4 bytes) */ + Stream_Read(s, (BYTE*) & (getAppidResp->applicationId), + 512); /* applicationId (256 UNICODE chars) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!s || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */ + return CHANNEL_RC_OK; +} + +static UINT rail_write_client_status_order(wStream* s, const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + if (!s || !clientStatus) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, clientStatus->flags); /* flags (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_client_exec_order(wStream* s, UINT16 flags, + const RAIL_UNICODE_STRING* exeOrFile, const RAIL_UNICODE_STRING* workingDir, + const RAIL_UNICODE_STRING* arguments) +{ + UINT error; + + if (!s || !exeOrFile || !workingDir || !arguments) + return ERROR_INVALID_PARAMETER; + + /* [MS-RDPERP] 2.2.2.3.1 Client Execute PDU (TS_RAIL_ORDER_EXEC) + * Check argument limits */ + if ((exeOrFile->length > 520) || (workingDir->length > 520) || + (arguments->length > 16000)) + { + WLog_ERR(TAG, + "TS_RAIL_ORDER_EXEC argument limits exceeded: ExeOrFile=%"PRIu16" [max=520], WorkingDir=%"PRIu16" [max=520], Arguments=%"PRIu16" [max=16000]", + exeOrFile->length, workingDir->length, arguments->length); + return ERROR_BAD_ARGUMENTS; + } + + Stream_Write_UINT16(s, flags); /* flags (2 bytes) */ + Stream_Write_UINT16(s, exeOrFile->length); /* exeOrFileLength (2 bytes) */ + Stream_Write_UINT16(s, workingDir->length); /* workingDirLength (2 bytes) */ + Stream_Write_UINT16(s, arguments->length); /* argumentsLength (2 bytes) */ + + if ((error = rail_write_unicode_string_value(s, exeOrFile))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %"PRIu32"", error); + return error; + } + + if ((error = rail_write_unicode_string_value(s, workingDir))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %"PRIu32"", error); + return error; + } + + if ((error = rail_write_unicode_string_value(s, arguments))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %"PRIu32"", error); + return error; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_write_client_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam) +{ + BYTE body; + UINT error = CHANNEL_RC_OK; + + if (!s || !sysparam) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, sysparam->param); /* systemParam (4 bytes) */ + + switch (sysparam->param) + { + case SPI_SET_DRAG_FULL_WINDOWS: + body = sysparam->dragFullWindows ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_KEYBOARD_CUES: + body = sysparam->keyboardCues ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_KEYBOARD_PREF: + body = sysparam->keyboardPref ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_MOUSE_BUTTON_SWAP: + body = sysparam->mouseButtonSwap ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_WORK_AREA: + Stream_Write_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */ + break; + + case SPI_DISPLAY_CHANGE: + Stream_Write_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */ + break; + + case SPI_TASKBAR_POS: + Stream_Write_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */ + break; + + case SPI_SET_HIGH_CONTRAST: + error = rail_write_high_contrast(s, &sysparam->highContrast); + break; + } + + return error; +} + +static UINT rail_write_client_activate_order(wStream* s, const RAIL_ACTIVATE_ORDER* activate) +{ + BYTE enabled; + + if (!s || !activate) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, activate->windowId); /* windowId (4 bytes) */ + enabled = activate->enabled ? 1 : 0; + Stream_Write_UINT8(s, enabled); /* enabled (1 byte) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_sysmenu_order(wStream* s, const RAIL_SYSMENU_ORDER* sysmenu) +{ + if (!s || !sysmenu) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, sysmenu->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT16(s, sysmenu->left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysmenu->top); /* top (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_syscommand_order(wStream* s, const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + if (!s || !syscommand) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, syscommand->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT16(s, syscommand->command); /* command (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_notify_event_order(wStream* s, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + if (!s || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, notifyEvent->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT32(s, notifyEvent->notifyIconId); /* notifyIconId (4 bytes) */ + Stream_Write_UINT32(s, notifyEvent->message); /* notifyIconId (4 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_window_move_order(wStream* s, + const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + if (!s || !windowMove) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, windowMove->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT16(s, windowMove->left); /* left (2 bytes) */ + Stream_Write_UINT16(s, windowMove->top); /* top (2 bytes) */ + Stream_Write_UINT16(s, windowMove->right); /* right (2 bytes) */ + Stream_Write_UINT16(s, windowMove->bottom); /* bottom (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_get_appid_req_order(wStream* s, + const RAIL_GET_APPID_REQ_ORDER* getAppidReq) +{ + if (!s || !getAppidReq) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidReq->windowId); /* windowId (4 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!s || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_order(railPlugin* rail, RAIL_HANDSHAKE_ORDER* handshake, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !handshake || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_handshake_order(s, handshake))) + { + WLog_ERR(TAG, "rail_read_handshake_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerHandshake, error, context, handshake); + + if (error) + WLog_ERR(TAG, "context.ServerHandshake failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_ex_order(railPlugin* rail, RAIL_HANDSHAKE_EX_ORDER* handshakeEx, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !handshakeEx || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_handshake_ex_order(s, handshakeEx))) + { + WLog_ERR(TAG, "rail_read_handshake_ex_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ClientHandshakeEx, error, context, handshakeEx); + + if (error) + WLog_ERR(TAG, "context.ClientHandshakeEx failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_exec_result_order(railPlugin* rail, RAIL_EXEC_RESULT_ORDER* execResult, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !execResult || !s) + return ERROR_INVALID_PARAMETER; + + ZeroMemory(execResult, sizeof(RAIL_EXEC_RESULT_ORDER)); + + if ((error = rail_read_server_exec_result_order(s, execResult))) + { + WLog_ERR(TAG, "rail_read_server_exec_result_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerExecuteResult, error, context, execResult); + + if (error) + WLog_ERR(TAG, "context.ServerExecuteResult failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_sysparam_order(railPlugin* rail, RAIL_SYSPARAM_ORDER* sysparam, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !sysparam || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_sysparam_order(s, sysparam))) + { + WLog_ERR(TAG, "rail_read_server_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerSystemParam, error, context, sysparam); + + if (error) + WLog_ERR(TAG, "context.ServerSystemParam failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_minmaxinfo_order(railPlugin* rail, RAIL_MINMAXINFO_ORDER* minMaxInfo, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !minMaxInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_minmaxinfo_order(s, minMaxInfo))) + { + WLog_ERR(TAG, "rail_read_server_minmaxinfo_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerMinMaxInfo, error, context, minMaxInfo); + + if (error) + WLog_ERR(TAG, "context.ServerMinMaxInfo failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_localmovesize_order(railPlugin* rail, + RAIL_LOCALMOVESIZE_ORDER* localMoveSize, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !localMoveSize || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_localmovesize_order(s, localMoveSize))) + { + WLog_ERR(TAG, "rail_read_server_localmovesize_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerLocalMoveSize, error, context, localMoveSize); + + if (error) + WLog_ERR(TAG, "context.ServerLocalMoveSize failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_get_appid_resp_order(railPlugin* rail, + RAIL_GET_APPID_RESP_ORDER* getAppIdResp, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !getAppIdResp || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_get_appid_resp_order(s, getAppIdResp))) + { + WLog_ERR(TAG, "rail_read_server_get_appid_resp_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerGetAppIdResponse, error, context, getAppIdResp); + + if (error) + WLog_ERR(TAG, "context.ServerGetAppIdResponse failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_langbar_info_order(railPlugin* rail, RAIL_LANGBAR_INFO_ORDER* langBarInfo, + wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT error; + + if (!context || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_langbar_info_order(s, langBarInfo))) + { + WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %"PRIu32"!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerLanguageBarInfo, error, context, langBarInfo); + + if (error) + WLog_ERR(TAG, "context.ServerLanguageBarInfo failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_order_recv(railPlugin* rail, wStream* s) +{ + UINT16 orderType; + UINT16 orderLength; + UINT error; + + if (!rail || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_pdu_header(s, &orderType, &orderLength))) + { + WLog_ERR(TAG, "rail_read_pdu_header failed with error %"PRIu32"!", error); + return error; + } + + WLog_Print(rail->log, WLOG_DEBUG, "Received %s PDU, length:%"PRIu16"", + RAIL_ORDER_TYPE_STRINGS[((orderType & 0xF0) >> 3) + (orderType & 0x0F)], orderLength); + + switch (orderType) + { + case RDP_RAIL_ORDER_HANDSHAKE: + { + RAIL_HANDSHAKE_ORDER handshake; + return rail_recv_handshake_order(rail, &handshake, s); + } + + case RDP_RAIL_ORDER_HANDSHAKE_EX: + { + RAIL_HANDSHAKE_EX_ORDER handshakeEx; + return rail_recv_handshake_ex_order(rail, &handshakeEx, s); + } + + case RDP_RAIL_ORDER_EXEC_RESULT: + { + RAIL_EXEC_RESULT_ORDER execResult = { 0 }; + error = rail_recv_exec_result_order(rail, &execResult, s); + free(execResult.exeOrFile.string); + return error; + } + + case RDP_RAIL_ORDER_SYSPARAM: + { + RAIL_SYSPARAM_ORDER sysparam; + return rail_recv_server_sysparam_order(rail, &sysparam, s); + } + + case RDP_RAIL_ORDER_MINMAXINFO: + { + RAIL_MINMAXINFO_ORDER minMaxInfo; + return rail_recv_server_minmaxinfo_order(rail, &minMaxInfo, s); + } + + case RDP_RAIL_ORDER_LOCALMOVESIZE: + { + RAIL_LOCALMOVESIZE_ORDER localMoveSize; + return rail_recv_server_localmovesize_order(rail, &localMoveSize, s); + } + + case RDP_RAIL_ORDER_GET_APPID_RESP: + { + RAIL_GET_APPID_RESP_ORDER getAppIdResp; + return rail_recv_server_get_appid_resp_order(rail, &getAppIdResp, s); + } + + case RDP_RAIL_ORDER_LANGBARINFO: + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + return rail_recv_langbar_info_order(rail, &langBarInfo, s); + } + + default: + WLog_ERR(TAG, "Unknown RAIL PDU order reveived."); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake) +{ + wStream* s; + UINT error; + + if (!rail || !handshake) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_handshake_order(s, handshake); + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_HANDSHAKE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + wStream* s; + UINT error; + + if (!rail || !handshakeEx) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_handshake_ex_order(s, handshakeEx); + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_HANDSHAKE_EX); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + wStream* s; + UINT error; + + if (!rail || !clientStatus) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_CLIENT_STATUS_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_status_order(s, clientStatus); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_CLIENTSTATUS); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_exec_order(railPlugin* rail, UINT16 flags, + const RAIL_UNICODE_STRING* exeOrFile, const RAIL_UNICODE_STRING* workingDir, + const RAIL_UNICODE_STRING* arguments) +{ + wStream* s; + UINT error; + size_t length; + + if (!rail || !exeOrFile || !workingDir || !arguments) + return ERROR_INVALID_PARAMETER; + + length = RAIL_EXEC_ORDER_LENGTH + + exeOrFile->length + + workingDir->length + + arguments->length; + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rail_write_client_exec_order(s, flags, exeOrFile, workingDir, arguments))) + { + WLog_ERR(TAG, "rail_write_client_exec_order failed with error %"PRIu32"!", error); + goto out; + } + + if ((error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_EXEC))) + { + WLog_ERR(TAG, "rail_send_pdu failed with error %"PRIu32"!", error); + goto out; + } + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_client_sysparam_order(railPlugin* rail, const RAIL_SYSPARAM_ORDER* sysparam) +{ + wStream* s; + size_t length = RAIL_SYSPARAM_ORDER_LENGTH; + UINT error; + + if (!rail || !sysparam) + return ERROR_INVALID_PARAMETER; + + switch (sysparam->param) + { + case SPI_SET_DRAG_FULL_WINDOWS: + case SPI_SET_KEYBOARD_CUES: + case SPI_SET_KEYBOARD_PREF: + case SPI_SET_MOUSE_BUTTON_SWAP: + length += 1; + break; + + case SPI_SET_WORK_AREA: + case SPI_DISPLAY_CHANGE: + case SPI_TASKBAR_POS: + length += 8; + break; + + case SPI_SET_HIGH_CONTRAST: + length += sysparam->highContrast.colorSchemeLength + 10; + break; + + default: + length += 8; + break; + } + + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rail_write_client_sysparam_order(s, sysparam))) + { + WLog_ERR(TAG, "rail_write_client_sysparam_order failed with error %"PRIu32"!", error); + goto out; + } + + if ((error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_SYSPARAM))) + { + WLog_ERR(TAG, "rail_send_pdu failed with error %"PRIu32"!", error); + goto out; + } + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_client_sysparams_order(railPlugin* rail, RAIL_SYSPARAM_ORDER* sysparam) +{ + UINT error = CHANNEL_RC_OK; + + if (!rail || !sysparam) + return ERROR_INVALID_PARAMETER; + + if (sysparam->params & SPI_MASK_SET_HIGH_CONTRAST) + { + sysparam->param = SPI_SET_HIGH_CONTRAST; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_TASKBAR_POS) + { + sysparam->param = SPI_TASKBAR_POS; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) + { + sysparam->param = SPI_SET_MOUSE_BUTTON_SWAP; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_SET_KEYBOARD_PREF) + { + sysparam->param = SPI_SET_KEYBOARD_PREF; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_SET_DRAG_FULL_WINDOWS) + { + sysparam->param = SPI_SET_DRAG_FULL_WINDOWS; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_SET_KEYBOARD_CUES) + { + sysparam->param = SPI_SET_KEYBOARD_CUES; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + if (sysparam->params & SPI_MASK_SET_WORK_AREA) + { + sysparam->param = SPI_SET_WORK_AREA; + + if ((error = rail_send_client_sysparam_order(rail, sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam_order failed with error %"PRIu32"!", error); + return error; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate) +{ + wStream* s; + UINT error; + + if (!rail || !activate) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_ACTIVATE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_activate_order(s, activate); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_ACTIVATE); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu) +{ + wStream* s; + UINT error; + + if (!rail || !sysmenu) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_SYSMENU_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_sysmenu_order(s, sysmenu); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_SYSMENU); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + wStream* s; + UINT error; + + if (!rail || !syscommand) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_SYSCOMMAND_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_syscommand_order(s, syscommand); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_SYSCOMMAND); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_notify_event_order(railPlugin* rail, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + wStream* s; + UINT error; + + if (!rail || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_NOTIFY_EVENT_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_notify_event_order(s, notifyEvent); + + if (ERROR_SUCCESS == error) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_NOTIFY_EVENT); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + wStream* s; + UINT error; + + if (!rail || !windowMove) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_WINDOW_MOVE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_window_move_order(s, windowMove); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_WINDOWMOVE); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_get_appid_req_order(railPlugin* rail, + const RAIL_GET_APPID_REQ_ORDER* getAppIdReq) +{ + wStream* s; + UINT error; + + if (!rail || !getAppIdReq) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_REQ_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_get_appid_req_order(s, getAppIdReq); + + if (error == ERROR_SUCCESS) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_GET_APPID_REQ); + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_langbar_info_order(railPlugin* rail, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + wStream* s; + UINT error; + + if (!rail || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_langbar_info_order(s, langBarInfo); + + if (ERROR_SUCCESS == error) + error = rail_send_pdu(rail, s, RDP_RAIL_ORDER_LANGBARINFO); + + Stream_Free(s, TRUE); + return error; +} diff --git a/channels/rail/client/rail_orders.h b/channels/rail/client/rail_orders.h new file mode 100644 index 0000000..0b4fd93 --- /dev/null +++ b/channels/rail/client/rail_orders.h @@ -0,0 +1,55 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Applications Integrated Locally (RAIL) + * + * Copyright 2009 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H +#define FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H + +#include + +#include "rail_main.h" + +#define TAG CHANNELS_TAG("rail.client") + +UINT rail_write_client_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam); + +UINT rail_order_recv(railPlugin* rail, wStream* s); +UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType); + +UINT rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake); +UINT rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx); +UINT rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus); +UINT rail_send_client_exec_order(railPlugin* rail, UINT16 flags, + const RAIL_UNICODE_STRING* exeOrFile, const RAIL_UNICODE_STRING* workingDir, + const RAIL_UNICODE_STRING* arguments); +UINT rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate); +UINT rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu); +UINT rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand); + +UINT rail_send_client_notify_event_order(railPlugin* rail, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent); +UINT rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove); +UINT rail_send_client_get_appid_req_order(railPlugin* rail, + const RAIL_GET_APPID_REQ_ORDER* getAppIdReq); +UINT rail_send_client_langbar_info_order(railPlugin* rail, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo); + +#endif /* FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H */ diff --git a/channels/rail/rail_common.c b/channels/rail/rail_common.c new file mode 100644 index 0000000..ac0103f --- /dev/null +++ b/channels/rail/rail_common.c @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL common functions + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "rail_common.h" + +#include + +const char* const RAIL_ORDER_TYPE_STRINGS[] = +{ + "", + "Execute", + "Activate", + "System Parameters Update", + "System Command", + "Handshake", + "Notify Event", + "", + "Window Move", + "Local Move/Size", + "Min Max Info", + "Client Status", + "System Menu", + "Language Bar Info", + "Get Application ID Request", + "Get Application ID Response", + "Execute Result", + "", + "", + "", + "", + "", + "" +}; + +BOOL rail_string_to_unicode_string(const char* string, RAIL_UNICODE_STRING* unicode_string) +{ + WCHAR* buffer = NULL; + int length = 0; + free(unicode_string->string); + unicode_string->string = NULL; + unicode_string->length = 0; + + if (!string || strlen(string) < 1) + return TRUE; + + length = ConvertToUnicode(CP_UTF8, 0, string, -1, &buffer, 0); + + if ((length < 0) || ((size_t)length * sizeof(WCHAR) > UINT16_MAX)) + { + free(buffer); + return FALSE; + } + + unicode_string->string = (BYTE*) buffer; + unicode_string->length = (UINT16) length * sizeof(WCHAR); + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_pdu_header(wStream* s, UINT16* orderType, UINT16* orderLength) +{ + if (!s || !orderType || !orderLength) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, *orderType); /* orderType (2 bytes) */ + Stream_Read_UINT16(s, *orderLength); /* orderLength (2 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength) +{ + Stream_Write_UINT16(s, orderType); /* orderType (2 bytes) */ + Stream_Write_UINT16(s, orderLength); /* orderLength (2 bytes) */ +} + +wStream* rail_pdu_init(size_t length) +{ + wStream* s; + s = Stream_New(NULL, length + RAIL_PDU_HEADER_LENGTH); + + if (!s) + return NULL; + + Stream_Seek(s, RAIL_PDU_HEADER_LENGTH); + return s; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_handshake_order(wStream* s, RAIL_HANDSHAKE_ORDER* handshake) +{ + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake) +{ + Stream_Write_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */ + Stream_Read_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + Stream_Write_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */ + Stream_Write_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */ +} diff --git a/channels/rail/rail_common.h b/channels/rail/rail_common.h new file mode 100644 index 0000000..fe1bc02 --- /dev/null +++ b/channels/rail/rail_common.h @@ -0,0 +1,57 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_COMMON_H +#define FREERDP_CHANNEL_RAIL_COMMON_H + +#include + +extern const char* const RAIL_ORDER_TYPE_STRINGS[]; + +#define RAIL_PDU_HEADER_LENGTH 4 + +/* Fixed length of PDUs, excluding variable lengths */ +#define RAIL_HANDSHAKE_ORDER_LENGTH 4 /* fixed */ +#define RAIL_HANDSHAKE_EX_ORDER_LENGTH 8 /* fixed */ +#define RAIL_CLIENT_STATUS_ORDER_LENGTH 4 /* fixed */ +#define RAIL_EXEC_ORDER_LENGTH 8 /* variable */ +#define RAIL_SYSPARAM_ORDER_LENGTH 4 /* variable */ +#define RAIL_ACTIVATE_ORDER_LENGTH 5 /* fixed */ +#define RAIL_SYSMENU_ORDER_LENGTH 8 /* fixed */ +#define RAIL_SYSCOMMAND_ORDER_LENGTH 6 /* fixed */ +#define RAIL_NOTIFY_EVENT_ORDER_LENGTH 12 /* fixed */ +#define RAIL_WINDOW_MOVE_ORDER_LENGTH 12 /* fixed */ +#define RAIL_GET_APPID_REQ_ORDER_LENGTH 4 /* fixed */ +#define RAIL_LANGBAR_INFO_ORDER_LENGTH 4 /* fixed */ + +BOOL rail_string_to_unicode_string(const char* string, RAIL_UNICODE_STRING* unicode_string); +UINT rail_read_handshake_order(wStream* s, RAIL_HANDSHAKE_ORDER* handshake); +void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake); +UINT rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx); +void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx); + +wStream* rail_pdu_init(size_t length); +UINT rail_read_pdu_header(wStream* s, UINT16* orderType, UINT16* orderLength); +void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength); + +#endif /* FREERDP_CHANNEL_RAIL_COMMON_H */ diff --git a/channels/rdpdr/CMakeLists.txt b/channels/rdpdr/CMakeLists.txt new file mode 100644 index 0000000..e9b4181 --- /dev/null +++ b/channels/rdpdr/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rdpdr") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpdr/ChannelOptions.cmake b/channels/rdpdr/ChannelOptions.cmake new file mode 100644 index 0000000..9a96676 --- /dev/null +++ b/channels/rdpdr/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpdr" TYPE "static" + DESCRIPTION "Device Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEFS] [MS-RDPEPC] [MS-RDPESC] [MS-RDPESP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpdr/client/CMakeLists.txt b/channels/rdpdr/client/CMakeLists.txt new file mode 100644 index 0000000..e41cc3c --- /dev/null +++ b/channels/rdpdr/client/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2016 Inuvika Inc. +# Copyright 2016 David PHAM-VAN +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpdr") + +set(${MODULE_PREFIX}_SRCS + irp.c + irp.h + devman.c + devman.h + rdpdr_main.c + rdpdr_main.h + rdpdr_capabilities.c + rdpdr_capabilities.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) +if(APPLE AND (NOT IOS)) + find_library(CORESERVICES_LIBRARY CoreServices) + target_link_libraries(${MODULE_NAME} ${CORESERVICES_LIBRARY}) +endif() + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/rdpdr/client/devman.c b/channels/rdpdr/client/devman.c new file mode 100644 index 0000000..93db1b7 --- /dev/null +++ b/channels/rdpdr/client/devman.c @@ -0,0 +1,195 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "rdpdr_main.h" + +#include "devman.h" + +static void devman_device_free(void* obj) +{ + DEVICE* device = (DEVICE*) obj; + + if (!device) + return; + + IFCALL(device->Free, device); +} + +DEVMAN* devman_new(rdpdrPlugin* rdpdr) +{ + DEVMAN* devman; + + if (!rdpdr) + return NULL; + + devman = (DEVMAN*) calloc(1, sizeof(DEVMAN)); + + if (!devman) + { + WLog_INFO(TAG, "calloc failed!"); + return NULL; + } + + devman->plugin = (void*) rdpdr; + devman->id_sequence = 1; + + devman->devices = ListDictionary_New(TRUE); + if (!devman->devices) + { + WLog_INFO(TAG, "ListDictionary_New failed!"); + free(devman); + return NULL; + } + + ListDictionary_ValueObject(devman->devices)->fnObjectFree = devman_device_free; + + return devman; +} + +void devman_free(DEVMAN* devman) +{ + ListDictionary_Free(devman->devices); + free(devman); +} + +void devman_unregister_device(DEVMAN* devman, void* key) +{ + DEVICE* device; + + if (!devman || !key) + return; + + device = (DEVICE*) ListDictionary_Remove(devman->devices, key); + + if (device) + devman_device_free(device); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT devman_register_device(DEVMAN* devman, DEVICE* device) +{ + void* key = NULL; + + if (!devman || !device) + return ERROR_INVALID_PARAMETER; + + device->id = devman->id_sequence++; + key = (void*) (size_t) device->id; + + if (!ListDictionary_Add(devman->devices, key, device)) + { + WLog_INFO(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + return CHANNEL_RC_OK; +} + +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id) +{ + DEVICE* device = NULL; + void* key = (void*) (size_t) id; + + if (!devman) + return NULL; + + device = (DEVICE*) ListDictionary_GetItemValue(devman->devices, key); + + return device; +} + +static char DRIVE_SERVICE_NAME[] = "drive"; +static char PRINTER_SERVICE_NAME[] = "printer"; +static char SMARTCARD_SERVICE_NAME[] = "smartcard"; +static char SERIAL_SERVICE_NAME[] = "serial"; +static char PARALLEL_SERVICE_NAME[] = "parallel"; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT devman_load_device_service(DEVMAN* devman, RDPDR_DEVICE* device, rdpContext* rdpcontext) +{ + char* ServiceName = NULL; + DEVICE_SERVICE_ENTRY_POINTS ep; + PDEVICE_SERVICE_ENTRY entry = NULL; + + if (!devman || !device || !rdpcontext) + return ERROR_INVALID_PARAMETER; + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + ServiceName = DRIVE_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PRINT) + ServiceName = PRINTER_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SMARTCARD) + ServiceName = SMARTCARD_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SERIAL) + ServiceName = SERIAL_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PARALLEL) + ServiceName = PARALLEL_SERVICE_NAME; + + if (!ServiceName) + { + WLog_INFO(TAG, "ServiceName %s did not match!", ServiceName); + return ERROR_INVALID_NAME; + } + + if (device->Name) + WLog_INFO(TAG, "Loading device service %s [%s] (static)", ServiceName, device->Name); + else + WLog_INFO(TAG, "Loading device service %s (static)", ServiceName); + entry = (PDEVICE_SERVICE_ENTRY) freerdp_load_channel_addin_entry(ServiceName, NULL, "DeviceServiceEntry", 0); + + if (!entry) + { + WLog_INFO(TAG, "freerdp_load_channel_addin_entry failed!"); + return ERROR_INTERNAL_ERROR; + } + + ep.devman = devman; + ep.RegisterDevice = devman_register_device; + ep.device = device; + ep.rdpcontext = rdpcontext; + + return entry(&ep); +} diff --git a/channels/rdpdr/client/devman.h b/channels/rdpdr/client/devman.h new file mode 100644 index 0000000..c99a179 --- /dev/null +++ b/channels/rdpdr/client/devman.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H + +#include "rdpdr_main.h" + +void devman_unregister_device(DEVMAN* devman, void* key); +UINT devman_load_device_service(DEVMAN* devman, RDPDR_DEVICE* device, rdpContext* rdpcontext); +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id); + +DEVMAN* devman_new(rdpdrPlugin* rdpdr); +void devman_free(DEVMAN* devman); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H */ diff --git a/channels/rdpdr/client/irp.c b/channels/rdpdr/client/irp.c new file mode 100644 index 0000000..21d4a46 --- /dev/null +++ b/channels/rdpdr/client/irp.c @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "rdpdr_main.h" +#include "devman.h" +#include "irp.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_free(IRP* irp) +{ + if (!irp) + return CHANNEL_RC_OK; + + Stream_Free(irp->input, TRUE); + Stream_Free(irp->output, TRUE); + + _aligned_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_complete(IRP* irp) +{ + size_t pos; + rdpdrPlugin* rdpdr; + UINT error; + + rdpdr = (rdpdrPlugin*) irp->devman->plugin; + + pos = Stream_GetPosition(irp->output); + Stream_SetPosition(irp->output, RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4); + Stream_Write_UINT32(irp->output, irp->IoStatus); /* IoStatus (4 bytes) */ + Stream_SetPosition(irp->output, pos); + + error = rdpdr_send(rdpdr, irp->output); + irp->output = NULL; + + irp_free(irp); + return error; +} + +IRP* irp_new(DEVMAN* devman, wStream* s, UINT* error) +{ + IRP* irp; + DEVICE* device; + UINT32 DeviceId; + + if (Stream_GetRemainingLength(s) < 20) + { + if (error) + *error = ERROR_INVALID_DATA; + return NULL; + } + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + device = devman_get_device_by_id(devman, DeviceId); + + if (!device) + { + WLog_WARN(TAG, "devman_get_device_by_id failed!"); + if (error) + *error = CHANNEL_RC_OK; + + return NULL; + }; + + irp = (IRP*) _aligned_malloc(sizeof(IRP), MEMORY_ALLOCATION_ALIGNMENT); + + if (!irp) + { + WLog_ERR(TAG, "_aligned_malloc failed!"); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return NULL; + } + + + ZeroMemory(irp, sizeof(IRP)); + + irp->input = s; + irp->device = device; + irp->devman = devman; + + Stream_Read_UINT32(s, irp->FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, irp->CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, irp->MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, irp->MinorFunction); /* MinorFunction (4 bytes) */ + + irp->output = Stream_New(NULL, 256); + if (!irp->output) + { + WLog_ERR(TAG, "Stream_New failed!"); + _aligned_free(irp); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return NULL; + } + Stream_Write_UINT16(irp->output, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(irp->output, PAKID_CORE_DEVICE_IOCOMPLETION); /* PacketId (2 bytes) */ + Stream_Write_UINT32(irp->output, DeviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(irp->output, irp->CompletionId); /* CompletionId (4 bytes) */ + Stream_Write_UINT32(irp->output, 0); /* IoStatus (4 bytes) */ + + irp->Complete = irp_complete; + irp->Discard = irp_free; + + irp->thread = NULL; + irp->cancelled = FALSE; + + if (error) + *error = CHANNEL_RC_OK; + + return irp; +} diff --git a/channels/rdpdr/client/irp.h b/channels/rdpdr/client/irp.h new file mode 100644 index 0000000..17d75ac --- /dev/null +++ b/channels/rdpdr/client/irp.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H + +#include "rdpdr_main.h" + +IRP* irp_new(DEVMAN* devman, wStream* s, UINT* error); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H */ diff --git a/channels/rdpdr/client/rdpdr_capabilities.c b/channels/rdpdr/client/rdpdr_capabilities.c new file mode 100644 index 0000000..4ff3ef5 --- /dev/null +++ b/channels/rdpdr/client/rdpdr_capabilities.c @@ -0,0 +1,262 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "rdpdr_main.h" +#include "rdpdr_capabilities.h" + +/* Output device redirection capability set header */ +static void rdpdr_write_capset_header(wStream* s, UINT16 capabilityType, UINT16 capabilityLength, UINT32 version) +{ + Stream_Write_UINT16(s, capabilityType); + Stream_Write_UINT16(s, capabilityLength); + Stream_Write_UINT32(s, version); +} + +/* Output device direction general capability set */ +static void rdpdr_write_general_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdr_write_capset_header(s, CAP_GENERAL_TYPE, 44, GENERAL_CAPABILITY_VERSION_02); + + Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion, unused and must be set to zero */ + Stream_Write_UINT16(s, 1); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, RDPDR_MINOR_RDP_VERSION_5_2); /* protocolMinorVersion */ + Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */ + Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */ + Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */ + Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, 0); /* SpecialTypeDeviceCap, number of special devices to be redirected before logon */ +} + +/* Process device direction general capability set */ +static UINT rdpdr_process_general_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (Stream_GetRemainingLength(s) < capabilityLength - 4) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4); + + return CHANNEL_RC_OK; +} + +/* Output printer direction capability set */ +static void rdpdr_write_printer_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdr_write_capset_header(s, CAP_PRINTER_TYPE, 8, PRINT_CAPABILITY_VERSION_01); +} + +/* Process printer direction capability set */ +static UINT rdpdr_process_printer_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (Stream_GetRemainingLength(s) < capabilityLength - 4) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4); + + return CHANNEL_RC_OK; +} + +/* Output port redirection capability set */ +static void rdpdr_write_port_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdr_write_capset_header(s, CAP_PORT_TYPE, 8, PORT_CAPABILITY_VERSION_01); +} + +/* Process port redirection capability set */ +static UINT rdpdr_process_port_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (Stream_GetRemainingLength(s) < capabilityLength - 4) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4); + + return CHANNEL_RC_OK; +} + +/* Output drive redirection capability set */ +static void rdpdr_write_drive_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdr_write_capset_header(s, CAP_DRIVE_TYPE, 8, DRIVE_CAPABILITY_VERSION_02); +} + +/* Process drive redirection capability set */ +static UINT rdpdr_process_drive_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (Stream_GetRemainingLength(s) < capabilityLength - 4) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4); + + return CHANNEL_RC_OK; +} + +/* Output smart card redirection capability set */ +static void rdpdr_write_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdr_write_capset_header(s, CAP_SMARTCARD_TYPE, 8, SMARTCARD_CAPABILITY_VERSION_01); +} + +/* Process smartcard redirection capability set */ +static UINT rdpdr_process_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (Stream_GetRemainingLength(s) < capabilityLength - 4) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4); + + return CHANNEL_RC_OK; +} + +UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 i; + UINT16 numCapabilities; + UINT16 capabilityType; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek(s, 2); /* pad (2 bytes) */ + + for (i = 0; i < numCapabilities; i++) + { + if (Stream_GetRemainingLength(s) < sizeof(UINT16)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityType); + + switch (capabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_process_general_capset(rdpdr, s); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_process_printer_capset(rdpdr, s); + break; + + case CAP_PORT_TYPE: + status = rdpdr_process_port_capset(rdpdr, s); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_process_drive_capset(rdpdr, s); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_process_smartcard_capset(rdpdr, s); + break; + + default: + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr) +{ + wStream* s; + + s = Stream_New(NULL, 256); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_CAPABILITY); + + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + + rdpdr_write_general_capset(rdpdr, s); + rdpdr_write_printer_capset(rdpdr, s); + rdpdr_write_port_capset(rdpdr, s); + rdpdr_write_drive_capset(rdpdr, s); + rdpdr_write_smartcard_capset(rdpdr, s); + + return rdpdr_send(rdpdr, s); +} diff --git a/channels/rdpdr/client/rdpdr_capabilities.h b/channels/rdpdr/client/rdpdr_capabilities.h new file mode 100644 index 0000000..d4e1ecb --- /dev/null +++ b/channels/rdpdr/client/rdpdr_capabilities.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H + +#include "rdpdr_main.h" + +UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s); +UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H */ diff --git a/channels/rdpdr/client/rdpdr_main.c b/channels/rdpdr/client/rdpdr_main.c new file mode 100644 index 0000000..602268f --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.c @@ -0,0 +1,1895 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#ifdef __MACOSX__ +#include +#include +#include +#include +#include +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "rdpdr_capabilities.h" + +#include "devman.h" +#include "irp.h" + +#include "rdpdr_main.h" + +typedef struct _DEVICE_DRIVE_EXT DEVICE_DRIVE_EXT; +/* IMPORTANT: Keep in sync with DRIVE_DEVICE */ +struct _DEVICE_DRIVE_EXT +{ + DEVICE device; + WCHAR* path; + BOOL automount; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_device_list_announce_request(rdpdrPlugin* rdpdr, + BOOL userLoggedOn); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_device_list_remove_request(rdpdrPlugin* rdpdr, + UINT32 count, UINT32 ids[]) +{ + UINT32 i; + wStream* s; + s = Stream_New(NULL, count * sizeof(UINT32) + 8); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_REMOVE); + Stream_Write_UINT32(s, count); + + for (i = 0; i < count; i++) + Stream_Write_UINT32(s, ids[i]); + + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +#ifdef _UWP + +void first_hotplug(rdpdrPlugin* rdpdr) +{ +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + return CHANNEL_RC_OK; +} + +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + return CHANNEL_RC_OK; +} + +#elif _WIN32 + +BOOL check_path(char* path) +{ + UINT type = GetDriveTypeA(path); + + if (!(type == DRIVE_FIXED ||type == DRIVE_REMOVABLE || type == DRIVE_CDROM || type == DRIVE_REMOTE)) + return FALSE; + + return GetVolumeInformationA(path, NULL, 0, NULL, NULL, NULL, NULL, 0); +} + +void first_hotplug(rdpdrPlugin* rdpdr) +{ + int i; + char drive_path[5] = { 'c', ':', '\\', '\0' }; + DWORD unitmask = GetLogicalDrives(); + + for (i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + RDPDR_DRIVE* drive; + drive_path[0] = 'A' + i; + drive_path[1] = ':'; + + if (check_path(drive_path)) + { + drive = (RDPDR_DRIVE*)malloc(sizeof(RDPDR_DRIVE)); + ZeroMemory(drive, sizeof(RDPDR_DRIVE)); + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Path = _strdup(drive_path); + drive_path[1] = '\0'; + drive->Name = _strdup(drive_path); + drive->automount = TRUE; + devman_load_device_service(rdpdr->devman, (RDPDR_DEVICE*)drive, + rdpdr->rdpcontext); + } + } + + unitmask = unitmask >> 1; + } +} + +LRESULT CALLBACK hotplug_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + rdpdrPlugin* rdpdr; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + UINT error; + rdpdr = (rdpdrPlugin*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + switch (Msg) + { + case WM_DEVICECHANGE: + switch (wParam) + { + case DBT_DEVICEARRIVAL: + if (lpdb -> dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + int i; + char drive_path[4] = { 'c', ':', '/', '\0'}; + + for (i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + RDPDR_DRIVE* drive; + drive_path[0] = 'A' + i; + drive_path[1] = ':'; + + if (check_path(drive_path)) + { + drive = (RDPDR_DRIVE*) malloc(sizeof(RDPDR_DRIVE)); + ZeroMemory(drive, sizeof(RDPDR_DRIVE)); + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Path = _strdup(drive_path); + drive_path[1] = '\0'; + drive->automount = TRUE; + drive->Name = _strdup(drive_path); + devman_load_device_service(rdpdr->devman, (RDPDR_DEVICE*)drive, + rdpdr->rdpcontext); + rdpdr_send_device_list_announce_request(rdpdr, TRUE); + } + } + + unitmask = unitmask >> 1; + } + } + + break; + + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb -> dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + int i, j, count; + char drive_name_upper, drive_name_lower; + ULONG_PTR* keys = NULL; + DEVICE_DRIVE_EXT* device_ext; + UINT32 ids[1]; + + for (i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + drive_name_upper = 'A' + i; + drive_name_lower = 'a' + i; + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (j = 0; j < count; j++) + { + device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue( + rdpdr->devman->devices, (void*)keys[j]); + + if (device_ext->path[0] == drive_name_upper + || device_ext->path[0] == drive_name_lower) + { + if (device_ext->automount) + { + devman_unregister_device(rdpdr->devman, (void*)keys[j]); + ids[0] = keys[j]; + + if ((error = rdpdr_send_device_list_remove_request(rdpdr, 1, ids))) + { + // dont end on error, just report ? + WLog_ERR(TAG, "rdpdr_send_device_list_remove_request failed with error %"PRIu32"!", + error); + } + + break; + } + } + } + + free(keys); + } + + unitmask = unitmask >> 1; + } + } + + break; + + default: + break; + } + + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + WNDCLASSEX wnd_cls; + HWND hwnd; + MSG msg; + BOOL bRet; + DEV_BROADCAST_HANDLE NotificationFilter; + HDEVNOTIFY hDevNotify; + rdpdr = (rdpdrPlugin*)arg; + /* init windows class */ + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_HREDRAW | CS_VREDRAW; + wnd_cls.lpfnWndProc = hotplug_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wnd_cls.hCursor = NULL; + wnd_cls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = L"DRIVE_HOTPLUG"; + wnd_cls.hInstance = NULL; + wnd_cls.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + /* create window */ + hwnd = CreateWindowEx(0, L"DRIVE_HOTPLUG", NULL, + 0, 0, 0, 0, 0, + NULL, NULL, NULL, NULL); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)rdpdr); + rdpdr->hotplug_wnd = hwnd; + /* register device interface to hwnd */ + NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); + NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; + hDevNotify = RegisterDeviceNotification(hwnd, &NotificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE); + + /* message loop */ + while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (bRet == -1) + { + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + UnregisterDeviceNotification(hDevNotify); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error = CHANNEL_RC_OK; + + if (rdpdr->hotplug_wnd && !PostMessage(rdpdr->hotplug_wnd, WM_QUIT, 0, 0)) + { + error = GetLastError(); + WLog_ERR(TAG, "PostMessage failed with error %"PRIu32"", error); + } + + return error; +} + +#elif __MACOSX__ + +#define MAX_USB_DEVICES 100 + +typedef struct _hotplug_dev +{ + char* path; + BOOL to_add; +} hotplug_dev; + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT handle_hotplug(rdpdrPlugin* rdpdr) +{ + struct dirent* pDirent; + DIR* pDir; + char fullpath[PATH_MAX]; + char* szdir = (char*)"/Volumes"; + struct stat buf; + hotplug_dev dev_array[MAX_USB_DEVICES]; + int count; + DEVICE_DRIVE_EXT* device_ext; + ULONG_PTR* keys = NULL; + int i, j; + int size = 0; + UINT error; + UINT32 ids[1]; + pDir = opendir(szdir); + + if (pDir == NULL) + { + printf("Cannot open directory\n"); + return ERROR_OPEN_FAILED; + } + + while ((pDirent = readdir(pDir)) != NULL) + { + if (pDirent->d_name[0] != '.') + { + sprintf_s(fullpath, ARRAYSIZE(fullpath), "%s/%s", szdir, pDirent->d_name); + lstat(fullpath, &buf); + + if (S_ISDIR(buf.st_mode)) + { + dev_array[size].path = _strdup(fullpath); + + if (!dev_array[size].path) + { + closedir(pDir); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + dev_array[size++].to_add = TRUE; + } + } + } + + closedir(pDir); + /* delete removed devices */ + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (j = 0; j < count; j++) + { + char* path = NULL; + BOOL dev_found = FALSE; + device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue( + rdpdr->devman->devices, (void*)keys[j]); + + if (!device_ext || !device_ext->automount) + continue; + + if (device_ext->path == NULL) + continue; + + if (ConvertFromUnicode(CP_UTF8, 0, device_ext->path, -1, &path, 0, NULL, FALSE) <= 0) + continue; + + /* not plugable device */ + if (strstr(path, "/Volumes/") == NULL) + { + free(path); + continue; + } + + for (i = 0; i < size; i++) + { + if (strstr(path, dev_array[i].path) != NULL) + { + dev_found = TRUE; + dev_array[i].to_add = FALSE; + break; + } + } + + free(path); + + if (!dev_found) + { + devman_unregister_device(rdpdr->devman, (void*)keys[j]); + ids[0] = keys[j]; + + if ((error = rdpdr_send_device_list_remove_request(rdpdr, 1, ids))) + { + WLog_ERR(TAG, "rdpdr_send_device_list_remove_request failed with error %"PRIu32"!", + error); + goto cleanup; + } + } + } + + /* add new devices */ + for (i = 0; i < size; i++) + { + RDPDR_DRIVE* drive; + + if (dev_array[i].to_add) + { + char* name; + drive = (RDPDR_DRIVE*) calloc(1, sizeof(RDPDR_DRIVE)); + + if (!drive) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Path = dev_array[i].path; + drive->automount = TRUE; + dev_array[i].path = NULL; + name = strrchr(drive->Path, '/') + 1; + drive->Name = _strdup(name); + + if (!drive->Name) + { + WLog_ERR(TAG, "_strdup failed!"); + free(drive->Path); + free(drive); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + if ((error = devman_load_device_service(rdpdr->devman, (RDPDR_DEVICE*)drive, + rdpdr->rdpcontext))) + { + WLog_ERR(TAG, "devman_load_device_service failed!"); + free(drive->Path); + free(drive->Name); + free(drive); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + } + } + +cleanup: + free(keys); + + for (i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + + +static void drive_hotplug_fsevent_callback(ConstFSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + rdpdrPlugin* rdpdr; + int i; + UINT error; + char** paths = (char**)eventPaths; + rdpdr = (rdpdrPlugin*) clientCallBackInfo; + + for (i = 0; i < numEvents; i++) + { + if (strcmp(paths[i], "/Volumes/") == 0) + { + if ((error = handle_hotplug(rdpdr))) + { + WLog_ERR(TAG, "handle_hotplug failed with error %"PRIu32"!", error); + } + else + rdpdr_send_device_list_announce_request(rdpdr, TRUE); + + return; + } + } +} + +void first_hotplug(rdpdrPlugin* rdpdr) +{ + UINT error; + + if ((error = handle_hotplug(rdpdr))) + { + WLog_ERR(TAG, "handle_hotplug failed with error %"PRIu32"!", error); + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + FSEventStreamRef fsev; + rdpdr = (rdpdrPlugin*) arg; + CFStringRef path = CFSTR("/Volumes/"); + CFArrayRef pathsToWatch = CFArrayCreate(kCFAllocatorMalloc, (const void**)&path, + 1, NULL); + FSEventStreamContext ctx; + ZeroMemory(&ctx, sizeof(ctx)); + ctx.info = arg; + fsev = FSEventStreamCreate(kCFAllocatorMalloc, drive_hotplug_fsevent_callback, + &ctx, pathsToWatch, kFSEventStreamEventIdSinceNow, 1, + kFSEventStreamCreateFlagNone); + rdpdr->runLoop = CFRunLoopGetCurrent(); + FSEventStreamScheduleWithRunLoop(fsev, rdpdr->runLoop, kCFRunLoopDefaultMode); + FSEventStreamStart(fsev); + CFRunLoopRun(); + FSEventStreamStop(fsev); + FSEventStreamRelease(fsev); + ExitThread(CHANNEL_RC_OK); + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error; + + if (rdpdr->hotplugThread) + { + CFRunLoopStop(rdpdr->runLoop); + + if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + rdpdr->hotplugThread = NULL; + } + + return CHANNEL_RC_OK; +} + +#else + +#define MAX_USB_DEVICES 100 + +typedef struct _hotplug_dev +{ + char* path; + BOOL to_add; +} hotplug_dev; + + +static const char* automountLocations[] = +{ + "/run/user/%lu/gvfs", + "/run/media/%s", + "/media/%s", + "/media", + "/mnt" +}; + +static BOOL isAutomountLocation(const char* path) +{ + const size_t nrLocations = sizeof(automountLocations) / sizeof(automountLocations[0]); + size_t x; + char buffer[MAX_PATH]; + uid_t uid = getuid(); + const char* uname = getlogin(); + + if (!path) + return FALSE; + + for (x = 0; x < nrLocations; x++) + { + const char* location = automountLocations[x]; + size_t length; + + if (strstr(location, "%lu")) + snprintf(buffer, sizeof(buffer), location, (unsigned long)uid); + else if (strstr(location, "%s")) + snprintf(buffer, sizeof(buffer), location, uname); + else + snprintf(buffer, sizeof(buffer), "%s", location); + + length = strnlen(buffer, sizeof(buffer)); + + if (strncmp(buffer, path, length) == 0) + { + const char* rest = &path[length]; + + /* Only consider mount locations with max depth of 1 below the + * base path or the base path itself. */ + if (*rest == '\0') + return TRUE; + else if (*rest == '/') + { + const char* token = strstr(&rest[1], "/"); + + if (!token || (token[1] == '\0')) + return TRUE; + } + } + } + + return FALSE; +} + +static char* next_line(FILE* fd, size_t* len) +{ + size_t newsiz; + int c; + char* newbuf; + char* lrbuf; + int lrsiz; + *len = 0; + lrsiz = 0; + lrbuf = NULL; + newbuf = NULL; + + for (;;) + { + c = fgetc(fd); + + if (ferror(fd)) + { + free(newbuf); + return NULL; + } + + if (c == EOF) + { + if (*len == 0) + return NULL; + else + { + lrbuf[(*len)] = '\0'; + return lrbuf; + } + } + else + { + if (*len == lrsiz) + { + newsiz = lrsiz + 4096; + newbuf = realloc(lrbuf, newsiz); + + if (newbuf == NULL) + return NULL; + + lrbuf = newbuf; + lrsiz = newsiz; + } + + lrbuf[(*len)] = c; + + if (c == '\n') + { + lrbuf[(*len)] = '\0'; + return lrbuf; + } + + (*len)++; + } + } +} + +static char* get_word(char* str, unsigned int* offset) +{ + char* p; + char* tmp; + char* word; + int wlen; + + if (*offset >= strlen(str)) + return NULL; + + p = str + *offset; + tmp = p; + + while (*tmp != ' ' && *tmp != '\0') + tmp++; + + wlen = tmp - p; + *offset += wlen; + + /* in case there are more than one space between words */ + while (*(str + *offset) == ' ') + (*offset)++; + + word = malloc(wlen + 1); + + if (word != NULL) + { + CopyMemory(word, p, wlen); + word[wlen] = '\0'; + } + + return word; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT handle_hotplug(rdpdrPlugin* rdpdr) +{ + FILE* f; + size_t len; + char* line; + char* word; + unsigned int wlen; + hotplug_dev dev_array[MAX_USB_DEVICES]; + int i, j; + int size = 0; + int count; + ULONG_PTR* keys = NULL; + UINT32 ids[1]; + UINT error = 0; + memset(dev_array, 0, sizeof(dev_array)); + f = fopen("/proc/mounts", "r"); + + if (f == NULL) + { + WLog_ERR(TAG, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + + while ((line = next_line(f, &len))) + { + wlen = 0; + + while ((word = get_word(line, &wlen))) + { + /* copy hotpluged device mount point to the dev_array */ + if (isAutomountLocation(word)) + { + dev_array[size].path = word; + dev_array[size++].to_add = TRUE; + } + else + free(word); + } + + free(line); + } + + fclose(f); + /* delete removed devices */ + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (j = 0; j < count; j++) + { + char* path = NULL; + BOOL dev_found = FALSE; + DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue( + rdpdr->devman->devices, (void*)keys[j]); + + if (!device_ext || !device_ext->path || !device_ext->automount) + continue; + + ConvertFromUnicode(CP_UTF8, 0, device_ext->path, -1, &path, 0, NULL, NULL); + + if (!path) + continue; + + /* not plugable device */ + if (isAutomountLocation(path)) + { + for (i = 0; i < size; i++) + { + if (strstr(path, dev_array[i].path) != NULL) + { + dev_found = TRUE; + dev_array[i].to_add = FALSE; + break; + } + } + } + + free(path); + + if (!dev_found) + { + devman_unregister_device(rdpdr->devman, (void*)keys[j]); + ids[0] = keys[j]; + + if ((error = rdpdr_send_device_list_remove_request(rdpdr, 1, ids))) + { + WLog_ERR(TAG, "rdpdr_send_device_list_remove_request failed with error %"PRIu32"!", + error); + goto cleanup; + } + } + } + + /* add new devices */ + for (i = 0; i < size; i++) + { + RDPDR_DRIVE* drive; + + if (dev_array[i].to_add) + { + char* name; + drive = (RDPDR_DRIVE*) calloc(1, sizeof(RDPDR_DRIVE)); + + if (!drive) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + drive->Type = RDPDR_DTYP_FILESYSTEM; + drive->Path = dev_array[i].path; + drive->automount = TRUE; + dev_array[i].path = NULL; + name = strrchr(drive->Path, '/') + 1; + drive->Name = _strdup(name); + + if (!drive->Name) + { + WLog_ERR(TAG, "_strdup failed!"); + free(drive->Path); + free(drive); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + if ((error = devman_load_device_service(rdpdr->devman, (RDPDR_DEVICE*)drive, + rdpdr->rdpcontext))) + { + WLog_ERR(TAG, "devman_load_device_service failed!"); + free(drive->Path); + free(drive->Name); + free(drive); + goto cleanup; + } + } + } + +cleanup: + free(keys); + + for (i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + UINT error; + + if ((error = handle_hotplug(rdpdr))) + { + WLog_ERR(TAG, "handle_hotplug failed with error %"PRIu32"!", error); + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + int mfd; + fd_set rfds; + struct timeval tv; + int rv; + UINT error = 0; + DWORD status; + rdpdr = (rdpdrPlugin*) arg; + + if (!(rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + mfd = open("/proc/mounts", O_RDONLY, 0); + + if (mfd < 0) + { + WLog_ERR(TAG, "ERROR: Unable to open /proc/mounts."); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + FD_ZERO(&rfds); + FD_SET(mfd, &rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + while ((rv = select(mfd + 1, NULL, NULL, &rfds, &tv)) >= 0) + { + status = WaitForSingleObject(rdpdr->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + goto out; + } + + if (status == WAIT_OBJECT_0) + break; + + if (FD_ISSET(mfd, &rfds)) + { + /* file /proc/mounts changed, handle this */ + if ((error = handle_hotplug(rdpdr))) + { + WLog_ERR(TAG, "handle_hotplug failed with error %"PRIu32"!", error); + goto out; + } + else + rdpdr_send_device_list_announce_request(rdpdr, TRUE); + } + + FD_ZERO(&rfds); + FD_SET(mfd, &rfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + } + +out: + + if (error && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "drive_hotplug_thread_func reported an error"); + + CloseHandle(rdpdr->stopEvent); + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error; + + if (rdpdr->hotplugThread) + { + if (rdpdr->stopEvent) + SetEvent(rdpdr->stopEvent); + + if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + rdpdr->hotplugThread = NULL; + } + + return CHANNEL_RC_OK; +} + +#endif + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_connect(rdpdrPlugin* rdpdr) +{ + UINT32 index; + RDPDR_DEVICE* device; + rdpSettings* settings; + UINT error = CHANNEL_RC_OK; + rdpdr->devman = devman_new(rdpdr); + + if (!rdpdr->devman) + { + WLog_ERR(TAG, "devman_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + settings = (rdpSettings*) rdpdr->channelEntryPoints.pExtendedData; + + if (settings->ClientHostname) + strncpy(rdpdr->computerName, settings->ClientHostname, + sizeof(rdpdr->computerName) - 1); + else + strncpy(rdpdr->computerName, settings->ComputerName, + sizeof(rdpdr->computerName) - 1); + + for (index = 0; index < settings->DeviceCount; index++) + { + device = settings->DeviceArray[index]; + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + { + RDPDR_DRIVE* drive = (RDPDR_DRIVE*)device; + + if (drive->Path && (strcmp(drive->Path, "*") == 0)) + { + first_hotplug(rdpdr); + + if (!(rdpdr->hotplugThread = CreateThread(NULL, 0, + drive_hotplug_thread_func, rdpdr, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + + continue; + } + } + + if ((error = devman_load_device_service(rdpdr->devman, device, + rdpdr->rdpcontext))) + { + WLog_ERR(TAG, "devman_load_device_service failed with error %"PRIu32"!", error); + return error; + } + } + + return error; +} + +static UINT rdpdr_process_server_announce_request(rdpdrPlugin* rdpdr, + wStream* s) +{ + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->versionMajor); + Stream_Read_UINT16(s, rdpdr->versionMinor); + Stream_Read_UINT32(s, rdpdr->clientID); + rdpdr->sequenceId++; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_announce_reply(rdpdrPlugin* rdpdr) +{ + wStream* s; + s = Stream_New(NULL, 12); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENTID_CONFIRM); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, rdpdr->versionMajor); + Stream_Write_UINT16(s, rdpdr->versionMinor); + Stream_Write_UINT32(s, (UINT32) rdpdr->clientID); + return rdpdr_send(rdpdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_name_request(rdpdrPlugin* rdpdr) +{ + wStream* s; + WCHAR* computerNameW = NULL; + size_t computerNameLenW; + + if (!rdpdr->computerName[0]) + gethostname(rdpdr->computerName, sizeof(rdpdr->computerName) - 1); + + computerNameLenW = ConvertToUnicode(CP_UTF8, 0, rdpdr->computerName, -1, + &computerNameW, 0) * 2; + s = Stream_New(NULL, 16 + computerNameLenW + 2); + + if (!s) + { + free(computerNameW); + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_NAME); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, 1); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, computerNameLenW + + 2); /* computerNameLen, including null terminator */ + Stream_Write(s, computerNameW, computerNameLenW); + Stream_Write_UINT16(s, 0); /* null terminator */ + free(computerNameW); + return rdpdr_send(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(rdpdrPlugin* rdpdr, + wStream* s) +{ + UINT16 versionMajor; + UINT16 versionMinor; + UINT32 clientID; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + Stream_Read_UINT32(s, clientID); + + if (versionMajor != rdpdr->versionMajor || versionMinor != rdpdr->versionMinor) + { + rdpdr->versionMajor = versionMajor; + rdpdr->versionMinor = versionMinor; + } + + if (clientID != rdpdr->clientID) + rdpdr->clientID = clientID; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_device_list_announce_request(rdpdrPlugin* rdpdr, + BOOL userLoggedOn) +{ + int i; + BYTE c; + size_t pos; + int index; + wStream* s; + UINT32 count; + size_t data_len; + size_t count_pos; + DEVICE* device; + int keyCount; + ULONG_PTR* pKeys = NULL; + s = Stream_New(NULL, 256); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_ANNOUNCE); /* PacketId (2 bytes) */ + count_pos = Stream_GetPosition(s); + count = 0; + Stream_Seek_UINT32(s); /* deviceCount */ + pKeys = NULL; + keyCount = ListDictionary_GetKeys(rdpdr->devman->devices, &pKeys); + + for (index = 0; index < keyCount; index++) + { + device = (DEVICE*) ListDictionary_GetItemValue(rdpdr->devman->devices, + (void*) pKeys[index]); + + /** + * 1. versionMinor 0x0005 doesn't send PAKID_CORE_USER_LOGGEDON + * so all devices should be sent regardless of user_loggedon + * 2. smartcard devices should be always sent + * 3. other devices are sent only after user_loggedon + */ + + if ((rdpdr->versionMinor == 0x0005) || + (device->type == RDPDR_DTYP_SMARTCARD) || userLoggedOn) + { + data_len = (device->data == NULL ? 0 : Stream_GetPosition(device->data)); + + if (!Stream_EnsureRemainingCapacity(s, 20 + data_len)) + { + free(pKeys); + Stream_Free(s, TRUE); + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Write_UINT32(s, device->type); /* deviceType */ + Stream_Write_UINT32(s, device->id); /* deviceID */ + strncpy((char*) Stream_Pointer(s), device->name, 8); + + for (i = 0; i < 8; i++) + { + Stream_Peek_UINT8(s, c); + + if (c > 0x7F) + Stream_Write_UINT8(s, '_'); + else + Stream_Seek_UINT8(s); + } + + Stream_Write_UINT32(s, data_len); + + if (data_len > 0) + Stream_Write(s, Stream_Buffer(device->data), data_len); + + count++; + WLog_INFO(TAG, "registered device #%"PRIu32": %s (type=%"PRIu32" id=%"PRIu32")", + count, device->name, device->type, device->id); + } + } + + free(pKeys); + pos = Stream_GetPosition(s); + Stream_SetPosition(s, count_pos); + Stream_Write_UINT32(s, count); + Stream_SetPosition(s, pos); + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_irp(rdpdrPlugin* rdpdr, wStream* s) +{ + IRP* irp; + UINT error = CHANNEL_RC_OK; + irp = irp_new(rdpdr->devman, s, &error); + + if (!irp) + { + WLog_ERR(TAG, "irp_new failed with %"PRIu32"!", error); + return error; + } + + IFCALLRET(irp->device->IRPRequest, error, irp->device, irp); + + if (error) + WLog_ERR(TAG, "device->IRPRequest failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_init(rdpdrPlugin* rdpdr) +{ + int index; + int keyCount; + DEVICE* device; + ULONG_PTR* pKeys = NULL; + UINT error = CHANNEL_RC_OK; + pKeys = NULL; + keyCount = ListDictionary_GetKeys(rdpdr->devman->devices, &pKeys); + + for (index = 0; index < keyCount; index++) + { + device = (DEVICE*) ListDictionary_GetItemValue(rdpdr->devman->devices, + (void*) pKeys[index]); + IFCALLRET(device->Init, error, device); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Init failed!"); + free(pKeys); + return error; + } + } + + free(pKeys); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_receive(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 component; + UINT16 packetId; + UINT32 deviceId; + UINT32 status; + UINT error; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, packetId); /* PacketId (2 bytes) */ + + if (component == RDPDR_CTYP_CORE) + { + switch (packetId) + { + case PAKID_CORE_SERVER_ANNOUNCE: + if ((error = rdpdr_process_server_announce_request(rdpdr, s))) + return error; + + if ((error = rdpdr_send_client_announce_reply(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_send_client_announce_reply failed with error %"PRIu32"", error); + return error; + } + + if ((error = rdpdr_send_client_name_request(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_send_client_name_request failed with error %"PRIu32"", error); + return error; + } + + if ((error = rdpdr_process_init(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_process_init failed with error %"PRIu32"", error); + return error; + } + + break; + + case PAKID_CORE_SERVER_CAPABILITY: + if ((error = rdpdr_process_capability_request(rdpdr, s))) + return error; + + if ((error = rdpdr_send_capability_response(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_send_capability_response failed with error %"PRIu32"", error); + return error; + } + + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + if ((error = rdpdr_process_server_clientid_confirm(rdpdr, s))) + return error; + + if ((error = rdpdr_send_device_list_announce_request(rdpdr, FALSE))) + { + WLog_ERR(TAG, "rdpdr_send_device_list_announce_request failed with error %"PRIu32"", + error); + return error; + } + + break; + + case PAKID_CORE_USER_LOGGEDON: + if ((error = rdpdr_send_device_list_announce_request(rdpdr, TRUE))) + { + WLog_ERR(TAG, "rdpdr_send_device_list_announce_request failed with error %"PRIu32"", + error); + return error; + } + + break; + + case PAKID_CORE_DEVICE_REPLY: + + /* connect to a specific resource */ + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, status); + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + if ((error = rdpdr_process_irp(rdpdr, s))) + { + WLog_ERR(TAG, "rdpdr_process_irp failed with error %"PRIu32"", error); + return error; + } + + s = NULL; + break; + + default: + WLog_ERR(TAG, "RDPDR_CTYP_CORE unknown PacketId: 0x%04"PRIX16"", packetId); + return ERROR_INVALID_DATA; + break; + } + } + else if (component == RDPDR_CTYP_PRN) + { + switch (packetId) + { + case PAKID_PRN_CACHE_DATA: + { + UINT32 eventID; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, eventID); + WLog_ERR(TAG, + "Ignoring unhandled message PAKID_PRN_CACHE_DATA (EventID: 0x%08"PRIX32")", eventID); + } + break; + + case PAKID_PRN_USING_XPS: + WLog_ERR(TAG, "Ignoring unhandled message PAKID_PRN_USING_XPS"); + break; + + default: + WLog_ERR(TAG, "Unknown printing component packetID: 0x%04"PRIX16"", packetId); + return ERROR_INVALID_DATA; + } + } + else + { + WLog_ERR(TAG, "Unknown message: Component: 0x%04"PRIX16" PacketId: 0x%04"PRIX16"", component, + packetId); + return ERROR_INVALID_DATA; + } + + Stream_Free(s, TRUE); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT status; + rdpdrPlugin* plugin = (rdpdrPlugin*) rdpdr; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + if (!plugin) + status = CHANNEL_RC_BAD_INIT_HANDLE; + else + { + status = plugin->channelEntryPoints.pVirtualChannelWriteEx(plugin->InitHandle, plugin->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_GetPosition(s), s); + } + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_data_received(rdpdrPlugin* rdpdr, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + /* + * According to MS-RDPBCGR 2.2.6.1, "All virtual channel traffic MUST be suspended. + * This flag is only valid in server-to-client virtual channel traffic. It MUST be + * ignored in client-to-server data." Thus it would be best practice to cease data + * transmission. However, simply returning here avoids a crash. + */ + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (rdpdr->data_in != NULL) + Stream_Free(rdpdr->data_in, TRUE); + + rdpdr->data_in = Stream_New(NULL, totalLength); + + if (!rdpdr->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = rdpdr->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, (int) dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "rdpdr_virtual_channel_event_data_received: read error"); + return ERROR_INTERNAL_ERROR; + } + + rdpdr->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(rdpdr->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*) lpUserParam; + + if (!rdpdr || !pData || (rdpdr->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = rdpdr_virtual_channel_event_data_received(rdpdr, pData, + dataLength, totalLength, dataFlags))) + WLog_ERR(TAG, + "rdpdr_virtual_channel_event_data_received failed with error %"PRIu32"!", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_open_event_ex reported an error"); + + return; +} + +static DWORD WINAPI rdpdr_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + rdpdrPlugin* rdpdr = (rdpdrPlugin*) arg; + UINT error; + + if (!rdpdr) + { + ExitThread((DWORD) CHANNEL_RC_NULL_DATA); + return CHANNEL_RC_NULL_DATA; + } + + if ((error = rdpdr_process_connect(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_process_connect failed with error %"PRIu32"!", error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; + } + + while (1) + { + if (!MessageQueue_Wait(rdpdr->queue)) + break; + + if (MessageQueue_Peek(rdpdr->queue, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + + if ((error = rdpdr_process_receive(rdpdr, data))) + { + WLog_ERR(TAG, "rdpdr_process_receive failed with error %"PRIu32"!", error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + ExitThread((DWORD) error); + return error; + } + } + } + } + + ExitThread(0); + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_connected(rdpdrPlugin* rdpdr, + LPVOID pData, UINT32 dataLength) +{ + UINT32 status; + status = rdpdr->channelEntryPoints.pVirtualChannelOpenEx(rdpdr->InitHandle, + &rdpdr->OpenHandle, rdpdr->channelDef.name, rdpdr_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpenEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + rdpdr->queue = MessageQueue_New(NULL); + + if (!rdpdr->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(rdpdr->thread = CreateThread(NULL, 0, + rdpdr_virtual_channel_client_thread, (void*) rdpdr, 0, + NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_disconnected(rdpdrPlugin* rdpdr) +{ + UINT error; + + if (rdpdr->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (MessageQueue_PostQuit(rdpdr->queue, 0) + && (WaitForSingleObject(rdpdr->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + MessageQueue_Free(rdpdr->queue); + CloseHandle(rdpdr->thread); + rdpdr->queue = NULL; + rdpdr->thread = NULL; + + if ((error = drive_hotplug_thread_terminate(rdpdr))) + { + WLog_ERR(TAG, "drive_hotplug_thread_terminate failed with error %"PRIu32"!", error); + return error; + } + + error = rdpdr->channelEntryPoints.pVirtualChannelCloseEx(rdpdr->InitHandle, rdpdr->OpenHandle); + + if (CHANNEL_RC_OK != error) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08"PRIX32"]", + WTSErrorToString(error), error); + } + + rdpdr->OpenHandle = 0; + + if (rdpdr->data_in) + { + Stream_Free(rdpdr->data_in, TRUE); + rdpdr->data_in = NULL; + } + + if (rdpdr->devman) + { + devman_free(rdpdr->devman); + rdpdr->devman = NULL; + } + + return error; +} + +static void rdpdr_virtual_channel_event_terminated(rdpdrPlugin* rdpdr) +{ + rdpdr->InitHandle = 0; + free(rdpdr); +} + +static VOID VCAPITYPE rdpdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, + LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*) lpUserParam; + + if (!rdpdr || (rdpdr->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + break; + + case CHANNEL_EVENT_CONNECTED: + if ((error = rdpdr_virtual_channel_event_connected(rdpdr, pData, dataLength))) + WLog_ERR(TAG, "rdpdr_virtual_channel_event_connected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rdpdr_virtual_channel_event_disconnected(rdpdr))) + WLog_ERR(TAG, "rdpdr_virtual_channel_event_disconnected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rdpdr_virtual_channel_event_terminated(rdpdr); + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + WLog_ERR(TAG, "unknown event %"PRIu32"!", event); + break; + } + + if (error && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_init_event_ex reported an error"); +} + +/* rdpdr is always built-in */ +#define VirtualChannelEntryEx rdpdr_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + rdpdrPlugin* rdpdr; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + rdpdr = (rdpdrPlugin*) calloc(1, sizeof(rdpdrPlugin)); + + if (!rdpdr) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + rdpdr->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP; + sprintf_s(rdpdr->channelDef.name, ARRAYSIZE(rdpdr->channelDef.name), "rdpdr"); + rdpdr->sequenceId = 0; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + rdpdr->rdpcontext = pEntryPointsEx->context; + } + + CopyMemory(&(rdpdr->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rdpdr->InitHandle = pInitHandle; + rc = rdpdr->channelEntryPoints.pVirtualChannelInitEx(rdpdr, NULL, pInitHandle, + &rdpdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rdpdr_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelInitEx failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + free(rdpdr); + return FALSE; + } + + return TRUE; +} diff --git a/channels/rdpdr/client/rdpdr_main.h b/channels/rdpdr/client/rdpdr_main.h new file mode 100644 index 0000000..1cdd2ce --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.h @@ -0,0 +1,83 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef __MACOSX__ +#include +#endif + +#define TAG CHANNELS_TAG("rdpdr.client") + +typedef struct rdpdr_plugin rdpdrPlugin; + +struct rdpdr_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + DEVMAN* devman; + + UINT16 versionMajor; + UINT16 versionMinor; + UINT16 clientID; + char computerName[256]; + + UINT32 sequenceId; + + /* hotplug support */ + HANDLE hotplugThread; +#ifdef _WIN32 + HWND hotplug_wnd; +#elif __MACOSX__ + CFRunLoopRef runLoop; +#else + HANDLE stopEvent; +#endif + rdpContext* rdpcontext; +}; + +UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H */ diff --git a/channels/rdpdr/server/CMakeLists.txt b/channels/rdpdr/server/CMakeLists.txt new file mode 100644 index 0000000..63f8a04 --- /dev/null +++ b/channels/rdpdr/server/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("rdpdr") + +set(${MODULE_PREFIX}_SRCS + rdpdr_main.c + rdpdr_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/rdpdr/server/rdpdr_main.c b/channels/rdpdr/server/rdpdr_main.c new file mode 100644 index 0000000..5abf8d0 --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.c @@ -0,0 +1,2655 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include "rdpdr_main.h" + +#define TAG "rdpdr.server" + +static UINT32 g_ClientId = 0; + +static RDPDR_IRP* rdpdr_server_irp_new() +{ + RDPDR_IRP* irp; + irp = (RDPDR_IRP*) calloc(1, sizeof(RDPDR_IRP)); + return irp; +} + +static void rdpdr_server_irp_free(RDPDR_IRP* irp) +{ + free(irp); +} + +static BOOL rdpdr_server_enqueue_irp(RdpdrServerContext* context, + RDPDR_IRP* irp) +{ + return ListDictionary_Add(context->priv->IrpList, + (void*)(size_t) irp->CompletionId, irp); +} + +static RDPDR_IRP* rdpdr_server_dequeue_irp(RdpdrServerContext* context, + UINT32 completionId) +{ + RDPDR_IRP* irp; + irp = (RDPDR_IRP*) ListDictionary_Remove(context->priv->IrpList, + (void*)(size_t) completionId); + return irp; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_announce_request(RdpdrServerContext* context) +{ + wStream* s; + BOOL status; + RDPDR_HEADER header; + ULONG written; + WLog_DBG(TAG, "RdpdrServerSendAnnounceRequest"); + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_ANNOUNCE; + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, + context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, + context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + Stream_SealLength(s); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_announce_response(RdpdrServerContext* context, + wStream* s, RDPDR_HEADER* header) +{ + UINT32 ClientId; + UINT16 VersionMajor; + UINT16 VersionMinor; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Read_UINT16(s, VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Read_UINT32(s, ClientId); /* ClientId (4 bytes) */ + WLog_DBG(TAG, + "Client Announce Response: VersionMajor: 0x%08"PRIX16" VersionMinor: 0x%04"PRIX16" ClientId: 0x%08"PRIX32"", + VersionMajor, VersionMinor, ClientId); + context->priv->ClientId = ClientId; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_client_name_request(RdpdrServerContext* + context, wStream* s, RDPDR_HEADER* header) +{ + UINT32 UnicodeFlag; + UINT32 ComputerNameLen; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, UnicodeFlag); /* UnicodeFlag (4 bytes) */ + Stream_Seek_UINT32(s); /* CodePage (4 bytes), MUST be set to zero */ + Stream_Read_UINT32(s, ComputerNameLen); /* ComputerNameLen (4 bytes) */ + /* UnicodeFlag is either 0 or 1, the other 31 bits must be ignored. + */ + UnicodeFlag = UnicodeFlag & 0x00000001; + + /** + * Caution: ComputerNameLen is given *bytes*, + * not in characters, including the NULL terminator! + */ + + if (UnicodeFlag) + { + if ((ComputerNameLen % 2) || ComputerNameLen > 512 || ComputerNameLen < 2) + { + WLog_ERR(TAG, "invalid unicode computer name length: %"PRIu32"", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + else + { + if (ComputerNameLen > 256 || ComputerNameLen < 1) + { + WLog_ERR(TAG, "invalid ascii computer name length: %"PRIu32"", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + + if (Stream_GetRemainingLength(s) < ComputerNameLen) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + /* ComputerName must be null terminated, check if it really is */ + + if (Stream_Pointer(s)[ComputerNameLen - 1] || + (UnicodeFlag && Stream_Pointer(s)[ComputerNameLen - 2])) + { + WLog_ERR(TAG, "computer name must be null terminated"); + return ERROR_INVALID_DATA; + } + + if (context->priv->ClientComputerName) + { + free(context->priv->ClientComputerName); + context->priv->ClientComputerName = NULL; + } + + if (UnicodeFlag) + { + if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(s), -1, + &(context->priv->ClientComputerName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert client computer name"); + return ERROR_INVALID_DATA; + } + } + else + { + context->priv->ClientComputerName = _strdup((char*) Stream_Pointer(s)); + + if (!context->priv->ClientComputerName) + { + WLog_ERR(TAG, "failed to duplicate client computer name"); + return CHANNEL_RC_NO_MEMORY; + } + } + + Stream_Seek(s, ComputerNameLen); + WLog_DBG(TAG, "ClientComputerName: %s", context->priv->ClientComputerName); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_capability_set_header(wStream* s, + RDPDR_CAPABILITY_HEADER* header) +{ + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, header->CapabilityType); /* CapabilityType (2 bytes) */ + Stream_Read_UINT16(s, + header->CapabilityLength); /* CapabilityLength (2 bytes) */ + Stream_Read_UINT32(s, header->Version); /* Version (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_capability_set_header(wStream* s, + RDPDR_CAPABILITY_HEADER* header) +{ + if (!Stream_EnsureRemainingCapacity(s, 8)) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Write_UINT16(s, header->CapabilityType); /* CapabilityType (2 bytes) */ + Stream_Write_UINT16(s, + header->CapabilityLength); /* CapabilityLength (2 bytes) */ + Stream_Write_UINT32(s, header->Version); /* Version (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_general_capability_set(RdpdrServerContext* + context, wStream* s, RDPDR_CAPABILITY_HEADER* header) +{ + UINT32 ioCode1; + UINT32 extraFlags1; + UINT32 extendedPdu; + UINT16 VersionMajor; + UINT16 VersionMinor; + UINT32 SpecialTypeDeviceCap; + + if (Stream_GetRemainingLength(s) < 32) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Seek_UINT32(s); /* osType (4 bytes), ignored on receipt */ + Stream_Seek_UINT32(s); /* osVersion (4 bytes), unused and must be set to zero */ + Stream_Read_UINT16(s, VersionMajor); /* protocolMajorVersion (2 bytes) */ + Stream_Read_UINT16(s, VersionMinor); /* protocolMinorVersion (2 bytes) */ + Stream_Read_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */ + Stream_Seek_UINT32( + s); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Read_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */ + Stream_Read_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */ + Stream_Seek_UINT32( + s); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + + if (header->Version == GENERAL_CAPABILITY_VERSION_02) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, + SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */ + } + + context->priv->UserLoggedOnPdu = (extendedPdu & RDPDR_USER_LOGGEDON_PDU) ? + TRUE : FALSE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_general_capability_set(RdpdrServerContext* + context, wStream* s) +{ + UINT32 ioCode1; + UINT32 extendedPdu; + UINT32 extraFlags1; + UINT32 SpecialTypeDeviceCap; + RDPDR_CAPABILITY_HEADER header; + header.CapabilityType = CAP_GENERAL_TYPE; + header.CapabilityLength = RDPDR_CAPABILITY_HEADER_LENGTH + 36; + header.Version = GENERAL_CAPABILITY_VERSION_02; + ioCode1 = 0; + ioCode1 |= RDPDR_IRP_MJ_CREATE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLEANUP; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLOSE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_READ; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_WRITE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_FLUSH_BUFFERS; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SHUTDOWN; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DEVICE_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DIRECTORY_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_LOCK_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_SECURITY; /* optional */ + ioCode1 |= RDPDR_IRP_MJ_SET_SECURITY; /* optional */ + extendedPdu = 0; + extendedPdu |= RDPDR_CLIENT_DISPLAY_NAME_PDU; /* always set */ + extendedPdu |= RDPDR_DEVICE_REMOVE_PDUS; /* optional */ + + if (context->priv->UserLoggedOnPdu) + extendedPdu |= RDPDR_USER_LOGGEDON_PDU; /* optional */ + + extraFlags1 = 0; + extraFlags1 |= ENABLE_ASYNCIO; /* optional */ + SpecialTypeDeviceCap = 0; + + if (!Stream_EnsureRemainingCapacity(s, header.CapabilityLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_capability_set_header(s, &header); + Stream_Write_UINT32(s, 0); /* osType (4 bytes), ignored on receipt */ + Stream_Write_UINT32(s, + 0); /* osVersion (4 bytes), unused and must be set to zero */ + Stream_Write_UINT16(s, + context->priv->VersionMajor); /* protocolMajorVersion (2 bytes) */ + Stream_Write_UINT16(s, + context->priv->VersionMinor); /* protocolMinorVersion (2 bytes) */ + Stream_Write_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */ + Stream_Write_UINT32(s, + 0); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */ + Stream_Write_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */ + Stream_Write_UINT32(s, + 0); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, + SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_printer_capability_set(RdpdrServerContext* + context, wStream* s, RDPDR_CAPABILITY_HEADER* header) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_printer_capability_set(RdpdrServerContext* + context, wStream* s) +{ + RDPDR_CAPABILITY_HEADER header; + header.CapabilityType = CAP_PRINTER_TYPE; + header.CapabilityLength = RDPDR_CAPABILITY_HEADER_LENGTH; + header.Version = PRINT_CAPABILITY_VERSION_01; + + if (!Stream_EnsureRemainingCapacity(s, header.CapabilityLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return rdpdr_server_write_capability_set_header(s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_port_capability_set(RdpdrServerContext* context, + wStream* s, RDPDR_CAPABILITY_HEADER* header) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_port_capability_set(RdpdrServerContext* context, + wStream* s) +{ + RDPDR_CAPABILITY_HEADER header; + header.CapabilityType = CAP_PORT_TYPE; + header.CapabilityLength = RDPDR_CAPABILITY_HEADER_LENGTH; + header.Version = PORT_CAPABILITY_VERSION_01; + + if (!Stream_EnsureRemainingCapacity(s, header.CapabilityLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return rdpdr_server_write_capability_set_header(s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_drive_capability_set(RdpdrServerContext* context, + wStream* s, RDPDR_CAPABILITY_HEADER* header) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_drive_capability_set(RdpdrServerContext* context, + wStream* s) +{ + RDPDR_CAPABILITY_HEADER header; + header.CapabilityType = CAP_DRIVE_TYPE; + header.CapabilityLength = RDPDR_CAPABILITY_HEADER_LENGTH; + header.Version = DRIVE_CAPABILITY_VERSION_02; + + if (!Stream_EnsureRemainingCapacity(s, header.CapabilityLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return rdpdr_server_write_capability_set_header(s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_smartcard_capability_set(RdpdrServerContext* + context, wStream* s, RDPDR_CAPABILITY_HEADER* header) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_smartcard_capability_set( + RdpdrServerContext* context, wStream* s) +{ + RDPDR_CAPABILITY_HEADER header; + header.CapabilityType = CAP_SMARTCARD_TYPE; + header.CapabilityLength = RDPDR_CAPABILITY_HEADER_LENGTH; + header.Version = SMARTCARD_CAPABILITY_VERSION_01; + + if (!Stream_EnsureRemainingCapacity(s, header.CapabilityLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_OK; + } + + return rdpdr_server_write_capability_set_header(s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_core_capability_request(RdpdrServerContext* + context) +{ + wStream* s; + BOOL status; + RDPDR_HEADER header; + UINT16 numCapabilities; + ULONG written; + UINT error; + WLog_DBG(TAG, "RdpdrServerSendCoreCapabilityRequest"); + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_CAPABILITY; + numCapabilities = 1; + + if (context->supportsDrives) + numCapabilities++; + + if (context->supportsPorts) + numCapabilities++; + + if (context->supportsPrinters) + numCapabilities++; + + if (context->supportsSmartcards) + numCapabilities++; + + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 512); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */ + + if ((error = rdpdr_server_write_general_capability_set(context, s))) + { + WLog_ERR(TAG, + "rdpdr_server_write_general_capability_set failed with error %"PRIu32"!", error); + goto out; + } + + if (context->supportsDrives) + { + if ((error = rdpdr_server_write_drive_capability_set(context, s))) + { + WLog_ERR(TAG, "rdpdr_server_write_drive_capability_set failed with error %"PRIu32"!", + error); + goto out; + } + } + + if (context->supportsPorts) + { + if ((error = rdpdr_server_write_port_capability_set(context, s))) + { + WLog_ERR(TAG, "rdpdr_server_write_port_capability_set failed with error %"PRIu32"!", + error); + goto out; + } + } + + if (context->supportsPrinters) + { + if ((error = rdpdr_server_write_printer_capability_set(context, s))) + { + WLog_ERR(TAG, + "rdpdr_server_write_printer_capability_set failed with error %"PRIu32"!", error); + goto out; + } + } + + if (context->supportsSmartcards) + { + if ((error = rdpdr_server_write_smartcard_capability_set(context, s))) + { + WLog_ERR(TAG, + "rdpdr_server_write_printer_capability_set failed with error %"PRIu32"!", error); + goto out; + } + } + + Stream_SealLength(s); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_core_capability_response( + RdpdrServerContext* context, wStream* s, RDPDR_HEADER* header) +{ + int i; + UINT status; + UINT16 numCapabilities; + RDPDR_CAPABILITY_HEADER capabilityHeader; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Seek_UINT16(s); /* Padding (2 bytes) */ + + for (i = 0; i < numCapabilities; i++) + { + if ((status = rdpdr_server_read_capability_set_header(s, &capabilityHeader))) + { + WLog_ERR(TAG, "rdpdr_server_read_capability_set_header failed with error %"PRIu32"!", + status); + return status; + } + + switch (capabilityHeader.CapabilityType) + { + case CAP_GENERAL_TYPE: + if ((status = rdpdr_server_read_general_capability_set(context, s, + &capabilityHeader))) + { + WLog_ERR(TAG, "rdpdr_server_read_general_capability_set failed with error %"PRIu32"!", + status); + return status; + } + + break; + + case CAP_PRINTER_TYPE: + if ((status = rdpdr_server_read_printer_capability_set(context, s, + &capabilityHeader))) + { + WLog_ERR(TAG, "rdpdr_server_read_printer_capability_set failed with error %"PRIu32"!", + status); + return status; + } + + break; + + case CAP_PORT_TYPE: + if ((status = rdpdr_server_read_port_capability_set(context, s, + &capabilityHeader))) + { + WLog_ERR(TAG, "rdpdr_server_read_port_capability_set failed with error %"PRIu32"!", + status); + return status; + } + + break; + + case CAP_DRIVE_TYPE: + if ((status = rdpdr_server_read_drive_capability_set(context, s, + &capabilityHeader))) + { + WLog_ERR(TAG, "rdpdr_server_read_drive_capability_set failed with error %"PRIu32"!", + status); + return status; + } + + break; + + case CAP_SMARTCARD_TYPE: + if ((status = rdpdr_server_read_smartcard_capability_set(context, s, + &capabilityHeader))) + { + WLog_ERR(TAG, + "rdpdr_server_read_smartcard_capability_set failed with error %"PRIu32"!", status); + return status; + } + + break; + + default: + WLog_DBG(TAG, "Unknown capabilityType %"PRIu16"", capabilityHeader.CapabilityType); + Stream_Seek(s, capabilityHeader.CapabilityLength - + RDPDR_CAPABILITY_HEADER_LENGTH); + return ERROR_INVALID_DATA; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_client_id_confirm(RdpdrServerContext* context) +{ + wStream* s; + BOOL status; + RDPDR_HEADER header; + ULONG written; + WLog_DBG(TAG, "RdpdrServerSendClientIdConfirm"); + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_CLIENTID_CONFIRM; + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, + context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, + context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + Stream_SealLength(s); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_announce_request( + RdpdrServerContext* context, wStream* s, RDPDR_HEADER* header) +{ + int i; + UINT32 DeviceCount; + UINT32 DeviceType; + UINT32 DeviceId; + char PreferredDosName[9]; + UINT32 DeviceDataLength; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_DBG(TAG, "DeviceCount: %"PRIu32"", DeviceCount); + + for (i = 0; i < DeviceCount; i++) + { + ZeroMemory(PreferredDosName, sizeof(PreferredDosName)); + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, DeviceType); /* DeviceType (4 bytes) */ + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read(s, PreferredDosName, 8); /* PreferredDosName (8 bytes) */ + Stream_Read_UINT32(s, DeviceDataLength); /* DeviceDataLength (4 bytes) */ + + if (Stream_GetRemainingLength(s) < DeviceDataLength) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + WLog_DBG(TAG, "Device %d Name: %s Id: 0x%08"PRIX32" DataLength: %"PRIu32"", + i, PreferredDosName, DeviceId, DeviceDataLength); + + switch (DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if (context->supportsDrives) + { + IFCALL(context->OnDriveCreate, context, DeviceId, PreferredDosName); + } + + break; + + case RDPDR_DTYP_PRINT: + if (context->supportsPrinters) + { + IFCALL(context->OnPrinterCreate, context, DeviceId, PreferredDosName); + } + + break; + + case RDPDR_DTYP_SERIAL: + case RDPDR_DTYP_PARALLEL: + if (context->supportsPorts) + { + IFCALL(context->OnPortCreate, context, DeviceId, PreferredDosName); + } + + break; + + case RDPDR_DTYP_SMARTCARD: + if (context->supportsSmartcards) + { + IFCALL(context->OnSmartcardCreate, context, DeviceId, PreferredDosName); + } + + break; + + default: + break; + } + + Stream_Seek(s, DeviceDataLength); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_remove_request( + RdpdrServerContext* context, wStream* s, RDPDR_HEADER* header) +{ + int i; + UINT32 DeviceCount; + UINT32 DeviceType; + UINT32 DeviceId; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_DBG(TAG, "DeviceCount: %"PRIu32"", DeviceCount); + + for (i = 0; i < DeviceCount; i++) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + WLog_DBG(TAG, "Device %d Id: 0x%08"PRIX32"", i, DeviceId); + DeviceType = 0; /* TODO: Save the device type on the announce request. */ + + switch (DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if (context->supportsDrives) + { + IFCALL(context->OnDriveDelete, context, DeviceId); + } + + break; + + case RDPDR_DTYP_PRINT: + if (context->supportsPrinters) + { + IFCALL(context->OnPrinterDelete, context, DeviceId); + } + + break; + + case RDPDR_DTYP_SERIAL: + case RDPDR_DTYP_PARALLEL: + if (context->supportsPorts) + { + IFCALL(context->OnPortDelete, context, DeviceId); + } + + break; + + case RDPDR_DTYP_SMARTCARD: + if (context->supportsSmartcards) + { + IFCALL(context->OnSmartcardDelete, context, DeviceId); + } + + break; + + default: + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_io_completion(RdpdrServerContext* + context, wStream* s, RDPDR_HEADER* header) +{ + UINT32 deviceId; + UINT32 completionId; + UINT32 ioStatus; + RDPDR_IRP* irp; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, completionId); + Stream_Read_UINT32(s, ioStatus); + WLog_DBG(TAG, "deviceId=%"PRIu32", completionId=0x%"PRIx32", ioStatus=0x%"PRIx32"", deviceId, + completionId, ioStatus); + irp = rdpdr_server_dequeue_irp(context, completionId); + + if (!irp) + { + WLog_ERR(TAG, "IRP not found for completionId=0x%"PRIx32"", completionId); + return ERROR_INTERNAL_ERROR; + } + + /* Invoke the callback. */ + if (irp->Callback) + { + error = (*irp->Callback)(context, s, irp, deviceId, completionId, ioStatus); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_user_logged_on(RdpdrServerContext* context) +{ + wStream* s; + BOOL status; + RDPDR_HEADER header; + ULONG written; + WLog_DBG(TAG, "RdpdrServerSendUserLoggedOn"); + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_USER_LOGGEDON; + s = Stream_New(NULL, RDPDR_HEADER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_SealLength(s); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_pdu(RdpdrServerContext* context, wStream* s, + RDPDR_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + WLog_DBG(TAG, "RdpdrServerReceivePdu: Component: 0x%04"PRIX16" PacketId: 0x%04"PRIX16"", + header->Component, header->PacketId); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + + if (header->Component == RDPDR_CTYP_CORE) + { + switch (header->PacketId) + { + case PAKID_CORE_CLIENTID_CONFIRM: + if ((error = rdpdr_server_receive_announce_response(context, s, header))) + { + WLog_ERR(TAG, "rdpdr_server_receive_announce_response failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case PAKID_CORE_CLIENT_NAME: + if ((error = rdpdr_server_receive_client_name_request(context, s, header))) + { + WLog_ERR(TAG, "rdpdr_server_receive_client_name_request failed with error %"PRIu32"!", + error); + return error; + } + + if ((error = rdpdr_server_send_core_capability_request(context))) + { + WLog_ERR(TAG, + "rdpdr_server_send_core_capability_request failed with error %"PRIu32"!", error); + return error; + } + + if ((error = rdpdr_server_send_client_id_confirm(context))) + { + WLog_ERR(TAG, "rdpdr_server_send_client_id_confirm failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case PAKID_CORE_CLIENT_CAPABILITY: + if ((error = rdpdr_server_receive_core_capability_response(context, s, header))) + { + WLog_ERR(TAG, + "rdpdr_server_receive_core_capability_response failed with error %"PRIu32"!", error); + return error; + } + + if (context->priv->UserLoggedOnPdu) + if ((error = rdpdr_server_send_user_logged_on(context))) + { + WLog_ERR(TAG, "rdpdr_server_send_user_logged_on failed with error %"PRIu32"!", error); + return error; + } + + break; + + case PAKID_CORE_DEVICELIST_ANNOUNCE: + if ((error = rdpdr_server_receive_device_list_announce_request(context, s, + header))) + { + WLog_ERR(TAG, + "rdpdr_server_receive_device_list_announce_request failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case PAKID_CORE_DEVICE_REPLY: + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + break; + + case PAKID_CORE_DEVICE_IOCOMPLETION: + if ((error = rdpdr_server_receive_device_io_completion(context, s, header))) + { + WLog_ERR(TAG, + "rdpdr_server_receive_device_io_completion failed with error %"PRIu32"!", error); + return error; + } + + break; + + case PAKID_CORE_DEVICELIST_REMOVE: + if ((error = rdpdr_server_receive_device_list_remove_request(context, s, + header))) + { + WLog_ERR(TAG, + "rdpdr_server_receive_device_io_completion failed with error %"PRIu32"!", error); + return error; + } + + break; + + default: + break; + } + } + else if (header->Component == RDPDR_CTYP_PRN) + { + switch (header->PacketId) + { + case PAKID_PRN_CACHE_DATA: + break; + + case PAKID_PRN_USING_XPS: + break; + + default: + break; + } + } + else + { + WLog_WARN(TAG, "Unknown RDPDR_HEADER.Component: 0x%04"PRIX16"", header->Component); + return ERROR_INVALID_DATA; + } + + return error; +} + +static DWORD WINAPI rdpdr_server_thread(LPVOID arg) +{ + wStream* s; + DWORD status; + DWORD nCount; + void* buffer; + HANDLE events[8]; + RDPDR_HEADER header; + HANDLE ChannelEvent; + DWORD BytesReturned; + RdpdrServerContext* context; + UINT error; + context = (RdpdrServerContext*) arg; + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + if ((error = rdpdr_server_send_announce_request(context))) + { + WLog_ERR(TAG, "rdpdr_server_send_announce_request failed with error %"PRIu32"!", + error); + goto out_stream; + } + + while (1) + { + BytesReturned = 0; + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + goto out_stream; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + goto out_stream; + } + + if (status == WAIT_OBJECT_0) + break; + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, + (PCHAR) Stream_Buffer(s), Stream_Capacity(s), &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned >= RDPDR_HEADER_LENGTH) + { + Stream_SetPosition(s, 0); + Stream_SetLength(s, BytesReturned); + + while (Stream_GetRemainingLength(s) >= RDPDR_HEADER_LENGTH) + { + Stream_Read_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + + if ((error = rdpdr_server_receive_pdu(context, s, &header))) + { + WLog_ERR(TAG, "rdpdr_server_receive_pdu failed with error %"PRIu32"!", error); + goto out_stream; + } + } + } + } + +out_stream: + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "rdpdr_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_start(RdpdrServerContext* context) +{ + context->priv->ChannelHandle = WTSVirtualChannelOpen(context->vcm, + WTS_CURRENT_SESSION, "rdpdr"); + + if (!context->priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return CHANNEL_RC_BAD_CHANNEL; + } + + if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = CreateThread(NULL, 0, + rdpdr_server_thread, (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_stop(RdpdrServerContext* context) +{ + UINT error; + + if (context->priv->StopEvent) + { + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(context->priv->Thread); + context->priv->Thread = NULL; + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + } + + return CHANNEL_RC_OK; +} + +static void rdpdr_server_write_device_iorequest( + wStream* s, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId, + UINT32 majorFunction, + UINT32 minorFunction) +{ + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICE_IOREQUEST); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, deviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Write_UINT32(s, completionId); /* CompletionId (4 bytes) */ + Stream_Write_UINT32(s, majorFunction); /* MajorFunction (4 bytes) */ + Stream_Write_UINT32(s, minorFunction); /* MinorFunction (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_file_directory_information(wStream* s, + FILE_DIRECTORY_INFORMATION* fdi) +{ + UINT32 fileNameLength; + ZeroMemory(fdi, sizeof(FILE_DIRECTORY_INFORMATION)); + + if (Stream_GetRemainingLength(s) < 64) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fdi->NextEntryOffset); /* NextEntryOffset (4 bytes) */ + Stream_Read_UINT32(s, fdi->FileIndex); /* FileIndex (4 bytes) */ + Stream_Read_UINT64(s, fdi->CreationTime); /* CreationTime (8 bytes) */ + Stream_Read_UINT64(s, fdi->LastAccessTime); /* LastAccessTime (8 bytes) */ + Stream_Read_UINT64(s, fdi->LastWriteTime); /* LastWriteTime (8 bytes) */ + Stream_Read_UINT64(s, fdi->ChangeTime); /* ChangeTime (8 bytes) */ + Stream_Read_UINT64(s, fdi->EndOfFile); /* EndOfFile (8 bytes) */ + Stream_Read_UINT64(s, fdi->AllocationSize); /* AllocationSize (8 bytes) */ + Stream_Read_UINT32(s, fdi->FileAttributes); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(s, fileNameLength); /* FileNameLength (4 bytes) */ + + if (Stream_GetRemainingLength(s) < fileNameLength) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) Stream_Pointer(s), fileNameLength / 2, + fdi->FileName, sizeof(fdi->FileName), NULL, NULL); + Stream_Seek(s, fileNameLength); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_create_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 completionId, + const char* path, + UINT32 desiredAccess, + UINT32 createOptions, + UINT32 createDisposition) +{ + UINT32 pathLength; + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, + "RdpdrServerSendDeviceCreateRequest: deviceId=%"PRIu32", path=%s, desiredAccess=0x%"PRIx32" createOptions=0x%"PRIx32" createDisposition=0x%"PRIx32"", + deviceId, path, desiredAccess, createOptions, createDisposition); + /* Compute the required Unicode size. */ + pathLength = (strlen(path) + 1) * sizeof(WCHAR); + s = Stream_New(NULL, 256 + pathLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, 0, completionId, IRP_MJ_CREATE, + 0); + Stream_Write_UINT32(s, desiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Write_UINT32(s, 0); /* AllocationSize (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Write_UINT32(s, 0); /* FileAttributes (4 bytes) */ + Stream_Write_UINT32(s, 3); /* SharedAccess (4 bytes) */ + Stream_Write_UINT32(s, createDisposition); /* CreateDisposition (4 bytes) */ + Stream_Write_UINT32(s, createOptions); /* CreateOptions (4 bytes) */ + Stream_Write_UINT32(s, pathLength); /* PathLength (4 bytes) */ + /* Convert the path to Unicode. */ + MultiByteToWideChar(CP_ACP, 0, path, -1, (LPWSTR) Stream_Pointer(s), + pathLength); + Stream_Seek(s, pathLength); + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_close_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId) +{ + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, "RdpdrServerSendDeviceCloseRequest: deviceId=%"PRIu32", fileId=%"PRIu32"", + deviceId, fileId); + s = Stream_New(NULL, 128); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, + IRP_MJ_CLOSE, 0); + Stream_Zero(s, 32); /* Padding (32 bytes) */ + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_read_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId, + UINT32 length, + UINT32 offset) +{ + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, + "RdpdrServerSendDeviceReadRequest: deviceId=%"PRIu32", fileId=%"PRIu32", length=%"PRIu32", offset=%"PRIu32"", + deviceId, fileId, length, offset); + s = Stream_New(NULL, 128); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, + IRP_MJ_READ, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_write_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId, + const char* data, + UINT32 length, + UINT32 offset) +{ + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, + "RdpdrServerSendDeviceWriteRequest: deviceId=%"PRIu32", fileId=%"PRIu32", length=%"PRIu32", offset=%"PRIu32"", + deviceId, fileId, length, offset); + s = Stream_New(NULL, 64 + length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, + IRP_MJ_WRITE, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + Stream_Write(s, data, length); /* WriteData (variable) */ + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_query_directory_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId, + const char* path) +{ + UINT32 pathLength; + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, + "RdpdrServerSendDeviceQueryDirectoryRequest: deviceId=%"PRIu32", fileId=%"PRIu32", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(NULL, 64 + pathLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, + IRP_MJ_DIRECTORY_CONTROL, IRP_MN_QUERY_DIRECTORY); + Stream_Write_UINT32(s, + FileDirectoryInformation); /* FsInformationClass (4 bytes) */ + Stream_Write_UINT8(s, path ? 1 : 0); /* InitialQuery (1 byte) */ + Stream_Write_UINT32(s, pathLength); /* PathLength (4 bytes) */ + Stream_Zero(s, 23); /* Padding (23 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + MultiByteToWideChar(CP_ACP, 0, path, -1, (LPWSTR) Stream_Pointer(s), + pathLength); + Stream_Seek(s, pathLength); + } + + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_file_rename_request( + RdpdrServerContext* context, + UINT32 deviceId, + UINT32 fileId, + UINT32 completionId, + const char* path) +{ + UINT32 pathLength; + ULONG written; + BOOL status; + wStream* s; + WLog_DBG(TAG, + "RdpdrServerSendDeviceFileNameRequest: deviceId=%"PRIu32", fileId=%"PRIu32", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(NULL, 64 + pathLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, + IRP_MJ_SET_INFORMATION, 0); + Stream_Write_UINT32(s, + FileRenameInformation); /* FsInformationClass (4 bytes) */ + Stream_Write_UINT32(s, pathLength + 6); /* Length (4 bytes) */ + Stream_Zero(s, 24); /* Padding (24 bytes) */ + /* RDP_FILE_RENAME_INFORMATION */ + Stream_Write_UINT8(s, 0); /* ReplaceIfExists (1 byte) */ + Stream_Write_UINT8(s, 0); /* RootDirectory (1 byte) */ + Stream_Write_UINT32(s, pathLength); /* FileNameLength (4 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + MultiByteToWideChar(CP_ACP, 0, path, -1, (LPWSTR) Stream_Pointer(s), + pathLength); + Stream_Seek(s, pathLength); + } + + Stream_SealLength(s); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static void rdpdr_server_convert_slashes(char* path, int size) +{ + int i; + + for (i = 0; (i < size) && (path[i] != '\0'); i++) + { + if (path[i] == '/') + path[i] = '\\'; + } +} + +/************************************************* + * Drive Create Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback2( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WLog_DBG(TAG, + "RdpdrServerDriveCreateDirectoryCallback2: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback1( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId; + UINT8 information; + WLog_DBG(TAG, + "RdpdrServerDriveCreateDirectoryCallback1: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE); +} + +/************************************************* + * Drive Delete Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback2( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WLog_DBG(TAG, + "RdpdrServerDriveDeleteDirectoryCallback2: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback1( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId; + UINT8 information; + WLog_DBG(TAG, + "RdpdrServerDriveDeleteDirectoryCallback1: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + DELETE | SYNCHRONIZE, FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE | + FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Query Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback2( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT error; + UINT32 length; + FILE_DIRECTORY_INFORMATION fdi; + WLog_DBG(TAG, + "RdpdrServerDriveQueryDirectoryCallback2: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length > 0) + { + if ((error = rdpdr_server_read_file_directory_information(s, &fdi))) + { + WLog_ERR(TAG, + "rdpdr_server_read_file_directory_information failed with error %"PRIu32"!", error); + return error; + } + } + else + { + if (Stream_GetRemainingLength(s) < 1) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Seek(s, 1); /* Padding (1 byte) */ + } + + if (ioStatus == STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, + length > 0 ? &fdi : NULL); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, irp->DeviceId, + irp->FileId, irp->CompletionId, NULL); + } + else + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, + NULL); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback1( + RdpdrServerContext* context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId; + WLog_DBG(TAG, + "RdpdrServerDriveQueryDirectoryCallback1: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, + NULL); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + strcat(irp->PathName, "\\*.*"); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, deviceId, + fileId, irp->CompletionId, irp->PathName); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Open File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file_callback(RdpdrServerContext* context, + wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 fileId; + UINT8 information; + WLog_DBG(TAG, + "RdpdrServerDriveOpenFileCallback: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Invoke the open file completion routine. */ + context->OnDriveOpenFileComplete(context, irp->CallbackData, ioStatus, deviceId, + fileId); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* path, UINT32 desiredAccess, + UINT32 createDisposition) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_open_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + desiredAccess | SYNCHRONIZE, FILE_SYNCHRONOUS_IO_NONALERT, createDisposition); +} + +/************************************************* + * Drive Read File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file_callback(RdpdrServerContext* context, + wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 length; + char* buffer = NULL; + WLog_DBG(TAG, + "RdpdrServerDriveReadFileCallback: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (Stream_GetRemainingLength(s) < length) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + if (length > 0) + { + buffer = (char*) Stream_Pointer(s); + Stream_Seek(s, length); + } + + /* Invoke the read file completion routine. */ + context->OnDriveReadFileComplete(context, irp->CallbackData, ioStatus, buffer, + length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, UINT32 fileId, UINT32 length, + UINT32 offset) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_read_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_read_request(context, deviceId, fileId, + irp->CompletionId, length, offset); +} + +/************************************************* + * Drive Write File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file_callback(RdpdrServerContext* context, + wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 length; + WLog_DBG(TAG, + "RdpdrServerDriveWriteFileCallback: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + + if (Stream_GetRemainingLength(s) < length) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + /* Invoke the write file completion routine. */ + context->OnDriveWriteFileComplete(context, irp->CallbackData, ioStatus, length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, UINT32 fileId, const char* buffer, + UINT32 length, UINT32 offset) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_write_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_write_request(context, deviceId, fileId, + irp->CompletionId, buffer, length, offset); +} + +/************************************************* + * Drive Close File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file_callback(RdpdrServerContext* context, + wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + WLog_DBG(TAG, + "RdpdrServerDriveCloseFileCallback: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + /* Invoke the close file completion routine. */ + context->OnDriveCloseFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, UINT32 fileId) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_close_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, + irp->CompletionId); +} + +/************************************************* + * Drive Delete File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback2(RdpdrServerContext* + context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + WLog_DBG(TAG, + "RdpdrServerDriveDeleteFileCallback2: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + /* Invoke the delete file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback1(RdpdrServerContext* + context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 fileId; + UINT8 information; + WLog_DBG(TAG, + "RdpdrServerDriveDeleteFileCallback1: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the close file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + FILE_READ_DATA | SYNCHRONIZE, + FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Rename File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback3(RdpdrServerContext* + context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + WLog_DBG(TAG, + "RdpdrServerDriveRenameFileCallback3: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback2(RdpdrServerContext* + context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 length; + WLog_DBG(TAG, + "RdpdrServerDriveRenameFileCallback2: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback3; + irp->DeviceId = deviceId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, irp->FileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback1(RdpdrServerContext* + context, wStream* s, RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, + UINT32 ioStatus) +{ + UINT32 fileId; + UINT8 information; + WLog_DBG(TAG, + "RdpdrServerDriveRenameFileCallback1: deviceId=%"PRIu32", completionId=%"PRIu32", ioStatus=0x%"PRIx32"", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (Stream_GetRemainingLength(s) < 5) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to rename the file */ + return rdpdr_server_send_device_file_rename_request(context, deviceId, fileId, + irp->CompletionId, irp->ExtraBuffer); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file(RdpdrServerContext* context, + void* callbackData, UINT32 deviceId, const char* oldPath, const char* newPath) +{ + RDPDR_IRP* irp; + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_ERR(TAG, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, oldPath, sizeof(irp->PathName) - 1); + strncpy(irp->ExtraBuffer, newPath, sizeof(irp->ExtraBuffer) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + rdpdr_server_convert_slashes(irp->ExtraBuffer, sizeof(irp->ExtraBuffer)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_ERR(TAG, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, + irp->CompletionId, irp->PathName, + FILE_READ_DATA | SYNCHRONIZE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +RdpdrServerContext* rdpdr_server_context_new(HANDLE vcm) +{ + RdpdrServerContext* context; + context = (RdpdrServerContext*) calloc(1, sizeof(RdpdrServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = rdpdr_server_start; + context->Stop = rdpdr_server_stop; + context->DriveCreateDirectory = rdpdr_server_drive_create_directory; + context->DriveDeleteDirectory = rdpdr_server_drive_delete_directory; + context->DriveQueryDirectory = rdpdr_server_drive_query_directory; + context->DriveOpenFile = rdpdr_server_drive_open_file; + context->DriveReadFile = rdpdr_server_drive_read_file; + context->DriveWriteFile = rdpdr_server_drive_write_file; + context->DriveCloseFile = rdpdr_server_drive_close_file; + context->DriveDeleteFile = rdpdr_server_drive_delete_file; + context->DriveRenameFile = rdpdr_server_drive_rename_file; + context->priv = (RdpdrServerPrivate*) calloc(1, sizeof(RdpdrServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return NULL; + } + + context->priv->VersionMajor = RDPDR_VERSION_MAJOR; + context->priv->VersionMinor = RDPDR_VERSION_MINOR_RDP6X; + context->priv->ClientId = g_ClientId++; + context->priv->UserLoggedOnPdu = TRUE; + context->priv->NextCompletionId = 1; + context->priv->IrpList = ListDictionary_New(TRUE); + + if (!context->priv->IrpList) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + free(context->priv); + free(context); + return NULL; + } + } + else + { + WLog_ERR(TAG, "calloc failed!"); + } + + return context; +} + +void rdpdr_server_context_free(RdpdrServerContext* context) +{ + if (context) + { + if (context->priv) + { + ListDictionary_Free(context->priv->IrpList); + free(context->priv); + } + + free(context); + } +} + diff --git a/channels/rdpdr/server/rdpdr_main.h b/channels/rdpdr/server/rdpdr_main.h new file mode 100644 index 0000000..035ff7d --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.h @@ -0,0 +1,89 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H + +#include +#include +#include +#include + +#include +#include + +struct _rdpdr_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + + UINT32 ClientId; + UINT16 VersionMajor; + UINT16 VersionMinor; + char* ClientComputerName; + + BOOL UserLoggedOnPdu; + + wListDictionary* IrpList; + UINT32 NextCompletionId; +}; + +#define RDPDR_HEADER_LENGTH 4 + +struct _RDPDR_HEADER +{ + UINT16 Component; + UINT16 PacketId; +}; +typedef struct _RDPDR_HEADER RDPDR_HEADER; + +#define RDPDR_VERSION_MAJOR 0x0001 + +#define RDPDR_VERSION_MINOR_RDP50 0x0002 +#define RDPDR_VERSION_MINOR_RDP51 0x0005 +#define RDPDR_VERSION_MINOR_RDP52 0x000A +#define RDPDR_VERSION_MINOR_RDP6X 0x000C + +#define RDPDR_CAPABILITY_HEADER_LENGTH 8 + +struct _RDPDR_CAPABILITY_HEADER +{ + UINT16 CapabilityType; + UINT16 CapabilityLength; + UINT32 Version; +}; +typedef struct _RDPDR_CAPABILITY_HEADER RDPDR_CAPABILITY_HEADER; + +struct _RDPDR_IRP +{ + UINT32 CompletionId; + UINT32 DeviceId; + UINT32 FileId; + char PathName[256]; + char ExtraBuffer[256]; + void *CallbackData; + UINT (*Callback)(RdpdrServerContext* context, wStream* s, struct _RDPDR_IRP* irp, UINT32 deviceId, UINT32 completionId, UINT32 ioStatus); +}; +typedef struct _RDPDR_IRP RDPDR_IRP; + +#endif /* FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H */ diff --git a/channels/rdpei/CMakeLists.txt b/channels/rdpei/CMakeLists.txt new file mode 100644 index 0000000..a93af67 --- /dev/null +++ b/channels/rdpei/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rdpei") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() \ No newline at end of file diff --git a/channels/rdpei/ChannelOptions.cmake b/channels/rdpei/ChannelOptions.cmake new file mode 100644 index 0000000..d3f8743 --- /dev/null +++ b/channels/rdpei/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "rdpei" TYPE "dynamic" + DESCRIPTION "Input Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEI]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpei/client/CMakeLists.txt b/channels/rdpei/client/CMakeLists.txt new file mode 100644 index 0000000..79cc5a1 --- /dev/null +++ b/channels/rdpei/client/CMakeLists.txt @@ -0,0 +1,38 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpei") + +set(${MODULE_PREFIX}_SRCS + rdpei_main.c + rdpei_main.h + ../rdpei_common.c + ../rdpei_common.h) + +include_directories(..) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/rdpei/client/rdpei_main.c b/channels/rdpei/client/rdpei_main.c new file mode 100644 index 0000000..f668159 --- /dev/null +++ b/channels/rdpei/client/rdpei_main.c @@ -0,0 +1,1015 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rdpei_common.h" + +#include "rdpei_main.h" + +/** + * Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd562197/ + * + * Windows Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317321/ + * + * Input: Touch injection sample + * http://code.msdn.microsoft.com/windowsdesktop/Touch-Injection-Sample-444d9bf7 + * + * Pointer Input Message Reference + * http://msdn.microsoft.com/en-us/library/hh454916/ + * + * POINTER_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454907/ + * + * POINTER_TOUCH_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454910/ + */ + +#define MAX_CONTACTS 512 + +struct _RDPEI_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _RDPEI_CHANNEL_CALLBACK RDPEI_CHANNEL_CALLBACK; + +struct _RDPEI_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + RDPEI_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _RDPEI_LISTENER_CALLBACK RDPEI_LISTENER_CALLBACK; + +struct _RDPEI_PLUGIN +{ + IWTSPlugin iface; + + IWTSListener* listener; + RDPEI_LISTENER_CALLBACK* listener_callback; + + RdpeiClientContext* context; + + int version; + UINT16 maxTouchContacts; + UINT64 currentFrameTime; + UINT64 previousFrameTime; + RDPINPUT_TOUCH_FRAME frame; + RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS]; + RDPINPUT_CONTACT_POINT* contactPoints; + + HANDLE event; + HANDLE stopEvent; + HANDLE thread; + + CRITICAL_SECTION lock; + rdpContext* rdpcontext; +}; +typedef struct _RDPEI_PLUGIN RDPEI_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_frame(RdpeiClientContext* context); + +const char* RDPEI_EVENTID_STRINGS[] = +{ + "", + "EVENTID_SC_READY", + "EVENTID_CS_READY", + "EVENTID_TOUCH", + "EVENTID_SUSPEND_TOUCH", + "EVENTID_RESUME_TOUCH", + "EVENTID_DISMISS_HOVERING_CONTACT" +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_add_frame(RdpeiClientContext* context) +{ + int i; + RDPINPUT_CONTACT_DATA* contact; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + rdpei->frame.contactCount = 0; + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + contact = (RDPINPUT_CONTACT_DATA*) & (rdpei->contactPoints[i].data); + + if (rdpei->contactPoints[i].dirty) + { + CopyMemory(&(rdpei->contacts[rdpei->frame.contactCount]), contact, + sizeof(RDPINPUT_CONTACT_DATA)); + rdpei->contactPoints[i].dirty = FALSE; + rdpei->frame.contactCount++; + } + else if (rdpei->contactPoints[i].active) + { + if (contact->contactFlags & CONTACT_FLAG_DOWN) + { + contact->contactFlags = CONTACT_FLAG_UPDATE; + contact->contactFlags |= CONTACT_FLAG_INRANGE; + contact->contactFlags |= CONTACT_FLAG_INCONTACT; + } + + CopyMemory(&(rdpei->contacts[rdpei->frame.contactCount]), contact, + sizeof(RDPINPUT_CONTACT_DATA)); + rdpei->frame.contactCount++; + } + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI rdpei_schedule_thread(LPVOID arg) +{ + DWORD status; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) arg; + RdpeiClientContext* context = (RdpeiClientContext*) rdpei->iface.pInterface; + HANDLE hdl[] = {rdpei->event, rdpei->stopEvent}; + UINT error = CHANNEL_RC_OK; + + if (!rdpei) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + if (!context) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + while (1) + { + status = WaitForMultipleObjects(2, hdl, FALSE, 20); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + if (status == WAIT_OBJECT_0 + 1) + break; + + EnterCriticalSection(&rdpei->lock); + + if ((error = rdpei_add_frame(context))) + { + WLog_ERR(TAG, "rdpei_add_frame failed with error %"PRIu32"!", error); + break; + } + + if (rdpei->frame.contactCount > 0) + { + if ((error = rdpei_send_frame(context))) + { + WLog_ERR(TAG, "rdpei_send_frame failed with error %"PRIu32"!", error); + break; + } + } + + if (status == WAIT_OBJECT_0) + ResetEvent(rdpei->event); + + LeaveCriticalSection(&rdpei->lock); + } + +out: + + if (error && rdpei && rdpei->rdpcontext) + setChannelError(rdpei->rdpcontext, error, + "rdpei_schedule_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s, + UINT16 eventId, UINT32 pduLength) +{ + UINT status; + Stream_SetPosition(s, 0); + Stream_Write_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Write_UINT32(s, pduLength); /* pduLength (4 bytes) */ + Stream_SetPosition(s, Stream_Length(s)); + status = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), + Stream_Buffer(s), NULL); +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "rdpei_send_pdu: eventId: %"PRIu16" (%s) length: %"PRIu32" status: %"PRIu32"", + eventId, RDPEI_EVENTID_STRINGS[eventId], pduLength, status); +#endif + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_cs_ready_pdu(RDPEI_CHANNEL_CALLBACK* callback) +{ + UINT status; + wStream* s; + UINT32 flags; + UINT32 pduLength; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) callback->plugin; + flags = 0; + flags |= READY_FLAGS_SHOW_TOUCH_VISUALS; + //flags |= READY_FLAGS_DISABLE_TIMESTAMP_INJECTION; + pduLength = RDPINPUT_HEADER_LENGTH + 10; + s = Stream_New(NULL, pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + Stream_Write_UINT32(s, flags); /* flags (4 bytes) */ + Stream_Write_UINT32(s, RDPINPUT_PROTOCOL_V10); /* protocolVersion (4 bytes) */ + Stream_Write_UINT16(s, + rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */ + Stream_SealLength(s); + status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength); + Stream_Free(s, TRUE); + return status; +} + +void rdpei_print_contact_flags(UINT32 contactFlags) +{ + if (contactFlags & CONTACT_FLAG_DOWN) + WLog_DBG(TAG, " CONTACT_FLAG_DOWN"); + + if (contactFlags & CONTACT_FLAG_UPDATE) + WLog_DBG(TAG, " CONTACT_FLAG_UPDATE"); + + if (contactFlags & CONTACT_FLAG_UP) + WLog_DBG(TAG, " CONTACT_FLAG_UP"); + + if (contactFlags & CONTACT_FLAG_INRANGE) + WLog_DBG(TAG, " CONTACT_FLAG_INRANGE"); + + if (contactFlags & CONTACT_FLAG_INCONTACT) + WLog_DBG(TAG, " CONTACT_FLAG_INCONTACT"); + + if (contactFlags & CONTACT_FLAG_CANCELED) + WLog_DBG(TAG, " CONTACT_FLAG_CANCELED"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_write_touch_frame(wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + UINT32 index; + int rectSize = 2; + RDPINPUT_CONTACT_DATA* contact; +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "contactCount: %"PRIu32"", frame->contactCount); + WLog_DBG(TAG, "frameOffset: 0x%016"PRIX64"", frame->frameOffset); +#endif + rdpei_write_2byte_unsigned(s, + frame->contactCount); /* contactCount (TWO_BYTE_UNSIGNED_INTEGER) */ + /** + * the time offset from the previous frame (in microseconds). + * If this is the first frame being transmitted then this field MUST be set to zero. + */ + rdpei_write_8byte_unsigned(s, + frame->frameOffset * 1000); /* frameOffset (EIGHT_BYTE_UNSIGNED_INTEGER) */ + + if (!Stream_EnsureRemainingCapacity(s, (size_t) frame->contactCount * 64)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < frame->contactCount; index++) + { + contact = &frame->contacts[index]; + contact->fieldsPresent |= CONTACT_DATA_CONTACTRECT_PRESENT; + contact->contactRectLeft = contact->x - rectSize; + contact->contactRectTop = contact->y - rectSize; + contact->contactRectRight = contact->x + rectSize; + contact->contactRectBottom = contact->y + rectSize; +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "contact[%"PRIu32"].contactId: %"PRIu32"", index, contact->contactId); + WLog_DBG(TAG, "contact[%"PRIu32"].fieldsPresent: %"PRIu32"", index, contact->fieldsPresent); + WLog_DBG(TAG, "contact[%"PRIu32"].x: %"PRId32"", index, contact->x); + WLog_DBG(TAG, "contact[%"PRIu32"].y: %"PRId32"", index, contact->y); + WLog_DBG(TAG, "contact[%"PRIu32"].contactFlags: 0x%08"PRIX32"", index, contact->contactFlags); + rdpei_print_contact_flags(contact->contactFlags); +#endif + Stream_Write_UINT8(s, contact->contactId); /* contactId (1 byte) */ + /* fieldsPresent (TWO_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, contact->fieldsPresent); + rdpei_write_4byte_signed(s, contact->x); /* x (FOUR_BYTE_SIGNED_INTEGER) */ + rdpei_write_4byte_signed(s, contact->y); /* y (FOUR_BYTE_SIGNED_INTEGER) */ + /* contactFlags (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->contactFlags); + + if (contact->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + /* contactRectLeft (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectLeft); + /* contactRectTop (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectTop); + /* contactRectRight (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectRight); + /* contactRectBottom (TWO_BYTE_SIGNED_INTEGER) */ + rdpei_write_2byte_signed(s, contact->contactRectBottom); + } + + if (contact->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) + { + /* orientation (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->orientation); + } + + if (contact->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) + { + /* pressure (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_4byte_unsigned(s, contact->pressure); + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_touch_event_pdu(RDPEI_CHANNEL_CALLBACK* callback, + RDPINPUT_TOUCH_FRAME* frame) +{ + UINT status; + wStream* s; + UINT32 pduLength; + pduLength = 64 + (frame->contactCount * 64); + s = Stream_New(NULL, pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + /** + * the time that has elapsed (in milliseconds) from when the oldest touch frame + * was generated to when it was encoded for transmission by the client. + */ + rdpei_write_4byte_unsigned(s, + (UINT32) frame->frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, 1); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + + if ((status = rdpei_write_touch_frame(s, frame))) + { + WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %"PRIu32"!", status); + Stream_Free(s, TRUE); + return status; + } + + Stream_SealLength(s); + pduLength = Stream_Length(s); + status = rdpei_send_pdu(callback, s, EVENTID_TOUCH, pduLength); + Stream_Free(s, TRUE); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_recv_sc_ready_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 protocolVersion; + Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */ +#if 0 + + if (protocolVersion != RDPINPUT_PROTOCOL_V10) + { + WLog_ERR(TAG, "Unknown [MS-RDPEI] protocolVersion: 0x%08"PRIX32"", protocolVersion); + return -1; + } + +#endif + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_recv_suspend_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + RdpeiClientContext* rdpei = (RdpeiClientContext*) callback->plugin->pInterface; + UINT error = CHANNEL_RC_OK; + IFCALLRET(rdpei->SuspendTouch, error, rdpei); + + if (error) + WLog_ERR(TAG, "rdpei->SuspendTouch failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_recv_resume_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + RdpeiClientContext* rdpei = (RdpeiClientContext*) callback->plugin->pInterface; + UINT error = CHANNEL_RC_OK; + IFCALLRET(rdpei->ResumeTouch, error, rdpei); + + if (error) + WLog_ERR(TAG, "rdpei->ResumeTouch failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_recv_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT16 eventId; + UINT32 pduLength; + UINT error; + Stream_Read_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */ +#ifdef WITH_DEBUG_RDPEI + WLog_DBG(TAG, "rdpei_recv_pdu: eventId: %"PRIu16" (%s) length: %"PRIu32"", + eventId, RDPEI_EVENTID_STRINGS[eventId], pduLength); +#endif + + switch (eventId) + { + case EVENTID_SC_READY: + if ((error = rdpei_recv_sc_ready_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_sc_ready_pdu failed with error %"PRIu32"!", error); + return error; + } + + if ((error = rdpei_send_cs_ready_pdu(callback))) + { + WLog_ERR(TAG, "rdpei_send_cs_ready_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case EVENTID_SUSPEND_TOUCH: + if ((error = rdpei_recv_suspend_touch_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_suspend_touch_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case EVENTID_RESUME_TOUCH: + if ((error = rdpei_recv_resume_touch_pdu(callback, s))) + { + WLog_ERR(TAG, "rdpei_recv_resume_touch_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, + wStream* data) +{ + RDPEI_CHANNEL_CALLBACK* callback = (RDPEI_CHANNEL_CALLBACK*) pChannelCallback; + return rdpei_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPEI_CHANNEL_CALLBACK* callback = (RDPEI_CHANNEL_CALLBACK*) pChannelCallback; + free(callback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_new_channel_connection(IWTSListenerCallback* + pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + RDPEI_CHANNEL_CALLBACK* callback; + RDPEI_LISTENER_CALLBACK* listener_callback = (RDPEI_LISTENER_CALLBACK*) + pListenerCallback; + callback = (RDPEI_CHANNEL_CALLBACK*) calloc(1, sizeof(RDPEI_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = rdpei_on_data_received; + callback->iface.OnClose = rdpei_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_plugin_initialize(IWTSPlugin* pPlugin, + IWTSVirtualChannelManager* pChannelMgr) +{ + UINT error; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) pPlugin; + rdpei->listener_callback = (RDPEI_LISTENER_CALLBACK*) calloc(1 , + sizeof(RDPEI_LISTENER_CALLBACK)); + + if (!rdpei->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpei->listener_callback->iface.OnNewChannelConnection = + rdpei_on_new_channel_connection; + rdpei->listener_callback->plugin = pPlugin; + rdpei->listener_callback->channel_mgr = pChannelMgr; + + if ((error = pChannelMgr->CreateListener(pChannelMgr, RDPEI_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*) rdpei->listener_callback, &(rdpei->listener)))) + { + WLog_ERR(TAG, "ChannelMgr->CreateListener failed with error %"PRIu32"!", error); + goto error_out; + } + + rdpei->listener->pInterface = rdpei->iface.pInterface; + InitializeCriticalSection(&rdpei->lock); + + if (!(rdpei->event = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto error_out; + } + + if (!(rdpei->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto error_out; + } + + if (!(rdpei->thread = CreateThread(NULL, 0, + rdpei_schedule_thread, (void*) rdpei, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto error_out; + } + + return error; +error_out: + CloseHandle(rdpei->stopEvent); + CloseHandle(rdpei->event); + free(rdpei->listener_callback); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_plugin_terminated(IWTSPlugin* pPlugin) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) pPlugin; + UINT error; + + if (!pPlugin) + return ERROR_INVALID_PARAMETER; + + SetEvent(rdpei->stopEvent); + EnterCriticalSection(&rdpei->lock); + + if (WaitForSingleObject(rdpei->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(rdpei->stopEvent); + CloseHandle(rdpei->event); + CloseHandle(rdpei->thread); + DeleteCriticalSection(&rdpei->lock); + free(rdpei->listener_callback); + free(rdpei->context); + free(rdpei); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +int rdpei_get_version(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + return rdpei->version; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_frame(RdpeiClientContext* context) +{ + UINT64 currentTime; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + RDPEI_CHANNEL_CALLBACK* callback = rdpei->listener_callback->channel_callback; + UINT error; + currentTime = GetTickCount64(); + + if (!rdpei->previousFrameTime && !rdpei->currentFrameTime) + { + rdpei->currentFrameTime = currentTime; + rdpei->frame.frameOffset = 0; + } + else + { + rdpei->currentFrameTime = currentTime; + rdpei->frame.frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime; + } + + if ((error = rdpei_send_touch_event_pdu(callback, &rdpei->frame))) + { + WLog_ERR(TAG, "rdpei_send_touch_event_pdu failed with error %"PRIu32"!", error); + return error; + } + + rdpei->previousFrameTime = rdpei->currentFrameTime; + rdpei->frame.contactCount = 0; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_add_contact(RdpeiClientContext* context, + RDPINPUT_CONTACT_DATA* contact) +{ + RDPINPUT_CONTACT_POINT* contactPoint; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + EnterCriticalSection(&rdpei->lock); + contactPoint = (RDPINPUT_CONTACT_POINT*) + &rdpei->contactPoints[contact->contactId]; + CopyMemory(&(contactPoint->data), contact, sizeof(RDPINPUT_CONTACT_DATA)); + contactPoint->dirty = TRUE; + SetEvent(rdpei->event); + LeaveCriticalSection(&rdpei->lock); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_touch_begin(RdpeiClientContext* context, int externalId, int x, + int y, int* contactId) +{ + unsigned int i; + int contactIdlocal = -1; + RDPINPUT_CONTACT_DATA contact; + RDPINPUT_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + UINT error = CHANNEL_RC_OK; + + /* Create a new contact point in an empty slot */ + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + contactPoint = (RDPINPUT_CONTACT_POINT*) &rdpei->contactPoints[i]; + + if (!contactPoint->active) + { + contactPoint->contactId = i; + contactIdlocal = contactPoint->contactId; + contactPoint->externalId = externalId; + contactPoint->active = TRUE; + contactPoint->state = RDPINPUT_CONTACT_STATE_ENGAGED; + break; + } + } + + if (contactIdlocal >= 0) + { + ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA)); + contactPoint->lastX = x; + contactPoint->lastY = y; + contact.x = x; + contact.y = y; + contact.contactId = (UINT32) contactIdlocal; + contact.contactFlags |= CONTACT_FLAG_DOWN; + contact.contactFlags |= CONTACT_FLAG_INRANGE; + contact.contactFlags |= CONTACT_FLAG_INCONTACT; + error = context->AddContact(context, &contact); + } + + *contactId = contactIdlocal; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_touch_update(RdpeiClientContext* context, int externalId, int x, + int y, int* contactId) +{ + unsigned int i; + int contactIdlocal = -1; + RDPINPUT_CONTACT_DATA contact; + RDPINPUT_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + UINT error = CHANNEL_RC_OK; + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + contactPoint = (RDPINPUT_CONTACT_POINT*) &rdpei->contactPoints[i]; + + if (!contactPoint->active) + continue; + + if (contactPoint->externalId == externalId) + { + contactIdlocal = contactPoint->contactId; + break; + } + } + + if (contactIdlocal >= 0) + { + ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA)); + contactPoint->lastX = x; + contactPoint->lastY = y; + contact.x = x; + contact.y = y; + contact.contactId = (UINT32) contactIdlocal; + contact.contactFlags |= CONTACT_FLAG_UPDATE; + contact.contactFlags |= CONTACT_FLAG_INRANGE; + contact.contactFlags |= CONTACT_FLAG_INCONTACT; + error = context->AddContact(context, &contact); + } + + *contactId = contactIdlocal; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_touch_end(RdpeiClientContext* context, int externalId, int x, int y, + int* contactId) +{ + unsigned int i; + int contactIdlocal = -1; + int tempvalue; + RDPINPUT_CONTACT_DATA contact; + RDPINPUT_CONTACT_POINT* contactPoint = NULL; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle; + UINT error; + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + contactPoint = (RDPINPUT_CONTACT_POINT*) &rdpei->contactPoints[i]; + + if (!contactPoint->active) + continue; + + if (contactPoint->externalId == externalId) + { + contactIdlocal = contactPoint->contactId; + break; + } + } + + if (contactIdlocal >= 0) + { + ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA)); + + if ((contactPoint->lastX != x) && (contactPoint->lastY != y)) + { + if ((error = context->TouchUpdate(context, externalId, x, y, &tempvalue))) + { + WLog_ERR(TAG, "context->TouchUpdate failed with error %"PRIu32"!", error); + return error; + } + } + + contact.x = x; + contact.y = y; + contact.contactId = (UINT32) contactIdlocal; + contact.contactFlags |= CONTACT_FLAG_UP; + + if ((error = context->AddContact(context, &contact))) + { + WLog_ERR(TAG, "context->AddContact failed with error %"PRIu32"!", error); + return error; + } + + contactPoint->externalId = 0; + contactPoint->active = FALSE; + contactPoint->flags = 0; + contactPoint->contactId = 0; + contactPoint->state = RDPINPUT_CONTACT_STATE_OUT_OF_RANGE; + } + + *contactId = contactIdlocal; + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry rdpei_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error; + RDPEI_PLUGIN* rdpei = NULL; + RdpeiClientContext* context = NULL; + rdpei = (RDPEI_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "rdpei"); + + if (!rdpei) + { + size_t size; + rdpei = (RDPEI_PLUGIN*) calloc(1, sizeof(RDPEI_PLUGIN)); + + if (!rdpei) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpei->iface.Initialize = rdpei_plugin_initialize; + rdpei->iface.Connected = NULL; + rdpei->iface.Disconnected = NULL; + rdpei->iface.Terminated = rdpei_plugin_terminated; + rdpei->version = 1; + rdpei->currentFrameTime = 0; + rdpei->previousFrameTime = 0; + rdpei->frame.contacts = (RDPINPUT_CONTACT_DATA*) rdpei->contacts; + rdpei->maxTouchContacts = 10; + size = rdpei->maxTouchContacts * sizeof(RDPINPUT_CONTACT_POINT); + rdpei->contactPoints = (RDPINPUT_CONTACT_POINT*) calloc(1, size); + rdpei->rdpcontext = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( + pEntryPoints))->instance)->context; + + if (!rdpei->contactPoints) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + context = (RdpeiClientContext*) calloc(1, sizeof(RdpeiClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + context->handle = (void*) rdpei; + context->GetVersion = rdpei_get_version; + context->AddContact = rdpei_add_contact; + context->TouchBegin = rdpei_touch_begin; + context->TouchUpdate = rdpei_touch_update; + context->TouchEnd = rdpei_touch_end; + rdpei->iface.pInterface = (void*) context; + + if ((error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpei", + (IWTSPlugin*) rdpei))) + { + WLog_ERR(TAG, "EntryPoints->RegisterPlugin failed with error %"PRIu32"!", error); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + rdpei->context = context; + } + + return CHANNEL_RC_OK; +error_out: + free(context); + free(rdpei->contactPoints); + free(rdpei); + return error; +} diff --git a/channels/rdpei/client/rdpei_main.h b/channels/rdpei/client/rdpei_main.h new file mode 100644 index 0000000..4dc4998 --- /dev/null +++ b/channels/rdpei/client/rdpei_main.h @@ -0,0 +1,91 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("rdpei.client") + +#define RDPINPUT_CONTACT_STATE_INITIAL 0x0000 +#define RDPINPUT_CONTACT_STATE_ENGAGED 0x0001 +#define RDPINPUT_CONTACT_STATE_HOVERING 0x0002 +#define RDPINPUT_CONTACT_STATE_OUT_OF_RANGE 0x0003 + +/** + * Touch Contact State Transitions + * + * ENGAGED -> UPDATE | INRANGE | INCONTACT -> ENGAGED + * ENGAGED -> UP | INRANGE -> HOVERING + * ENGAGED -> UP -> OUT_OF_RANGE + * ENGAGED -> UP | CANCELED -> OUT_OF_RANGE + * + * HOVERING -> UPDATE | INRANGE -> HOVERING + * HOVERING -> DOWN | INRANGE | INCONTACT -> ENGAGED + * HOVERING -> UPDATE -> OUT_OF_RANGE + * HOVERING -> UPDATE | CANCELED -> OUT_OF_RANGE + * + * OUT_OF_RANGE -> DOWN | INRANGE | INCONTACT -> ENGAGED + * OUT_OF_RANGE -> UPDATE | INRANGE -> HOVERING + * + * When a contact is in the "hovering" or "engaged" state, it is referred to as being "active". + * "Hovering" contacts are in range of the digitizer, while "engaged" contacts are in range of + * the digitizer and in contact with the digitizer surface. MS-RDPEI remotes only active contacts + * and contacts that are transitioning to the "out of range" state; see section 2.2.3.3.1.1 for + * an enumeration of valid state flags combinations. + * + * When transitioning from the "engaged" state to the "hovering" state, or from the "engaged" + * state to the "out of range" state, the contact position cannot change; it is only allowed + * to change after the transition has taken place. + * + */ + +struct _RDPINPUT_CONTACT_POINT +{ + int lastX; + int lastY; + BOOL dirty; + BOOL active; + UINT32 state; + UINT32 flags; + UINT32 contactId; + int externalId; + RDPINPUT_CONTACT_DATA data; +}; +typedef struct _RDPINPUT_CONTACT_POINT RDPINPUT_CONTACT_POINT; + +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) do { } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H */ + diff --git a/channels/rdpei/rdpei_common.c b/channels/rdpei/rdpei_common.c new file mode 100644 index 0000000..41a2ec3 --- /dev/null +++ b/channels/rdpei/rdpei_common.c @@ -0,0 +1,610 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2014 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "rdpei_common.h" + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT32* value) +{ + BYTE byte; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + + if (byte & 0x80) + { + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + *value = (byte & 0x7F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + } + else + { + *value = (byte & 0x7F); + } + + return TRUE; +} + +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT32 value) +{ + BYTE byte; + + if (value > 0x7FFF) + return FALSE; + + if (value >= 0x7F) + { + byte = ((value & 0x7F00) >> 8); + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x7F); + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_2byte_signed(wStream* s, INT32* value) +{ + BYTE byte; + BOOL negative; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + + negative = (byte & 0x40) ? TRUE : FALSE; + + *value = (byte & 0x3F); + + if (byte & 0x80) + { + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + *value = (*value << 8) | byte; + } + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_2byte_signed(wStream* s, INT32 value) +{ + BYTE byte; + BOOL negative = FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value > 0x3FFF) + return FALSE; + + if (value >= 0x3F) + { + byte = ((value & 0x3F00) >> 8); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x3F); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value) +{ + BYTE byte; + BYTE count; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + + if (Stream_GetRemainingLength(s) < count) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x3F); + break; + + case 1: + *value = (byte & 0x3F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x3F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x3F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value) +{ + BYTE byte; + + if (value <= 0x3F) + { + Stream_Write_UINT8(s, value); + } + else if (value <= 0x3FFF) + { + byte = (value >> 8) & 0x3F; + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFF) + { + byte = (value >> 16) & 0x3F; + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFF) + { + byte = (value >> 24) & 0x3F; + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_4byte_signed(wStream* s, INT32* value) +{ + BYTE byte; + BYTE count; + BOOL negative; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + negative = (byte & 0x20); + + if (Stream_GetRemainingLength(s) < count) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_4byte_signed(wStream* s, INT32 value) +{ + BYTE byte; + BOOL negative = FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value <= 0x1F) + { + byte = value & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFF) + { + byte = (value >> 8) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFF) + { + byte = (value >> 16) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFF) + { + byte = (value >> 24) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value) +{ + BYTE byte; + BYTE count; + + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xE0) >> 5; + + if (Stream_GetRemainingLength(s) < count) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 4: + *value = ((UINT64) (byte & 0x1F)) << 32; + Stream_Read_UINT8(s, byte); + *value |= (byte << 24); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 5: + *value = ((UINT64) (byte & 0x1F)) << 40; + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 32); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 6: + *value = ((UINT64) (byte & 0x1F)) << 48; + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 40); + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 32); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 7: + *value = ((UINT64) (byte & 0x1F)) << 56; + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 48); + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 40); + Stream_Read_UINT8(s, byte); + *value |= (((UINT64) byte) << 32); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value) +{ + BYTE byte; + + if (value <= 0x1F) + { + byte = value & 0x1F; + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFF) + { + byte = (value >> 8) & 0x1F; + byte |= (1 << 5); + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFF) + { + byte = (value >> 16) & 0x1F; + byte |= (2 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFF) + { + byte = (value >> 24) & 0x1F; + byte |= (3 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFF) + { + byte = (value >> 32) & 0x1F; + byte |= (4 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFF) + { + byte = (value >> 40) & 0x1F; + byte |= (5 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFF) + { + byte = (value >> 48) & 0x1F; + byte |= (6 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFFF) + { + byte = (value >> 56) & 0x1F; + byte |= (7 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 48) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +void touch_event_reset(RDPINPUT_TOUCH_EVENT *event) +{ + int i; + + for (i = 0; i < event->frameCount; i++) + touch_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = NULL; + event->frameCount = 0; +} + + +void touch_frame_reset(RDPINPUT_TOUCH_FRAME *frame) +{ + free(frame->contacts); + frame->contacts = NULL; + frame->contactCount = 0; +} diff --git a/channels/rdpei/rdpei_common.h b/channels/rdpei/rdpei_common.h new file mode 100644 index 0000000..34d1edd --- /dev/null +++ b/channels/rdpei/rdpei_common.h @@ -0,0 +1,53 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2014 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_COMMON_H +#define FREERDP_CHANNEL_RDPEI_COMMON_H + +#include +#include +#include + +/** @brief input event ids */ +enum { + EVENTID_SC_READY = 0x0001, + EVENTID_CS_READY = 0x0002, + EVENTID_TOUCH = 0x0003, + EVENTID_SUSPEND_TOUCH = 0x0004, + EVENTID_RESUME_TOUCH = 0x0005, + EVENTID_DISMISS_HOVERING_CONTACT = 0x0006 +}; + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT32* value); +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT32 value); +BOOL rdpei_read_2byte_signed(wStream* s, INT32* value); +BOOL rdpei_write_2byte_signed(wStream* s, INT32 value); +BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value); +BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value); +BOOL rdpei_read_4byte_signed(wStream* s, INT32* value); +BOOL rdpei_write_4byte_signed(wStream* s, INT32 value); +BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value); +BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value); + +void touch_event_reset(RDPINPUT_TOUCH_EVENT *event); +void touch_frame_reset(RDPINPUT_TOUCH_FRAME *frame); + +#endif /* FREERDP_CHANNEL_RDPEI_COMMON_H */ + diff --git a/channels/rdpei/server/CMakeLists.txt b/channels/rdpei/server/CMakeLists.txt new file mode 100644 index 0000000..b2a464d --- /dev/null +++ b/channels/rdpei/server/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2014 Thincast Technologies Gmbh. +# Copyright 2014 David FORT +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("rdpei") + +set(${MODULE_PREFIX}_SRCS + rdpei_main.c + rdpei_main.h + ../rdpei_common.c + ../rdpei_common.h +) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/rdpei/server/rdpei_main.c b/channels/rdpei/server/rdpei_main.c new file mode 100644 index 0000000..a475584 --- /dev/null +++ b/channels/rdpei/server/rdpei_main.c @@ -0,0 +1,578 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 Thincast Technologies Gmbh. + * Copyright 2014 David FORT + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "rdpei_main.h" +#include "../rdpei_common.h" +#include +#include + +/** @brief */ +enum RdpEiState { + STATE_INITIAL, + STATE_WAITING_CLIENT_READY, + STATE_WAITING_FRAME, + STATE_SUSPENDED, +}; + +struct _rdpei_server_private +{ + HANDLE channelHandle; + HANDLE eventHandle; + + UINT32 expectedBytes; + BOOL waitingHeaders; + wStream *inputStream; + wStream *outputStream; + + UINT16 currentMsgType; + + RDPINPUT_TOUCH_EVENT touchEvent; + + enum RdpEiState automataState; +}; + + +RdpeiServerContext* rdpei_server_context_new(HANDLE vcm) +{ + RdpeiServerContext *ret = calloc(1, sizeof(*ret)); + RdpeiServerPrivate *priv; + + if (!ret) + return NULL; + + ret->priv = priv = calloc(1, sizeof(*ret->priv)); + if (!priv) + goto out_free; + + priv->inputStream = Stream_New(NULL, 256); + if (!priv->inputStream) + goto out_free_priv; + + priv->outputStream = Stream_New(NULL, 200); + if (!priv->inputStream) + goto out_free_input_stream; + + ret->vcm = vcm; + rdpei_server_context_reset(ret); + return ret; + +out_free_input_stream: + Stream_Free(priv->inputStream, TRUE); +out_free_priv: + free(ret->priv); +out_free: + free(ret); + return NULL; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_init(RdpeiServerContext *context) +{ + void *buffer = NULL; + DWORD bytesReturned; + RdpeiServerPrivate *priv = context->priv; + + priv->channelHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, RDPEI_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!priv->channelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if (!WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer, &bytesReturned) || (bytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed or invalid invalid returned size(%"PRIu32")!", bytesReturned); + if (buffer) + WTSFreeMemory(buffer); + goto out_close; + } + CopyMemory(&priv->eventHandle, buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + + return CHANNEL_RC_OK; + +out_close: + WTSVirtualChannelClose(priv->channelHandle); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + + +void rdpei_server_context_reset(RdpeiServerContext *context) +{ + RdpeiServerPrivate *priv = context->priv; + + priv->channelHandle = INVALID_HANDLE_VALUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = TRUE; + priv->automataState = STATE_INITIAL; + Stream_SetPosition(priv->inputStream, 0); +} + +void rdpei_server_context_free(RdpeiServerContext* context) +{ + RdpeiServerPrivate *priv = context->priv; + if (priv->channelHandle != INVALID_HANDLE_VALUE) + WTSVirtualChannelClose(priv->channelHandle); + Stream_Free(priv->inputStream, TRUE); + free(priv); + free(context); +} + +HANDLE rdpei_server_get_event_handle(RdpeiServerContext *context) +{ + return context->priv->eventHandle; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_cs_ready_message(RdpeiServerContext *context, wStream *s) +{ + UINT error = CHANNEL_RC_OK; + if (Stream_GetRemainingLength(s) < 10) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, context->protocolFlags); + Stream_Read_UINT32(s, context->clientVersion); + Stream_Read_UINT16(s, context->maxTouchPoints); + + switch (context->clientVersion) + { + case RDPINPUT_PROTOCOL_V10: + case RDPINPUT_PROTOCOL_V101: + break; + default: + WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%"PRIx32"", context->clientVersion); + break; + } + + IFCALLRET(context->onClientReady, error, context); + if (error) + WLog_ERR(TAG, "context->onClientReady failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_contact_data(RdpeiServerContext *context, wStream *s, RDPINPUT_CONTACT_DATA *contactData) +{ + if (Stream_GetRemainingLength(s) < 1) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, contactData->contactId); + if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) || + !rdpei_read_4byte_signed(s, &contactData->x) || + !rdpei_read_4byte_signed(s, &contactData->y) || + !rdpei_read_4byte_unsigned(s, &contactData->contactFlags)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) || + !rdpei_read_2byte_signed(s, &contactData->contactRectTop) || + !rdpei_read_2byte_signed(s, &contactData->contactRectRight) || + !rdpei_read_2byte_signed(s, &contactData->contactRectBottom)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->orientation)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + + if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->pressure)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_frame(RdpeiServerContext *context, wStream *s, RDPINPUT_TOUCH_FRAME *frame) +{ + int i; + RDPINPUT_CONTACT_DATA *contact; + UINT error; + + if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) || !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA)); + if (!frame->contacts) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (i = 0; i < frame->contactCount; i++, contact++) + { + if ((error = read_touch_contact_data(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %"PRIu32"!", error); + frame->contactCount = i; + touch_frame_reset(frame); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_event(RdpeiServerContext *context, wStream *s) +{ + UINT32 frameCount; + int i; + RDPINPUT_TOUCH_EVENT *event = &context->priv->touchEvent; + RDPINPUT_TOUCH_FRAME *frame; + UINT error = CHANNEL_RC_OK; + + if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) || !rdpei_read_2byte_unsigned(s, &frameCount)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + event->frameCount = frameCount; + event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (i = 0; i < frameCount; i++, frame++) + { + if ((error = read_touch_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %"PRIu32"!", error); + event->frameCount = i; + goto out_cleanup; + } + } + + + IFCALLRET(context->onTouchEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onTouchEvent failed with error %"PRIu32"", error); + +out_cleanup: + touch_event_reset(event); + return error; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_dismiss_hovering_contact(RdpeiServerContext *context, wStream *s) { + BYTE contactId; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 1) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, contactId); + + IFCALLRET(context->onTouchReleased, error, context, contactId); + if (error) + WLog_ERR(TAG, "context->onTouchReleased failed with error %"PRIu32"", error); + + return error; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_handle_messages(RdpeiServerContext *context) { + DWORD bytesReturned; + RdpeiServerPrivate *priv = context->priv; + wStream *s = priv->inputStream; + UINT error = CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(priv->channelHandle, 0, (PCHAR)Stream_Pointer(s), priv->expectedBytes, &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_READ_FAULT; + + WLog_DBG(TAG, "channel connection closed"); + return CHANNEL_RC_OK; + } + priv->expectedBytes -= bytesReturned; + Stream_Seek(s, bytesReturned); + + if (priv->expectedBytes) + return CHANNEL_RC_OK; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (priv->waitingHeaders) + { + UINT32 pduLen; + + /* header case */ + Stream_Read_UINT16(s, priv->currentMsgType); + Stream_Read_UINT16(s, pduLen); + + if (pduLen < RDPINPUT_HEADER_LENGTH) + { + WLog_ERR(TAG, "invalid pduLength %"PRIu32"", pduLen); + return ERROR_INVALID_DATA; + } + priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = FALSE; + Stream_SetPosition(s, 0); + if (priv->expectedBytes) + { + if (!Stream_EnsureCapacity(s, priv->expectedBytes)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + return CHANNEL_RC_OK; + } + } + + /* when here we have the header + the body */ + switch (priv->currentMsgType) + { + case EVENTID_CS_READY: + if (priv->automataState != STATE_WAITING_CLIENT_READY) + { + WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%d)", priv->automataState); + return ERROR_INVALID_STATE; + } + + if ((error = read_cs_ready_message(context, s))) + { + WLog_ERR(TAG, "read_cs_ready_message failed with error %"PRIu32"", error); + return error; + } + break; + + case EVENTID_TOUCH: + if ((error = read_touch_event(context, s))) + { + WLog_ERR(TAG, "read_touch_event failed with error %"PRIu32"", error); + return error; + } + break; + case EVENTID_DISMISS_HOVERING_CONTACT: + if ((error = read_dismiss_hovering_contact(context, s))) + { + WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %"PRIu32"", error); + return error; + } + break; + default: + WLog_ERR(TAG, "unexpected message type 0x%"PRIx16"", priv->currentMsgType); + } + + Stream_SetPosition(s, 0); + priv->waitingHeaders = TRUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + return error; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_send_sc_ready(RdpeiServerContext *context, UINT32 version) +{ + ULONG written; + RdpeiServerPrivate *priv = context->priv; + + if (priv->automataState != STATE_INITIAL) + { + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + 4)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + 4); + Stream_Write_UINT32(priv->outputStream, version); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_CLIENT_READY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_suspend(RdpeiServerContext *context) +{ + ULONG written; + RdpeiServerPrivate *priv = context->priv; + + switch (priv->automataState) + { + case STATE_SUSPENDED: + WLog_ERR(TAG, "already suspended"); + return CHANNEL_RC_OK; + case STATE_WAITING_FRAME: + break; + default: + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_SUSPENDED; + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_resume(RdpeiServerContext *context) +{ + ULONG written; + RdpeiServerPrivate *priv = context->priv; + + switch (priv->automataState) + { + case STATE_WAITING_FRAME: + WLog_ERR(TAG, "not suspended"); + return CHANNEL_RC_OK; + case STATE_SUSPENDED: + break; + default: + WLog_ERR(TAG, "called from unexpected state %d", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_SetPosition(priv->outputStream, 0); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream), + Stream_GetPosition(priv->outputStream), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_FRAME; + return CHANNEL_RC_OK; +} + diff --git a/channels/rdpei/server/rdpei_main.h b/channels/rdpei/server/rdpei_main.h new file mode 100644 index 0000000..f6505f5 --- /dev/null +++ b/channels/rdpei/server/rdpei_main.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 David Fort + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpei.server") + + + +#endif /* FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H */ + diff --git a/channels/rdpgfx/CMakeLists.txt b/channels/rdpgfx/CMakeLists.txt new file mode 100644 index 0000000..04820de --- /dev/null +++ b/channels/rdpgfx/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rdpgfx") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpgfx/ChannelOptions.cmake b/channels/rdpgfx/ChannelOptions.cmake new file mode 100644 index 0000000..acb8de8 --- /dev/null +++ b/channels/rdpgfx/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "rdpgfx" TYPE "dynamic" + DESCRIPTION "Graphics Pipeline Extension" + SPECIFICATIONS "[MS-RDPEGFX]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpgfx/client/CMakeLists.txt b/channels/rdpgfx/client/CMakeLists.txt new file mode 100644 index 0000000..0de358a --- /dev/null +++ b/channels/rdpgfx/client/CMakeLists.txt @@ -0,0 +1,41 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpgfx") + +set(${MODULE_PREFIX}_SRCS + rdpgfx_main.c + rdpgfx_main.h + rdpgfx_codec.c + rdpgfx_codec.h + ../rdpgfx_common.c + ../rdpgfx_common.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + diff --git a/channels/rdpgfx/client/rdpgfx_codec.c b/channels/rdpgfx/client/rdpgfx_codec.c new file mode 100644 index 0000000..d2dfbb3 --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_codec.c @@ -0,0 +1,310 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "rdpgfx_common.h" + +#include "rdpgfx_codec.h" + +#define TAG CHANNELS_TAG("rdpgfx.client") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_read_h264_metablock(RDPGFX_PLUGIN* gfx, wStream* s, + RDPGFX_H264_METABLOCK* meta) +{ + UINT32 index; + RECTANGLE_16* regionRect; + RDPGFX_H264_QUANT_QUALITY* quantQualityVal; + UINT error = ERROR_INVALID_DATA; + meta->regionRects = NULL; + meta->quantQualityVals = NULL; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data!"); + goto error_out; + } + + Stream_Read_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */ + + if (Stream_GetRemainingLength(s) < (meta->numRegionRects * 8)) + { + WLog_ERR(TAG, "not enough data!"); + goto error_out; + } + + meta->regionRects = (RECTANGLE_16*) calloc(meta->numRegionRects, sizeof(RECTANGLE_16)); + + if (!meta->regionRects) + { + WLog_ERR(TAG, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + meta->quantQualityVals = (RDPGFX_H264_QUANT_QUALITY*) calloc(meta->numRegionRects, sizeof( + RDPGFX_H264_QUANT_QUALITY)); + + if (!meta->quantQualityVals) + { + WLog_ERR(TAG, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + WLog_DBG(TAG, "H264_METABLOCK: numRegionRects: %"PRIu32"", meta->numRegionRects); + + for (index = 0; index < meta->numRegionRects; index++) + { + regionRect = &(meta->regionRects[index]); + + if ((error = rdpgfx_read_rect16(s, regionRect))) + { + WLog_ERR(TAG, "rdpgfx_read_rect16 failed with error %"PRIu32"!", error); + goto error_out; + } + + WLog_DBG(TAG, + "regionRects[%"PRIu32"]: left: %"PRIu16" top: %"PRIu16" right: %"PRIu16" bottom: %"PRIu16"", + index, regionRect->left, regionRect->top, regionRect->right, regionRect->bottom); + } + + if (Stream_GetRemainingLength(s) < (meta->numRegionRects * 2)) + { + WLog_ERR(TAG, "not enough data!"); + error = ERROR_INVALID_DATA; + goto error_out; + } + + for (index = 0; index < meta->numRegionRects; index++) + { + quantQualityVal = &(meta->quantQualityVals[index]); + Stream_Read_UINT8(s, quantQualityVal->qpVal); /* qpVal (1 byte) */ + Stream_Read_UINT8(s, quantQualityVal->qualityVal); /* qualityVal (1 byte) */ + quantQualityVal->qp = quantQualityVal->qpVal & 0x3F; + quantQualityVal->r = (quantQualityVal->qpVal >> 6) & 1; + quantQualityVal->p = (quantQualityVal->qpVal >> 7) & 1; + WLog_DBG(TAG, + "quantQualityVals[%"PRIu32"]: qp: %"PRIu8" r: %"PRIu8" p: %"PRIu8" qualityVal: %"PRIu8"", + index, quantQualityVal->qp, quantQualityVal->r, quantQualityVal->p, quantQualityVal->qualityVal); + } + + return CHANNEL_RC_OK; +error_out: + free(meta->regionRects); + meta->regionRects = NULL; + free(meta->quantQualityVals); + meta->quantQualityVals = NULL; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_decode_AVC420(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error; + wStream* s; + RDPGFX_AVC420_BITMAP_STREAM h264; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + s = Stream_New(cmd->data, cmd->length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.meta)))) + { + Stream_Free(s, FALSE); + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %"PRIu32"!", error); + return error; + } + + h264.data = Stream_Pointer(s); + h264.length = (UINT32) Stream_GetRemainingLength(s); + Stream_Free(s, FALSE); + cmd->extra = (void*) &h264; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %"PRIu32"", error); + } + + free(h264.meta.regionRects); + free(h264.meta.quantQualityVals); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_decode_AVC444(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error; + UINT32 tmp; + size_t pos1, pos2; + wStream* s; + RDPGFX_AVC444_BITMAP_STREAM h264 = { 0 }; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + s = Stream_New(cmd->data, cmd->length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (Stream_GetRemainingLength(s) < 4) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + Stream_Read_UINT32(s, tmp); + h264.cbAvc420EncodedBitstream1 = tmp & 0x3FFFFFFFUL; + h264.LC = (tmp >> 30UL) & 0x03UL; + + if (h264.LC == 0x03) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + pos1 = Stream_GetPosition(s); + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[0].meta)))) + { + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %"PRIu32"!", error); + goto fail; + } + + pos2 = Stream_GetPosition(s); + h264.bitstream[0].data = Stream_Pointer(s); + + if (h264.LC == 0) + { + tmp = h264.cbAvc420EncodedBitstream1 - pos2 + pos1; + + if (Stream_GetRemainingLength(s) < tmp) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + h264.bitstream[0].length = tmp; + Stream_Seek(s, tmp); + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[1].meta)))) + { + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %"PRIu32"!", error); + goto fail; + } + + h264.bitstream[1].data = Stream_Pointer(s); + h264.bitstream[1].length = Stream_GetRemainingLength(s); + } + else + { + h264.bitstream[0].length = Stream_GetRemainingLength(s); + memset(&h264.bitstream[1], 0, sizeof(h264.bitstream[1])); + } + + cmd->extra = (void*) &h264; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %"PRIu32"", error); + } + +fail: + Stream_Free(s, FALSE); + free(h264.bitstream[0].meta.regionRects); + free(h264.bitstream[0].meta.quantQualityVals); + free(h264.bitstream[1].meta.regionRects); + free(h264.bitstream[1].meta.quantQualityVals); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = CHANNEL_RC_OK; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + PROFILER_ENTER(context->SurfaceProfiler) + + switch (cmd->codecId) + { + case RDPGFX_CODECID_AVC420: + if ((error = rdpgfx_decode_AVC420(gfx, cmd))) + WLog_ERR(TAG, "rdpgfx_decode_AVC420 failed with error %"PRIu32"", error); + + break; + + case RDPGFX_CODECID_AVC444: + case RDPGFX_CODECID_AVC444v2: + if ((error = rdpgfx_decode_AVC444(gfx, cmd))) + WLog_ERR(TAG, "rdpgfx_decode_AVC444 failed with error %"PRIu32"", error); + + break; + + default: + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %"PRIu32"", error); + } + + break; + } + + PROFILER_EXIT(context->SurfaceProfiler) + return error; +} diff --git a/channels/rdpgfx/client/rdpgfx_codec.h b/channels/rdpgfx/client/rdpgfx_codec.h new file mode 100644 index 0000000..03d1bac --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_codec.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H +#define FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H + +#include +#include + +#include +#include + +#include "rdpgfx_main.h" + +FREERDP_LOCAL UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd); + +#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H */ diff --git a/channels/rdpgfx/client/rdpgfx_main.c b/channels/rdpgfx/client/rdpgfx_main.c new file mode 100644 index 0000000..0090db0 --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_main.c @@ -0,0 +1,1754 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013-2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rdpgfx_common.h" +#include "rdpgfx_codec.h" + +#include "rdpgfx_main.h" + +#define TAG CHANNELS_TAG("rdpgfx.client") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_caps_advertise_pdu(RDPGFX_CHANNEL_CALLBACK* callback) +{ + UINT error; + wStream* s; + UINT16 index; + RDPGFX_PLUGIN* gfx; + RDPGFX_HEADER header; + RDPGFX_CAPSET* capsSet; + RDPGFX_CAPSET capsSets[RDPGFX_NUMBER_CAPSETS]; + RDPGFX_CAPS_ADVERTISE_PDU pdu; + gfx = (RDPGFX_PLUGIN*) callback->plugin; + header.flags = 0; + header.cmdId = RDPGFX_CMDID_CAPSADVERTISE; + pdu.capsSetCount = 0; + pdu.capsSets = (RDPGFX_CAPSET*) capsSets; + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_8; + capsSet->flags = 0; + + if (gfx->ThinClient) + capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT; + + /* in CAPVERSION_8 the spec says that we should not have both + * thinclient and smallcache (and thinclient implies a small cache) + */ + if (gfx->SmallCache && !gfx->ThinClient) + capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_81; + capsSet->flags = 0; + + if (gfx->ThinClient) + capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT; + + if (gfx->SmallCache) + capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + +#ifdef WITH_GFX_H264 + if (gfx->H264) + capsSet->flags |= RDPGFX_CAPS_FLAG_AVC420_ENABLED; +#endif + + if (!gfx->H264 || gfx->AVC444) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_10; + capsSet->flags = 0; + + if (gfx->SmallCache) + capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + +#ifdef WITH_GFX_H264 + if (!gfx->AVC444) + capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; +#else + capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; +#endif + + capsSets[pdu.capsSetCount] = *capsSet; + capsSets[pdu.capsSetCount++].version = RDPGFX_CAPVERSION_102; + capsSets[pdu.capsSetCount] = *capsSet; + capsSets[pdu.capsSetCount++].version = RDPGFX_CAPVERSION_103; + } + + header.pduLength = RDPGFX_HEADER_SIZE + 2 + (pdu.capsSetCount * RDPGFX_CAPSET_SIZE); + WLog_Print(gfx->log, WLOG_DEBUG, "SendCapsAdvertisePdu %"PRIu16"", pdu.capsSetCount); + s = Stream_New(NULL, header.pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_CAPS_ADVERTISE_PDU */ + Stream_Write_UINT16(s, pdu.capsSetCount); /* capsSetCount (2 bytes) */ + + for (index = 0; index < pdu.capsSetCount; index++) + { + capsSet = &(pdu.capsSets[index]); + Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Write_UINT32(s, 4); /* capsDataLength (4 bytes) */ + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + } + + Stream_SealLength(s); + error = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), + Stream_Buffer(s), NULL); +fail: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_caps_confirm_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_CAPSET capsSet; + UINT32 capsDataLength; + RDPGFX_CAPS_CONFIRM_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + + pdu.capsSet = &capsSet; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, capsSet.version); /* version (4 bytes) */ + Stream_Read_UINT32(s, capsDataLength); /* capsDataLength (4 bytes) */ + Stream_Read_UINT32(s, capsSet.flags); /* capsData (4 bytes) */ + + gfx->ConnectionCaps = capsSet; + + WLog_Print(gfx->log, WLOG_DEBUG, "RecvCapsConfirmPdu: version: 0x%08"PRIX32" flags: 0x%08"PRIX32"", + capsSet.version, capsSet.flags); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_frame_acknowledge_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + RDPGFX_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error; + wStream* s; + RDPGFX_HEADER header; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_FRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + WLog_Print(gfx->log, WLOG_DEBUG, "SendFrameAcknowledgePdu: %"PRIu32"", pdu->frameId); + + s = Stream_New(NULL, header.pduLength); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */ + Stream_Write_UINT32(s, pdu->queueDepth); /* queueDepth (4 bytes) */ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + Stream_Write_UINT32(s, + pdu->totalFramesDecoded); /* totalFramesDecoded (4 bytes) */ + error = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), + Stream_Buffer(s), NULL); +fail: + Stream_Free(s, TRUE); + return error; +} + +static UINT rdpgfx_send_qoe_frame_acknowledge_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error; + wStream* s; + RDPGFX_HEADER header; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + WLog_Print(gfx->log, WLOG_DEBUG, "SendQoeFrameAcknowledgePdu: %"PRIu32"", pdu->frameId); + + s = Stream_New(NULL, header.pduLength); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */ + Stream_Write_UINT32(s, pdu->frameId); + Stream_Write_UINT32(s, pdu->timestamp); + Stream_Write_UINT16(s, pdu->timeDiffSE); + Stream_Write_UINT16(s, pdu->timeDiffEDR); + + error = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), + Stream_Buffer(s), NULL); +fail: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_reset_graphics_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + int pad; + UINT32 index; + MONITOR_DEF* monitor; + RDPGFX_RESET_GRAPHICS_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + GraphicsResetEventArgs graphicsReset; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.width); /* width (4 bytes) */ + Stream_Read_UINT32(s, pdu.height); /* height (4 bytes) */ + Stream_Read_UINT32(s, pdu.monitorCount); /* monitorCount (4 bytes) */ + + if (Stream_GetRemainingLength(s) < (pdu.monitorCount * 20)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.monitorDefArray = (MONITOR_DEF*) calloc(pdu.monitorCount, sizeof(MONITOR_DEF)); + if (!pdu.monitorDefArray) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.monitorCount; index++) + { + monitor = &(pdu.monitorDefArray[index]); + Stream_Read_UINT32(s, monitor->left); /* left (4 bytes) */ + Stream_Read_UINT32(s, monitor->top); /* top (4 bytes) */ + Stream_Read_UINT32(s, monitor->right); /* right (4 bytes) */ + Stream_Read_UINT32(s, monitor->bottom); /* bottom (4 bytes) */ + Stream_Read_UINT32(s, monitor->flags); /* flags (4 bytes) */ + } + + pad = 340 - (RDPGFX_HEADER_SIZE + 12 + (pdu.monitorCount * 20)); + + if (Stream_GetRemainingLength(s) < (size_t) pad) + { + WLog_Print(gfx->log, WLOG_ERROR, "Stream_GetRemainingLength failed!"); + free(pdu.monitorDefArray); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, pad); /* pad (total size is 340 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvResetGraphicsPdu: width: %"PRIu32" height: %"PRIu32" count: %"PRIu32"", + pdu.width, pdu.height, pdu.monitorCount); + + for (index = 0; index < pdu.monitorCount; index++) + { + monitor = &(pdu.monitorDefArray[index]); + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvResetGraphicsPdu: monitor left:%"PRIi32" top:%"PRIi32" right:%"PRIi32" left:%"PRIi32" flags:0x%"PRIx32"", + monitor->left, monitor->top, monitor->right, monitor->bottom, monitor->flags); + } + + if (context) + { + IFCALLRET(context->ResetGraphics, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->ResetGraphics failed with error %"PRIu32"", error); + } + + /* some listeners may be interested (namely the display channel) */ + EventArgsInit(&graphicsReset, "xfreerdp"); + graphicsReset.width = pdu.width; + graphicsReset.height = pdu.height; + PubSub_OnGraphicsReset(gfx->rdpcontext->pubSub, gfx->rdpcontext, &graphicsReset); + + free(pdu.monitorDefArray); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_evict_cache_entry_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvEvictCacheEntryPdu: cacheSlot: %"PRIu16"", pdu.cacheSlot); + + if (context) + { + IFCALLRET(context->EvictCacheEntry, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->EvictCacheEntry failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_import_reply_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + UINT16 index; + RDPGFX_CACHE_IMPORT_REPLY_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.importedEntriesCount); /* cacheSlot (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (size_t)(pdu.importedEntriesCount * 2)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.cacheSlots = (UINT16*) calloc(pdu.importedEntriesCount, sizeof(UINT16)); + if (!pdu.cacheSlots) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.importedEntriesCount; index++) + { + Stream_Read_UINT16(s, pdu.cacheSlots[index]); /* cacheSlot (2 bytes) */ + } + + WLog_Print(gfx->log, WLOG_DEBUG, "RecvCacheImportReplyPdu: importedEntriesCount: %"PRIu16"", + pdu.importedEntriesCount); + + if (context) + { + IFCALLRET(context->CacheImportReply, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->CacheImportReply failed with error %"PRIu32"", error); + } + + free(pdu.cacheSlots); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_create_surface_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_CREATE_SURFACE_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 7) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.width); /* width (2 bytes) */ + Stream_Read_UINT16(s, pdu.height); /* height (2 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */ + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvCreateSurfacePdu: surfaceId: %"PRIu16" width: %"PRIu16" height: %"PRIu16" pixelFormat: 0x%02"PRIX8"", + pdu.surfaceId, pdu.width, pdu.height, pdu.pixelFormat); + + if (context) + { + IFCALLRET(context->CreateSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->CreateSurface failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_delete_surface_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_DELETE_SURFACE_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvDeleteSurfacePdu: surfaceId: %"PRIu16"", pdu.surfaceId); + + if (context) + { + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->DeleteSurface failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_start_frame_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_START_FRAME_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < RDPGFX_START_FRAME_PDU_SIZE) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */ + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvStartFramePdu: frameId: %"PRIu32" timestamp: 0x%08"PRIX32"", + pdu.frameId, pdu.timestamp); + + gfx->StartDecodingTime = GetTickCountPrecise(); + if (context) + { + IFCALLRET(context->StartFrame, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->StartFrame failed with error %"PRIu32"", error); + } + + gfx->UnacknowledgedFrames++; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_end_frame_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_END_FRAME_PDU pdu; + RDPGFX_FRAME_ACKNOWLEDGE_PDU ack; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < RDPGFX_END_FRAME_PDU_SIZE) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvEndFramePdu: frameId: %"PRIu32"", pdu.frameId); + + if (context) + { + IFCALLRET(context->EndFrame, error, context, &pdu); + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "context->EndFrame failed with error %"PRIu32"", error); + return error; + } + } + + gfx->UnacknowledgedFrames--; + gfx->TotalDecodedFrames++; + ack.frameId = pdu.frameId; + ack.totalFramesDecoded = gfx->TotalDecodedFrames; + + if (gfx->suspendFrameAcks) + { + ack.queueDepth = SUSPEND_FRAME_ACKNOWLEDGEMENT; + + if (gfx->TotalDecodedFrames == 1) + if ((error = rdpgfx_send_frame_acknowledge_pdu(callback, &ack))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_send_frame_acknowledge_pdu failed with error %"PRIu32"", error); + } + else + { + ack.queueDepth = QUEUE_DEPTH_UNAVAILABLE; + + if ((error = rdpgfx_send_frame_acknowledge_pdu(callback, &ack))) + WLog_Print(gfx->log, WLOG_DEBUG, "rdpgfx_send_frame_acknowledge_pdu failed with error %"PRIu32"", error); + } + + switch(gfx->ConnectionCaps.version) + { + case RDPGFX_CAPVERSION_10: + case RDPGFX_CAPVERSION_102: + case RDPGFX_CAPVERSION_103: + if (gfx->SendQoeAck) + { + RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU qoe; + UINT32 diff = (GetTickCountPrecise() - gfx->StartDecodingTime); + + if (diff > 65000) + diff = 0; + + qoe.frameId = pdu.frameId; + qoe.timestamp = gfx->StartDecodingTime; + qoe.timeDiffSE = diff; + qoe.timeDiffEDR = 1; + if ((error = rdpgfx_send_qoe_frame_acknowledge_pdu(callback, &qoe))) + WLog_Print(gfx->log, WLOG_DEBUG, "rdpgfx_send_frame_acknowledge_pdu failed with error %"PRIu32"", error); + } + break; + default: + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_wire_to_surface_1_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_SURFACE_COMMAND cmd; + RDPGFX_WIRE_TO_SURFACE_PDU_1 pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + UINT error; + + if (Stream_GetRemainingLength(s) < RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.destRect)))) /* destRect (8 bytes) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %"PRIu32"", error); + return error; + } + + Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */ + + if (pdu.bitmapDataLength > Stream_GetRemainingLength(s)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.bitmapData = Stream_Pointer(s); + Stream_Seek(s, pdu.bitmapDataLength); + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvWireToSurface1Pdu: surfaceId: %"PRIu16" codecId: %s (0x%04"PRIX16") pixelFormat: 0x%02"PRIX8" " + "destRect: left: %"PRIu16" top: %"PRIu16" right: %"PRIu16" bottom: %"PRIu16" bitmapDataLength: %"PRIu32"", + pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), + pdu.codecId, pdu.pixelFormat, + pdu.destRect.left, pdu.destRect.top, + pdu.destRect.right, pdu.destRect.bottom, + pdu.bitmapDataLength); + cmd.surfaceId = pdu.surfaceId; + cmd.codecId = pdu.codecId; + cmd.contextId = 0; + + switch (pdu.pixelFormat) + { + case GFX_PIXEL_FORMAT_XRGB_8888: + cmd.format = PIXEL_FORMAT_BGRX32; + break; + + case GFX_PIXEL_FORMAT_ARGB_8888: + cmd.format = PIXEL_FORMAT_BGRA32; + break; + + default: + return ERROR_INVALID_DATA; + } + + cmd.left = pdu.destRect.left; + cmd.top = pdu.destRect.top; + cmd.right = pdu.destRect.right; + cmd.bottom = pdu.destRect.bottom; + cmd.width = cmd.right - cmd.left; + cmd.height = cmd.bottom - cmd.top; + cmd.length = pdu.bitmapDataLength; + cmd.data = pdu.bitmapData; + cmd.extra = NULL; + + if ((error = rdpgfx_decode(gfx, &cmd))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_decode failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_wire_to_surface_2_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_SURFACE_COMMAND cmd; + RDPGFX_WIRE_TO_SURFACE_PDU_2 pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */ + Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */ + Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */ + pdu.bitmapData = Stream_Pointer(s); + Stream_Seek(s, pdu.bitmapDataLength); + WLog_Print(gfx->log, WLOG_DEBUG, "RecvWireToSurface2Pdu: surfaceId: %"PRIu16" codecId: %s (0x%04"PRIX16") " + "codecContextId: %"PRIu32" pixelFormat: 0x%02"PRIX8" bitmapDataLength: %"PRIu32"", + pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), pdu.codecId, + pdu.codecContextId, pdu.pixelFormat, pdu.bitmapDataLength); + cmd.surfaceId = pdu.surfaceId; + cmd.codecId = pdu.codecId; + cmd.contextId = pdu.codecContextId; + cmd.format = pdu.pixelFormat; + cmd.left = 0; + cmd.top = 0; + cmd.right = 0; + cmd.bottom = 0; + cmd.width = 0; + cmd.height = 0; + cmd.length = pdu.bitmapDataLength; + cmd.data = pdu.bitmapData; + cmd.extra = NULL; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, &cmd); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->SurfaceCommand failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_delete_encoding_context_pdu(RDPGFX_CHANNEL_CALLBACK* + callback, wStream* s) +{ + RDPGFX_DELETE_ENCODING_CONTEXT_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvDeleteEncodingContextPdu: surfaceId: %"PRIu16" codecContextId: %"PRIu32"", + pdu.surfaceId, pdu.codecContextId); + + if (context) + { + IFCALLRET(context->DeleteEncodingContext, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->DeleteEncodingContext failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_solid_fill_pdu(RDPGFX_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT16 index; + RECTANGLE_16* fillRect; + RDPGFX_SOLID_FILL_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + + if ((error = rdpgfx_read_color32(s, &(pdu.fillPixel)))) /* fillPixel (4 bytes) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_color32 failed with error %"PRIu32"!", error); + return error; + } + + Stream_Read_UINT16(s, pdu.fillRectCount); /* fillRectCount (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (size_t)(pdu.fillRectCount * 8)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.fillRects = (RECTANGLE_16*) calloc(pdu.fillRectCount, sizeof(RECTANGLE_16)); + if (!pdu.fillRects) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.fillRectCount; index++) + { + fillRect = &(pdu.fillRects[index]); + + if ((error = rdpgfx_read_rect16(s, fillRect))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %"PRIu32"!", error); + free(pdu.fillRects); + return error; + } + } + + WLog_Print(gfx->log, WLOG_DEBUG, "RecvSolidFillPdu: surfaceId: %"PRIu16" fillRectCount: %"PRIu16"", + pdu.surfaceId, pdu.fillRectCount); + + if (context) + { + IFCALLRET(context->SolidFill, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->SolidFill failed with error %"PRIu32"", error); + } + + free(pdu.fillRects); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_surface_to_surface_pdu(RDPGFX_CHANNEL_CALLBACK* + callback, wStream* s) +{ + UINT16 index; + RDPGFX_POINT16* destPt; + RDPGFX_SURFACE_TO_SURFACE_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error; + + if (Stream_GetRemainingLength(s) < 14) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceIdSrc); /* surfaceIdSrc (2 bytes) */ + Stream_Read_UINT16(s, pdu.surfaceIdDest); /* surfaceIdDest (2 bytes) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %"PRIu32"!", error); + return error; + } + + Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (size_t)(pdu.destPtsCount * 4)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.destPts = (RDPGFX_POINT16*) calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16)); + if (!pdu.destPts) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.destPtsCount; index++) + { + destPt = &(pdu.destPts[index]); + + if ((error = rdpgfx_read_point16(s, destPt))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %"PRIu32"!", error); + free(pdu.destPts); + return error; + } + } + + WLog_Print(gfx->log, WLOG_DEBUG, "RecvSurfaceToSurfacePdu: surfaceIdSrc: %"PRIu16" surfaceIdDest: %"PRIu16" " + "left: %"PRIu16" top: %"PRIu16" right: %"PRIu16" bottom: %"PRIu16" destPtsCount: %"PRIu16"", + pdu.surfaceIdSrc, pdu.surfaceIdDest, + pdu.rectSrc.left, pdu.rectSrc.top, pdu.rectSrc.right, pdu.rectSrc.bottom, + pdu.destPtsCount); + + if (context) + { + IFCALLRET(context->SurfaceToSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->SurfaceToSurface failed with error %"PRIu32"", error); + } + + free(pdu.destPts); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_surface_to_cache_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_SURFACE_TO_CACHE_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error; + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT64(s, pdu.cacheKey); /* cacheKey (8 bytes) */ + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %"PRIu32"!", error); + return error; + } + + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvSurfaceToCachePdu: surfaceId: %"PRIu16" cacheKey: 0x%016"PRIX64" cacheSlot: %"PRIu16" " + "left: %"PRIu16" top: %"PRIu16" right: %"PRIu16" bottom: %"PRIu16"", + pdu.surfaceId, pdu.cacheKey, pdu.cacheSlot, + pdu.rectSrc.left, pdu.rectSrc.top, + pdu.rectSrc.right, pdu.rectSrc.bottom); + + if (context) + { + IFCALLRET(context->SurfaceToCache, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->SurfaceToCache failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_to_surface_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + UINT16 index; + RDPGFX_POINT16* destPt; + RDPGFX_CACHE_TO_SURFACE_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (size_t)(pdu.destPtsCount * 4)) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.destPts = (RDPGFX_POINT16*) calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16)); + if (!pdu.destPts) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.destPtsCount; index++) + { + destPt = &(pdu.destPts[index]); + + if ((error = rdpgfx_read_point16(s, destPt))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %"PRIu32"", error); + free(pdu.destPts); + return error; + } + } + + WLog_Print(gfx->log, WLOG_DEBUG, + "RdpGfxRecvCacheToSurfacePdu: cacheSlot: %"PRIu16" surfaceId: %"PRIu16" destPtsCount: %"PRIu16"", + pdu.cacheSlot, pdu.surfaceId, pdu.destPtsCount); + + if (context) + { + IFCALLRET(context->CacheToSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->CacheToSurface failed with error %"PRIu32"", error); + } + + free(pdu.destPts); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_map_surface_to_output_pdu(RDPGFX_CHANNEL_CALLBACK* + callback, wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.reserved); /* reserved (2 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginY); /* outputOriginY (4 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvMapSurfaceToOutputPdu: surfaceId: %"PRIu16" outputOriginX: %"PRIu32" outputOriginY: %"PRIu32"", + pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY); + + if (context) + { + IFCALLRET(context->MapSurfaceToOutput, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->MapSurfaceToOutput failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_map_surface_to_window_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_WINDOW_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 18) + { + WLog_Print(gfx->log, WLOG_ERROR, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */ + Stream_Read_UINT32(s, pdu.mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.mappedHeight); /* mappedHeight (4 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvMapSurfaceToWindowPdu: surfaceId: %"PRIu16" windowId: 0x%016"PRIX64" mappedWidth: %"PRIu32" mappedHeight: %"PRIu32"", + pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight); + + if (context && context->MapSurfaceToWindow) + { + IFCALLRET(context->MapSurfaceToWindow, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->MapSurfaceToWindow failed with error %"PRIu32"", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_pdu(RDPGFX_CHANNEL_CALLBACK* callback, wStream* s) +{ + size_t beg, end; + RDPGFX_HEADER header; + UINT error; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + + beg = Stream_GetPosition(s); + + if ((error = rdpgfx_read_header(s, &header))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_header failed with error %"PRIu32"!", error); + return error; + } + + WLog_Print(gfx->log, WLOG_DEBUG, "cmdId: %s (0x%04"PRIX16") flags: 0x%04"PRIX16" pduLength: %"PRIu32"", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, header.flags, + header.pduLength); + + switch (header.cmdId) + { + case RDPGFX_CMDID_WIRETOSURFACE_1: + if ((error = rdpgfx_recv_wire_to_surface_1_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_wire_to_surface_1_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_WIRETOSURFACE_2: + if ((error = rdpgfx_recv_wire_to_surface_2_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_wire_to_surface_2_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_DELETEENCODINGCONTEXT: + if ((error = rdpgfx_recv_delete_encoding_context_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_delete_encoding_context_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_SOLIDFILL: + if ((error = rdpgfx_recv_solid_fill_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_solid_fill_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_SURFACETOSURFACE: + if ((error = rdpgfx_recv_surface_to_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_surface_to_surface_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_SURFACETOCACHE: + if ((error = rdpgfx_recv_surface_to_cache_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_surface_to_cache_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_CACHETOSURFACE: + if ((error = rdpgfx_recv_cache_to_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_cache_to_surface_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_EVICTCACHEENTRY: + if ((error = rdpgfx_recv_evict_cache_entry_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_evict_cache_entry_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_CREATESURFACE: + if ((error = rdpgfx_recv_create_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_create_surface_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_DELETESURFACE: + if ((error = rdpgfx_recv_delete_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_delete_surface_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_STARTFRAME: + if ((error = rdpgfx_recv_start_frame_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_start_frame_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_ENDFRAME: + if ((error = rdpgfx_recv_end_frame_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_end_frame_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_RESETGRAPHICS: + if ((error = rdpgfx_recv_reset_graphics_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_reset_graphics_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOOUTPUT: + if ((error = rdpgfx_recv_map_surface_to_output_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_map_surface_to_output_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_CACHEIMPORTREPLY: + if ((error = rdpgfx_recv_cache_import_reply_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_cache_import_reply_pdu failed with error %"PRIu32"!", + error); + + break; + + case RDPGFX_CMDID_CAPSCONFIRM: + if ((error = rdpgfx_recv_caps_confirm_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_caps_confirm_pdu failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOWINDOW: + if ((error = rdpgfx_recv_map_surface_to_window_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_map_surface_to_window_pdu failed with error %"PRIu32"!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + break; + } + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "Error while parsing GFX cmdId: %s (0x%04"PRIX16")", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId); + return error; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.pduLength)) + { + WLog_Print(gfx->log, WLOG_ERROR, "Unexpected gfx pdu end: Actual: %d, Expected: %"PRIu32"", + end, (beg + header.pduLength)); + Stream_SetPosition(s, (beg + header.pduLength)); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_data_received(IWTSVirtualChannelCallback* + pChannelCallback, wStream* data) +{ + wStream* s; + int status = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*) pChannelCallback; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + UINT error = CHANNEL_RC_OK; + status = zgfx_decompress(gfx->zgfx, Stream_Pointer(data), Stream_GetRemainingLength(data), + &pDstData, &DstSize, 0); + + if (status < 0) + { + WLog_Print(gfx->log, WLOG_ERROR, "zgfx_decompress failure! status: %d", status); + return ERROR_INTERNAL_ERROR; + } + + s = Stream_New(pDstData, DstSize); + if (!s) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((error = rdpgfx_recv_pdu(callback, s))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_pdu failed with error %"PRIu32"!", error); + break; + } + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*) pChannelCallback; + WLog_DBG(TAG, "OnOpen"); + return rdpgfx_send_caps_advertise_pdu(callback); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + int count; + int index; + ULONG_PTR* pKeys = NULL; + RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*) pChannelCallback; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + + WLog_Print(gfx->log, WLOG_DEBUG, "OnClose"); + free(callback); + gfx->UnacknowledgedFrames = 0; + gfx->TotalDecodedFrames = 0; + + if (gfx->zgfx) + { + zgfx_context_free(gfx->zgfx); + gfx->zgfx = zgfx_context_new(FALSE); + + if (!gfx->zgfx) + return CHANNEL_RC_NO_MEMORY; + } + + count = HashTable_GetKeys(gfx->SurfaceTable, &pKeys); + + for (index = 0; index < count; index++) + { + RDPGFX_DELETE_SURFACE_PDU pdu; + pdu.surfaceId = ((UINT16) pKeys[index]) - 1; + + if (context && context->DeleteSurface) + { + context->DeleteSurface(context, &pdu); + } + } + + free(pKeys); + + for (index = 0; index < gfx->MaxCacheSlot; index++) + { + if (gfx->CacheSlots[index]) + { + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu; + pdu.cacheSlot = (UINT16) index; + + if (context && context->EvictCacheEntry) + { + context->EvictCacheEntry(context, &pdu); + } + + gfx->CacheSlots[index] = NULL; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_new_channel_connection(IWTSListenerCallback* + pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + RDPGFX_CHANNEL_CALLBACK* callback; + RDPGFX_LISTENER_CALLBACK* listener_callback = (RDPGFX_LISTENER_CALLBACK*)pListenerCallback; + + callback = (RDPGFX_CHANNEL_CALLBACK*) calloc(1, sizeof(RDPGFX_CHANNEL_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = rdpgfx_on_data_received; + callback->iface.OnOpen = rdpgfx_on_open; + callback->iface.OnClose = rdpgfx_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_plugin_initialize(IWTSPlugin* pPlugin, + IWTSVirtualChannelManager* pChannelMgr) +{ + UINT error; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) pPlugin; + + gfx->listener_callback = (RDPGFX_LISTENER_CALLBACK*) calloc(1, sizeof(RDPGFX_LISTENER_CALLBACK)); + if (!gfx->listener_callback) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + gfx->listener_callback->iface.OnNewChannelConnection = rdpgfx_on_new_channel_connection; + gfx->listener_callback->plugin = pPlugin; + gfx->listener_callback->channel_mgr = pChannelMgr; + error = pChannelMgr->CreateListener(pChannelMgr, RDPGFX_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*) gfx->listener_callback, &(gfx->listener)); + gfx->listener->pInterface = gfx->iface.pInterface; + WLog_Print(gfx->log, WLOG_DEBUG, "Initialize"); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_plugin_terminated(IWTSPlugin* pPlugin) +{ + int count; + int index; + ULONG_PTR* pKeys = NULL; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) pPlugin; + RdpgfxClientContext* context = (RdpgfxClientContext*) gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + WLog_Print(gfx->log, WLOG_DEBUG, "Terminated"); + + if (gfx->listener_callback) + { + free(gfx->listener_callback); + gfx->listener_callback = NULL; + } + + if (gfx->zgfx) + { + zgfx_context_free(gfx->zgfx); + gfx->zgfx = NULL; + } + + count = HashTable_GetKeys(gfx->SurfaceTable, &pKeys); + + for (index = 0; index < count; index++) + { + RDPGFX_DELETE_SURFACE_PDU pdu; + pdu.surfaceId = ((UINT16) pKeys[index]) - 1; + + if (context) + { + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "context->DeleteSurface failed with error %"PRIu32"", error); + free(pKeys); + free(context); + free(gfx); + return error; + } + } + } + + free(pKeys); + HashTable_Free(gfx->SurfaceTable); + + for (index = 0; index < gfx->MaxCacheSlot; index++) + { + if (gfx->CacheSlots[index]) + { + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu; + pdu.cacheSlot = (UINT16) index; + + if (context) + { + IFCALLRET(context->EvictCacheEntry, error, context, &pdu); + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "context->EvictCacheEntry failed with error %"PRIu32"", error); + free(context); + free(gfx); + return error; + } + } + + gfx->CacheSlots[index] = NULL; + } + } + + free(context); + free(gfx); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_set_surface_data(RdpgfxClientContext* context, + UINT16 surfaceId, void* pData) +{ + ULONG_PTR key; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) context->handle; + key = ((ULONG_PTR) surfaceId) + 1; + + if (pData) + HashTable_Add(gfx->SurfaceTable, (void*) key, pData); + else + HashTable_Remove(gfx->SurfaceTable, (void*) key); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_get_surface_ids(RdpgfxClientContext* context, + UINT16** ppSurfaceIds, UINT16* count_out) +{ + int count; + int index; + UINT16* pSurfaceIds; + ULONG_PTR* pKeys = NULL; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) context->handle; + count = HashTable_GetKeys(gfx->SurfaceTable, &pKeys); + + if (count < 1) + { + *count_out = 0; + return CHANNEL_RC_OK; + } + + pSurfaceIds = (UINT16*) calloc(count, sizeof(UINT16)); + + if (!pSurfaceIds) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + free(pKeys); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < count; index++) + { + pSurfaceIds[index] = pKeys[index] - 1; + } + + free(pKeys); + *ppSurfaceIds = pSurfaceIds; + *count_out = (UINT16)count; + return CHANNEL_RC_OK; +} + +static void* rdpgfx_get_surface_data(RdpgfxClientContext* context, + UINT16 surfaceId) +{ + ULONG_PTR key; + void* pData = NULL; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) context->handle; + key = ((ULONG_PTR) surfaceId) + 1; + pData = HashTable_GetItemValue(gfx->SurfaceTable, (void*) key); + return pData; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_set_cache_slot_data(RdpgfxClientContext* context, + UINT16 cacheSlot, void* pData) +{ + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) context->handle; + + if (cacheSlot >= gfx->MaxCacheSlot) + { + WLog_ERR(TAG, "%s: invalid cache slot %"PRIu16" maxAllowed=%"PRIu16"", __FUNCTION__, + cacheSlot, gfx->MaxCacheSlot); + return ERROR_INVALID_INDEX; + } + + gfx->CacheSlots[cacheSlot] = pData; + return CHANNEL_RC_OK; +} + +static void* rdpgfx_get_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot) +{ + void* pData = NULL; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*) context->handle; + + if (cacheSlot >= gfx->MaxCacheSlot) + { + WLog_ERR(TAG, "%s: invalid cache slot %"PRIu16" maxAllowed=%"PRIu16"", __FUNCTION__, + cacheSlot, gfx->MaxCacheSlot); + return NULL; + } + + pData = gfx->CacheSlots[cacheSlot]; + return pData; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry rdpgfx_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_PLUGIN* gfx; + RdpgfxClientContext* context; + gfx = (RDPGFX_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "rdpgfx"); + + if (!gfx) + { + gfx = (RDPGFX_PLUGIN*) calloc(1, sizeof(RDPGFX_PLUGIN)); + + if (!gfx) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + gfx->log = WLog_Get(TAG); + if (!gfx->log) + { + free(gfx); + WLog_ERR(TAG, "Failed to acquire reference to WLog %s", TAG); + return ERROR_INTERNAL_ERROR; + } + + gfx->settings = (rdpSettings*) pEntryPoints->GetRdpSettings(pEntryPoints); + gfx->iface.Initialize = rdpgfx_plugin_initialize; + gfx->iface.Connected = NULL; + gfx->iface.Disconnected = NULL; + gfx->iface.Terminated = rdpgfx_plugin_terminated; + gfx->rdpcontext = ((freerdp *)gfx->settings->instance)->context; + + gfx->SurfaceTable = HashTable_New(TRUE); + if (!gfx->SurfaceTable) + { + free(gfx); + WLog_ERR(TAG, "HashTable_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + gfx->ThinClient = gfx->settings->GfxThinClient; + gfx->SmallCache = gfx->settings->GfxSmallCache; + gfx->Progressive = gfx->settings->GfxProgressive; + gfx->ProgressiveV2 = gfx->settings->GfxProgressiveV2; + gfx->H264 = gfx->settings->GfxH264; + gfx->AVC444 = gfx->settings->GfxAVC444; + gfx->SendQoeAck = gfx->settings->GfxSendQoeAck; + + if (gfx->H264) + gfx->SmallCache = TRUE; + + gfx->MaxCacheSlot = gfx->SmallCache ? 4096 : 25600; + + context = (RdpgfxClientContext *)calloc(1, sizeof(RdpgfxClientContext)); + if (!context) + { + free(gfx); + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + context->handle = (void*) gfx; + context->GetSurfaceIds = rdpgfx_get_surface_ids; + context->SetSurfaceData = rdpgfx_set_surface_data; + context->GetSurfaceData = rdpgfx_get_surface_data; + context->SetCacheSlotData = rdpgfx_set_cache_slot_data; + context->GetCacheSlotData = rdpgfx_get_cache_slot_data; + gfx->iface.pInterface = (void*) context; + gfx->zgfx = zgfx_context_new(FALSE); + + if (!gfx->zgfx) + { + free(gfx); + free(context); + WLog_ERR(TAG, "zgfx_context_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpgfx", (IWTSPlugin*) gfx); + } + + return error; +} diff --git a/channels/rdpgfx/client/rdpgfx_main.h b/channels/rdpgfx/client/rdpgfx_main.h new file mode 100644 index 0000000..7efe298 --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_main.h @@ -0,0 +1,90 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +struct _RDPGFX_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _RDPGFX_CHANNEL_CALLBACK RDPGFX_CHANNEL_CALLBACK; + +struct _RDPGFX_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + RDPGFX_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _RDPGFX_LISTENER_CALLBACK RDPGFX_LISTENER_CALLBACK; + +struct _RDPGFX_PLUGIN +{ + IWTSPlugin iface; + + IWTSListener* listener; + RDPGFX_LISTENER_CALLBACK* listener_callback; + + rdpSettings* settings; + + BOOL ThinClient; + BOOL SmallCache; + BOOL Progressive; + BOOL ProgressiveV2; + BOOL H264; + BOOL AVC444; + + ZGFX_CONTEXT* zgfx; + UINT32 UnacknowledgedFrames; + UINT32 TotalDecodedFrames; + UINT32 StartDecodingTime; + BOOL suspendFrameAcks; + + wHashTable* SurfaceTable; + + UINT16 MaxCacheSlot; + void* CacheSlots[25600]; + rdpContext* rdpcontext; + + wLog* log; + RDPGFX_CAPSET ConnectionCaps; + BOOL SendQoeAck; +}; +typedef struct _RDPGFX_PLUGIN RDPGFX_PLUGIN; + +#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H */ + diff --git a/channels/rdpgfx/rdpgfx_common.c b/channels/rdpgfx/rdpgfx_common.c new file mode 100644 index 0000000..2a6243b --- /dev/null +++ b/channels/rdpgfx/rdpgfx_common.c @@ -0,0 +1,235 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpgfx.common") + +#include "rdpgfx_common.h" + +static const char* RDPGFX_CMDID_STRINGS[] = +{ + "RDPGFX_CMDID_UNUSED_0000", + "RDPGFX_CMDID_WIRETOSURFACE_1", + "RDPGFX_CMDID_WIRETOSURFACE_2", + "RDPGFX_CMDID_DELETEENCODINGCONTEXT", + "RDPGFX_CMDID_SOLIDFILL", + "RDPGFX_CMDID_SURFACETOSURFACE", + "RDPGFX_CMDID_SURFACETOCACHE", + "RDPGFX_CMDID_CACHETOSURFACE", + "RDPGFX_CMDID_EVICTCACHEENTRY", + "RDPGFX_CMDID_CREATESURFACE", + "RDPGFX_CMDID_DELETESURFACE", + "RDPGFX_CMDID_STARTFRAME", + "RDPGFX_CMDID_ENDFRAME", + "RDPGFX_CMDID_FRAMEACKNOWLEDGE", + "RDPGFX_CMDID_RESETGRAPHICS", + "RDPGFX_CMDID_MAPSURFACETOOUTPUT", + "RDPGFX_CMDID_CACHEIMPORTOFFER", + "RDPGFX_CMDID_CACHEIMPORTREPLY", + "RDPGFX_CMDID_CAPSADVERTISE", + "RDPGFX_CMDID_CAPSCONFIRM", + "RDPGFX_CMDID_UNUSED_0014", + "RDPGFX_CMDID_MAPSURFACETOWINDOW", + "RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE" +}; + +const char* rdpgfx_get_cmd_id_string(UINT16 cmdId) +{ + if (cmdId <= RDPGFX_CMDID_MAPSURFACETOWINDOW) + return RDPGFX_CMDID_STRINGS[cmdId]; + else + return "RDPGFX_CMDID_UNKNOWN"; +} + +const char* rdpgfx_get_codec_id_string(UINT16 codecId) +{ + switch (codecId) + { + case RDPGFX_CODECID_UNCOMPRESSED: + return "RDPGFX_CODECID_UNCOMPRESSED"; + + case RDPGFX_CODECID_CAVIDEO: + return "RDPGFX_CODECID_CAVIDEO"; + + case RDPGFX_CODECID_CLEARCODEC: + return "RDPGFX_CODECID_CLEARCODEC"; + + case RDPGFX_CODECID_PLANAR: + return "RDPGFX_CODECID_PLANAR"; + + case RDPGFX_CODECID_AVC420: + return "RDPGFX_CODECID_AVC420"; + + case RDPGFX_CODECID_AVC444: + return "RDPGFX_CODECID_AVC444"; + + case RDPGFX_CODECID_AVC444v2: + return "RDPGFX_CODECID_AVC444v2"; + + case RDPGFX_CODECID_ALPHA: + return "RDPGFX_CODECID_ALPHA"; + + case RDPGFX_CODECID_CAPROGRESSIVE: + return "RDPGFX_CODECID_CAPROGRESSIVE"; + + case RDPGFX_CODECID_CAPROGRESSIVE_V2: + return "RDPGFX_CODECID_CAPROGRESSIVE_V2"; + } + + return "RDPGFX_CODECID_UNKNOWN"; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header) +{ + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Read_UINT16(s, header->cmdId); /* cmdId (2 bytes) */ + Stream_Read_UINT16(s, header->flags); /* flags (2 bytes) */ + Stream_Read_UINT32(s, header->pduLength); /* pduLength (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_header(wStream* s, RDPGFX_HEADER* header) +{ + Stream_Write_UINT16(s, header->cmdId); /* cmdId (2 bytes) */ + Stream_Write_UINT16(s, header->flags); /* flags (2 bytes) */ + Stream_Write_UINT32(s, header->pduLength); /* pduLength (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pt16->x); /* x (2 bytes) */ + Stream_Read_UINT16(s, pt16->y); /* y (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_point16(wStream* s, RDPGFX_POINT16* point16) +{ + Stream_Write_UINT16(s, point16->x); /* x (2 bytes) */ + Stream_Write_UINT16(s, point16->y); /* y (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16) +{ + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, rect16->left); /* left (2 bytes) */ + Stream_Read_UINT16(s, rect16->top); /* top (2 bytes) */ + Stream_Read_UINT16(s, rect16->right); /* right (2 bytes) */ + Stream_Read_UINT16(s, rect16->bottom); /* bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_rect16(wStream* s, RECTANGLE_16* rect16) +{ + Stream_Write_UINT16(s, rect16->left); /* left (2 bytes) */ + Stream_Write_UINT16(s, rect16->top); /* top (2 bytes) */ + Stream_Write_UINT16(s, rect16->right); /* right (2 bytes) */ + Stream_Write_UINT16(s, rect16->bottom); /* bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, color32->B); /* B (1 byte) */ + Stream_Read_UINT8(s, color32->G); /* G (1 byte) */ + Stream_Read_UINT8(s, color32->R); /* R (1 byte) */ + Stream_Read_UINT8(s, color32->XA); /* XA (1 byte) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_color32(wStream* s, RDPGFX_COLOR32* color32) +{ + Stream_Write_UINT8(s, color32->B); /* B (1 byte) */ + Stream_Write_UINT8(s, color32->G); /* G (1 byte) */ + Stream_Write_UINT8(s, color32->R); /* R (1 byte) */ + Stream_Write_UINT8(s, color32->XA); /* XA (1 byte) */ + return CHANNEL_RC_OK; +} diff --git a/channels/rdpgfx/rdpgfx_common.h b/channels/rdpgfx/rdpgfx_common.h new file mode 100644 index 0000000..d9d7270 --- /dev/null +++ b/channels/rdpgfx/rdpgfx_common.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_COMMON_H +#define FREERDP_CHANNEL_RDPGFX_COMMON_H + +#include +#include + +#include +#include + +FREERDP_LOCAL const char* rdpgfx_get_cmd_id_string(UINT16 cmdId); +FREERDP_LOCAL const char* rdpgfx_get_codec_id_string(UINT16 codecId); + +FREERDP_LOCAL UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header); +FREERDP_LOCAL UINT rdpgfx_write_header(wStream* s, RDPGFX_HEADER* header); + +FREERDP_LOCAL UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16); +FREERDP_LOCAL UINT rdpgfx_write_point16(wStream* s, RDPGFX_POINT16* point16); + +FREERDP_LOCAL UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16); +FREERDP_LOCAL UINT rdpgfx_write_rect16(wStream* s, RECTANGLE_16* rect16); + +FREERDP_LOCAL UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32); +FREERDP_LOCAL UINT rdpgfx_write_color32(wStream* s, RDPGFX_COLOR32* color32); + +#endif /* FREERDP_CHANNEL_RDPGFX_COMMON_H */ + diff --git a/channels/rdpgfx/server/CMakeLists.txt b/channels/rdpgfx/server/CMakeLists.txt new file mode 100644 index 0000000..1b1f48b --- /dev/null +++ b/channels/rdpgfx/server/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2016 Jiang Zihao +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("rdpgfx") + +set(${MODULE_PREFIX}_SRCS + rdpgfx_main.c + rdpgfx_main.h + ../rdpgfx_common.c + ../rdpgfx_common.h) + +include_directories(..) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/rdpgfx/server/rdpgfx_main.c b/channels/rdpgfx/server/rdpgfx_main.c new file mode 100644 index 0000000..e3e645c --- /dev/null +++ b/channels/rdpgfx/server/rdpgfx_main.c @@ -0,0 +1,1667 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2016 Jiang Zihao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "rdpgfx_common.h" +#include "rdpgfx_main.h" + +#define TAG CHANNELS_TAG("rdpgfx.server") +#define RDPGFX_RESET_GRAPHICS_PDU_SIZE 340 + +/** + * Function description + * Calculate packet size from data length. + * It would be data length + header. + * + * @param dataLen estimated data length without header + * + * @return new stream + */ +static INLINE UINT32 rdpgfx_pdu_length(UINT32 dataLen) +{ + return RDPGFX_HEADER_SIZE + dataLen; +} + +static INLINE UINT rdpgfx_server_packet_init_header(wStream* s, + UINT16 cmdId, UINT32 pduLength) +{ + RDPGFX_HEADER header; + header.flags = 0; + header.cmdId = cmdId; + header.pduLength = pduLength; + /* Write header. Note that actual length might be changed + * after the entire packet has been constructed. */ + return rdpgfx_write_header(s, &header); +} + +/** + * Function description + * Complete the rdpgfx packet header. + * + * @param s stream + * @param start saved start pos of the packet in the stream + */ +static INLINE void rdpgfx_server_packet_complete_header(wStream* s, + size_t start) +{ + size_t current = Stream_GetPosition(s); + /* Fill actual length */ + Stream_SetPosition(s, start + RDPGFX_HEADER_SIZE - sizeof(UINT32)); + Stream_Write_UINT32(s, current - start); /* pduLength (4 bytes) */ + Stream_SetPosition(s, current); +} + +/** + * Function description + * Send the stream for rdpgfx server packet. + * The packet would be compressed according to [MS-RDPEGFX]. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_server_packet_send(RdpgfxServerContext* context, wStream* s) +{ + UINT error; + UINT32 flags = 0; + ULONG written; + BYTE* pSrcData = Stream_Buffer(s); + UINT32 SrcSize = Stream_GetPosition(s); + wStream* fs; + /* Allocate new stream with enough capacity. Additional overhead is + * descriptor (1 bytes) + segmentCount (2 bytes) + uncompressedSize (4 bytes) + * + segmentCount * size (4 bytes) */ + fs = Stream_New(NULL, SrcSize + 7 + + (SrcSize / ZGFX_SEGMENTED_MAXSIZE + 1) * 4); + + if (!fs) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (zgfx_compress_to_stream(context->priv->zgfx, fs, pSrcData, + SrcSize, &flags) < 0) + { + WLog_ERR(TAG, "zgfx_compress_to_stream failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (!WTSVirtualChannelWrite(context->priv->rdpgfx_channel, + (PCHAR) Stream_Buffer(fs), + Stream_GetPosition(fs), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(fs)) + { + WLog_WARN(TAG, "Unexpected bytes written: %"PRIu32"/%"PRIuz"", + written, Stream_GetPosition(fs)); + } + + error = CHANNEL_RC_OK; +out: + Stream_Free(fs, TRUE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * Create new stream for single rdpgfx packet. The new stream length + * would be required data length + header. The header will be written + * to the stream before return, but the pduLength field might be + * changed in rdpgfx_server_single_packet_send. + * + * @param cmdId + * @param dataLen estimated data length without header + * + * @return new stream + */ +static wStream* rdpgfx_server_single_packet_new(UINT16 cmdId, UINT32 dataLen) +{ + UINT error; + wStream* s; + UINT32 pduLength = rdpgfx_pdu_length(dataLen); + s = Stream_New(NULL, pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto error; + } + + if ((error = rdpgfx_server_packet_init_header(s, cmdId, pduLength))) + { + WLog_ERR(TAG, "Failed to init header with error %"PRIu32"!", error); + goto error; + } + + return s; +error: + Stream_Free(s, TRUE); + return NULL; +} + +/** + * Function description + * Send the stream for single rdpgfx packet. + * The header will be filled with actual length. + * The packet would be compressed according to [MS-RDPEGFX]. + * + * @return 0 on success, otherwise a Win32 error code + */ +static INLINE UINT rdpgfx_server_single_packet_send( + RdpgfxServerContext* context, wStream* s) +{ + /* Fill actual length */ + rdpgfx_server_packet_complete_header(s, 0); + return rdpgfx_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_caps_confirm_pdu(RdpgfxServerContext* context, + RDPGFX_CAPS_CONFIRM_PDU* capsConfirm) +{ + RDPGFX_CAPSET* capsSet = capsConfirm->capsSet; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_CAPSCONFIRM, RDPGFX_CAPSET_SIZE); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Write_UINT32(s, 4); /* capsDataLength (4 bytes) */ + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_reset_graphics_pdu(RdpgfxServerContext* context, + RDPGFX_RESET_GRAPHICS_PDU* pdu) +{ + UINT32 index; + MONITOR_DEF* monitor; + wStream* s; + + /* Check monitorCount. This ensures total size within 340 bytes) */ + if (pdu->monitorCount >= 16) + { + WLog_ERR(TAG, "Monitor count MUST be less than or equal to 16: %"PRIu32"", + pdu->monitorCount); + return ERROR_INVALID_DATA; + } + + s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_RESETGRAPHICS, + RDPGFX_RESET_GRAPHICS_PDU_SIZE - RDPGFX_HEADER_SIZE); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, pdu->width); /* width (4 bytes) */ + Stream_Write_UINT32(s, pdu->height); /* height (4 bytes) */ + Stream_Write_UINT32(s, pdu->monitorCount); /* monitorCount (4 bytes) */ + + for (index = 0; index < pdu->monitorCount; index++) + { + monitor = &(pdu->monitorDefArray[index]); + Stream_Write_UINT32(s, monitor->left); /* left (4 bytes) */ + Stream_Write_UINT32(s, monitor->top); /* top (4 bytes) */ + Stream_Write_UINT32(s, monitor->right); /* right (4 bytes) */ + Stream_Write_UINT32(s, monitor->bottom); /* bottom (4 bytes) */ + Stream_Write_UINT32(s, monitor->flags); /* flags (4 bytes) */ + } + + /* pad (total size must be 340 bytes) */ + Stream_SetPosition(s, RDPGFX_RESET_GRAPHICS_PDU_SIZE); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_evict_cache_entry_pdu(RdpgfxServerContext* context, + RDPGFX_EVICT_CACHE_ENTRY_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_EVICTCACHEENTRY, 2); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_import_reply_pdu(RdpgfxServerContext* context, + RDPGFX_CACHE_IMPORT_REPLY_PDU* pdu) +{ + UINT16 index; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_CACHEIMPORTREPLY, + 2 + 2 * pdu->importedEntriesCount); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* importedEntriesCount (2 bytes) */ + Stream_Write_UINT16(s, pdu->importedEntriesCount); + + for (index = 0; index < pdu->importedEntriesCount; index++) + { + Stream_Write_UINT16(s, pdu->cacheSlots[index]); /* cacheSlot (2 bytes) */ + } + + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_create_surface_pdu(RdpgfxServerContext* context, + RDPGFX_CREATE_SURFACE_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_CREATESURFACE, 7); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, pdu->width); /* width (2 bytes) */ + Stream_Write_UINT16(s, pdu->height); /* height (2 bytes) */ + Stream_Write_UINT8(s, pdu->pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_delete_surface_pdu(RdpgfxServerContext* context, + RDPGFX_DELETE_SURFACE_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_DELETESURFACE, 2); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +static INLINE void rdpgfx_write_start_frame_pdu(wStream* s, + RDPGFX_START_FRAME_PDU* pdu) +{ + Stream_Write_UINT32(s, pdu->timestamp); /* timestamp (4 bytes) */ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ +} + +static INLINE void rdpgfx_write_end_frame_pdu(wStream* s, + RDPGFX_END_FRAME_PDU* pdu) +{ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_start_frame_pdu(RdpgfxServerContext* context, + RDPGFX_START_FRAME_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_STARTFRAME, + RDPGFX_START_FRAME_PDU_SIZE); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpgfx_write_start_frame_pdu(s, pdu); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_end_frame_pdu(RdpgfxServerContext* context, + RDPGFX_END_FRAME_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_ENDFRAME, + RDPGFX_END_FRAME_PDU_SIZE); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpgfx_write_end_frame_pdu(s, pdu); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * Estimate RFX_AVC420_BITMAP_STREAM structure size in stream + * + * @return estimated size + */ +static INLINE UINT32 rdpgfx_estimate_h264_avc420( + RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */ + return sizeof(UINT32) /* numRegionRects */ + + 10 /* regionRects + quantQualityVals */ + * havc420->meta.numRegionRects + + havc420->length; +} + +/** + * Function description + * Estimate surface command packet size in stream without header + * + * @return estimated size + */ +static INLINE UINT32 rdpgfx_estimate_surface_command(RDPGFX_SURFACE_COMMAND* + cmd) +{ + RDPGFX_AVC420_BITMAP_STREAM* havc420 = NULL; + RDPGFX_AVC444_BITMAP_STREAM* havc444 = NULL; + UINT32 h264Size = 0; + + /* Estimate stream size according to codec. */ + switch (cmd->codecId) + { + case RDPGFX_CODECID_CAPROGRESSIVE: + case RDPGFX_CODECID_CAPROGRESSIVE_V2: + return RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE + cmd->length; + + case RDPGFX_CODECID_AVC420: + havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra; + h264Size = rdpgfx_estimate_h264_avc420(havc420); + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size; + + case RDPGFX_CODECID_AVC444: + havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra; + h264Size = sizeof(UINT32); /* cbAvc420EncodedBitstream1 */ + /* avc420EncodedBitstream1 */ + havc420 = &(havc444->bitstream[0]); + h264Size += rdpgfx_estimate_h264_avc420(havc420); + + /* avc420EncodedBitstream2 */ + if (havc444->LC == 0) + { + havc420 = &(havc444->bitstream[1]); + h264Size += rdpgfx_estimate_h264_avc420(havc420); + } + + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size; + + default: + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + cmd->length; + } +} + +/** + * Function description + * Resolve RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * according to codecId + * + * @return 0 on success, otherwise a Win32 error code + */ +static INLINE UINT16 rdpgfx_surface_command_cmdid(RDPGFX_SURFACE_COMMAND* cmd) +{ + if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE || + cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2) + { + return RDPGFX_CMDID_WIRETOSURFACE_2; + } + + return RDPGFX_CMDID_WIRETOSURFACE_1; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_write_h264_metablock(wStream* s, RDPGFX_H264_METABLOCK* meta) +{ + UINT32 index; + RECTANGLE_16* regionRect; + RDPGFX_H264_QUANT_QUALITY* quantQualityVal; + UINT error = CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, 4 + meta->numRegionRects * 10)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */ + + for (index = 0; index < meta->numRegionRects; index++) + { + regionRect = &(meta->regionRects[index]); + + if ((error = rdpgfx_write_rect16(s, regionRect))) + { + WLog_ERR(TAG, "rdpgfx_write_rect16 failed with error %"PRIu32"!", error); + return error; + } + } + + for (index = 0; index < meta->numRegionRects; index++) + { + quantQualityVal = &(meta->quantQualityVals[index]); + Stream_Write_UINT8(s, quantQualityVal->qp + | (quantQualityVal->r << 6) + | (quantQualityVal->p << 7)); /* qpVal (1 byte) */ + /* qualityVal (1 byte) */ + Stream_Write_UINT8(s, quantQualityVal->qualityVal); + } + + return error; +} + +/** + * Function description + * Write RFX_AVC420_BITMAP_STREAM structure to stream + * + * @return 0 on success, otherwise a Win32 error code + */ +static INLINE UINT rdpgfx_write_h264_avc420(wStream* s, + RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + UINT error = CHANNEL_RC_OK; + + if ((error = rdpgfx_write_h264_metablock(s, &(havc420->meta)))) + { + WLog_ERR(TAG, "rdpgfx_write_h264_metablock failed with error %"PRIu32"!", + error); + return error; + } + + if (!Stream_EnsureRemainingCapacity(s, havc420->length)) + return ERROR_OUTOFMEMORY; + + Stream_Write(s, havc420->data, havc420->length); + return error; +} + +/** + * Function description + * Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * to the stream according to RDPGFX_SURFACE_COMMAND message + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_write_surface_command(wStream* s, + RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_AVC420_BITMAP_STREAM* havc420 = NULL; + RDPGFX_AVC444_BITMAP_STREAM* havc444 = NULL; + UINT32 bitmapDataStart = 0; + UINT32 bitmapDataLength = 0; + UINT8 pixelFormat = 0; + + switch (cmd->format) + { + case PIXEL_FORMAT_BGRX32: + pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888; + break; + + case PIXEL_FORMAT_BGRA32: + pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888; + break; + + default: + WLog_ERR(TAG, "Format %s not supported!", FreeRDPGetColorFormatName(cmd->format)); + return ERROR_INVALID_DATA; + } + + if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE || + cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2) + { + /* Write RDPGFX_CMDID_WIRETOSURFACE_2 format for CAPROGRESSIVE */ + Stream_Write_UINT16(s, cmd->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, cmd->codecId); /* codecId (2 bytes) */ + Stream_Write_UINT32(s, cmd->contextId); /* codecContextId (4 bytes) */ + Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */ + Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */ + Stream_Write(s, cmd->data, cmd->length); + } + else + { + /* Write RDPGFX_CMDID_WIRETOSURFACE_1 format for others */ + Stream_Write_UINT16(s, cmd->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, cmd->codecId); /* codecId (2 bytes) */ + Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */ + Stream_Write_UINT16(s, cmd->left); /* left (2 bytes) */ + Stream_Write_UINT16(s, cmd->top); /* top (2 bytes) */ + Stream_Write_UINT16(s, cmd->right); /* right (2 bytes) */ + Stream_Write_UINT16(s, cmd->bottom); /* bottom (2 bytes) */ + Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */ + bitmapDataStart = Stream_GetPosition(s); + + if (cmd->codecId == RDPGFX_CODECID_AVC420) + { + havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra; + error = rdpgfx_write_h264_avc420(s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + } + else if ((cmd->codecId == RDPGFX_CODECID_AVC444) || (cmd->codecId == RDPGFX_CODECID_AVC444v2)) + { + havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra; + havc420 = &(havc444->bitstream[0]); /* avc420EncodedBitstreamInfo (4 bytes) */ + Stream_Write_UINT32(s, havc444->cbAvc420EncodedBitstream1 | (havc444->LC << 30UL)); + /* avc420EncodedBitstream1 */ + error = rdpgfx_write_h264_avc420(s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + + /* avc420EncodedBitstream2 */ + if (havc444->LC == 0) + { + havc420 = &(havc444->bitstream[1]); + error = rdpgfx_write_h264_avc420(s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + } + } + else + { + Stream_Write(s, cmd->data, cmd->length); + } + + /* Fill actual bitmap data length */ + bitmapDataLength = Stream_GetPosition(s) - bitmapDataStart; + Stream_SetPosition(s, bitmapDataStart - sizeof(UINT32)); + Stream_Write_UINT32(s, bitmapDataLength); /* bitmapDataLength (4 bytes) */ + Stream_Seek(s, bitmapDataLength); + } + + return error; +} + +/** + * Function description + * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * message according to codecId + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_command(RdpgfxServerContext* context, + RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = CHANNEL_RC_OK; + wStream* s; + s = rdpgfx_server_single_packet_new( + rdpgfx_surface_command_cmdid(cmd), + rdpgfx_estimate_surface_command(cmd)); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rdpgfx_write_surface_command(s, cmd); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpgfx_write_surface_command failed!"); + goto error; + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * message according to codecId. + * Prepend/append start/end frame message in same packet if exists. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_frame_command(RdpgfxServerContext* context, + RDPGFX_SURFACE_COMMAND* cmd, RDPGFX_START_FRAME_PDU* startFrame, + RDPGFX_END_FRAME_PDU* endFrame) + +{ + UINT error = CHANNEL_RC_OK; + wStream* s; + UINT32 position = 0; + UINT32 size = rdpgfx_pdu_length(rdpgfx_estimate_surface_command(cmd)); + + if (startFrame) + { + size += rdpgfx_pdu_length(RDPGFX_START_FRAME_PDU_SIZE); + } + + if (endFrame) + { + size += rdpgfx_pdu_length(RDPGFX_END_FRAME_PDU_SIZE); + } + + s = Stream_New(NULL, size); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Write start frame if exists */ + if (startFrame) + { + position = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, + RDPGFX_CMDID_STARTFRAME, 0); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Failed to init header with error %"PRIu32"!", error); + goto error; + } + + rdpgfx_write_start_frame_pdu(s, startFrame); + rdpgfx_server_packet_complete_header(s, position); + } + + /* Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 */ + position = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, + rdpgfx_surface_command_cmdid(cmd), + 0); // Actual length will be filled later + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Failed to init header with error %"PRIu32"!", error); + goto error; + } + + error = rdpgfx_write_surface_command(s, cmd); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpgfx_write_surface_command failed!"); + goto error; + } + + rdpgfx_server_packet_complete_header(s, position); + + /* Write end frame if exists */ + if (endFrame) + { + position = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, + RDPGFX_CMDID_ENDFRAME, 0); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Failed to init header with error %"PRIu32"!", error); + goto error; + } + + rdpgfx_write_end_frame_pdu(s, endFrame); + rdpgfx_server_packet_complete_header(s, position); + } + + return rdpgfx_server_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_delete_encoding_context_pdu(RdpgfxServerContext* + context, + RDPGFX_DELETE_ENCODING_CONTEXT_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_DELETEENCODINGCONTEXT, 6); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT32(s, pdu->codecContextId); /* codecContextId (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_solid_fill_pdu(RdpgfxServerContext* context, + RDPGFX_SOLID_FILL_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + UINT16 index; + RECTANGLE_16* fillRect; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_SOLIDFILL, + 8 + 8 * pdu->fillRectCount); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + + /* fillPixel (4 bytes) */ + if ((error = rdpgfx_write_color32(s, &(pdu->fillPixel)))) + { + WLog_ERR(TAG, "rdpgfx_write_color32 failed with error %"PRIu32"!", error); + goto error; + } + + Stream_Write_UINT16(s, pdu->fillRectCount); /* fillRectCount (2 bytes) */ + + for (index = 0; index < pdu->fillRectCount; index++) + { + fillRect = &(pdu->fillRects[index]); + + if ((error = rdpgfx_write_rect16(s, fillRect))) + { + WLog_ERR(TAG, "rdpgfx_write_rect16 failed with error %"PRIu32"!", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_to_surface_pdu(RdpgfxServerContext* context, + RDPGFX_SURFACE_TO_SURFACE_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + UINT16 index; + RDPGFX_POINT16* destPt; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_SURFACETOSURFACE, + 14 + 4 * pdu->destPtsCount); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceIdSrc); /* surfaceIdSrc (2 bytes) */ + Stream_Write_UINT16(s, pdu->surfaceIdDest); /* surfaceIdDest (2 bytes) */ + + /* rectSrc (8 bytes ) */ + if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc)))) + { + WLog_ERR(TAG, "rdpgfx_write_rect16 failed with error %"PRIu32"!", error); + goto error; + } + + Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */ + + for (index = 0; index < pdu->destPtsCount; index++) + { + destPt = &(pdu->destPts[index]); + + if ((error = rdpgfx_write_point16(s, destPt))) + { + WLog_ERR(TAG, "rdpgfx_write_point16 failed with error %"PRIu32"!", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_to_cache_pdu(RdpgfxServerContext* context, + RDPGFX_SURFACE_TO_CACHE_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_SURFACETOCACHE, 20); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT64(s, pdu->cacheKey); /* cacheKey (8 bytes) */ + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + + /* rectSrc (8 bytes ) */ + if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc)))) + { + WLog_ERR(TAG, "rdpgfx_write_rect16 failed with error %"PRIu32"!", error); + goto error; + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_to_surface_pdu(RdpgfxServerContext* context, + RDPGFX_CACHE_TO_SURFACE_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + UINT16 index; + RDPGFX_POINT16* destPt; + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_CACHETOSURFACE, + 6 + 4 * pdu->destPtsCount); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */ + + for (index = 0; index < pdu->destPtsCount; index++) + { + destPt = &(pdu->destPts[index]); + + if ((error = rdpgfx_write_point16(s, destPt))) + { + WLog_ERR(TAG, "rdpgfx_write_point16 failed with error %"PRIu32"", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_map_surface_to_output_pdu(RdpgfxServerContext* context, + RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_MAPSURFACETOOUTPUT, 12); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, 0); /* reserved (2 bytes). Must be 0 */ + Stream_Write_UINT32(s, pdu->outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Write_UINT32(s, pdu->outputOriginY); /* outputOriginY (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_map_surface_to_window_pdu(RdpgfxServerContext* context, + RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new( + RDPGFX_CMDID_MAPSURFACETOWINDOW, 18); + + if (!s) + { + WLog_ERR(TAG, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT64(s, pdu->windowId); /* windowId (8 bytes) */ + Stream_Write_UINT32(s, pdu->mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->mappedHeight); /* mappedHeight (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_frame_acknowledge_pdu(RdpgfxServerContext* context, + wStream* s) +{ + RDPGFX_FRAME_ACKNOWLEDGE_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.queueDepth); /* queueDepth (4 bytes) */ + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + /* totalFramesDecoded (4 bytes) */ + Stream_Read_UINT32(s, pdu.totalFramesDecoded); + + if (context) + { + IFCALLRET(context->FrameAcknowledge, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->FrameAcknowledge failed with error %"PRIu32"", + error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, + wStream* s) +{ + UINT16 index; + RDPGFX_CACHE_IMPORT_OFFER_PDU pdu; + RDPGFX_CACHE_ENTRY_METADATA* cacheEntries; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + /* cacheEntriesCount (2 bytes) */ + Stream_Read_UINT16(s, pdu.cacheEntriesCount); + + if (pdu.cacheEntriesCount <= 0) + { + /* According to the latest spec, capsSetCount <= 3 */ + WLog_ERR(TAG, "Invalid cacheEntriesCount: %"PRIu16"", pdu.cacheEntriesCount); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < (pdu.cacheEntriesCount * 12)) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.cacheEntries = (RDPGFX_CACHE_ENTRY_METADATA*) + calloc(pdu.cacheEntriesCount, + sizeof(RDPGFX_CACHE_ENTRY_METADATA)); + + if (!pdu.cacheEntries) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (index = 0; index < pdu.cacheEntriesCount; index++) + { + cacheEntries = &(pdu.cacheEntries[index]); + Stream_Read_UINT64(s, cacheEntries->cacheKey); /* cacheKey (8 bytes) */ + /* bitmapLength (4 bytes) */ + Stream_Read_UINT32(s, cacheEntries->bitmapLength); + } + + if (context) + { + IFCALLRET(context->CacheImportOffer, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->CacheImportOffer failed with error %"PRIu32"", + error); + } + + free(pdu.cacheEntries); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_caps_advertise_pdu(RdpgfxServerContext* context, + wStream* s) +{ + UINT16 index; + RDPGFX_CAPSET* capsSets; + RDPGFX_CAPS_ADVERTISE_PDU pdu; + UINT error = CHANNEL_RC_OK; + UINT32 capsDataLength; + + if (Stream_GetRemainingLength(s) < 2) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, pdu.capsSetCount); /* capsSetCount (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (pdu.capsSetCount * RDPGFX_CAPSET_SIZE)) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + capsSets = calloc(pdu.capsSetCount, RDPGFX_CAPSET_SIZE); + + if (!capsSets) + return ERROR_OUTOFMEMORY; + + pdu.capsSets = (RDPGFX_CAPSET*) capsSets; + + for (index = 0; index < pdu.capsSetCount; index++) + { + RDPGFX_CAPSET* capsSet = &(pdu.capsSets[index]); + Stream_Read_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Read_UINT32(s, capsDataLength); /* capsDataLength (4 bytes) */ + + if (capsDataLength != 4) + { + WLog_ERR(TAG, "capsDataLength does not equal to 4: %"PRIu32"", + capsDataLength); + free(capsSets); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + } + + if (context) + { + IFCALLRET(context->CapsAdvertise, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->CapsAdvertise failed with error %"PRIu32"", error); + } + + free(capsSets); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_qoe_frame_acknowledge_pdu(RdpgfxServerContext* context, + wStream* s) +{ + RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */ + Stream_Read_UINT16(s, pdu.timeDiffSE); /* timeDiffSE (2 bytes) */ + Stream_Read_UINT16(s, pdu.timeDiffEDR); /* timeDiffEDR (2 bytes) */ + + if (context) + { + IFCALLRET(context->QoeFrameAcknowledge, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->QoeFrameAcknowledge failed with error %"PRIu32"", + error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_server_receive_pdu(RdpgfxServerContext* context, wStream* s) +{ + size_t beg, end; + RDPGFX_HEADER header; + UINT error = CHANNEL_RC_OK; + beg = Stream_GetPosition(s); + + if ((error = rdpgfx_read_header(s, &header))) + { + WLog_ERR(TAG, "rdpgfx_read_header failed with error %"PRIu32"!", error); + return error; + } + + WLog_DBG(TAG, "cmdId: %s (0x%04"PRIX16") flags: 0x%04"PRIX16" pduLength: %"PRIu32"", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, + header.flags, header.pduLength); + + switch (header.cmdId) + { + case RDPGFX_CMDID_FRAMEACKNOWLEDGE: + if ((error = rdpgfx_recv_frame_acknowledge_pdu(context, s))) + WLog_ERR(TAG, "rdpgfx_recv_frame_acknowledge_pdu " + "failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_CACHEIMPORTOFFER: + if ((error = rdpgfx_recv_cache_import_offer_pdu(context, s))) + WLog_ERR(TAG, "rdpgfx_recv_cache_import_offer_pdu " + "failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_CAPSADVERTISE: + if ((error = rdpgfx_recv_caps_advertise_pdu(context, s))) + WLog_ERR(TAG, "rdpgfx_recv_caps_advertise_pdu " + "failed with error %"PRIu32"!", error); + + break; + + case RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE: + if ((error = rdpgfx_recv_qoe_frame_acknowledge_pdu(context, s))) + WLog_ERR(TAG, "rdpgfx_recv_qoe_frame_acknowledge_pdu " + "failed with error %"PRIu32"!", error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + break; + } + + if (error) + { + WLog_ERR(TAG, "Error while parsing GFX cmdId: %s (0x%04"PRIX16")", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId); + return error; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.pduLength)) + { + WLog_ERR(TAG, "Unexpected gfx pdu end: Actual: %d, Expected: %"PRIu32"", + end, (beg + header.pduLength)); + Stream_SetPosition(s, (beg + header.pduLength)); + } + + return error; +} + +static DWORD WINAPI rdpgfx_server_thread_func(LPVOID arg) +{ + RdpgfxServerContext* context = (RdpgfxServerContext*) arg; + RdpgfxServerPrivate* priv = context->priv; + DWORD status; + DWORD nCount; + void* buffer; + HANDLE events[8]; + UINT error = CHANNEL_RC_OK; + buffer = NULL; + nCount = 0; + events[nCount++] = priv->stopEvent; + events[nCount++] = priv->channelEvent; + + /* Main virtual channel loop. RDPGFX do not need version negotiation */ + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + break; + } + + /* Stop Event */ + if (status == WAIT_OBJECT_0) + break; + + if ((error = rdpgfx_server_handle_messages(context))) + { + WLog_ERR(TAG, "rdpgfx_server_handle_messages failed with error %"PRIu32"", + error); + break; + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "rdpgfx_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static BOOL rdpgfx_server_open(RdpgfxServerContext* context) +{ + RdpgfxServerPrivate* priv = (RdpgfxServerPrivate*) context->priv; + void* buffer = NULL; + + if (!priv->isOpened) + { + PULONG pSessionId = NULL; + DWORD BytesReturned = 0; + priv->SessionId = WTS_CURRENT_SESSION; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, + WTSSessionId, (LPSTR*) &pSessionId, + &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return FALSE; + } + + priv->SessionId = (DWORD) * pSessionId; + WTSFreeMemory(pSessionId); + priv->rdpgfx_channel = WTSVirtualChannelOpenEx(priv->SessionId, + RDPGFX_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!priv->rdpgfx_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + return FALSE; + } + + /* Query for channel event handle */ + if (!WTSVirtualChannelQuery(priv->rdpgfx_channel, WTSVirtualEventHandle, + &buffer, &BytesReturned) + || (BytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed " + "or invalid returned size(%"PRIu32")", + BytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + goto out_close; + } + + CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + + if (!(priv->zgfx = zgfx_context_new(TRUE))) + { + WLog_ERR(TAG, "Create zgfx context failed!"); + goto out_close; + } + + if (priv->ownThread) + { + if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_zgfx; + } + + if (!(priv->thread = CreateThread(NULL, 0, + rdpgfx_server_thread_func, + (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_stopEvent; + } + } + + priv->isOpened = TRUE; + priv->isReady = FALSE; + return TRUE; + } + + WLog_ERR(TAG, "RDPGFX channel is already opened!"); + return FALSE; +out_stopEvent: + CloseHandle(priv->stopEvent); + priv->stopEvent = NULL; +out_zgfx: + zgfx_context_free(priv->zgfx); + priv->zgfx = NULL; +out_close: + WTSVirtualChannelClose(priv->rdpgfx_channel); + priv->rdpgfx_channel = NULL; + priv->channelEvent = NULL; + return FALSE; +} + +static BOOL rdpgfx_server_close(RdpgfxServerContext* context) +{ + RdpgfxServerPrivate* priv = (RdpgfxServerPrivate*) context->priv; + + if (priv->ownThread && priv->thread) + { + SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", + GetLastError()); + return FALSE; + } + + CloseHandle(priv->thread); + CloseHandle(priv->stopEvent); + priv->thread = NULL; + priv->stopEvent = NULL; + } + + zgfx_context_free(priv->zgfx); + priv->zgfx = NULL; + + if (priv->rdpgfx_channel) + { + WTSVirtualChannelClose(priv->rdpgfx_channel); + priv->rdpgfx_channel = NULL; + } + + priv->channelEvent = NULL; + priv->isOpened = FALSE; + priv->isReady = FALSE; + return TRUE; +} + +RdpgfxServerContext* rdpgfx_server_context_new(HANDLE vcm) +{ + RdpgfxServerContext* context; + RdpgfxServerPrivate* priv; + context = (RdpgfxServerContext*)calloc(1, sizeof(RdpgfxServerContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + context->vcm = vcm; + context->Open = rdpgfx_server_open; + context->Close = rdpgfx_server_close; + context->ResetGraphics = rdpgfx_send_reset_graphics_pdu; + context->StartFrame = rdpgfx_send_start_frame_pdu; + context->EndFrame = rdpgfx_send_end_frame_pdu; + context->SurfaceCommand = rdpgfx_send_surface_command; + context->SurfaceFrameCommand = rdpgfx_send_surface_frame_command; + context->DeleteEncodingContext = rdpgfx_send_delete_encoding_context_pdu; + context->CreateSurface = rdpgfx_send_create_surface_pdu; + context->DeleteSurface = rdpgfx_send_delete_surface_pdu; + context->SolidFill = rdpgfx_send_solid_fill_pdu; + context->SurfaceToSurface = rdpgfx_send_surface_to_surface_pdu; + context->SurfaceToCache = rdpgfx_send_surface_to_cache_pdu; + context->CacheToSurface = rdpgfx_send_cache_to_surface_pdu; + context->CacheImportOffer = NULL; + context->CacheImportReply = rdpgfx_send_cache_import_reply_pdu; + context->EvictCacheEntry = rdpgfx_send_evict_cache_entry_pdu; + context->MapSurfaceToOutput = rdpgfx_send_map_surface_to_output_pdu; + context->MapSurfaceToWindow = rdpgfx_send_map_surface_to_window_pdu; + context->CapsAdvertise = NULL; + context->CapsConfirm = rdpgfx_send_caps_confirm_pdu; + context->FrameAcknowledge = NULL; + context->QoeFrameAcknowledge = NULL; + context->priv = priv = (RdpgfxServerPrivate*) + calloc(1, sizeof(RdpgfxServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto out_free; + } + + /* Create shared input stream */ + priv->input_stream = Stream_New(NULL, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto out_free_priv; + } + + priv->isOpened = FALSE; + priv->isReady = FALSE; + priv->ownThread = TRUE; + return (RdpgfxServerContext*) context; +out_free_priv: + free(context->priv); +out_free: + free(context); + return NULL; +} + +void rdpgfx_server_context_free(RdpgfxServerContext* context) +{ + rdpgfx_server_close(context); + + if (context->priv) + Stream_Free(context->priv->input_stream, TRUE); + + free(context->priv); + free(context); +} + +HANDLE rdpgfx_server_get_event_handle(RdpgfxServerContext* context) +{ + return context->priv->channelEvent; +} + +/* + * Handle rpdgfx messages - server side + * + * @param Server side context + * + * @return 0 on success + * ERROR_NO_DATA if no data could be read this time + * otherwise a Win32 error code + */ +UINT rdpgfx_server_handle_messages(RdpgfxServerContext* context) +{ + DWORD BytesReturned; + void* buffer; + UINT ret = CHANNEL_RC_OK; + RdpgfxServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + /* Check whether the dynamic channel is ready */ + if (!priv->isReady) + { + if (WTSVirtualChannelQuery(priv->rdpgfx_channel, + WTSVirtualChannelReady, + &buffer, &BytesReturned) == FALSE) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); + return ERROR_INTERNAL_ERROR; + } + + priv->isReady = *((BOOL*) buffer); + WTSFreeMemory(buffer); + } + + /* Consume channel event only after the gfx dynamic channel is ready */ + if (priv->isReady) + { + Stream_SetPosition(s, 0); + + if (!WTSVirtualChannelRead(priv->rdpgfx_channel, + 0, NULL, 0, &BytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned < 1) + return CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (WTSVirtualChannelRead(priv->rdpgfx_channel, 0, + (PCHAR) Stream_Buffer(s), + Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SetLength(s, BytesReturned); + Stream_SetPosition(s, 0); + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((ret = rdpgfx_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, "rdpgfx_server_receive_pdu " + "failed with error %"PRIu32"!", ret); + return ret; + } + } + } + + return ret; +} diff --git a/channels/rdpgfx/server/rdpgfx_main.h b/channels/rdpgfx/server/rdpgfx_main.h new file mode 100644 index 0000000..be29b76 --- /dev/null +++ b/channels/rdpgfx/server/rdpgfx_main.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2016 Jiang Zihao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H + +#include +#include + +struct _rdpgfx_server_private +{ + ZGFX_CONTEXT* zgfx; + BOOL ownThread; + HANDLE thread; + HANDLE stopEvent; + HANDLE channelEvent; + void* rdpgfx_channel; + DWORD SessionId; + wStream* input_stream; + BOOL isOpened; + BOOL isReady; +}; + +#endif /* FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H */ diff --git a/channels/rdpsnd/CMakeLists.txt b/channels/rdpsnd/CMakeLists.txt new file mode 100644 index 0000000..08b6836 --- /dev/null +++ b/channels/rdpsnd/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("rdpsnd") + +include_directories(common) +add_subdirectory(common) + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpsnd/ChannelOptions.cmake b/channels/rdpsnd/ChannelOptions.cmake new file mode 100644 index 0000000..909da1d --- /dev/null +++ b/channels/rdpsnd/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpsnd" TYPE "static" + DESCRIPTION "Audio Output Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEA]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpsnd/client/CMakeLists.txt b/channels/rdpsnd/client/CMakeLists.txt new file mode 100644 index 0000000..e66f23a --- /dev/null +++ b/channels/rdpsnd/client/CMakeLists.txt @@ -0,0 +1,60 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpsnd") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_main.c + rdpsnd_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + +target_link_libraries(${MODULE_NAME} + winpr freerdp ${CMAKE_THREAD_LIBS_INIT} +) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "") +endif() + +if(WITH_IOSAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "") +endif() + +if(WITH_MACAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "") +endif() + +if(WITH_WINMM) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "") +endif() + +if(WITH_OPENSLES) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "") +endif() + +add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "fake" "") diff --git a/channels/rdpsnd/client/alsa/CMakeLists.txt b/channels/rdpsnd/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..761a639 --- /dev/null +++ b/channels/rdpsnd/client/alsa/CMakeLists.txt @@ -0,0 +1,36 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "alsa" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_alsa.c) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${ALSA_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/ALSA") diff --git a/channels/rdpsnd/client/alsa/rdpsnd_alsa.c b/channels/rdpsnd/client/alsa/rdpsnd_alsa.c new file mode 100644 index 0000000..65f298a --- /dev/null +++ b/channels/rdpsnd/client/alsa/rdpsnd_alsa.c @@ -0,0 +1,579 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_alsa_plugin rdpsndAlsaPlugin; + +struct rdpsnd_alsa_plugin +{ + rdpsndDevicePlugin device; + + UINT32 latency; + AUDIO_FORMAT aformat; + char* device_name; + snd_pcm_t* pcm_handle; + snd_mixer_t* mixer_handle; + + UINT32 actual_rate; + snd_pcm_format_t format; + UINT32 actual_channels; + + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; +}; + +#define SND_PCM_CHECK(_func, _status) \ + if (_status < 0) \ + { \ + WLog_ERR(TAG, "%s: %d\n", _func, _status); \ + return -1; \ + } + +static int rdpsnd_alsa_set_hw_params(rdpsndAlsaPlugin* alsa) +{ + int status; + snd_pcm_hw_params_t* hw_params; + snd_pcm_uframes_t buffer_size_max; + status = snd_pcm_hw_params_malloc(&hw_params); + SND_PCM_CHECK("snd_pcm_hw_params_malloc", status); + status = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params); + SND_PCM_CHECK("snd_pcm_hw_params_any", status); + /* Set interleaved read/write access */ + status = snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + SND_PCM_CHECK("snd_pcm_hw_params_set_access", status); + /* Set sample format */ + status = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, alsa->format); + SND_PCM_CHECK("snd_pcm_hw_params_set_format", status); + /* Set sample rate */ + status = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, NULL); + SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status); + /* Set number of channels */ + status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels); + SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status); + /* Get maximum buffer size */ + status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max); + SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status); + /** + * ALSA Parameters + * + * http://www.alsa-project.org/main/index.php/FramesPeriods + * + * buffer_size = period_size * periods + * period_bytes = period_size * bytes_per_frame + * bytes_per_frame = channels * bytes_per_sample + * + * A frame is equivalent of one sample being played, + * irrespective of the number of channels or the number of bits + * + * A period is the number of frames in between each hardware interrupt. + * + * The buffer size always has to be greater than one period size. + * Commonly this is (2 * period_size), but some hardware can do 8 periods per buffer. + * It is also possible for the buffer size to not be an integer multiple of the period size. + */ + int interrupts_per_sec_near = 50; + int bytes_per_sec = (alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels); + alsa->buffer_size = buffer_size_max; + alsa->period_size = (bytes_per_sec / interrupts_per_sec_near); + + if (alsa->period_size > buffer_size_max) + { + WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n", + alsa->buffer_size, buffer_size_max); + alsa->period_size = (buffer_size_max / 8); + } + + /* Set buffer size */ + status = snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size); + SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status); + /* Set period size */ + status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size, + NULL); + SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status); + status = snd_pcm_hw_params(alsa->pcm_handle, hw_params); + SND_PCM_CHECK("snd_pcm_hw_params", status); + snd_pcm_hw_params_free(hw_params); + return 0; +} + +static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa) +{ + int status; + snd_pcm_sw_params_t* sw_params; + status = snd_pcm_sw_params_malloc(&sw_params); + SND_PCM_CHECK("snd_pcm_sw_params_malloc", status); + status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params); + SND_PCM_CHECK("snd_pcm_sw_params_current", status); + status = snd_pcm_sw_params_set_avail_min(alsa->pcm_handle, sw_params, + (alsa->aformat.nChannels * alsa->actual_channels)); + SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status); + status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params, + alsa->aformat.nBlockAlign); + SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status); + status = snd_pcm_sw_params(alsa->pcm_handle, sw_params); + SND_PCM_CHECK("snd_pcm_sw_params", status); + snd_pcm_sw_params_free(sw_params); + status = snd_pcm_prepare(alsa->pcm_handle); + SND_PCM_CHECK("snd_pcm_prepare", status); + return 0; +} + +static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa) +{ + int status; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size); + SND_PCM_CHECK("snd_pcm_get_params", status); + return 0; +} + +static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa) +{ + snd_pcm_drop(alsa->pcm_handle); + + if (rdpsnd_alsa_set_hw_params(alsa) < 0) + return -1; + + if (rdpsnd_alsa_set_sw_params(alsa) < 0) + return -1; + + return rdpsnd_alsa_validate_params(alsa); +} + +static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + + if (format) + { + alsa->aformat = *format; + alsa->actual_rate = format->nSamplesPerSec; + alsa->actual_channels = format->nChannels; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + alsa->format = SND_PCM_FORMAT_S8; + break; + + case 16: + alsa->format = SND_PCM_FORMAT_S16_LE; + break; + + default: + return FALSE; + } + + break; + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + break; + + default: + return FALSE; + } + } + + alsa->latency = latency; + return (rdpsnd_alsa_set_params(alsa) == 0); +} + +static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa) +{ + if (alsa && alsa->mixer_handle) + { + snd_mixer_close(alsa->mixer_handle); + alsa->mixer_handle = NULL; + } +} + +static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa) +{ + int status; + + if (alsa->mixer_handle) + return TRUE; + + status = snd_mixer_open(&alsa->mixer_handle, 0); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_open failed"); + goto fail; + } + + status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_attach failed"); + goto fail; + } + + status = snd_mixer_selem_register(alsa->mixer_handle, NULL, NULL); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_selem_register failed"); + goto fail; + } + + status = snd_mixer_load(alsa->mixer_handle); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_load failed"); + goto fail; + } + + return TRUE; +fail: + rdpsnd_alsa_close_mixer(alsa); + return FALSE; +} + +static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa) +{ + if (alsa && alsa->pcm_handle) + { + snd_pcm_drain(alsa->pcm_handle); + snd_pcm_close(alsa->pcm_handle); + alsa->pcm_handle = 0; + } +} + +static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + int mode; + int status; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + + if (alsa->pcm_handle) + return TRUE; + + mode = 0; + /*mode |= SND_PCM_NONBLOCK;*/ + status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode); + + if (status < 0) + { + WLog_ERR(TAG, "snd_pcm_open failed"); + return FALSE; + } + + return rdpsnd_alsa_set_format(device, format, latency) && + rdpsnd_alsa_open_mixer(alsa); +} + +static void rdpsnd_alsa_close(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + + if (!alsa) + return; + + rdpsnd_alsa_close_mixer(alsa); +} + +static void rdpsnd_alsa_free(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + rdpsnd_alsa_pcm_close(alsa); + rdpsnd_alsa_close_mixer(alsa); + free(alsa->device_name); + free(alsa); +} + +static BOOL rdpsnd_alsa_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && + format->nSamplesPerSec <= 48000 && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device) +{ + long volume_min; + long volume_max; + long volume_left; + long volume_right; + UINT32 dwVolume; + UINT16 dwVolumeLeft; + UINT16 dwVolumeRight; + snd_mixer_elem_t* elem; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + + if (!rdpsnd_alsa_open_mixer(alsa)) + return 0; + + for (elem = snd_mixer_first_elem(alsa->mixer_handle); elem; elem = snd_mixer_elem_next(elem)) + { + if (snd_mixer_selem_has_playback_volume(elem)) + { + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max); + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left); + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right); + dwVolumeLeft = (UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min)); + dwVolumeRight = (UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min)); + break; + } + } + + dwVolume = (dwVolumeLeft << 16) | dwVolumeRight; + return dwVolume; +} + +static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + long left; + long right; + long volume_min; + long volume_max; + long volume_left; + long volume_right; + snd_mixer_elem_t* elem; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + + if (!rdpsnd_alsa_open_mixer(alsa)) + return FALSE; + + left = (value & 0xFFFF); + right = ((value >> 16) & 0xFFFF); + + for (elem = snd_mixer_first_elem(alsa->mixer_handle); elem; elem = snd_mixer_elem_next(elem)) + { + if (snd_mixer_selem_has_playback_volume(elem)) + { + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max); + volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF; + volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF; + + if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) < 0) || + (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, volume_right) < 0)) + { + WLog_ERR(TAG, "error setting the volume\n"); + return FALSE; + } + } + } + + return TRUE; +} + +static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + UINT latency; + size_t offset; + int frame_size; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + offset = 0; + frame_size = alsa->actual_channels * alsa->aformat.wBitsPerSample / 8; + + while (offset < size) + { + snd_pcm_sframes_t status = snd_pcm_writei(alsa->pcm_handle, &data[offset], + (size - offset) / frame_size); + + if (status < 0) + status = snd_pcm_recover(alsa->pcm_handle, status, 0); + + if (status < 0) + { + WLog_ERR(TAG, "status: %d\n", status); + rdpsnd_alsa_close(device); + rdpsnd_alsa_open(device, NULL, alsa->latency); + break; + } + + offset += status * frame_size; + } + + { + snd_pcm_sframes_t available, delay; + int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay); + + if (rc != 0) + latency = 0; + else if (available == 0) /* Get [ms] from number of samples */ + latency = delay * 1000 / alsa->actual_rate; + else + latency = 0; + } + + return latency + alsa->latency; +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*) device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_alsa_args, flags, + alsa, NULL, NULL); + + if (status < 0) + { + WLog_ERR(TAG, "CommandLineParseArgumentsA failed!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + arg = rdpsnd_alsa_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + alsa->device_name = _strdup(arg->Value); + + if (!alsa->device_name) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry alsa_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndAlsaPlugin* alsa; + UINT error; + alsa = (rdpsndAlsaPlugin*) calloc(1, sizeof(rdpsndAlsaPlugin)); + + if (!alsa) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + alsa->device.Open = rdpsnd_alsa_open; + alsa->device.FormatSupported = rdpsnd_alsa_format_supported; + alsa->device.GetVolume = rdpsnd_alsa_get_volume; + alsa->device.SetVolume = rdpsnd_alsa_set_volume; + alsa->device.Play = rdpsnd_alsa_play; + alsa->device.Close = rdpsnd_alsa_close; + alsa->device.Free = rdpsnd_alsa_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + if ((error = rdpsnd_alsa_parse_addin_args((rdpsndDevicePlugin*) alsa, args))) + { + WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %"PRIu32"", error); + goto error_parse_args; + } + } + + if (!alsa->device_name) + { + alsa->device_name = _strdup("default"); + + if (!alsa->device_name) + { + WLog_ERR(TAG, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_strdup; + } + } + + alsa->pcm_handle = 0; + alsa->actual_rate = 22050; + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->actual_channels = 2; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*) alsa); + return CHANNEL_RC_OK; +error_strdup: + free(alsa->device_name); +error_parse_args: + free(alsa); + return error; +} diff --git a/channels/rdpsnd/client/fake/CMakeLists.txt b/channels/rdpsnd/client/fake/CMakeLists.txt new file mode 100644 index 0000000..fd0240a --- /dev/null +++ b/channels/rdpsnd/client/fake/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Armin Novak +# Copyright 2019 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "fake" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_fake.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +list(APPEND ${MODULE_PREFIX}_LIBS freerdp) +list(APPEND ${MODULE_PREFIX}_LIBS winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/Fake") diff --git a/channels/rdpsnd/client/fake/rdpsnd_fake.c b/channels/rdpsnd/client/fake/rdpsnd_fake.c new file mode 100644 index 0000000..51c184b --- /dev/null +++ b/channels/rdpsnd/client/fake/rdpsnd_fake.c @@ -0,0 +1,172 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2019 Armin Novak + * Copyright 2019 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_fake_plugin rdpsndFakePlugin; + +struct rdpsnd_fake_plugin +{ + rdpsndDevicePlugin device; +}; + +static BOOL rdpsnd_fake_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + return TRUE; +} + +static void rdpsnd_fake_close(rdpsndDevicePlugin* device) +{ +} + +static BOOL rdpsnd_fake_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + return TRUE; +} + +static void rdpsnd_fake_free(rdpsndDevicePlugin* device) +{ + rdpsndFakePlugin* fake = (rdpsndFakePlugin*) device; + + if (!fake) + return; + + free(fake); +} + +static BOOL rdpsnd_fake_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + return TRUE; +} + +static BOOL rdpsnd_fake_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + int latency) +{ + return TRUE; +} + +static UINT rdpsnd_fake_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + return CHANNEL_RC_OK; +} + +static void rdpsnd_fake_start(rdpsndDevicePlugin* device) +{ +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_fake_args[] = +{ + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_fake_parse_addin_args(rdpsndFakePlugin* fake, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + rdpsnd_fake_args, flags, fake, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = rdpsnd_fake_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry fake_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndFakePlugin* fake; + UINT ret; + fake = (rdpsndFakePlugin*) calloc(1, sizeof(rdpsndFakePlugin)); + + if (!fake) + return CHANNEL_RC_NO_MEMORY; + + fake->device.Open = rdpsnd_fake_open; + fake->device.FormatSupported = rdpsnd_fake_format_supported; + fake->device.SetVolume = rdpsnd_fake_set_volume; + fake->device.Play = rdpsnd_fake_play; + fake->device.Start = rdpsnd_fake_start; + fake->device.Close = rdpsnd_fake_close; + fake->device.Free = rdpsnd_fake_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + ret = rdpsnd_fake_parse_addin_args(fake, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + } + + ret = CHANNEL_RC_NO_MEMORY; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &fake->device); + return CHANNEL_RC_OK; +error: + rdpsnd_fake_free(&fake->device); + return ret; +} diff --git a/channels/rdpsnd/client/ios/CMakeLists.txt b/channels/rdpsnd/client/ios/CMakeLists.txt new file mode 100644 index 0000000..ae9f9a7 --- /dev/null +++ b/channels/rdpsnd/client/ios/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Laxmikant Rashinkar +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Corey Clayton +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "ios" "") + +FIND_LIBRARY(CORE_AUDIO CoreAudio) +FIND_LIBRARY(AUDIO_TOOL AudioToolbox) +FIND_LIBRARY(CORE_FOUNDATION CoreFoundation) + +set(${MODULE_PREFIX}_SRCS + rdpsnd_ios.c + TPCircularBuffer.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} + ${AUDIO_TOOL} + ${CORE_AUDIO} + ${CORE_FOUNDATION}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/ios") diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.c b/channels/rdpsnd/client/ios/TPCircularBuffer.c new file mode 100644 index 0000000..1c94027 --- /dev/null +++ b/channels/rdpsnd/client/ios/TPCircularBuffer.c @@ -0,0 +1,140 @@ +// +// TPCircularBuffer.c +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#include + +#include "TPCircularBuffer.h" +#include "rdpsnd_main.h" + +#include +#include + +#define reportResult(result,operation) (_reportResult((result),(operation),__FILE__,__LINE__)) +static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { + if ( result != ERR_SUCCESS ) { + WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); + return false; + } + return true; +} + +bool TPCircularBufferInit(TPCircularBuffer *buffer, int length) { + + // Keep trying until we get our buffer, needed to handle race conditions + int retries = 3; + while ( true ) { + + buffer->length = round_page(length); // We need whole page sizes + + // Temporarily allocate twice the length, so we have the contiguous address space to + // support a second instance of the buffer directly after + vm_address_t bufferAddress; + kern_return_t result = vm_allocate(mach_task_self(), + &bufferAddress, + buffer->length * 2, + VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer allocation"); + return false; + } + // Try again if we fail + continue; + } + + // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... + result = vm_deallocate(mach_task_self(), + bufferAddress + buffer->length, + buffer->length); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer deallocation"); + return false; + } + // If this fails somehow, deallocate the whole region and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + // Re-map the buffer to the address space immediately after the buffer + vm_address_t virtualAddress = bufferAddress + buffer->length; + vm_prot_t cur_prot, max_prot; + result = vm_remap(mach_task_self(), + &virtualAddress, // mirror target + buffer->length, // size of mirror + 0, // auto alignment + 0, // force remapping to virtualAddress + mach_task_self(), // same task + bufferAddress, // mirror source + 0, // MAP READ-WRITE, NOT COPY + &cur_prot, // unused protection struct + &max_prot, // unused protection struct + VM_INHERIT_DEFAULT); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Remap buffer memory"); + return false; + } + // If this remap failed, we hit a race condition, so deallocate and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + if ( virtualAddress != bufferAddress+buffer->length ) { + // If the memory is not contiguous, clean up both allocated buffers and try again + if ( retries-- == 0 ) { + WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer"); + return false; + } + + vm_deallocate(mach_task_self(), virtualAddress, buffer->length); + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + buffer->buffer = (void*)bufferAddress; + buffer->fillCount = 0; + buffer->head = buffer->tail = 0; + + return true; + } + return false; +} + +void TPCircularBufferCleanup(TPCircularBuffer *buffer) { + vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); + memset(buffer, 0, sizeof(TPCircularBuffer)); +} + +void TPCircularBufferClear(TPCircularBuffer *buffer) { + int32_t fillCount; + if ( TPCircularBufferTail(buffer, &fillCount) ) { + TPCircularBufferConsume(buffer, fillCount); + } +} diff --git a/channels/rdpsnd/client/ios/TPCircularBuffer.h b/channels/rdpsnd/client/ios/TPCircularBuffer.h new file mode 100644 index 0000000..cd2a4d8 --- /dev/null +++ b/channels/rdpsnd/client/ios/TPCircularBuffer.h @@ -0,0 +1,195 @@ +// +// TPCircularBuffer.h +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// +// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy +// of the buffer memory directly after the buffer's end, negating the need for any buffer wrap-around +// logic. Clients can simply use the returned memory address as if it were contiguous space. +// +// The implementation is thread-safe in the case of a single producer and single consumer. +// +// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and +// adapted to Darwin by Kurt Revis (http://www.snoize.com, +// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) +// +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef TPCircularBuffer_h +#define TPCircularBuffer_h + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + void *buffer; + int32_t length; + int32_t tail; + int32_t head; + volatile int32_t fillCount; +} TPCircularBuffer; + +/*! + * Initialise buffer + * + * Note that the length is advisory only: Because of the way the + * memory mirroring technique works, the true buffer length will + * be multiples of the device page size (e.g. 4096 bytes) + * + * @param buffer Circular buffer + * @param length Length of buffer + */ +bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length); + +/*! + * Cleanup buffer + * + * Releases buffer resources. + */ +void TPCircularBufferCleanup(TPCircularBuffer *buffer); + +/*! + * Clear buffer + * + * Resets buffer to original, empty state. + * + * This is safe for use by consumer while producer is accessing + * buffer. + */ +void TPCircularBufferClear(TPCircularBuffer *buffer); + +// Reading (consuming) + +/*! + * Access end of buffer + * + * This gives you a pointer to the end of the buffer, ready + * for reading, and the number of available bytes to read. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for reading + * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty + */ +static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, int32_t* availableBytes) { + *availableBytes = buffer->fillCount; + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->tail); +} + +/*! + * Consume bytes in buffer + * + * This frees up the just-read bytes, ready for writing again. + * + * @param buffer Circular buffer + * @param amount Number of bytes to consume + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, int32_t amount) { + buffer->tail = (buffer->tail + amount) % buffer->length; + OSAtomicAdd32Barrier(-amount, &buffer->fillCount); + assert(buffer->fillCount >= 0); +} + +/*! + * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in single-threaded contexts + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, int32_t amount) { + buffer->tail = (buffer->tail + amount) % buffer->length; + buffer->fillCount -= amount; + assert(buffer->fillCount >= 0); +} + +/*! + * Access front of buffer + * + * This gives you a pointer to the front of the buffer, ready + * for writing, and the number of available bytes to write. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for writing + * @return Pointer to the first bytes ready for writing, or NULL if buffer is full + */ +static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, int32_t* availableBytes) { + *availableBytes = (buffer->length - buffer->fillCount); + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->head); +} + +// Writing (producing) + +/*! + * Produce bytes in buffer + * + * This marks the given section of the buffer ready for reading. + * + * @param buffer Circular buffer + * @param amount Number of bytes to produce + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, int amount) { + buffer->head = (buffer->head + amount) % buffer->length; + OSAtomicAdd32Barrier(amount, &buffer->fillCount); + assert(buffer->fillCount <= buffer->length); +} + +/*! + * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in single-threaded contexts + */ +static __inline__ __attribute__((always_inline)) void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, int amount) { + buffer->head = (buffer->head + amount) % buffer->length; + buffer->fillCount += amount; + assert(buffer->fillCount <= buffer->length); +} + +/*! + * Helper routine to copy bytes to buffer + * + * This copies the given bytes to the buffer, and marks them ready for writing. + * + * @param buffer Circular buffer + * @param src Source buffer + * @param len Number of bytes in source buffer + * @return true if bytes copied, false if there was insufficient space + */ +static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, int32_t len) { + int32_t space; + void *ptr = TPCircularBufferHead(buffer, &space); + if ( space < len ) return false; + memcpy(ptr, src, len); + TPCircularBufferProduce(buffer, len); + return true; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/channels/rdpsnd/client/ios/rdpsnd_ios.c b/channels/rdpsnd/client/ios/rdpsnd_ios.c new file mode 100644 index 0000000..6431644 --- /dev/null +++ b/channels/rdpsnd/client/ios/rdpsnd_ios.c @@ -0,0 +1,309 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2013 Dell Software + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#import + +#include "rdpsnd_main.h" +#include "TPCircularBuffer.h" + +#define INPUT_BUFFER_SIZE 32768 +#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4) + +typedef struct rdpsnd_ios_plugin +{ + rdpsndDevicePlugin device; + AudioComponentInstance audio_unit; + TPCircularBuffer buffer; + BOOL is_opened; + BOOL is_playing; +} rdpsndIOSPlugin; + +#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr) + +static OSStatus rdpsnd_ios_render_cb( + void* inRefCon, + AudioUnitRenderActionFlags __unused* ioActionFlags, + const AudioTimeStamp __unused* inTimeStamp, + UInt32 inBusNumber, + UInt32 __unused inNumberFrames, + AudioBufferList* ioData +) +{ + unsigned int i; + + if (inBusNumber != 0) + { + return noErr; + } + + rdpsndIOSPlugin* p = THIS(inRefCon); + + for (i = 0; i < ioData->mNumberBuffers; i++) + { + AudioBuffer* target_buffer = &ioData->mBuffers[i]; + int32_t available_bytes = 0; + const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes); + + if (buffer != NULL && available_bytes > 0) + { + const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes); + memcpy(target_buffer->mData, buffer, bytes_to_copy); + target_buffer->mDataByteSize = bytes_to_copy; + TPCircularBufferConsume(&p->buffer, bytes_to_copy); + } + else + { + target_buffer->mDataByteSize = 0; + AudioOutputUnitStop(p->audio_unit); + p->is_playing = 0; + } + } + + return noErr; +} + +static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device, AUDIO_FORMAT* format) +{ + if (format->wFormatTag == WAVE_FORMAT_PCM) + { + return 1; + } + + return 0; +} + +static BOOL rdpsnd_ios_set_format(rdpsndDevicePlugin* __unused device, + AUDIO_FORMAT* __unused format, int __unused latency) +{ + return TRUE; +} + +static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value) +{ + return TRUE; +} + +static void rdpsnd_ios_start(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + + /* If this device is not playing... */ + if (!p->is_playing) + { + /* Start the device. */ + int32_t available_bytes = 0; + TPCircularBufferTail(&p->buffer, &available_bytes); + + if (available_bytes > 0) + { + p->is_playing = 1; + AudioOutputUnitStart(p->audio_unit); + } + } +} + +static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device) +{ + rdpsndIOSPlugin* p = THIS(device); + + /* If the device is playing... */ + if (p->is_playing) + { + /* Stop the device. */ + AudioOutputUnitStop(p->audio_unit); + p->is_playing = 0; + /* Free all buffers. */ + TPCircularBufferClear(&p->buffer); + } +} + +static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, BYTE* data, int size) +{ + rdpsndIOSPlugin* p = THIS(device); + const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size); + + if (!ok) + return 0; + + rdpsnd_ios_start(device); + return 10; /* TODO: Get real latencry in [ms] */ +} + +static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int __unused latency) +{ + rdpsndIOSPlugin* p = THIS(device); + + if (p->is_opened) + return TRUE; + + /* Find the output audio unit. */ + AudioComponentDescription desc; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + AudioComponent audioComponent = AudioComponentFindNext(NULL, &desc); + + if (audioComponent == NULL) + return FALSE; + + /* Open the audio unit. */ + OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit); + + if (status != 0) + return FALSE; + + /* Set the format for the AudioUnit. */ + AudioStreamBasicDescription audioFormat = {0}; + audioFormat.mSampleRate = format->nSamplesPerSec; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */ + audioFormat.mChannelsPerFrame = format->nChannels; + audioFormat.mBitsPerChannel = format->wBitsPerSample; + audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8; + audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket; + status = AudioUnitSetProperty( + p->audio_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &audioFormat, + sizeof(audioFormat)); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = NULL; + return FALSE; + } + + /* Set up the AudioUnit callback. */ + AURenderCallbackStruct callbackStruct = {0}; + callbackStruct.inputProc = rdpsnd_ios_render_cb; + callbackStruct.inputProcRefCon = p; + status = AudioUnitSetProperty( + p->audio_unit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &callbackStruct, + sizeof(callbackStruct)); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = NULL; + return FALSE; + } + + /* Initialize the AudioUnit. */ + status = AudioUnitInitialize(p->audio_unit); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = NULL; + return FALSE; + } + + /* Allocate the circular buffer. */ + const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE); + + if (!ok) + { + AudioUnitUninitialize(p->audio_unit); + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = NULL; + return FALSE; + } + + p->is_opened = 1; + return TRUE; +} + +static void rdpsnd_ios_close(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + /* Make sure the device is stopped. */ + rdpsnd_ios_stop(device); + + /* If the device is open... */ + if (p->is_opened) + { + /* Close the device. */ + AudioUnitUninitialize(p->audio_unit); + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = NULL; + p->is_opened = 0; + /* Destroy the circular buffer. */ + TPCircularBufferCleanup(&p->buffer); + } +} + +static void rdpsnd_ios_free(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + /* Ensure the device is closed. */ + rdpsnd_ios_close(device); + /* Free memory associated with the device. */ + free(p); +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry ios_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + rdpsndIOSPlugin* p = (rdpsndIOSPlugin*) calloc(1, sizeof(rdpsndIOSPlugin)); + + if (!p) + return CHANNEL_RC_NO_MEMORY; + + p->device.Open = rdpsnd_ios_open; + p->device.FormatSupported = rdpsnd_ios_format_supported; + p->device.SetFormat = rdpsnd_ios_set_format; + p->device.SetVolume = rdpsnd_ios_set_volume; + p->device.Play = rdpsnd_ios_play; + p->device.Start = rdpsnd_ios_start; + p->device.Close = rdpsnd_ios_close; + p->device.Free = rdpsnd_ios_free; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p); + return CHANNEL_RC_OK; +} diff --git a/channels/rdpsnd/client/mac/CMakeLists.txt b/channels/rdpsnd/client/mac/CMakeLists.txt new file mode 100644 index 0000000..63c4331 --- /dev/null +++ b/channels/rdpsnd/client/mac/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Laxmikant Rashinkar +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Corey Clayton +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "mac" "") + +FIND_LIBRARY(CORE_AUDIO CoreAudio) +FIND_LIBRARY(AUDIO_TOOL AudioToolbox) +FIND_LIBRARY(CORE_FOUNDATION CoreFoundation) + +set(${MODULE_PREFIX}_SRCS + rdpsnd_mac.c) + +include_directories(..) +include_directories(${MACAUDIO_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} + ${AUDIO_TOOL} + ${CORE_AUDIO} + ${CORE_FOUNDATION}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/Mac") diff --git a/channels/rdpsnd/client/mac/rdpsnd_mac.c b/channels/rdpsnd/client/mac/rdpsnd_mac.c new file mode 100644 index 0000000..3bb2902 --- /dev/null +++ b/channels/rdpsnd/client/mac/rdpsnd_mac.c @@ -0,0 +1,367 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2012 Laxmikant Rashinkar + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include + +#define __COREFOUNDATION_CFPLUGINCOM__ 1 +#define IUNKNOWN_C_GUTS void *_reserved; void* QueryInterface; void* AddRef; void* Release + +#include +#include + +#include "rdpsnd_main.h" + +#define MAC_AUDIO_QUEUE_NUM_BUFFERS 10 +#define MAC_AUDIO_QUEUE_BUFFER_SIZE 32768 + +struct rdpsnd_mac_plugin +{ + rdpsndDevicePlugin device; + + BOOL isOpen; + BOOL isPlaying; + + UINT32 latency; + AUDIO_FORMAT format; + size_t lastAudioBufferIndex; + size_t audioBufferIndex; + + AudioQueueRef audioQueue; + AudioStreamBasicDescription audioFormat; + AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS]; +}; +typedef struct rdpsnd_mac_plugin rdpsndMacPlugin; + +static void mac_audio_queue_output_cb(void* inUserData, AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer) +{ + rdpsndMacPlugin* mac = (rdpsndMacPlugin*)inUserData; + + if (inBuffer == mac->audioBuffers[mac->lastAudioBufferIndex]) + { + AudioQueuePause(mac->audioQueue); + mac->isPlaying = FALSE; + } +} + +static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + mac->latency = (UINT32) latency; + CopyMemory(&(mac->format), format, sizeof(AUDIO_FORMAT)); + mac->audioFormat.mSampleRate = format->nSamplesPerSec; + mac->audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + mac->audioFormat.mFramesPerPacket = 1; + mac->audioFormat.mChannelsPerFrame = format->nChannels; + mac->audioFormat.mBitsPerChannel = format->wBitsPerSample; + mac->audioFormat.mBytesPerFrame = mac->audioFormat.mBitsPerChannel * + mac->audioFormat.mChannelsPerFrame / 8; + mac->audioFormat.mBytesPerPacket = mac->audioFormat.mBytesPerFrame * + mac->audioFormat.mFramesPerPacket; + mac->audioFormat.mReserved = 0; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_ALAW: + mac->audioFormat.mFormatID = kAudioFormatALaw; + break; + + case WAVE_FORMAT_MULAW: + mac->audioFormat.mFormatID = kAudioFormatULaw; + break; + + case WAVE_FORMAT_PCM: + mac->audioFormat.mFormatID = kAudioFormatLinearPCM; + break; + + default: + return FALSE; + } + + audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format); + return TRUE; +} + +static char* FormatError(OSStatus st) +{ + switch (st) + { + case kAudioFileUnspecifiedError: + return "kAudioFileUnspecifiedError"; + + case kAudioFileUnsupportedFileTypeError: + return "kAudioFileUnsupportedFileTypeError"; + + case kAudioFileUnsupportedDataFormatError: + return "kAudioFileUnsupportedDataFormatError"; + + case kAudioFileUnsupportedPropertyError: + return "kAudioFileUnsupportedPropertyError"; + + case kAudioFileBadPropertySizeError: + return "kAudioFileBadPropertySizeError"; + + case kAudioFilePermissionsError: + return "kAudioFilePermissionsError"; + + case kAudioFileNotOptimizedError: + return "kAudioFileNotOptimizedError"; + + case kAudioFileInvalidChunkError: + return "kAudioFileInvalidChunkError"; + + case kAudioFileDoesNotAllow64BitDataSizeError: + return "kAudioFileDoesNotAllow64BitDataSizeError"; + + case kAudioFileInvalidPacketOffsetError: + return "kAudioFileInvalidPacketOffsetError"; + + case kAudioFileInvalidFileError: + return "kAudioFileInvalidFileError"; + + case kAudioFileOperationNotSupportedError: + return "kAudioFileOperationNotSupportedError"; + + case kAudioFileNotOpenError: + return "kAudioFileNotOpenError"; + + case kAudioFileEndOfFileError: + return "kAudioFileEndOfFileError"; + + case kAudioFilePositionError: + return "kAudioFilePositionError"; + + case kAudioFileFileNotFoundError: + return "kAudioFileFileNotFoundError"; + + default: + return "unknown error"; + } +} + +static BOOL rdpsnd_mac_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + int index; + OSStatus status; + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + + if (mac->isOpen) + return TRUE; + + mac->audioBufferIndex = 0; + + if (!rdpsnd_mac_set_format(device, format, latency)) + return FALSE; + + status = AudioQueueNewOutput(&(mac->audioFormat), + mac_audio_queue_output_cb, mac, + NULL, NULL, 0, &(mac->audioQueue)); + + if (status != 0) + { + const char* err = FormatError(status); + WLog_ERR(TAG, "AudioQueueNewOutput failure %s", err); + return FALSE; + } + + UInt32 DecodeBufferSizeFrames; + UInt32 propertySize = sizeof(DecodeBufferSizeFrames); + status = AudioQueueGetProperty(mac->audioQueue, + kAudioQueueProperty_DecodeBufferSizeFrames, + &DecodeBufferSizeFrames, + &propertySize); + + if (status != 0) + { + WLog_DBG(TAG, "AudioQueueGetProperty failure: kAudioQueueProperty_DecodeBufferSizeFrames\n"); + return FALSE; + } + + for (index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++) + { + status = AudioQueueAllocateBuffer(mac->audioQueue, MAC_AUDIO_QUEUE_BUFFER_SIZE, + &mac->audioBuffers[index]); + + if (status != 0) + { + WLog_ERR(TAG, "AudioQueueAllocateBuffer failed\n"); + return FALSE; + } + } + + mac->isOpen = TRUE; + return TRUE; +} + +static void rdpsnd_mac_close(rdpsndDevicePlugin* device) +{ + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + + if (mac->isOpen) + { + size_t index; + mac->isOpen = FALSE; + AudioQueueStop(mac->audioQueue, true); + + for (index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++) + { + AudioQueueFreeBuffer(mac->audioQueue, mac->audioBuffers[index]); + } + + AudioQueueDispose(mac->audioQueue, true); + mac->audioQueue = NULL; + mac->isPlaying = FALSE; + } +} + +static void rdpsnd_mac_free(rdpsndDevicePlugin* device) +{ + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + device->Close(device); + free(mac); +} + +static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + return TRUE; + + default: + return FALSE; + } +} + +static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + OSStatus status; + Float32 fVolume; + UINT16 volumeLeft; + UINT16 volumeRight; + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + + if (!mac->audioQueue) + return FALSE; + + volumeLeft = (value & 0xFFFF); + volumeRight = ((value >> 16) & 0xFFFF); + fVolume = ((float) volumeLeft) / 65535.0; + status = AudioQueueSetParameter(mac->audioQueue, kAudioQueueParam_Volume, fVolume); + + if (status != 0) + { + WLog_ERR(TAG, "AudioQueueSetParameter kAudioQueueParam_Volume failed: %f\n", fVolume); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_mac_start(rdpsndDevicePlugin* device) +{ + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + + if (!mac->isPlaying) + { + OSStatus status; + + if (!mac->audioQueue) + return; + + status = AudioQueueStart(mac->audioQueue, NULL); + + if (status != 0) + { + WLog_ERR(TAG, "AudioQueueStart failed\n"); + } + + mac->isPlaying = TRUE; + } +} + +static UINT rdpsnd_mac_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + size_t length; + AudioQueueBufferRef audioBuffer; + AudioTimeStamp outActualStartTime; + rdpsndMacPlugin* mac = (rdpsndMacPlugin*) device; + + if (!mac->isOpen) + return 0; + + audioBuffer = mac->audioBuffers[mac->audioBufferIndex]; + length = size > audioBuffer->mAudioDataBytesCapacity ? audioBuffer->mAudioDataBytesCapacity : size; + CopyMemory(audioBuffer->mAudioData, data, length); + audioBuffer->mAudioDataByteSize = length; + audioBuffer->mUserData = mac; + AudioQueueEnqueueBufferWithParameters(mac->audioQueue, audioBuffer, 0, 0, 0, 0, 0, NULL, NULL, + &outActualStartTime); + mac->lastAudioBufferIndex = mac->audioBufferIndex; + mac->audioBufferIndex++; + mac->audioBufferIndex %= MAC_AUDIO_QUEUE_NUM_BUFFERS; + rdpsnd_mac_start(device); + return 10; /* TODO: Get real latencry in [ms] */ +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry mac_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + rdpsndMacPlugin* mac; + mac = (rdpsndMacPlugin*) calloc(1, sizeof(rdpsndMacPlugin)); + + if (!mac) + return CHANNEL_RC_NO_MEMORY; + + mac->device.Open = rdpsnd_mac_open; + mac->device.FormatSupported = rdpsnd_mac_format_supported; + mac->device.SetVolume = rdpsnd_mac_set_volume; + mac->device.Play = rdpsnd_mac_play; + mac->device.Close = rdpsnd_mac_close; + mac->device.Free = rdpsnd_mac_free; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*) mac); + return CHANNEL_RC_OK; +} diff --git a/channels/rdpsnd/client/opensles/CMakeLists.txt b/channels/rdpsnd/client/opensles/CMakeLists.txt new file mode 100644 index 0000000..410a4b4 --- /dev/null +++ b/channels/rdpsnd/client/opensles/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Armin Novak +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "opensles" "") + +set(${MODULE_PREFIX}_SRCS + opensl_io.c + rdpsnd_opensles.c) + +include_directories(..) +include_directories(${OPENSLES_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS freerdp ${OPENSLES_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/rdpsnd/client/opensles/opensl_io.c b/channels/rdpsnd/client/opensles/opensl_io.c new file mode 100644 index 0000000..ffc24e1 --- /dev/null +++ b/channels/rdpsnd/client/opensles/opensl_io.c @@ -0,0 +1,416 @@ +/* +opensl_io.c: +Android OpenSL input/output module +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "rdpsnd_main.h" +#include "opensl_io.h" +#define CONV16BIT 32768 +#define CONVMYFLT (1./32768.) + +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context); + +// creates the OpenSL ES audio engine +static SLresult openSLCreateEngine(OPENSL_STREAM* p) +{ + SLresult result; + // create engine + result = slCreateEngine(&(p->engineObject), 0, NULL, 0, NULL, NULL); + DEBUG_SND("engineObject=%p", (void*) p->engineObject); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + + // realize the engine + result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%"PRIu32"", result); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + + // get the engine interface, which is needed in order to create other objects + result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, + &(p->engineEngine)); + DEBUG_SND("engineEngine=%p", (void*) p->engineEngine); + + if (result != SL_RESULT_SUCCESS) goto engine_end; + +engine_end: + return result; +} + +// opens the OpenSL ES device for output +static SLresult openSLPlayOpen(OPENSL_STREAM* p) +{ + SLresult result; + SLuint32 sr = p->sr; + SLuint32 channels = p->outchannels; + assert(p->engineObject); + assert(p->engineEngine); + + if (channels) + { + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = + { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + p->queuesize + }; + + switch (sr) + { + case 8000: + sr = SL_SAMPLINGRATE_8; + break; + + case 11025: + sr = SL_SAMPLINGRATE_11_025; + break; + + case 16000: + sr = SL_SAMPLINGRATE_16; + break; + + case 22050: + sr = SL_SAMPLINGRATE_22_05; + break; + + case 24000: + sr = SL_SAMPLINGRATE_24; + break; + + case 32000: + sr = SL_SAMPLINGRATE_32; + break; + + case 44100: + sr = SL_SAMPLINGRATE_44_1; + break; + + case 48000: + sr = SL_SAMPLINGRATE_48; + break; + + case 64000: + sr = SL_SAMPLINGRATE_64; + break; + + case 88200: + sr = SL_SAMPLINGRATE_88_2; + break; + + case 96000: + sr = SL_SAMPLINGRATE_96; + break; + + case 192000: + sr = SL_SAMPLINGRATE_192; + break; + + default: + return -1; + } + + const SLInterfaceID ids[] = {SL_IID_VOLUME}; + const SLboolean req[] = {SL_BOOLEAN_FALSE}; + result = (*p->engineEngine)->CreateOutputMix(p->engineEngine, + &(p->outputMixObject), 1, ids, req); + DEBUG_SND("engineEngine=%p", (void*) p->engineEngine); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // realize the output mix + result = (*p->outputMixObject)->Realize(p->outputMixObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%"PRIu32"", result); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + int speakers; + + if (channels > 1) + speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + else speakers = SL_SPEAKER_FRONT_CENTER; + + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channels, sr, + SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, + speakers, SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + // configure audio sink + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, p->outputMixObject}; + SLDataSink audioSnk = {&loc_outmix, NULL}; + // create audio player + const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME}; + const SLboolean req1[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + result = (*p->engineEngine)->CreateAudioPlayer(p->engineEngine, + &(p->bqPlayerObject), &audioSrc, &audioSnk, 2, ids1, req1); + DEBUG_SND("bqPlayerObject=%p", (void*) p->bqPlayerObject); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // realize the player + result = (*p->bqPlayerObject)->Realize(p->bqPlayerObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%"PRIu32"", result); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // get the play interface + result = (*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_PLAY, + &(p->bqPlayerPlay)); + DEBUG_SND("bqPlayerPlay=%p", (void*) p->bqPlayerPlay); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // get the volume interface + result = (*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_VOLUME, + &(p->bqPlayerVolume)); + DEBUG_SND("bqPlayerVolume=%p", (void*) p->bqPlayerVolume); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // get the buffer queue interface + result = (*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &(p->bqPlayerBufferQueue)); + DEBUG_SND("bqPlayerBufferQueue=%p", (void*) p->bqPlayerBufferQueue); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // register callback on the buffer queue + result = (*p->bqPlayerBufferQueue)->RegisterCallback(p->bqPlayerBufferQueue, + bqPlayerCallback, p); + DEBUG_SND("bqPlayerCallback=%p", (void*) p->bqPlayerCallback); + assert(!result); + + if (result != SL_RESULT_SUCCESS) goto end_openaudio; + + // set the player's state to playing + result = (*p->bqPlayerPlay)->SetPlayState(p->bqPlayerPlay, + SL_PLAYSTATE_PLAYING); + DEBUG_SND("SetPlayState=%"PRIu32"", result); + assert(!result); + end_openaudio: + assert(!result); + return result; + } + + return SL_RESULT_SUCCESS; +} + +// close the OpenSL IO and destroy the audio engine +static void openSLDestroyEngine(OPENSL_STREAM* p) +{ + // destroy buffer queue audio player object, and invalidate all associated interfaces + if (p->bqPlayerObject != NULL) + { + (*p->bqPlayerObject)->Destroy(p->bqPlayerObject); + p->bqPlayerObject = NULL; + p->bqPlayerVolume = NULL; + p->bqPlayerPlay = NULL; + p->bqPlayerBufferQueue = NULL; + p->bqPlayerEffectSend = NULL; + } + + // destroy output mix object, and invalidate all associated interfaces + if (p->outputMixObject != NULL) + { + (*p->outputMixObject)->Destroy(p->outputMixObject); + p->outputMixObject = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (p->engineObject != NULL) + { + (*p->engineObject)->Destroy(p->engineObject); + p->engineObject = NULL; + p->engineEngine = NULL; + } +} + + +// open the android audio device for and/or output +OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, + int bufferframes) +{ + OPENSL_STREAM* p; + p = (OPENSL_STREAM*) calloc(1, sizeof(OPENSL_STREAM)); + + if (!p) + return NULL; + + p->queuesize = bufferframes; + p->outchannels = outchannels; + p->sr = sr; + + if (openSLCreateEngine(p) != SL_RESULT_SUCCESS) + { + android_CloseAudioDevice(p); + return NULL; + } + + if (openSLPlayOpen(p) != SL_RESULT_SUCCESS) + { + android_CloseAudioDevice(p); + return NULL; + } + + p->queue = Queue_New(TRUE, -1, -1); + + if (!p->queue) + { + android_CloseAudioDevice(p); + return NULL; + } + + return p; +} + +// close the android audio device +void android_CloseAudioDevice(OPENSL_STREAM* p) +{ + if (p == NULL) + return; + + openSLDestroyEngine(p); + + if (p->queue) + Queue_Free(p->queue); + + free(p); +} + +// this callback handler is called every time a buffer finishes playing +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context) +{ + OPENSL_STREAM* p = (OPENSL_STREAM*) context; + assert(p); + assert(p->queue); + void* data = Queue_Dequeue(p->queue); + free(data); +} + +// puts a buffer of size samples to the device +int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size) +{ + assert(p); + assert(buffer); + assert(size > 0); + + /* Assure, that the queue is not full. */ + if (p->queuesize <= Queue_Count(p->queue) + && WaitForSingleObject(p->queue->event, INFINITE) == WAIT_FAILED) + { + DEBUG_SND("WaitForSingleObject failed!"); + return -1; + } + + void* data = calloc(size, sizeof(short)); + + if (!data) + { + DEBUG_SND("unable to allocate a buffer"); + return -1; + } + + memcpy(data, buffer, size * sizeof(short)); + Queue_Enqueue(p->queue, data); + (*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue, + data, sizeof(short) * size); + return size; +} + +int android_GetOutputMute(OPENSL_STREAM* p) +{ + SLboolean mute; + assert(p); + assert(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute); + + if (SL_RESULT_SUCCESS != rc) + return SL_BOOLEAN_FALSE; + + return mute; +} + +BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL _mute) +{ + SLboolean mute = _mute; + assert(p); + assert(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute); + + if (SL_RESULT_SUCCESS != rc) + return FALSE; + + return TRUE; +} + +int android_GetOutputVolume(OPENSL_STREAM* p) +{ + SLmillibel level; + assert(p); + assert(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level); + + if (SL_RESULT_SUCCESS != rc) + return 0; + + return level; +} + +int android_GetOutputVolumeMax(OPENSL_STREAM* p) +{ + SLmillibel level; + assert(p); + assert(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, + &level); + + if (SL_RESULT_SUCCESS != rc) + return 0; + + return level; +} + + +BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level) +{ + SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level); + + if (SL_RESULT_SUCCESS != rc) + return FALSE; + + return TRUE; +} + diff --git a/channels/rdpsnd/client/opensles/opensl_io.h b/channels/rdpsnd/client/opensles/opensl_io.h new file mode 100644 index 0000000..0bb121a --- /dev/null +++ b/channels/rdpsnd/client/opensles/opensl_io.h @@ -0,0 +1,111 @@ +/* +opensl_io.c: +Android OpenSL input/output module header +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H +#define FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct opensl_stream +{ + // engine interfaces + SLObjectItf engineObject; + SLEngineItf engineEngine; + + // output mix interfaces + SLObjectItf outputMixObject; + + // buffer queue player interfaces + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + SLVolumeItf bqPlayerVolume; + SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; + SLEffectSendItf bqPlayerEffectSend; + + unsigned int outchannels; + unsigned int sr; + + unsigned int queuesize; + wQueue* queue; +} OPENSL_STREAM; + +/* +Open the audio device with a given sampling rate (sr), output channels and IO buffer size +in frames. Returns a handle to the OpenSL stream +*/ +FREERDP_LOCAL OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, + int bufferframes); +/* +Close the audio device +*/ +FREERDP_LOCAL void android_CloseAudioDevice(OPENSL_STREAM* p); +/* +Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written. +*/ +FREERDP_LOCAL int android_AudioOut(OPENSL_STREAM* p, const short* buffer, + int size); +/* + * Set the volume input level. + */ +FREERDP_LOCAL void android_SetInputVolume(OPENSL_STREAM* p, int level); +/* + * Get the current output mute setting. + */ +FREERDP_LOCAL int android_GetOutputMute(OPENSL_STREAM* p); +/* + * Change the current output mute setting. + */ +FREERDP_LOCAL BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL mute); +/* + * Get the current output volume level. + */ +FREERDP_LOCAL int android_GetOutputVolume(OPENSL_STREAM* p); +/* + * Get the maximum output volume level. + */ +FREERDP_LOCAL int android_GetOutputVolumeMax(OPENSL_STREAM* p); + +/* + * Set the volume output level. + */ +FREERDP_LOCAL BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level); +#ifdef __cplusplus +}; +#endif + +#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H */ diff --git a/channels/rdpsnd/client/opensles/rdpsnd_opensles.c b/channels/rdpsnd/client/opensles/rdpsnd_opensles.c new file mode 100644 index 0000000..26ea455 --- /dev/null +++ b/channels/rdpsnd/client/opensles/rdpsnd_opensles.c @@ -0,0 +1,405 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2013 Armin Novak + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "opensl_io.h" +#include "rdpsnd_main.h" + +typedef struct rdpsnd_opensles_plugin rdpsndopenslesPlugin; + +struct rdpsnd_opensles_plugin +{ + rdpsndDevicePlugin device; + + UINT32 latency; + int wformat; + int block_size; + char* device_name; + + OPENSL_STREAM* stream; + + UINT32 volume; + + UINT32 rate; + UINT32 channels; + int format; +}; + +static int rdpsnd_opensles_volume_to_millibel(unsigned short level, int max) +{ + const int min = SL_MILLIBEL_MIN; + const int step = max - min; + const int rc = (level * step / 0xFFFF) + min; + DEBUG_SND("level=%hu, min=%d, max=%d, step=%d, result=%d", + level, min, max, step, rc); + return rc; +} + +static unsigned short rdpsnd_opensles_millibel_to_volume(int millibel, int max) +{ + const int min = SL_MILLIBEL_MIN; + const int range = max - min; + const int rc = ((millibel - min) * 0xFFFF + range / 2 + 1) / range; + DEBUG_SND("millibel=%d, min=%d, max=%d, range=%d, result=%d", + millibel, min, max, range, rc); + return rc; +} + +static bool rdpsnd_opensles_check_handle(const rdpsndopenslesPlugin* hdl) +{ + bool rc = true; + + if (!hdl) + rc = false; + else + { + if (!hdl->stream) + rc = false; + } + + return rc; +} + +static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, + UINT32 volume); + +static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles) +{ + DEBUG_SND("opensles=%p", (void*) opensles); + + if (!rdpsnd_opensles_check_handle(opensles)) + return 0; + + if (opensles->stream) + android_CloseAudioDevice(opensles->stream); + + opensles->stream = android_OpenAudioDevice( + opensles->rate, opensles->channels, 20); + return 0; +} + +static BOOL rdpsnd_opensles_set_format(rdpsndDevicePlugin* device, + const AUDIO_FORMAT* format, UINT32 latency) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + rdpsnd_opensles_check_handle(opensles); + DEBUG_SND("opensles=%p format=%p, latency=%"PRIu32, (void*) opensles, (void*) format, latency); + + if (format) + { + DEBUG_SND("format=%"PRIu16", cbsize=%"PRIu16", samples=%"PRIu32", bits=%"PRIu16", channels=%"PRIu16", align=%"PRIu16"", + format->wFormatTag, format->cbSize, format->nSamplesPerSec, + format->wBitsPerSample, format->nChannels, format->nBlockAlign); + opensles->rate = format->nSamplesPerSec; + opensles->channels = format->nChannels; + opensles->format = format->wFormatTag; + opensles->wformat = format->wFormatTag; + opensles->block_size = format->nBlockAlign; + } + + opensles->latency = latency; + return (rdpsnd_opensles_set_params(opensles) == 0); +} + +static BOOL rdpsnd_opensles_open(rdpsndDevicePlugin* device, + const AUDIO_FORMAT* format, UINT32 latency) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p format=%p, latency=%"PRIu32", rate=%"PRIu32"", + (void*) opensles, (void*) format, latency, opensles->rate); + + if (rdpsnd_opensles_check_handle(opensles)) + return TRUE; + + opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, + 20); + assert(opensles->stream); + + if (!opensles->stream) + WLog_ERR(TAG, "android_OpenAudioDevice failed"); + else + rdpsnd_opensles_set_volume(device, opensles->volume); + + return rdpsnd_opensles_set_format(device, format, latency); +} + +static void rdpsnd_opensles_close(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p", (void*) opensles); + + if (!rdpsnd_opensles_check_handle(opensles)) + return; + + android_CloseAudioDevice(opensles->stream); + opensles->stream = NULL; +} + +static void rdpsnd_opensles_free(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p", (void*) opensles); + assert(opensles); + assert(opensles->device_name); + free(opensles->device_name); + free(opensles); +} + +static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device, + const AUDIO_FORMAT* format) +{ + DEBUG_SND("format=%"PRIu16", cbsize=%"PRIu16", samples=%"PRIu32", bits=%"PRIu16", channels=%"PRIu16", align=%"PRIu16"", + format->wFormatTag, format->cbSize, format->nSamplesPerSec, + format->wBitsPerSample, format->nChannels, format->nBlockAlign); + assert(device); + assert(format); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && + format->nSamplesPerSec <= 48000 && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + + default: + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p", (void*) opensles); + assert(opensles); + + if (opensles->stream) + { + const int max = android_GetOutputVolumeMax(opensles->stream); + const int rc = android_GetOutputVolume(opensles->stream); + + if (android_GetOutputMute(opensles->stream)) + opensles->volume = 0; + else + { + const unsigned short vol = rdpsnd_opensles_millibel_to_volume(rc, max); + opensles->volume = (vol << 16) | (vol & 0xFFFF); + } + } + + return opensles->volume; +} + +static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, + UINT32 value) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p, value=%"PRIu32"", (void*) opensles, value); + assert(opensles); + opensles->volume = value; + + if (opensles->stream) + { + if (0 == opensles->volume) + return android_SetOutputMute(opensles->stream, true); + else + { + const int max = android_GetOutputVolumeMax(opensles->stream); + const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max); + + if (!android_SetOutputMute(opensles->stream, false)) + return FALSE; + + if (!android_SetOutputVolume(opensles->stream, vol)) + return FALSE; + } + } + + return TRUE; +} + +static UINT rdpsnd_opensles_play(rdpsndDevicePlugin* device, + const BYTE* data, size_t size) +{ + union + { + const BYTE* b; + const short* s; + } src; + int ret; + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + DEBUG_SND("opensles=%p, data=%p, size=%d", (void*) opensles, (void*) data, size); + + if (!rdpsnd_opensles_check_handle(opensles)) + return 0; + + src.b = data; + DEBUG_SND("size=%d, src=%p", size, (void*) src.b); + assert(0 == size % 2); + assert(size > 0); + assert(src.b); + ret = android_AudioOut(opensles->stream, src.s, size / 2); + + if (ret < 0) + WLog_ERR(TAG, "android_AudioOut failed (%d)", ret); + + return 10; /* TODO: Get real latencry in [ms] */ +} + +static void rdpsnd_opensles_start(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + rdpsnd_opensles_check_handle(opensles); + DEBUG_SND("opensles=%p", (void*) opensles); +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] = +{ + { + "dev", COMMAND_LINE_VALUE_REQUIRED, "", + NULL, NULL, -1, NULL, "device" + }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, + ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*) device; + assert(opensles); + assert(args); + DEBUG_SND("opensles=%p, args=%p", (void*) opensles, (void*) args); + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | + COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + rdpsnd_opensles_args, flags, opensles, NULL, NULL); + + if (status < 0) + return status; + + arg = rdpsnd_opensles_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + opensles->device_name = _strdup(arg->Value); + + if (!opensles->device_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return status; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry \ + opensles_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry \ + FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndopenslesPlugin* opensles; + UINT error; + DEBUG_SND("pEntryPoints=%p", (void*) pEntryPoints); + opensles = (rdpsndopenslesPlugin*) calloc(1, sizeof(rdpsndopenslesPlugin)); + + if (!opensles) + return CHANNEL_RC_NO_MEMORY; + + opensles->device.Open = rdpsnd_opensles_open; + opensles->device.FormatSupported = rdpsnd_opensles_format_supported; + opensles->device.GetVolume = rdpsnd_opensles_get_volume; + opensles->device.SetVolume = rdpsnd_opensles_set_volume; + opensles->device.Start = rdpsnd_opensles_start; + opensles->device.Play = rdpsnd_opensles_play; + opensles->device.Close = rdpsnd_opensles_close; + opensles->device.Free = rdpsnd_opensles_free; + args = pEntryPoints->args; + rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*) opensles, args); + + if (!opensles->device_name) + { + opensles->device_name = _strdup("default"); + + if (!opensles->device_name) + { + error = CHANNEL_RC_NO_MEMORY; + goto outstrdup; + } + } + + opensles->rate = 44100; + opensles->channels = 2; + opensles->format = WAVE_FORMAT_ADPCM; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, + (rdpsndDevicePlugin*) opensles); + DEBUG_SND("success"); + return CHANNEL_RC_OK; +outstrdup: + free(opensles); + return error; +} diff --git a/channels/rdpsnd/client/oss/CMakeLists.txt b/channels/rdpsnd/client/oss/CMakeLists.txt new file mode 100644 index 0000000..53ae5fa --- /dev/null +++ b/channels/rdpsnd/client/oss/CMakeLists.txt @@ -0,0 +1,36 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "oss" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_oss.c) + +include_directories(..) +include_directories(${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${OSS_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/OSS") diff --git a/channels/rdpsnd/client/oss/rdpsnd_oss.c b/channels/rdpsnd/client/oss/rdpsnd_oss.c new file mode 100644 index 0000000..ff402c9 --- /dev/null +++ b/channels/rdpsnd/client/oss/rdpsnd_oss.c @@ -0,0 +1,485 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright (c) 2015 Rozhuk Ivan + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#if defined(__OpenBSD__) +#include +#else +#include +#endif +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_oss_plugin rdpsndOssPlugin; + +struct rdpsnd_oss_plugin +{ + rdpsndDevicePlugin device; + + int pcm_handle; + int mixer_handle; + int dev_unit; + + int supported_formats; + + UINT32 latency; + AUDIO_FORMAT format; +}; + +#define OSS_LOG_ERR(_text, _error) \ + { \ + if (_error != 0) \ + WLog_ERR(TAG, "%s: %i - %s", _text, _error, strerror(_error)); \ + } + + +static int rdpsnd_oss_get_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + return AFMT_S8; + + case 16: + return AFMT_S16_LE; + } + + break; + + case WAVE_FORMAT_ALAW: + return AFMT_A_LAW; + + case WAVE_FORMAT_MULAW: + return AFMT_MU_LAW; + } + + return 0; +} + +static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + int req_fmt = 0; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL || format == NULL) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize != 0 || + format->nSamplesPerSec > 48000 || + (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + + break; + + case WAVE_FORMAT_MULAW: + case WAVE_FORMAT_ALAW: + break; + + default: + return FALSE; + } + + req_fmt = rdpsnd_oss_get_format(format); + + /* Check really supported formats by dev. */ + if (oss->pcm_handle != -1) + { + if ((req_fmt & oss->supported_formats) == 0) + return FALSE; + } + else + { + if (req_fmt == 0) + return FALSE; + } + + return TRUE; +} + +static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + int tmp; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->pcm_handle == -1 || format == NULL) + return FALSE; + + oss->latency = latency; + CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT)); + tmp = rdpsnd_oss_get_format(format); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + return FALSE; + } + + tmp = format->nChannels; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + return FALSE; + } + + tmp = format->nSamplesPerSec; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + return FALSE; + } + + tmp = format->nBlockAlign; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss) +{ + int devmask = 0; + char mixer_name[PATH_MAX] = "/dev/mixer"; + + if (oss->mixer_handle != -1) + return; + + if (oss->dev_unit != -1) + sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit); + + if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0) + { + OSS_LOG_ERR("mixer open failed", errno); + oss->mixer_handle = -1; + return; + } + + if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) + { + OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno); + close(oss->mixer_handle); + oss->mixer_handle = -1; + return; + } +} + +static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + char dev_name[PATH_MAX] = "/dev/dsp"; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->pcm_handle != -1) + return TRUE; + + if (oss->dev_unit != -1) + sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit); + + WLog_INFO(TAG, "open: %s", dev_name); + + if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return FALSE; + } + +#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */ + int mask = 0; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno); + } + else if ((mask & PCM_CAP_OUTPUT) == 0) + { + OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return; + } + +#endif + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + rdpsnd_oss_set_format(device, format, latency); + rdpsnd_oss_open_mixer(oss); + return TRUE; +} + +static void rdpsnd_oss_close(rdpsndDevicePlugin* device) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL) + return; + + if (oss->pcm_handle != -1) + { + WLog_INFO(TAG, "close: dsp"); + close(oss->pcm_handle); + oss->pcm_handle = -1; + } + + if (oss->mixer_handle != -1) + { + WLog_INFO(TAG, "close: mixer"); + close(oss->mixer_handle); + oss->mixer_handle = -1; + } +} + +static void rdpsnd_oss_free(rdpsndDevicePlugin* device) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL) + return; + + rdpsnd_oss_close(device); + free(oss); +} + +static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device) +{ + int vol; + UINT32 dwVolume; + UINT16 dwVolumeLeft, dwVolumeRight; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + /* On error return 50% volume. */ + dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + + if (device == NULL || oss->mixer_handle == -1) + return dwVolume; + + if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1) + { + OSS_LOG_ERR("MIXER_READ", errno); + return dwVolume; + } + + dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100); + dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100); + dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + return dwVolume; +} + +static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + int left, right; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->mixer_handle == -1) + return FALSE; + + left = (((value & 0xFFFF) * 100) / 0xFFFF); + right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF); + + if (left < 0) + left = 0; + else if (left > 100) + left = 100; + + if (right < 0) + right = 0; + else if (right > 100) + right = 100; + + left |= (right << 8); + + if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1) + { + OSS_LOG_ERR("WRITE_MIXER", errno); + return FALSE; + } + + return TRUE; +} + +static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == NULL || oss->mixer_handle == -1) + return 0; + + while (size > 0) + { + ssize_t status = write(oss->pcm_handle, data, size); + + if (status < 0) + { + OSS_LOG_ERR("write fail", errno); + rdpsnd_oss_close(device); + rdpsnd_oss_open(device, NULL, oss->latency); + break; + } + + data += status; + + if (status <= size) + size -= status; + else + size = 0; + } + + return 10; /* TODO: Get real latency in [ms] */ +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + int status; + char* str_num, *eptr; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, + oss, NULL, NULL); + + if (status < 0) + return status; + + arg = rdpsnd_oss_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + return ERROR_OUTOFMEMORY; + + { + long val = strtol(str_num, &eptr, 10); + + if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX)) + { + free(str_num); + return CHANNEL_RC_NULL_DATA; + } + + oss->dev_unit = val; + } + + if (oss->dev_unit < 0 || *eptr != '\0') + oss->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return status; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry oss_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndOssPlugin* oss; + oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin)); + + if (!oss) + return CHANNEL_RC_NO_MEMORY; + + oss->device.Open = rdpsnd_oss_open; + oss->device.FormatSupported = rdpsnd_oss_format_supported; + oss->device.GetVolume = rdpsnd_oss_get_volume; + oss->device.SetVolume = rdpsnd_oss_set_volume; + oss->device.Play = rdpsnd_oss_play; + oss->device.Close = rdpsnd_oss_close; + oss->device.Free = rdpsnd_oss_free; + oss->pcm_handle = -1; + oss->mixer_handle = -1; + oss->dev_unit = -1; + args = pEntryPoints->args; + rdpsnd_oss_parse_addin_args((rdpsndDevicePlugin*)oss, args); + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss); + return CHANNEL_RC_OK; +} diff --git a/channels/rdpsnd/client/pulse/CMakeLists.txt b/channels/rdpsnd/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..3a57448 --- /dev/null +++ b/channels/rdpsnd/client/pulse/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "pulse" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_pulse.c) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +list(APPEND ${MODULE_PREFIX}_LIBS ${PULSE_LIBRARY}) +list(APPEND ${MODULE_PREFIX}_LIBS freerdp) +list(APPEND ${MODULE_PREFIX}_LIBS winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/Pulse") diff --git a/channels/rdpsnd/client/pulse/rdpsnd_pulse.c b/channels/rdpsnd/client/pulse/rdpsnd_pulse.c new file mode 100644 index 0000000..b367d95 --- /dev/null +++ b/channels/rdpsnd/client/pulse/rdpsnd_pulse.c @@ -0,0 +1,632 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_pulse_plugin rdpsndPulsePlugin; + +struct rdpsnd_pulse_plugin +{ + rdpsndDevicePlugin device; + + char* device_name; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; + UINT32 latency; + UINT32 volume; +}; + +static BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format); + +static void rdpsnd_pulse_get_sink_info(pa_context* c, const pa_sink_info* i, int eol, + void* userdata) +{ + uint8_t x; + UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */; + UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) userdata; + + if (!pulse || !c || !i) + return; + + for (x = 0; x < i->volume.channels; x++) + { + pa_volume_t volume = i->volume.values[x]; + + if (volume >= PA_VOLUME_NORM) + volume = PA_VOLUME_NORM - 1; + + switch (x) + { + case 0: + dwVolumeLeft = (UINT16)volume; + break; + + case 1: + dwVolumeRight = (UINT16)volume; + break; + + default: + break; + } + } + + pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight; +} + +static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata) +{ + pa_context_state_t state; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) userdata; + state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + break; + } +} + +static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device) +{ + pa_operation* o; + pa_context_state_t state; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->context) + return FALSE; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + return FALSE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse); + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_CONTEXT_READY) + { + return TRUE; + } + else + { + pa_context_disconnect(pulse->context); + return FALSE; + } +} + +static void rdpsnd_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) userdata; + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation) +{ + if (!operation) + return; + + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_operation_unref(operation); +} + +static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + pa_stream_state_t state; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) userdata; + state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + break; + } +} + +static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) userdata; + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_close(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->context || !pulse->stream) + return; + + pa_threaded_mainloop_lock(pulse->mainloop); + rdpsnd_pulse_wait_for_operation(pulse, pa_stream_drain(pulse->stream, + rdpsnd_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); +} + +static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format) +{ + pa_sample_spec sample_spec = { 0 }; + + if (!pulse->context) + return FALSE; + + if (!rdpsnd_pulse_format_supported(&pulse->device, format)) + return FALSE; + + sample_spec.rate = format->nSamplesPerSec; + sample_spec.channels = format->nChannels; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + sample_spec.format = PA_SAMPLE_U8; + break; + + case 16: + sample_spec.format = PA_SAMPLE_S16LE; + break; + + default: + return FALSE; + } + + break; + + case WAVE_FORMAT_ALAW: + sample_spec.format = PA_SAMPLE_ALAW; + break; + + case WAVE_FORMAT_MULAW: + sample_spec.format = PA_SAMPLE_ULAW; + break; + + default: + return FALSE; + } + + pulse->sample_spec = sample_spec; + return TRUE; +} + +static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + pa_stream_state_t state; + pa_stream_flags_t flags; + pa_buffer_attr buffer_attr = { 0 }; + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX]; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->context || pulse->stream) + return TRUE; + + if (!rdpsnd_pulse_set_format_spec(pulse, format)) + return FALSE; + + pulse->latency = latency; + + if (pa_sample_spec_valid(&pulse->sample_spec) == 0) + { + pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec); + return TRUE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", &pulse->sample_spec, NULL); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + /* register essential callbacks */ + pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse); + flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; + + if (pulse->latency > 0) + { + buffer_attr.maxlength = pa_usec_to_bytes(pulse->latency * 2 * 1000, &pulse->sample_spec); + buffer_attr.tlength = pa_usec_to_bytes(pulse->latency * 1000, &pulse->sample_spec); + buffer_attr.prebuf = (UINT32) - 1; + buffer_attr.minreq = (UINT32) - 1; + buffer_attr.fragsize = (UINT32) - 1; + flags |= PA_STREAM_ADJUST_LATENCY; + } + + if (pa_stream_connect_playback(pulse->stream, + pulse->device_name, pulse->latency > 0 ? &buffer_attr : NULL, flags, NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_STREAM_READY) + return TRUE; + + rdpsnd_pulse_close(device); + return FALSE; +} + +static void rdpsnd_pulse_free(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse) + return; + + rdpsnd_pulse_close(device); + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + + free(pulse->device_name); + free(pulse); +} + +BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (format->cbSize == 0 && + (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device) +{ + pa_operation* o; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse) + return 0; + + if (!pulse->context || !pulse->mainloop) + return 0; + + pa_threaded_mainloop_lock(pulse->mainloop); + o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse); + pa_operation_unref(o); + pa_threaded_mainloop_unlock(pulse->mainloop); + return pulse->volume; +} + +static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + pa_cvolume cv; + pa_volume_t left; + pa_volume_t right; + pa_operation* operation; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->context || !pulse->stream) + return FALSE; + + left = (pa_volume_t)(value & 0xFFFF); + right = (pa_volume_t)((value >> 16) & 0xFFFF); + pa_cvolume_init(&cv); + cv.channels = 2; + cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / 0xFFFF; + cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / 0xFFFF; + pa_threaded_mainloop_lock(pulse->mainloop); + operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream), + &cv, NULL, NULL); + + if (operation) + pa_operation_unref(operation); + + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + size_t length; + int status; + pa_usec_t latency; + int negative; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->stream || !data) + return 0; + + pa_threaded_mainloop_lock(pulse->mainloop); + + while (size > 0) + { + while ((length = pa_stream_writable_size(pulse->stream)) == 0) + pa_threaded_mainloop_wait(pulse->mainloop); + + if (length == (size_t) -1) + break; + + if (length > size) + length = size; + + status = pa_stream_write(pulse->stream, data, length, NULL, 0LL, PA_SEEK_RELATIVE); + + if (status < 0) + { + break; + } + + data += length; + size -= length; + } + + if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0) + latency = 0; + + pa_threaded_mainloop_unlock(pulse->mainloop); + return latency / 1000; +} + +static void rdpsnd_pulse_start(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + + if (!pulse->stream) + return; + + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_trigger(pulse->stream, NULL, NULL); + pa_threaded_mainloop_unlock(pulse->mainloop); +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = +{ + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_pulse_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*) device; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, + rdpsnd_pulse_args, flags, pulse, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = rdpsnd_pulse_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dev") + { + pulse->device_name = _strdup(arg->Value); + + if (!pulse->device_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry pulse_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndPulsePlugin* pulse; + UINT ret; + pulse = (rdpsndPulsePlugin*) calloc(1, sizeof(rdpsndPulsePlugin)); + + if (!pulse) + return CHANNEL_RC_NO_MEMORY; + + pulse->device.Open = rdpsnd_pulse_open; + pulse->device.FormatSupported = rdpsnd_pulse_format_supported; + pulse->device.GetVolume = rdpsnd_pulse_get_volume; + pulse->device.SetVolume = rdpsnd_pulse_set_volume; + pulse->device.Play = rdpsnd_pulse_play; + pulse->device.Start = rdpsnd_pulse_start; + pulse->device.Close = rdpsnd_pulse_close; + pulse->device.Free = rdpsnd_pulse_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + ret = rdpsnd_pulse_parse_addin_args((rdpsndDevicePlugin*) pulse, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + } + + ret = CHANNEL_RC_NO_MEMORY; + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + goto error; + + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + + if (!pulse->context) + goto error; + + pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse); + ret = ERROR_INVALID_OPERATION; + + if (!rdpsnd_pulse_connect((rdpsndDevicePlugin*)pulse)) + goto error; + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*) pulse); + return CHANNEL_RC_OK; +error: + rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse); + return ret; +} diff --git a/channels/rdpsnd/client/rdpsnd_main.c b/channels/rdpsnd/client/rdpsnd_main.c new file mode 100644 index 0000000..cc4167c --- /dev/null +++ b/channels/rdpsnd/client/rdpsnd_main.c @@ -0,0 +1,1313 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2012-2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rdpsnd_common.h" +#include "rdpsnd_main.h" + +struct rdpsnd_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + HANDLE thread; + wStreamPool* pool; + wStream* data_in; + + void* InitHandle; + DWORD OpenHandle; + + wLog* log; + HANDLE stopEvent; + + BYTE cBlockNo; + UINT16 wQualityMode; + UINT16 wCurrentFormatNo; + + AUDIO_FORMAT* ServerFormats; + UINT16 NumberOfServerFormats; + + AUDIO_FORMAT* ClientFormats; + UINT16 NumberOfClientFormats; + + BOOL attached; + BOOL connected; + + BOOL expectingWave; + BYTE waveData[4]; + UINT16 waveDataSize; + UINT32 wTimeStamp; + UINT32 wArrivalTime; + + UINT32 latency; + BOOL isOpen; + AUDIO_FORMAT* fixed_format; + + char* subsystem; + char* device_name; + + /* Device plugin */ + rdpsndDevicePlugin* device; + rdpContext* rdpcontext; + + wQueue* queue; + FREERDP_DSP_CONTEXT* dsp_context; +}; + +static void rdpsnd_recv_close_pdu(rdpsndPlugin* rdpsnd); +static void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_quality_mode_pdu(rdpsndPlugin* rdpsnd) +{ + wStream* pdu; + pdu = Stream_New(NULL, 8); + + if (!pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_QUALITYMODE); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, 4); /* BodySize */ + Stream_Write_UINT16(pdu, rdpsnd->wQualityMode); /* wQualityMode */ + Stream_Write_UINT16(pdu, 0); /* Reserved */ + WLog_Print(rdpsnd->log, WLOG_DEBUG, "QualityMode: %"PRIu16"", rdpsnd->wQualityMode); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +static void rdpsnd_select_supported_audio_formats(rdpsndPlugin* rdpsnd) +{ + UINT16 index; + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = NULL; + + if (!rdpsnd->NumberOfServerFormats) + return; + + rdpsnd->ClientFormats = audio_formats_new(rdpsnd->NumberOfServerFormats); + + if (!rdpsnd->ClientFormats) + return; + + for (index = 0; index < rdpsnd->NumberOfServerFormats; index++) + { + const AUDIO_FORMAT* serverFormat = &rdpsnd->ServerFormats[index]; + + if (!audio_format_compatible(rdpsnd->fixed_format, serverFormat)) + continue; + + if (freerdp_dsp_supports_format(serverFormat, FALSE) || + rdpsnd->device->FormatSupported(rdpsnd->device, serverFormat)) + { + AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[rdpsnd->NumberOfClientFormats++]; + audio_format_copy(serverFormat, clientFormat); + } + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_client_audio_formats(rdpsndPlugin* rdpsnd) +{ + UINT16 index; + wStream* pdu; + UINT16 length; + UINT32 dwVolume; + UINT16 wNumberOfFormats; + dwVolume = IFCALLRESULT(0, rdpsnd->device->GetVolume, rdpsnd->device); + wNumberOfFormats = rdpsnd->NumberOfClientFormats; + length = 4 + 20; + + for (index = 0; index < wNumberOfFormats; index++) + length += (18 + rdpsnd->ClientFormats[index].cbSize); + + pdu = Stream_New(NULL, length); + + if (!pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_FORMATS); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, length - 4); /* BodySize */ + Stream_Write_UINT32(pdu, TSSNDCAPS_ALIVE | TSSNDCAPS_VOLUME); /* dwFlags */ + Stream_Write_UINT32(pdu, dwVolume); /* dwVolume */ + Stream_Write_UINT32(pdu, 0); /* dwPitch */ + Stream_Write_UINT16(pdu, 0); /* wDGramPort */ + Stream_Write_UINT16(pdu, wNumberOfFormats); /* wNumberOfFormats */ + Stream_Write_UINT8(pdu, 0); /* cLastBlockConfirmed */ + Stream_Write_UINT16(pdu, CHANNEL_VERSION_WIN_MAX); /* wVersion */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + + for (index = 0; index < wNumberOfFormats; index++) + { + const AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[index]; + + if (!audio_format_write(pdu, clientFormat)) + { + Stream_Free(pdu, TRUE); + return ERROR_INTERNAL_ERROR; + } + } + + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Client Audio Formats"); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_server_audio_formats_pdu(rdpsndPlugin* rdpsnd, + wStream* s) +{ + UINT16 index; + UINT16 wVersion; + UINT16 wNumberOfFormats; + UINT ret = ERROR_BAD_LENGTH; + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = NULL; + + if (Stream_GetRemainingLength(s) < 30) + return ERROR_BAD_LENGTH; + + /* http://msdn.microsoft.com/en-us/library/cc240956.aspx */ + Stream_Seek_UINT32(s); /* dwFlags */ + Stream_Seek_UINT32(s); /* dwVolume */ + Stream_Seek_UINT32(s); /* dwPitch */ + Stream_Seek_UINT16(s); /* wDGramPort */ + Stream_Read_UINT16(s, wNumberOfFormats); + Stream_Read_UINT8(s, rdpsnd->cBlockNo); /* cLastBlockConfirmed */ + Stream_Read_UINT16(s, wVersion); /* wVersion */ + Stream_Seek_UINT8(s); /* bPad */ + rdpsnd->NumberOfServerFormats = wNumberOfFormats; + + if (Stream_GetRemainingLength(s) / 14 < wNumberOfFormats) + return ERROR_BAD_LENGTH; + + rdpsnd->ServerFormats = audio_formats_new(wNumberOfFormats); + + if (!rdpsnd->ServerFormats) + return CHANNEL_RC_NO_MEMORY; + + for (index = 0; index < wNumberOfFormats; index++) + { + AUDIO_FORMAT* format = &rdpsnd->ServerFormats[index]; + + if (!audio_format_read(s, format)) + goto out_fail; + } + + rdpsnd_select_supported_audio_formats(rdpsnd); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Server Audio Formats"); + ret = rdpsnd_send_client_audio_formats(rdpsnd); + + if (ret == CHANNEL_RC_OK) + { + if (wVersion >= CHANNEL_VERSION_WIN_7) + ret = rdpsnd_send_quality_mode_pdu(rdpsnd); + } + + return ret; +out_fail: + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->ServerFormats = NULL; + rdpsnd->NumberOfServerFormats = 0; + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_training_confirm_pdu(rdpsndPlugin* rdpsnd, + UINT16 wTimeStamp, UINT16 wPackSize) +{ + wStream* pdu; + pdu = Stream_New(NULL, 8); + + if (!pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_TRAINING); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, 4); /* BodySize */ + Stream_Write_UINT16(pdu, wTimeStamp); + Stream_Write_UINT16(pdu, wPackSize); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "Training Response: wTimeStamp: %"PRIu16" wPackSize: %"PRIu16"", + wTimeStamp, wPackSize); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_training_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + UINT16 wTimeStamp; + UINT16 wPackSize; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT16(s, wTimeStamp); + Stream_Read_UINT16(s, wPackSize); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "Training Request: wTimeStamp: %"PRIu16" wPackSize: %"PRIu16"", + wTimeStamp, wPackSize); + return rdpsnd_send_training_confirm_pdu(rdpsnd, wTimeStamp, wPackSize); +} + +static BOOL rdpsnd_ensure_device_is_open(rdpsndPlugin* rdpsnd, UINT32 wFormatNo, + const AUDIO_FORMAT* format) +{ + if (!rdpsnd) + return FALSE; + + if (!rdpsnd->isOpen || (wFormatNo != rdpsnd->wCurrentFormatNo)) + { + BOOL rc; + BOOL supported; + AUDIO_FORMAT deviceFormat = *format; + rdpsnd_recv_close_pdu(rdpsnd); + supported = IFCALLRESULT(FALSE, rdpsnd->device->FormatSupported, rdpsnd->device, format); + + if (!supported) + { + deviceFormat.wFormatTag = WAVE_FORMAT_PCM; + deviceFormat.wBitsPerSample = 16; + deviceFormat.cbSize = 0; + } + + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Opening device with format %s [backend %s]", + audio_format_get_tag_string(format->wFormatTag), + audio_format_get_tag_string(deviceFormat.wFormatTag)); + rc = IFCALLRESULT(FALSE, rdpsnd->device->Open, rdpsnd->device, &deviceFormat, rdpsnd->latency); + + if (!rc) + return FALSE; + + if (!supported) + { + if (!freerdp_dsp_context_reset(rdpsnd->dsp_context, format)) + return FALSE; + } + + rdpsnd->isOpen = TRUE; + rdpsnd->wCurrentFormatNo = wFormatNo; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_wave_info_pdu(rdpsndPlugin* rdpsnd, wStream* s, + UINT16 BodySize) +{ + UINT16 wFormatNo; + const AUDIO_FORMAT* format; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_BAD_LENGTH; + + rdpsnd->wArrivalTime = GetTickCount(); + Stream_Read_UINT16(s, rdpsnd->wTimeStamp); + Stream_Read_UINT16(s, wFormatNo); + + if (wFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, rdpsnd->cBlockNo); + Stream_Seek(s, 3); /* bPad */ + Stream_Read(s, rdpsnd->waveData, 4); + rdpsnd->waveDataSize = BodySize - 8; + format = &rdpsnd->ClientFormats[wFormatNo]; + WLog_Print(rdpsnd->log, WLOG_DEBUG, "WaveInfo: cBlockNo: %"PRIu8" wFormatNo: %"PRIu16" [%s]", + rdpsnd->cBlockNo, wFormatNo, audio_format_get_tag_string(format->wFormatTag)); + + if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format)) + return ERROR_INTERNAL_ERROR; + + rdpsnd->expectingWave = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, + UINT16 wTimeStamp, BYTE cConfirmedBlockNo) +{ + wStream* pdu; + pdu = Stream_New(NULL, 8); + + if (!pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_WAVECONFIRM); + Stream_Write_UINT8(pdu, 0); + Stream_Write_UINT16(pdu, 4); + Stream_Write_UINT16(pdu, wTimeStamp); + Stream_Write_UINT8(pdu, cConfirmedBlockNo); /* cConfirmedBlockNo */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +static UINT rdpsnd_treat_wave(rdpsndPlugin* rdpsnd, wStream* s, size_t size) +{ + BYTE* data; + AUDIO_FORMAT* format; + DWORD end; + DWORD diffMS; + UINT latency = 0; + + if (Stream_GetRemainingLength(s) < size) + return ERROR_BAD_LENGTH; + + data = Stream_Pointer(s); + format = &rdpsnd->ClientFormats[rdpsnd->wCurrentFormatNo]; + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Wave: cBlockNo: %"PRIu8" wTimeStamp: %"PRIu16", size: %"PRIdz, + rdpsnd->cBlockNo, rdpsnd->wTimeStamp, size); + + if (rdpsnd->device && rdpsnd->attached) + { + UINT status = CHANNEL_RC_OK; + wStream* pcmData = StreamPool_Take(rdpsnd->pool, 4096); + + if (rdpsnd->device->FormatSupported(rdpsnd->device, format)) + latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device, data, size); + else if (freerdp_dsp_decode(rdpsnd->dsp_context, format, data, size, pcmData)) + { + Stream_SealLength(pcmData); + latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device, Stream_Buffer(pcmData), + Stream_Length(pcmData)); + } + else + status = ERROR_INTERNAL_ERROR; + + StreamPool_Return(rdpsnd->pool, pcmData); + + if (status != CHANNEL_RC_OK) + return status; + } + + end = GetTickCount(); + diffMS = end - rdpsnd->wArrivalTime + latency; + return rdpsnd_send_wave_confirm_pdu(rdpsnd, rdpsnd->wTimeStamp + diffMS, rdpsnd->cBlockNo); +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_wave_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + rdpsnd->expectingWave = FALSE; + + /** + * The Wave PDU is a special case: it is always sent after a Wave Info PDU, + * and we do not process its header. Instead, the header is pad that needs + * to be filled with the first four bytes of the audio sample data sent as + * part of the preceding Wave Info PDU. + */ + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + CopyMemory(Stream_Buffer(s), rdpsnd->waveData, 4); + return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize); +} + +static UINT rdpsnd_recv_wave2_pdu(rdpsndPlugin* rdpsnd, wStream* s, UINT16 BodySize) +{ + UINT16 wFormatNo; + AUDIO_FORMAT* format; + UINT32 dwAudioTimeStamp; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT16(s, rdpsnd->wTimeStamp); + Stream_Read_UINT16(s, wFormatNo); + Stream_Read_UINT8(s, rdpsnd->cBlockNo); + Stream_Seek(s, 3); /* bPad */ + Stream_Read_UINT32(s, dwAudioTimeStamp); + rdpsnd->waveDataSize = BodySize - 12; + format = &rdpsnd->ClientFormats[wFormatNo]; + rdpsnd->wArrivalTime = GetTickCount(); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Wave2PDU: cBlockNo: %"PRIu8" wFormatNo: %"PRIu16", align=%hu", + rdpsnd->cBlockNo, wFormatNo, format->nBlockAlign); + + if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format)) + return ERROR_INTERNAL_ERROR; + + return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize); +} + +static void rdpsnd_recv_close_pdu(rdpsndPlugin* rdpsnd) +{ + if (rdpsnd->isOpen) + { + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Closing device"); + IFCALL(rdpsnd->device->Close, rdpsnd->device); + rdpsnd->isOpen = FALSE; + } + else + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Device already closed"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_volume_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + BOOL rc; + UINT32 dwVolume; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT32(s, dwVolume); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "Volume: 0x%08"PRIX32"", dwVolume); + rc = IFCALLRESULT(FALSE, rdpsnd->device->SetVolume, rdpsnd->device, dwVolume); + + if (!rc) + { + WLog_ERR(TAG, "error setting volume"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + BYTE msgType; + UINT16 BodySize; + UINT status = CHANNEL_RC_OK; + + if (rdpsnd->expectingWave) + { + status = rdpsnd_recv_wave_pdu(rdpsnd, s); + goto out; + } + + if (Stream_GetRemainingLength(s) < 4) + { + status = ERROR_BAD_LENGTH; + goto out; + } + + Stream_Read_UINT8(s, msgType); /* msgType */ + Stream_Seek_UINT8(s); /* bPad */ + Stream_Read_UINT16(s, BodySize); + + switch (msgType) + { + case SNDC_FORMATS: + status = rdpsnd_recv_server_audio_formats_pdu(rdpsnd, s); + break; + + case SNDC_TRAINING: + status = rdpsnd_recv_training_pdu(rdpsnd, s); + break; + + case SNDC_WAVE: + status = rdpsnd_recv_wave_info_pdu(rdpsnd, s, BodySize); + break; + + case SNDC_CLOSE: + rdpsnd_recv_close_pdu(rdpsnd); + break; + + case SNDC_SETVOLUME: + status = rdpsnd_recv_volume_pdu(rdpsnd, s); + break; + + case SNDC_WAVE2: + status = rdpsnd_recv_wave2_pdu(rdpsnd, s, BodySize); + break; + + default: + WLog_ERR(TAG, "unknown msgType %"PRIu8"", msgType); + break; + } + +out: + StreamPool_Return(rdpsnd->pool, s); + return status; +} + +static void rdpsnd_register_device_plugin(rdpsndPlugin* rdpsnd, + rdpsndDevicePlugin* device) +{ + if (rdpsnd->device) + { + WLog_ERR(TAG, "existing device, abort."); + return; + } + + rdpsnd->device = device; + device->rdpsnd = rdpsnd; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_load_device_plugin(rdpsndPlugin* rdpsnd, const char* name, + ADDIN_ARGV* args) +{ + PFREERDP_RDPSND_DEVICE_ENTRY entry; + FREERDP_RDPSND_DEVICE_ENTRY_POINTS entryPoints; + UINT error; + entry = (PFREERDP_RDPSND_DEVICE_ENTRY) + freerdp_load_channel_addin_entry("rdpsnd", (LPSTR) name, NULL, 0); + + if (!entry) + return ERROR_INTERNAL_ERROR; + + entryPoints.rdpsnd = rdpsnd; + entryPoints.pRegisterRdpsndDevice = rdpsnd_register_device_plugin; + entryPoints.args = args; + + if ((error = entry(&entryPoints))) + WLog_ERR(TAG, "%s entry returns error %"PRIu32"", name, error); + + WLog_INFO(TAG, "Loaded %s backend for rdpsnd", name); + return error; +} + +static BOOL rdpsnd_set_subsystem(rdpsndPlugin* rdpsnd, const char* subsystem) +{ + free(rdpsnd->subsystem); + rdpsnd->subsystem = _strdup(subsystem); + return (rdpsnd->subsystem != NULL); +} + +BOOL rdpsnd_set_device_name(rdpsndPlugin* rdpsnd, const char* device_name) +{ + free(rdpsnd->device_name); + rdpsnd->device_name = _strdup(device_name); + return (rdpsnd->device_name != NULL); +} + +static COMMAND_LINE_ARGUMENT_A rdpsnd_args[] = +{ + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device" }, + { "format", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "format" }, + { "rate", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "rate" }, + { "channel", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "channel" }, + { "latency", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "latency" }, + { "quality", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "quality mode" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_process_addin_args(rdpsndPlugin* rdpsnd, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsnd->wQualityMode = HIGH_QUALITY; /* default quality mode */ + + if (args->argc > 1) + { + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, + rdpsnd_args, flags, rdpsnd, NULL, NULL); + + if (status < 0) + return CHANNEL_RC_INITIALIZATION_ERROR; + + arg = rdpsnd_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "sys") + { + if (!rdpsnd_set_subsystem(rdpsnd, arg->Value)) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchCase(arg, "dev") + { + if (!rdpsnd_set_device_name(rdpsnd, arg->Value)) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchCase(arg, "format") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->wFormatTag = val; + } + CommandLineSwitchCase(arg, "rate") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->nSamplesPerSec = val; + } + CommandLineSwitchCase(arg, "channel") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->nChannels = val; + } + CommandLineSwitchCase(arg, "latency") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > INT32_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->latency = val; + } + CommandLineSwitchCase(arg, "quality") + { + long wQualityMode = DYNAMIC_QUALITY; + + if (_stricmp(arg->Value, "dynamic") == 0) + wQualityMode = DYNAMIC_QUALITY; + else if (_stricmp(arg->Value, "medium") == 0) + wQualityMode = MEDIUM_QUALITY; + else if (_stricmp(arg->Value, "high") == 0) + wQualityMode = HIGH_QUALITY; + else + { + wQualityMode = strtol(arg->Value, NULL, 0); + + if (errno != 0) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if ((wQualityMode < 0) || (wQualityMode > 2)) + wQualityMode = DYNAMIC_QUALITY; + + rdpsnd->wQualityMode = (UINT16) wQualityMode; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_process_connect(rdpsndPlugin* rdpsnd) +{ + const struct + { + const char* subsystem; + const char* device; + } + backends[] = + { +#if defined(WITH_IOSAUDIO) + {"ios", ""}, +#endif +#if defined(WITH_OPENSLES) + {"opensles", ""}, +#endif +#if defined(WITH_PULSE) + {"pulse", ""}, +#endif +#if defined(WITH_ALSA) + {"alsa", "default"}, +#endif +#if defined(WITH_OSS) + {"oss", ""}, +#endif +#if defined(WITH_MACAUDIO) + {"mac", "default"}, +#endif +#if defined(WITH_WINMM) + { "winmm", ""}, +#endif + { "fake", "" } + }; + ADDIN_ARGV* args; + UINT status = ERROR_INTERNAL_ERROR; + rdpsnd->latency = 0; + args = (ADDIN_ARGV*) rdpsnd->channelEntryPoints.pExtendedData; + + if (args) + { + status = rdpsnd_process_addin_args(rdpsnd, args); + + if (status != CHANNEL_RC_OK) + return status; + } + + if (rdpsnd->subsystem) + { + if ((status = rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args))) + { + WLog_ERR(TAG, "unable to load the %s subsystem plugin because of error %"PRIu32"", + rdpsnd->subsystem, status); + return status; + } + } + else + { + size_t x; + + for (x = 0; x < ARRAYSIZE(backends); x++) + { + const char* subsystem_name = backends[x].subsystem; + const char* device_name = backends[x].device; + + if ((status = rdpsnd_load_device_plugin(rdpsnd, subsystem_name, args))) + WLog_ERR(TAG, "unable to load the %s subsystem plugin because of error %"PRIu32"", + subsystem_name, status); + + if (!rdpsnd->device) + continue; + + if (!rdpsnd_set_subsystem(rdpsnd, subsystem_name) || + !rdpsnd_set_device_name(rdpsnd, device_name)) + return CHANNEL_RC_NO_MEMORY; + + break; + } + + if (!rdpsnd->device || status) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return CHANNEL_RC_OK; +} + +static void rdpsnd_process_disconnect(rdpsndPlugin* rdpsnd) +{ + rdpsnd_recv_close_pdu(rdpsnd); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s) +{ + UINT status = CHANNEL_RC_BAD_INIT_HANDLE; + + if (rdpsnd) + { + status = rdpsnd->channelEntryPoints.pVirtualChannelWriteEx(rdpsnd->InitHandle, rdpsnd->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_GetPosition(s), s); + } + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_data_received(rdpsndPlugin* plugin, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + return CHANNEL_RC_OK; + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (!plugin->data_in) + plugin->data_in = StreamPool_Take(plugin->pool, totalLength); + + Stream_SetPosition(plugin->data_in, 0); + } + + if (!Stream_EnsureRemainingCapacity(plugin->data_in, dataLength)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write(plugin->data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + Stream_SealLength(plugin->data_in); + Stream_SetPosition(plugin->data_in, 0); + + if (!Queue_Enqueue(plugin->queue, plugin->data_in)) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return ERROR_INTERNAL_ERROR; + } + + plugin->data_in = NULL; + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpsnd_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*) lpUserParam; + + if (!rdpsnd || (rdpsnd->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = rdpsnd_virtual_channel_event_data_received(rdpsnd, pData, + dataLength, totalLength, dataFlags))) + WLog_ERR(TAG, + "rdpsnd_virtual_channel_event_data_received failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rdpsnd->rdpcontext) + setChannelError(rdpsnd->rdpcontext, error, + "rdpsnd_virtual_channel_open_event_ex reported an error"); +} + +static DWORD WINAPI rdpsnd_virtual_channel_client_thread(LPVOID arg) +{ + BOOL running = TRUE; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*) arg; + DWORD error = CHANNEL_RC_OK; + HANDLE events[2]; + + if ((error = rdpsnd_process_connect(rdpsnd))) + { + WLog_ERR(TAG, "error connecting sound channel"); + goto out; + } + + events[1] = rdpsnd->stopEvent; + events[0] = Queue_Event(rdpsnd->queue); + + do + { + const DWORD status = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, INFINITE); + + switch (status) + { + case WAIT_OBJECT_0: + { + wStream* s = Queue_Dequeue(rdpsnd->queue); + error = rdpsnd_recv_pdu(rdpsnd, s); + } + break; + + case WAIT_OBJECT_0 + 1: + running = FALSE; + break; + + default: + error = status; + break; + } + } + while ((error == CHANNEL_RC_OK) && running); + +out: + + if (error && rdpsnd->rdpcontext) + setChannelError(rdpsnd->rdpcontext, error, + "rdpsnd_virtual_channel_client_thread reported an error"); + + rdpsnd_process_disconnect(rdpsnd); + ExitThread((DWORD)error); + return error; +} + +/* Called during cleanup. + * All streams still in the queue have been removed + * from the streampool and nead cleanup. */ +static void rdpsnd_queue_free(void* data) +{ + wStream* s = (wStream*)data; + Stream_Free(s, TRUE); +} + +static UINT rdpsnd_virtual_channel_event_initialized(rdpsndPlugin* rdpsnd, + LPVOID pData, UINT32 dataLength) +{ + rdpsnd->stopEvent = CreateEventA(NULL, TRUE, FALSE, "rdpsnd->stopEvent"); + + if (!rdpsnd->stopEvent) + goto fail; + + return CHANNEL_RC_OK; +fail: + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_connected(rdpsndPlugin* rdpsnd, + LPVOID pData, UINT32 dataLength) +{ + UINT32 status; + status = rdpsnd->channelEntryPoints.pVirtualChannelOpenEx(rdpsnd->InitHandle, + &rdpsnd->OpenHandle, rdpsnd->channelDef.name, + rdpsnd_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpenEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + rdpsnd->dsp_context = freerdp_dsp_context_new(FALSE); + + if (!rdpsnd->dsp_context) + goto fail; + + rdpsnd->queue = Queue_New(TRUE, 32, 2); + + if (!rdpsnd->queue) + goto fail; + + rdpsnd->queue->object.fnObjectFree = rdpsnd_queue_free; + rdpsnd->pool = StreamPool_New(TRUE, 4096); + + if (!rdpsnd->pool) + goto fail; + + ResetEvent(rdpsnd->stopEvent); + rdpsnd->thread = CreateThread(NULL, 0, + rdpsnd_virtual_channel_client_thread, (void*) rdpsnd, + 0, NULL); + + if (!rdpsnd->thread) + goto fail; + + return CHANNEL_RC_OK; +fail: + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Free(rdpsnd->pool); + Queue_Free(rdpsnd->queue); + + if (rdpsnd->stopEvent) + CloseHandle(rdpsnd->stopEvent); + + if (rdpsnd->thread) + CloseHandle(rdpsnd->thread); + + return CHANNEL_RC_NO_MEMORY; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_disconnected(rdpsndPlugin* rdpsnd) +{ + UINT error; + + if (rdpsnd->OpenHandle == 0) + return CHANNEL_RC_OK; + + SetEvent(rdpsnd->stopEvent); + + if (WaitForSingleObject(rdpsnd->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(rdpsnd->thread); + error = rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, rdpsnd->OpenHandle); + + if (CHANNEL_RC_OK != error) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08"PRIX32"]", + WTSErrorToString(error), error); + return error; + } + + rdpsnd->OpenHandle = 0; + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Return(rdpsnd->pool, rdpsnd->data_in); + StreamPool_Free(rdpsnd->pool); + Queue_Free(rdpsnd->queue); + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = NULL; + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = NULL; + + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = NULL; + } + + return CHANNEL_RC_OK; +} + +static void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd) +{ + if (rdpsnd) + { + audio_formats_free(rdpsnd->fixed_format, 1); + free(rdpsnd->subsystem); + free(rdpsnd->device_name); + CloseHandle(rdpsnd->stopEvent); + rdpsnd->InitHandle = 0; + } + + free(rdpsnd); +} + +static VOID VCAPITYPE rdpsnd_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* plugin = (rdpsndPlugin*) lpUserParam; + + if (!plugin || (plugin->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + if ((error = rdpsnd_virtual_channel_event_initialized(plugin, pData, dataLength))) + WLog_ERR(TAG, "rdpsnd_virtual_channel_event_initialized failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_CONNECTED: + if ((error = rdpsnd_virtual_channel_event_connected(plugin, pData, dataLength))) + WLog_ERR(TAG, "rdpsnd_virtual_channel_event_connected failed with error %"PRIu32"!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rdpsnd_virtual_channel_event_disconnected(plugin))) + WLog_ERR(TAG, + "rdpsnd_virtual_channel_event_disconnected failed with error %"PRIu32"!", error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rdpsnd_virtual_channel_event_terminated(plugin); + break; + + case CHANNEL_EVENT_ATTACHED: + plugin->attached = TRUE; + break; + + case CHANNEL_EVENT_DETACHED: + plugin->attached = FALSE; + break; + + default: + break; + } + + if (error && plugin && plugin->rdpcontext) + setChannelError(plugin->rdpcontext, error, + "rdpsnd_virtual_channel_init_event reported an error"); +} + +/* rdpsnd is always built-in */ +#define VirtualChannelEntryEx rdpsnd_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + rdpsndPlugin* rdpsnd; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + + if (!pEntryPoints) + return FALSE; + + rdpsnd = (rdpsndPlugin*) calloc(1, sizeof(rdpsndPlugin)); + + if (!rdpsnd) + return FALSE; + + rdpsnd->attached = TRUE; + rdpsnd->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP; + sprintf_s(rdpsnd->channelDef.name, ARRAYSIZE(rdpsnd->channelDef.name), "rdpsnd"); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + rdpsnd->rdpcontext = pEntryPointsEx->context; + } + + rdpsnd->fixed_format = audio_format_new(); + + if (!rdpsnd->fixed_format) + { + free(rdpsnd); + return FALSE; + } + + rdpsnd->log = WLog_Get("com.freerdp.channels.rdpsnd.client"); + CopyMemory(&(rdpsnd->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rdpsnd->InitHandle = pInitHandle; + rc = rdpsnd->channelEntryPoints.pVirtualChannelInitEx(rdpsnd, NULL, pInitHandle, + &rdpsnd->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rdpsnd_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelInitEx failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + free(rdpsnd); + return FALSE; + } + + return TRUE; +} diff --git a/channels/rdpsnd/client/rdpsnd_main.h b/channels/rdpsnd/client/rdpsnd_main.h new file mode 100644 index 0000000..aee8658 --- /dev/null +++ b/channels/rdpsnd/client/rdpsnd_main.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012-2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpsnd.client") + +#if defined(WITH_DEBUG_SND) +#define DEBUG_SND(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_SND(...) do { } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H */ diff --git a/channels/rdpsnd/client/winmm/CMakeLists.txt b/channels/rdpsnd/client/winmm/CMakeLists.txt new file mode 100644 index 0000000..b4a337d --- /dev/null +++ b/channels/rdpsnd/client/winmm/CMakeLists.txt @@ -0,0 +1,39 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "winmm" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_winmm.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winmm.lib) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client/WinMM") diff --git a/channels/rdpsnd/client/winmm/rdpsnd_winmm.c b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c new file mode 100644 index 0000000..c61e02c --- /dev/null +++ b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c @@ -0,0 +1,321 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2012 Jay Sorg + * Copyright 2010-2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_winmm_plugin rdpsndWinmmPlugin; + +struct rdpsnd_winmm_plugin +{ + rdpsndDevicePlugin device; + + HWAVEOUT hWaveOut; + WAVEFORMATEX format; + UINT32 volume; + HANDLE next; +}; + +static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out) +{ + if (!in || !out) + return FALSE; + + ZeroMemory(out, sizeof(WAVEFORMATEX)); + out->wFormatTag = WAVE_FORMAT_PCM; + out->nChannels = in->nChannels; + out->nSamplesPerSec = in->nSamplesPerSec; + + switch (in->wFormatTag) + { + case WAVE_FORMAT_PCM: + out->wBitsPerSample = in->wBitsPerSample; + break; + + default: + return FALSE; + } + + out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8; + out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign; + return TRUE; +} + +static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (!rdpsnd_winmm_convert_format(format, &winmm->format)) + return FALSE; + + return TRUE; +} + +static void CALLBACK rdpsnd_winmm_callback_function(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + LPWAVEHDR lpWaveHdr; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) dwInstance; + + switch (uMsg) + { + case MM_WOM_OPEN: + WLog_DBG(TAG, "MM_WOM_OPEN"); + break; + + case MM_WOM_CLOSE: + WLog_DBG(TAG, "MM_WOM_CLOSE"); + SetEvent(winmm->next); + break; + + case MM_WOM_DONE: + WLog_DBG(TAG, "MM_WOM_DONE"); + lpWaveHdr = (LPWAVEHDR) dwParam1; + free(lpWaveHdr); + SetEvent(winmm->next); + break; + + default: + WLog_DBG(TAG, "UNKNOWN [0x%08"PRIx32"]", uMsg); + break; + } +} + +static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm->hWaveOut) + return TRUE; + + if (!rdpsnd_winmm_set_format(device, format, latency)) + return FALSE; + + mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format, + (DWORD_PTR) rdpsnd_winmm_callback_function, (DWORD_PTR) winmm, CALLBACK_FUNCTION); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_ERR(TAG, "waveOutOpen failed: %"PRIu32"", mmResult); + return FALSE; + } + + mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_ERR(TAG, "waveOutSetVolume failed: %"PRIu32"", mmResult); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_winmm_close(rdpsndDevicePlugin* device) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm->hWaveOut) + { + mmResult = waveOutReset(winmm->hWaveOut); + mmResult = waveOutClose(winmm->hWaveOut); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_ERR(TAG, "waveOutClose failure: %"PRIu32"", mmResult); + } + + winmm->hWaveOut = NULL; + } +} + +static void rdpsnd_winmm_free(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (winmm) + { + rdpsnd_winmm_close(device); + CloseHandle(winmm->next); + free(winmm); + } +} + +static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + MMRESULT result; + WAVEFORMATEX out; + + if (rdpsnd_winmm_convert_format(format, &out)) + { + result = waveOutOpen(NULL, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY); + + if (result == MMSYSERR_NOERROR) + return TRUE; + } + + return FALSE; +} + +static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device) +{ + DWORD dwVolume; + UINT16 dwVolumeLeft; + UINT16 dwVolumeRight; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + dwVolume = (dwVolumeLeft << 16) | dwVolumeRight; + + if (!winmm->hWaveOut) + return dwVolume; + + waveOutGetVolume(winmm->hWaveOut, &dwVolume); + return dwVolume; +} + +static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + winmm->volume = value; + + if (!winmm->hWaveOut) + return TRUE; + + return (waveOutSetVolume(winmm->hWaveOut, value) == MMSYSERR_NOERROR); +} + +static void rdpsnd_winmm_start(rdpsndDevicePlugin* device) +{ + //rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; +} + +static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + MMRESULT mmResult; + LPWAVEHDR lpWaveHdr; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*) device; + + if (!winmm->hWaveOut) + return 0; + + lpWaveHdr = (LPWAVEHDR) malloc(sizeof(WAVEHDR)); + + if (!lpWaveHdr) + return 0; + + ZeroMemory(lpWaveHdr, sizeof(WAVEHDR)); + lpWaveHdr->dwFlags = 0; + lpWaveHdr->dwLoops = 0; + lpWaveHdr->lpData = (LPSTR) data; + lpWaveHdr->dwBufferLength = size; + lpWaveHdr->dwUser = NULL; + lpWaveHdr->lpNext = NULL; + mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_ERR(TAG, "waveOutPrepareHeader failure: %"PRIu32"", mmResult); + return 0; + } + + mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_ERR(TAG, "waveOutWrite failure: %"PRIu32"", mmResult); + waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + free(lpWaveHdr); + return 0; + } + + WaitForSingleObject(winmm->next, INFINITE); + return 10; /* TODO: Get real latencry in [ms] */ +} + +static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry winmm_freerdp_rdpsnd_client_subsystem_entry +#else +#define freerdp_rdpsnd_client_subsystem_entry FREERDP_API freerdp_rdpsnd_client_subsystem_entry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT freerdp_rdpsnd_client_subsystem_entry(PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints) +{ + ADDIN_ARGV* args; + rdpsndWinmmPlugin* winmm; + winmm = (rdpsndWinmmPlugin*) calloc(1, sizeof(rdpsndWinmmPlugin)); + + if (!winmm) + return CHANNEL_RC_NO_MEMORY; + + winmm->device.Open = rdpsnd_winmm_open; + winmm->device.FormatSupported = rdpsnd_winmm_format_supported; + winmm->device.GetVolume = rdpsnd_winmm_get_volume; + winmm->device.SetVolume = rdpsnd_winmm_set_volume; + winmm->device.Start = rdpsnd_winmm_start; + winmm->device.Play = rdpsnd_winmm_play; + winmm->device.Close = rdpsnd_winmm_close; + winmm->device.Free = rdpsnd_winmm_free; + winmm->next = CreateEventA(NULL, FALSE, FALSE, "winmm-play-event"); + + if (!winmm->next) + { + free(winmm); + return CHANNEL_RC_NO_MEMORY; + } + + args = pEntryPoints->args; + rdpsnd_winmm_parse_addin_args((rdpsndDevicePlugin*) winmm, args); + winmm->volume = 0xFFFFFFFF; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*) winmm); + return CHANNEL_RC_OK; +} diff --git a/channels/rdpsnd/common/CMakeLists.txt b/channels/rdpsnd/common/CMakeLists.txt new file mode 100644 index 0000000..32fa918 --- /dev/null +++ b/channels/rdpsnd/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2018 Armin Novak +# Copyright 2018 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(SRCS + rdpsnd_common.h + rdpsnd_common.c) + +# Library currently header only +#add_library(rdpsnd-common STATIC ${SRCS}) diff --git a/channels/rdpsnd/common/rdpsnd_common.h b/channels/rdpsnd/common/rdpsnd_common.h new file mode 100644 index 0000000..6afcbc7 --- /dev/null +++ b/channels/rdpsnd/common/rdpsnd_common.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H +#define FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H + +#include +#include +#include + +#include +#include +#include +#include + +typedef enum +{ + CHANNEL_VERSION_WIN_XP = 0x02, + CHANNEL_VERSION_WIN_XP_SP1 = 0x05, + CHANNEL_VERSION_WIN_VISTA = 0x05, + CHANNEL_VERSION_WIN_7 = 0x06, + CHANNEL_VERSION_WIN_8 = 0x08, + CHANNEL_VERSION_WIN_MAX = CHANNEL_VERSION_WIN_8 +} RdpSndChannelVersion; + +#endif /* FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H */ diff --git a/channels/rdpsnd/server/CMakeLists.txt b/channels/rdpsnd/server/CMakeLists.txt new file mode 100644 index 0000000..9df47f2 --- /dev/null +++ b/channels/rdpsnd/server/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("rdpsnd") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_main.c + rdpsnd_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/rdpsnd/server/rdpsnd_main.c b/channels/rdpsnd/server/rdpsnd_main.c new file mode 100644 index 0000000..53d4d5c --- /dev/null +++ b/channels/rdpsnd/server/rdpsnd_main.c @@ -0,0 +1,992 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include "rdpsnd_common.h" +#include "rdpsnd_main.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_formats(RdpsndServerContext* context, wStream* s) +{ + size_t pos; + UINT16 i; + BOOL status = FALSE; + ULONG written; + Stream_Write_UINT8(s, SNDC_FORMATS); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + Stream_Write_UINT32(s, 0); /* dwFlags */ + Stream_Write_UINT32(s, 0); /* dwVolume */ + Stream_Write_UINT32(s, 0); /* dwPitch */ + Stream_Write_UINT16(s, 0); /* wDGramPort */ + Stream_Write_UINT16(s, context->num_server_formats); /* wNumberOfFormats */ + Stream_Write_UINT8(s, context->block_no); /* cLastBlockConfirmed */ + Stream_Write_UINT16(s, CHANNEL_VERSION_WIN_MAX); /* wVersion */ + Stream_Write_UINT8(s, 0); /* bPad */ + + for (i = 0; i < context->num_server_formats; i++) + { + AUDIO_FORMAT format = context->server_formats[i]; + // TODO: Eliminate this!!! + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nChannels * format.wBitsPerSample / 8; + + if (!audio_format_write(s, &format)) + goto fail; + } + + pos = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, pos - 4); + Stream_SetPosition(s, pos); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_GetPosition(s), &written); + Stream_SetPosition(s, 0); +fail: + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_waveconfirm(RdpsndServerContext* context, + wStream* s) +{ + UINT16 timestamp; + BYTE confirmBlockNum; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, timestamp); + Stream_Read_UINT8(s, confirmBlockNum); + Stream_Seek_UINT8(s); + IFCALLRET(context->ConfirmBlock, error, context, confirmBlockNum, timestamp); + + if (error) + WLog_ERR(TAG, "context->ConfirmBlock failed with error %"PRIu32"", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_quality_mode(RdpsndServerContext* context, + wStream* s) +{ + UINT16 quality; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, quality); + Stream_Seek_UINT16(s); // reserved + WLog_DBG(TAG, "Client requested sound quality: 0x%04"PRIX16"", quality); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_formats(RdpsndServerContext* context, wStream* s) +{ + UINT16 i, num_known_format = 0; + UINT32 flags, vol, pitch; + UINT16 udpPort; + BYTE lastblock; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, flags); /* dwFlags */ + Stream_Read_UINT32(s, vol); /* dwVolume */ + Stream_Read_UINT32(s, pitch); /* dwPitch */ + Stream_Read_UINT16(s, udpPort); /* wDGramPort */ + Stream_Read_UINT16(s, context->num_client_formats); /* wNumberOfFormats */ + Stream_Read_UINT8(s, lastblock); /* cLastBlockConfirmed */ + Stream_Read_UINT16(s, context->clientVersion); /* wVersion */ + Stream_Seek_UINT8(s); /* bPad */ + + /* this check is only a guess as cbSize can influence the size of a format record */ + if (Stream_GetRemainingLength(s) < context->num_client_formats * 18) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + if (!context->num_client_formats) + { + WLog_ERR(TAG, "client doesn't support any format!"); + return ERROR_INTERNAL_ERROR; + } + + context->client_formats = audio_formats_new(context->num_client_formats); + + if (!context->client_formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (i = 0; i < context->num_client_formats; i++) + { + if (Stream_GetRemainingLength(s) < 18) + { + WLog_ERR(TAG, "not enough data in stream!"); + error = ERROR_INVALID_DATA; + goto out_free; + } + + Stream_Read_UINT16(s, context->client_formats[i].wFormatTag); + Stream_Read_UINT16(s, context->client_formats[i].nChannels); + Stream_Read_UINT32(s, context->client_formats[i].nSamplesPerSec); + Stream_Read_UINT32(s, context->client_formats[i].nAvgBytesPerSec); + Stream_Read_UINT16(s, context->client_formats[i].nBlockAlign); + Stream_Read_UINT16(s, context->client_formats[i].wBitsPerSample); + Stream_Read_UINT16(s, context->client_formats[i].cbSize); + + if (context->client_formats[i].cbSize > 0) + { + if (!Stream_SafeSeek(s, context->client_formats[i].cbSize)) + { + WLog_ERR(TAG, "Stream_SafeSeek failed!"); + error = ERROR_INTERNAL_ERROR; + goto out_free; + } + } + + if (context->client_formats[i].wFormatTag != 0) + { + //lets call this a known format + //TODO: actually look through our own list of known formats + num_known_format++; + } + } + + if (!context->num_client_formats) + { + WLog_ERR(TAG, "client doesn't support any known format!"); + goto out_free; + } + + return CHANNEL_RC_OK; +out_free: + free(context->client_formats); + return error; +} + +static DWORD WINAPI rdpsnd_server_thread(LPVOID arg) +{ + DWORD nCount, status; + HANDLE events[8]; + RdpsndServerContext* context; + UINT error = CHANNEL_RC_OK; + context = (RdpsndServerContext*)arg; + nCount = 0; + events[nCount++] = context->priv->channelEvent; + events[nCount++] = context->priv->StopEvent; + + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + if ((error = rdpsnd_server_handle_messages(context))) + { + WLog_ERR(TAG, "rdpsnd_server_handle_messages failed with error %"PRIu32"", error); + break; + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "rdpsnd_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_initialize(RdpsndServerContext* context, + BOOL ownThread) +{ + context->priv->ownThread = ownThread; + return context->Start(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_select_format(RdpsndServerContext* context, + UINT16 client_format_index) +{ + int bs; + int out_buffer_size; + AUDIO_FORMAT* format; + UINT error = CHANNEL_RC_OK; + + if ((client_format_index >= context->num_client_formats) + || (!context->src_format)) + { + WLog_ERR(TAG, "index %d is not correct.", client_format_index); + return ERROR_INVALID_DATA; + } + + EnterCriticalSection(&context->priv->lock); + context->priv->src_bytes_per_sample = context->src_format->wBitsPerSample / 8; + context->priv->src_bytes_per_frame = context->priv->src_bytes_per_sample * + context->src_format->nChannels; + context->selected_client_format = client_format_index; + format = &context->client_formats[client_format_index]; + + if (format->nSamplesPerSec == 0) + { + WLog_ERR(TAG, "invalid Client Sound Format!!"); + error = ERROR_INVALID_DATA; + goto out; + } + + if (context->latency <= 0) + context->latency = 50; + + context->priv->out_frames = context->src_format->nSamplesPerSec * + context->latency / 1000; + + if (context->priv->out_frames < 1) + context->priv->out_frames = 1; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_DVI_ADPCM: + bs = (format->nBlockAlign - 4 * format->nChannels) * 4; + context->priv->out_frames -= context->priv->out_frames % bs; + + if (context->priv->out_frames < bs) + context->priv->out_frames = bs; + + break; + + case WAVE_FORMAT_ADPCM: + bs = (format->nBlockAlign - 7 * format->nChannels) * 2 / format->nChannels + 2; + context->priv->out_frames -= context->priv->out_frames % bs; + + if (context->priv->out_frames < bs) + context->priv->out_frames = bs; + + break; + } + + context->priv->out_pending_frames = 0; + out_buffer_size = context->priv->out_frames * + context->priv->src_bytes_per_frame; + + if (context->priv->out_buffer_size < out_buffer_size) + { + BYTE* newBuffer; + newBuffer = (BYTE*)realloc(context->priv->out_buffer, out_buffer_size); + + if (!newBuffer) + { + WLog_ERR(TAG, "realloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + context->priv->out_buffer = newBuffer; + context->priv->out_buffer_size = out_buffer_size; + } + + freerdp_dsp_context_reset(context->priv->dsp_context, format); +out: + LeaveCriticalSection(&context->priv->lock); + return error; +} + +static BOOL rdpsnd_server_align_wave_pdu(wStream* s, UINT32 alignment) +{ + size_t size; + Stream_SealLength(s); + size = Stream_Length(s); + + if ((size % alignment) != 0) + { + size_t offset = alignment - size % alignment; + + if (!Stream_EnsureRemainingCapacity(s, offset)) + return FALSE; + + Stream_Zero(s, offset); + } + + Stream_SealLength(s); + return TRUE; +} + +/** + * Function description + * context->priv->lock should be obtained before calling this function + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_wave_pdu(RdpsndServerContext* context, + UINT16 wTimestamp) +{ + size_t length; + size_t start, end = 0; + const BYTE* src; + AUDIO_FORMAT* format; + ULONG written; + wStream* s = context->priv->rdpsnd_pdu; + UINT error = CHANNEL_RC_OK; + + if (context->selected_client_format >= context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + format = &context->client_formats[context->selected_client_format]; + /* WaveInfo PDU */ + Stream_SetPosition(s, 0); + Stream_Write_UINT8(s, SNDC_WAVE); /* msgType */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT16(s, 0); /* BodySize */ + Stream_Write_UINT16(s, wTimestamp); /* wTimeStamp */ + Stream_Write_UINT16(s, context->selected_client_format); /* wFormatNo */ + Stream_Write_UINT8(s, context->block_no); /* cBlockNo */ + Stream_Seek(s, 3); /* bPad */ + start = Stream_GetPosition(s); + src = context->priv->out_buffer; + length = context->priv->out_pending_frames * context->priv->src_bytes_per_frame; + + if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, src, length, s)) + return ERROR_INTERNAL_ERROR; + else + { + /* Set stream size */ + if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign)) + return ERROR_INTERNAL_ERROR; + + end = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, end - start + 8); + Stream_SetPosition(s, end); + context->block_no = (context->block_no + 1) % 256; + + if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), start + 4, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + } + } + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + Stream_SetPosition(s, start); + Stream_Write_UINT32(s, 0); /* bPad */ + Stream_SetPosition(s, start); + + if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Pointer(s), end - start, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + } + +out: + Stream_SetPosition(s, 0); + context->priv->out_pending_frames = 0; + return error; +} + +/** + * Function description + * context->priv->lock should be obtained before calling this function + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_wave2_pdu(RdpsndServerContext* context, + UINT16 wTimestamp) +{ + size_t length; + size_t end = 0; + const BYTE* src; + AUDIO_FORMAT* format; + ULONG written; + wStream* s = context->priv->rdpsnd_pdu; + UINT error = CHANNEL_RC_OK; + + if (context->selected_client_format >= context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + format = &context->client_formats[context->selected_client_format]; + /* WaveInfo PDU */ + Stream_SetPosition(s, 0); + Stream_Write_UINT8(s, SNDC_WAVE2); /* msgType */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT16(s, 0); /* BodySize */ + Stream_Write_UINT16(s, wTimestamp); /* wTimeStamp */ + Stream_Write_UINT16(s, context->selected_client_format); /* wFormatNo */ + Stream_Write_UINT8(s, context->block_no); /* cBlockNo */ + Stream_Seek(s, 3); /* bPad */ + Stream_Write_UINT32(s, wTimestamp); /* dwAudioTimeStamp */ + src = context->priv->out_buffer; + length = context->priv->out_pending_frames * context->priv->src_bytes_per_frame; + + if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, src, length, s)) + error = ERROR_INTERNAL_ERROR; + else + { + BOOL rc; + + /* Set stream size */ + if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign)) + return ERROR_INTERNAL_ERROR; + + end = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, end - 4); + context->block_no = (context->block_no + 1) % 256; + rc = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), end, &written); + + if (!rc || (end != written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed! [stream length=%"PRIdz" - written=%"PRIu32, end, + written); + error = ERROR_INTERNAL_ERROR; + } + } + + Stream_SetPosition(s, 0); + context->priv->out_pending_frames = 0; + return error; +} + +/* Wrapper function to send WAVE or WAVE2 PDU depending on client connected */ +static UINT rdpsnd_server_send_audio_pdu(RdpsndServerContext* context, + UINT16 wTimestamp) +{ + if (context->clientVersion >= CHANNEL_VERSION_WIN_8) + return rdpsnd_server_send_wave2_pdu(context, wTimestamp); + else + return rdpsnd_server_send_wave_pdu(context, wTimestamp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_samples(RdpsndServerContext* context, + const void* buf, int nframes, UINT16 wTimestamp) +{ + int cframes; + int cframesize; + UINT error = CHANNEL_RC_OK; + EnterCriticalSection(&context->priv->lock); + + if (context->selected_client_format >= context->num_client_formats) + { + /* It's possible while format negotiation has not been done */ + WLog_WARN(TAG, "Drop samples because client format has not been negotiated."); + error = ERROR_NOT_READY; + goto out; + } + + while (nframes > 0) + { + cframes = MIN(nframes, context->priv->out_frames - + context->priv->out_pending_frames); + cframesize = cframes * context->priv->src_bytes_per_frame; + CopyMemory(context->priv->out_buffer + + (context->priv->out_pending_frames * context->priv->src_bytes_per_frame), buf, + cframesize); + buf = (BYTE*) buf + cframesize; + nframes -= cframes; + context->priv->out_pending_frames += cframes; + + if (context->priv->out_pending_frames >= context->priv->out_frames) + { + if ((error = rdpsnd_server_send_audio_pdu(context, wTimestamp))) + { + WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %"PRIu32"", error); + break; + } + } + } + +out: + LeaveCriticalSection(&context->priv->lock); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_set_volume(RdpsndServerContext* context, int left, + int right) +{ + size_t pos; + BOOL status; + ULONG written; + wStream* s = context->priv->rdpsnd_pdu; + Stream_Write_UINT8(s, SNDC_SETVOLUME); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + Stream_Write_UINT16(s, left); + Stream_Write_UINT16(s, right); + pos = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, pos - 4); + Stream_SetPosition(s, pos); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_GetPosition(s), &written); + Stream_SetPosition(s, 0); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_close(RdpsndServerContext* context) +{ + size_t pos; + BOOL status; + ULONG written; + wStream* s = context->priv->rdpsnd_pdu; + UINT error = CHANNEL_RC_OK; + EnterCriticalSection(&context->priv->lock); + + if (context->priv->out_pending_frames > 0) + { + if (context->selected_client_format >= context->num_client_formats) + { + WLog_ERR(TAG, "Pending audio frame exists while no format selected."); + error = ERROR_INVALID_DATA; + } + else if ((error = rdpsnd_server_send_audio_pdu(context, 0))) + { + WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %"PRIu32"", error); + } + } + + LeaveCriticalSection(&context->priv->lock); + + if (error) + return error; + + context->selected_client_format = 0xFFFF; + Stream_Write_UINT8(s, SNDC_CLOSE); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + pos = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, pos - 4); + Stream_SetPosition(s, pos); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_GetPosition(s), &written); + Stream_SetPosition(s, 0); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_start(RdpsndServerContext* context) +{ + void* buffer = NULL; + DWORD bytesReturned; + RdpsndServerPrivate* priv = context->priv; + UINT error = ERROR_INTERNAL_ERROR; + priv->ChannelHandle = WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, + "rdpsnd"); + + if (!priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!WTSVirtualChannelQuery(priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &bytesReturned) || (bytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned size(%"PRIu32")", + bytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + goto out_close; + } + + CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + priv->rdpsnd_pdu = Stream_New(NULL, 4096); + + if (!priv->rdpsnd_pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_close; + } + + if (!InitializeCriticalSectionEx(&context->priv->lock, 0, 0)) + { + WLog_ERR(TAG, "InitializeCriticalSectionEx failed!"); + goto out_pdu; + } + + if ((error = rdpsnd_server_send_formats(context, context->priv->rdpsnd_pdu))) + { + WLog_ERR(TAG, "rdpsnd_server_send_formats failed with error %"PRIu32"", error); + goto out_lock; + } + + if (priv->ownThread) + { + context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!context->priv->StopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_lock; + } + + context->priv->Thread = CreateThread(NULL, 0, + rdpsnd_server_thread, (void*) context, 0, NULL); + + if (!context->priv->Thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_stopEvent; + } + } + + return CHANNEL_RC_OK; +out_stopEvent: + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; +out_lock: + DeleteCriticalSection(&context->priv->lock); +out_pdu: + Stream_Free(context->priv->rdpsnd_pdu, TRUE); + context->priv->rdpsnd_pdu = NULL; +out_close: + WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = NULL; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_stop(RdpsndServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + + if (context->priv->ownThread) + { + if (context->priv->StopEvent) + { + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(context->priv->Thread); + CloseHandle(context->priv->StopEvent); + } + } + + DeleteCriticalSection(&context->priv->lock); + + if (context->priv->rdpsnd_pdu) + Stream_Free(context->priv->rdpsnd_pdu, TRUE); + + return error; +} + +RdpsndServerContext* rdpsnd_server_context_new(HANDLE vcm) +{ + RdpsndServerContext* context; + RdpsndServerPrivate* priv; + context = (RdpsndServerContext*)calloc(1, sizeof(RdpsndServerContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + context->vcm = vcm; + context->Start = rdpsnd_server_start; + context->Stop = rdpsnd_server_stop; + context->selected_client_format = 0xFFFF; + context->Initialize = rdpsnd_server_initialize; + context->SelectFormat = rdpsnd_server_select_format; + context->SendSamples = rdpsnd_server_send_samples; + context->SetVolume = rdpsnd_server_set_volume; + context->Close = rdpsnd_server_close; + context->priv = priv = (RdpsndServerPrivate*)calloc(1, + sizeof(RdpsndServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto out_free; + } + + priv->dsp_context = freerdp_dsp_context_new(TRUE); + + if (!priv->dsp_context) + { + WLog_ERR(TAG, "freerdp_dsp_context_new failed!"); + goto out_free_priv; + } + + priv->input_stream = Stream_New(NULL, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto out_free_dsp; + } + + priv->expectedBytes = 4; + priv->waitingHeader = TRUE; + priv->ownThread = TRUE; + return context; +out_free_dsp: + freerdp_dsp_context_free(priv->dsp_context); +out_free_priv: + free(context->priv); +out_free: + free(context); + return NULL; +} + + +void rdpsnd_server_context_reset(RdpsndServerContext* context) +{ + context->priv->expectedBytes = 4; + context->priv->waitingHeader = TRUE; + Stream_SetPosition(context->priv->input_stream, 0); +} + +void rdpsnd_server_context_free(RdpsndServerContext* context) +{ + if (context->priv->ChannelHandle) + WTSVirtualChannelClose(context->priv->ChannelHandle); + + free(context->priv->out_buffer); + + if (context->priv->dsp_context) + freerdp_dsp_context_free(context->priv->dsp_context); + + if (context->priv->input_stream) + Stream_Free(context->priv->input_stream, TRUE); + + free(context->client_formats); + free(context->priv); + free(context); +} + +HANDLE rdpsnd_server_get_event_handle(RdpsndServerContext* context) +{ + return context->priv->channelEvent; +} + +/* + * Handle rpdsnd messages - server side + * + * @param Server side context + * + * @return 0 on success + * ERROR_NO_DATA if no data could be read this time + * otherwise error + */ +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpsnd_server_handle_messages(RdpsndServerContext* context) +{ + DWORD bytesReturned; + UINT ret = CHANNEL_RC_OK; + RdpsndServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + if (!WTSVirtualChannelRead(priv->ChannelHandle, 0, (PCHAR)Stream_Pointer(s), + priv->expectedBytes, &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + priv->expectedBytes -= bytesReturned; + Stream_Seek(s, bytesReturned); + + if (priv->expectedBytes) + return CHANNEL_RC_OK; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (priv->waitingHeader) + { + /* header case */ + Stream_Read_UINT8(s, priv->msgType); + Stream_Seek_UINT8(s); /* bPad */ + Stream_Read_UINT16(s, priv->expectedBytes); + priv->waitingHeader = FALSE; + Stream_SetPosition(s, 0); + + if (priv->expectedBytes) + { + if (!Stream_EnsureCapacity(s, priv->expectedBytes)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return CHANNEL_RC_OK; + } + } + + /* when here we have the header + the body */ +#ifdef WITH_DEBUG_SND + WLog_DBG(TAG, "message type %"PRIu8"", priv->msgType); +#endif + priv->expectedBytes = 4; + priv->waitingHeader = TRUE; + + switch (priv->msgType) + { + case SNDC_WAVECONFIRM: + ret = rdpsnd_server_recv_waveconfirm(context, s); + break; + + case SNDC_FORMATS: + ret = rdpsnd_server_recv_formats(context, s); + + if ((ret == CHANNEL_RC_OK) && (context->clientVersion < CHANNEL_VERSION_WIN_7)) + IFCALL(context->Activated, context); + + break; + + case SNDC_QUALITYMODE: + ret = rdpsnd_server_recv_quality_mode(context, s); + Stream_SetPosition(s, + 0); /* in case the Activated callback tries to treat some messages */ + + if ((ret == CHANNEL_RC_OK) && (context->clientVersion >= CHANNEL_VERSION_WIN_7)) + IFCALL(context->Activated, context); + + break; + + default: + WLog_ERR(TAG, "UNKNOWN MESSAGE TYPE!! (0x%02"PRIX8")", priv->msgType); + ret = ERROR_INVALID_DATA; + break; + } + + Stream_SetPosition(s, 0); + return ret; +} diff --git a/channels/rdpsnd/server/rdpsnd_main.h b/channels/rdpsnd/server/rdpsnd_main.h new file mode 100644 index 0000000..f058abb --- /dev/null +++ b/channels/rdpsnd/server/rdpsnd_main.h @@ -0,0 +1,58 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H + +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpsnd.server") + +struct _rdpsnd_server_private +{ + BOOL ownThread; + HANDLE Thread; + HANDLE StopEvent; + HANDLE channelEvent; + void* ChannelHandle; + + BOOL waitingHeader; + DWORD expectedBytes; + BYTE msgType; + wStream* input_stream; + wStream* rdpsnd_pdu; + BYTE* out_buffer; + int out_buffer_size; + int out_frames; + int out_pending_frames; + UINT32 src_bytes_per_sample; + UINT32 src_bytes_per_frame; + FREERDP_DSP_CONTEXT* dsp_context; + CRITICAL_SECTION lock; /* Protect out_buffer and related parameters */ +}; + +#endif /* FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H */ diff --git a/channels/remdesk/CMakeLists.txt b/channels/remdesk/CMakeLists.txt new file mode 100644 index 0000000..23f1cf7 --- /dev/null +++ b/channels/remdesk/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("remdesk") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/remdesk/ChannelOptions.cmake b/channels/remdesk/ChannelOptions.cmake new file mode 100644 index 0000000..17518e6 --- /dev/null +++ b/channels/remdesk/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "remdesk" TYPE "static" + DESCRIPTION "Remote Assistance Virtual Channel Extension" + SPECIFICATIONS "[MS-RA]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/remdesk/client/CMakeLists.txt b/channels/remdesk/client/CMakeLists.txt new file mode 100644 index 0000000..bb66f6e --- /dev/null +++ b/channels/remdesk/client/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("remdesk") + +set(${MODULE_PREFIX}_SRCS + remdesk_main.c + remdesk_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/remdesk/client/remdesk_main.c b/channels/remdesk/client/remdesk_main.c new file mode 100644 index 0000000..9252929 --- /dev/null +++ b/channels/remdesk/client/remdesk_main.c @@ -0,0 +1,1065 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include +#include + +#include "remdesk_main.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_write(remdeskPlugin* remdesk, wStream* s) +{ + UINT32 status; + + if (!remdesk) + { + WLog_ERR(TAG, "remdesk was null!"); + return CHANNEL_RC_INVALID_INSTANCE; + } + + status = remdesk->channelEntryPoints.pVirtualChannelWriteEx(remdesk->InitHandle, + remdesk->OpenHandle, + Stream_Buffer(s), (UINT32) Stream_Length(s), s); + + if (status != CHANNEL_RC_OK) + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_generate_expert_blob(remdeskPlugin* remdesk) +{ + char* name; + char* pass; + char* password; + rdpSettings* settings = remdesk->settings; + + if (remdesk->ExpertBlob) + return CHANNEL_RC_OK; + + if (settings->RemoteAssistancePassword) + password = settings->RemoteAssistancePassword; + else + password = settings->Password; + + if (!password) + { + WLog_ERR(TAG, "password was not set!"); + return ERROR_INTERNAL_ERROR; + } + + name = settings->Username; + + if (!name) + name = "Expert"; + + remdesk->EncryptedPassStub = freerdp_assistance_encrypt_pass_stub(password, + settings->RemoteAssistancePassStub, &(remdesk->EncryptedPassStubSize)); + + if (!remdesk->EncryptedPassStub) + { + WLog_ERR(TAG, "freerdp_assistance_encrypt_pass_stub failed!"); + return ERROR_INTERNAL_ERROR; + } + + pass = freerdp_assistance_bin_to_hex_string(remdesk->EncryptedPassStub, + remdesk->EncryptedPassStubSize); + + if (!pass) + { + WLog_ERR(TAG, "freerdp_assistance_bin_to_hex_string failed!"); + return ERROR_INTERNAL_ERROR; + } + + remdesk->ExpertBlob = freerdp_assistance_construct_expert_blob(name, pass); + free(pass); + + if (!remdesk->ExpertBlob) + { + WLog_ERR(TAG, "freerdp_assistance_construct_expert_blob failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_read_channel_header(wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + int status; + UINT32 ChannelNameLen; + char* pChannelName = NULL; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Read_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + + if (ChannelNameLen > 64) + { + WLog_ERR(TAG, "ChannelNameLen > 64!"); + return ERROR_INVALID_DATA; + } + + if ((ChannelNameLen % 2) != 0) + { + WLog_ERR(TAG, "ChannelNameLen %% 2) != 0 "); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < ChannelNameLen) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + ZeroMemory(header->ChannelName, sizeof(header->ChannelName)); + pChannelName = (char*) header->ChannelName; + status = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(s), + ChannelNameLen / 2, &pChannelName, 32, NULL, NULL); + Stream_Seek(s, ChannelNameLen); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_write_channel_header(wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + int index; + UINT32 ChannelNameLen; + WCHAR ChannelNameW[32]; + ZeroMemory(ChannelNameW, sizeof(ChannelNameW)); + + for (index = 0; index < 32; index++) + { + ChannelNameW[index] = (WCHAR) header->ChannelName[index]; + } + + ChannelNameLen = (strlen(header->ChannelName) + 1) * 2; + Stream_Write_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Write_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + Stream_Write(s, ChannelNameW, ChannelNameLen); /* ChannelName (variable) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_write_ctl_header(wStream* s, REMDESK_CTL_HEADER* ctlHeader) +{ + remdesk_write_channel_header(s, (REMDESK_CHANNEL_HEADER*) ctlHeader); + Stream_Write_UINT32(s, ctlHeader->msgType); /* msgType (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, + UINT32 msgType, UINT32 msgSize) +{ + ctlHeader->msgType = msgType; + sprintf_s(ctlHeader->ChannelName, ARRAYSIZE(ctlHeader->ChannelName), REMDESK_CHANNEL_CTL_NAME); + ctlHeader->DataLength = 4 + msgSize; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_server_announce_pdu(remdeskPlugin* remdesk, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_version_info_pdu(remdeskPlugin* remdesk, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + UINT32 versionMajor; + UINT32 versionMinor; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, versionMajor); /* versionMajor (4 bytes) */ + Stream_Read_UINT32(s, versionMinor); /* versionMinor (4 bytes) */ + remdesk->Version = versionMajor; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_version_info_pdu(remdeskPlugin* remdesk) +{ + wStream* s; + REMDESK_CTL_VERSION_INFO_PDU pdu; + UINT error; + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8); + pdu.versionMajor = 1; + pdu.versionMinor = 2; + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */ + Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + + if (error != CHANNEL_RC_OK) + Stream_Free(s, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_result_pdu(remdeskPlugin* remdesk, wStream* s, + REMDESK_CHANNEL_HEADER* header, UINT32* pResult) +{ + UINT32 result; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, result); /* result (4 bytes) */ + *pResult = result; + //WLog_DBG(TAG, "RemdeskRecvResult: 0x%08"PRIX32"", result); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_authenticate_pdu(remdeskPlugin* remdesk) +{ + int status; + UINT error; + wStream* s = NULL; + int cbExpertBlobW = 0; + WCHAR* expertBlobW = NULL; + int cbRaConnectionStringW = 0; + WCHAR* raConnectionStringW = NULL; + REMDESK_CTL_AUTHENTICATE_PDU pdu; + + if ((error = remdesk_generate_expert_blob(remdesk))) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %"PRIu32"", error); + return error; + } + + pdu.expertBlob = remdesk->ExpertBlob; + pdu.raConnectionString = remdesk->settings->RemoteAssistanceRCTicket; + status = ConvertToUnicode(CP_UTF8, 0, pdu.raConnectionString, -1, + &raConnectionStringW, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + cbRaConnectionStringW = status * 2; + status = ConvertToUnicode(CP_UTF8, 0, pdu.expertBlob, -1, &expertBlobW, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + cbExpertBlobW = status * 2; + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_AUTHENTICATE, + cbRaConnectionStringW + cbExpertBlobW); + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + Stream_Write(s, (BYTE*) raConnectionStringW, cbRaConnectionStringW); + Stream_Write(s, (BYTE*) expertBlobW, cbExpertBlobW); + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + +out: + free(raConnectionStringW); + free(expertBlobW); + + if (error != CHANNEL_RC_OK) + Stream_Free(s, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_remote_control_desktop_pdu(remdeskPlugin* remdesk) +{ + int status; + UINT error; + wStream* s = NULL; + int cbRaConnectionStringW = 0; + WCHAR* raConnectionStringW = NULL; + REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU pdu; + pdu.raConnectionString = remdesk->settings->RemoteAssistanceRCTicket; + status = ConvertToUnicode(CP_UTF8, 0, pdu.raConnectionString, -1, + &raConnectionStringW, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + cbRaConnectionStringW = status * 2; + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_REMOTE_CONTROL_DESKTOP, + cbRaConnectionStringW); + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + Stream_Write(s, (BYTE*) raConnectionStringW, cbRaConnectionStringW); + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + +out: + free(raConnectionStringW); + + if (error != CHANNEL_RC_OK) + Stream_Free(s, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_verify_password_pdu(remdeskPlugin* remdesk) +{ + int status; + UINT error; + wStream* s; + int cbExpertBlobW = 0; + WCHAR* expertBlobW = NULL; + REMDESK_CTL_VERIFY_PASSWORD_PDU pdu; + + if ((error = remdesk_generate_expert_blob(remdesk))) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %"PRIu32"!", error); + return error; + } + + pdu.expertBlob = remdesk->ExpertBlob; + status = ConvertToUnicode(CP_UTF8, 0, pdu.expertBlob, -1, &expertBlobW, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertToUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + cbExpertBlobW = status * 2; + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERIFY_PASSWORD, + cbExpertBlobW); + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + Stream_Write(s, (BYTE*) expertBlobW, cbExpertBlobW); + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + +out: + free(expertBlobW); + + if (error != CHANNEL_RC_OK) + Stream_Free(s, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_expert_on_vista_pdu(remdeskPlugin* remdesk) +{ + UINT error; + wStream* s; + REMDESK_CTL_EXPERT_ON_VISTA_PDU pdu; + + if ((error = remdesk_generate_expert_blob(remdesk))) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %"PRIu32"!", error); + return error; + } + + pdu.EncryptedPasswordLength = remdesk->EncryptedPassStubSize; + pdu.EncryptedPassword = remdesk->EncryptedPassStub; + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_EXPERT_ON_VISTA, + pdu.EncryptedPasswordLength); + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + Stream_Write(s, pdu.EncryptedPassword, pdu.EncryptedPasswordLength); + Stream_SealLength(s); + return remdesk_virtual_channel_write(remdesk, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_pdu(remdeskPlugin* remdesk, wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + UINT32 msgType = 0; + UINT32 result = 0; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */ + + //WLog_DBG(TAG, "msgType: %"PRIu32"", msgType); + + switch (msgType) + { + case REMDESK_CTL_REMOTE_CONTROL_DESKTOP: + break; + + case REMDESK_CTL_RESULT: + if ((error = remdesk_recv_ctl_result_pdu(remdesk, s, header, &result))) + WLog_ERR(TAG, "remdesk_recv_ctl_result_pdu failed with error %"PRIu32"", error); + + break; + + case REMDESK_CTL_AUTHENTICATE: + break; + + case REMDESK_CTL_SERVER_ANNOUNCE: + if ((error = remdesk_recv_ctl_server_announce_pdu(remdesk, s, header))) + WLog_ERR(TAG, "remdesk_recv_ctl_server_announce_pdu failed with error %"PRIu32"", + error); + + break; + + case REMDESK_CTL_DISCONNECT: + break; + + case REMDESK_CTL_VERSIONINFO: + if ((error = remdesk_recv_ctl_version_info_pdu(remdesk, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %"PRIu32"", error); + break; + } + + if (remdesk->Version == 1) + { + if ((error = remdesk_send_ctl_version_info_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %"PRIu32"", error); + break; + } + + if ((error = remdesk_send_ctl_authenticate_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_authenticate_pdu failed with error %"PRIu32"", error); + break; + } + + if ((error = remdesk_send_ctl_remote_control_desktop_pdu(remdesk))) + { + WLog_ERR(TAG, + "remdesk_send_ctl_remote_control_desktop_pdu failed with error %"PRIu32"", error); + break; + } + } + else if (remdesk->Version == 2) + { + if ((error = remdesk_send_ctl_expert_on_vista_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_expert_on_vista_pdu failed with error %"PRIu32"", + error); + break; + } + + if ((error = remdesk_send_ctl_verify_password_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_verify_password_pdu failed with error %"PRIu32"", + error); + break; + } + } + + break; + + case REMDESK_CTL_ISCONNECTED: + break; + + case REMDESK_CTL_VERIFY_PASSWORD: + break; + + case REMDESK_CTL_EXPERT_ON_VISTA: + break; + + case REMDESK_CTL_RANOVICE_NAME: + break; + + case REMDESK_CTL_RAEXPERT_NAME: + break; + + case REMDESK_CTL_TOKEN: + break; + + default: + WLog_ERR(TAG, "unknown msgType: %"PRIu32"", msgType); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_process_receive(remdeskPlugin* remdesk, wStream* s) +{ + UINT status; + REMDESK_CHANNEL_HEADER header; +#if 0 + WLog_DBG(TAG, "RemdeskReceive: %"PRIuz"", Stream_GetRemainingLength(s)); + winpr_HexDump(Stream_Pointer(s), Stream_GetRemainingLength(s)); +#endif + + if ((status = remdesk_read_channel_header(s, &header))) + { + WLog_ERR(TAG, "remdesk_read_channel_header failed with error %"PRIu32"", status); + return status; + } + + if (strcmp(header.ChannelName, "RC_CTL") == 0) + { + status = remdesk_recv_ctl_pdu(remdesk, s, &header); + } + else if (strcmp(header.ChannelName, "70") == 0) + { + } + else if (strcmp(header.ChannelName, "71") == 0) + { + } + else if (strcmp(header.ChannelName, ".") == 0) + { + } + else if (strcmp(header.ChannelName, "1000.") == 0) + { + } + else if (strcmp(header.ChannelName, "RA_FX") == 0) + { + } + else + { + } + + return status; +} + +static void remdesk_process_connect(remdeskPlugin* remdesk) +{ + remdesk->settings = (rdpSettings*) remdesk->channelEntryPoints.pExtendedData; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_data_received(remdeskPlugin* remdesk, + void* pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + wStream* data_in; + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (remdesk->data_in) + Stream_Free(remdesk->data_in, TRUE); + + remdesk->data_in = Stream_New(NULL, totalLength); + + if (!remdesk->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = remdesk->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, (int) dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "read error"); + return ERROR_INTERNAL_ERROR; + } + + remdesk->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (!MessageQueue_Post(remdesk->queue, NULL, 0, (void*) data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE remdesk_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + remdeskPlugin* remdesk = (remdeskPlugin*) lpUserParam; + + if (!remdesk || (remdesk->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if ((error = remdesk_virtual_channel_event_data_received(remdesk, pData, + dataLength, totalLength, dataFlags))) + WLog_ERR(TAG, + "remdesk_virtual_channel_event_data_received failed with error %"PRIu32"!", error); + + break; + + case CHANNEL_EVENT_WRITE_COMPLETE: + break; + + case CHANNEL_EVENT_USER: + break; + + default: + WLog_ERR(TAG, "unhandled event %"PRIu32"!", event); + error = ERROR_INTERNAL_ERROR; + } + + if (error && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_open_event_ex reported an error"); +} + +static DWORD WINAPI remdesk_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data; + wMessage message; + remdeskPlugin* remdesk = (remdeskPlugin*) arg; + UINT error = CHANNEL_RC_OK; + remdesk_process_connect(remdesk); + + while (1) + { + if (!MessageQueue_Wait(remdesk->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(remdesk->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*) message.wParam; + + if ((error = remdesk_process_receive(remdesk, data))) + { + WLog_ERR(TAG, "remdesk_process_receive failed with error %"PRIu32"!", error); + break; + } + } + } + + if (error && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_connected(remdeskPlugin* remdesk, + LPVOID pData, UINT32 dataLength) +{ + UINT32 status; + UINT error; + status = remdesk->channelEntryPoints.pVirtualChannelOpenEx(remdesk->InitHandle, + &remdesk->OpenHandle, remdesk->channelDef.name, + remdesk_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "pVirtualChannelOpenEx failed with %s [%08"PRIX32"]", + WTSErrorToString(status), status); + return status; + } + + remdesk->queue = MessageQueue_New(NULL); + + if (!remdesk->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + remdesk->thread = CreateThread(NULL, 0, + remdesk_virtual_channel_client_thread, (void*) remdesk, + 0, NULL); + + if (!remdesk->thread) + { + WLog_ERR(TAG, "CreateThread failed"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + MessageQueue_Free(remdesk->queue); + remdesk->queue = NULL; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_disconnected(remdeskPlugin* remdesk) +{ + UINT rc; + + if (remdesk->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (MessageQueue_PostQuit(remdesk->queue, 0) + && (WaitForSingleObject(remdesk->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", rc); + return rc; + } + + MessageQueue_Free(remdesk->queue); + CloseHandle(remdesk->thread); + remdesk->queue = NULL; + remdesk->thread = NULL; + rc = remdesk->channelEntryPoints.pVirtualChannelCloseEx(remdesk->InitHandle, remdesk->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + } + + remdesk->OpenHandle = 0; + + if (remdesk->data_in) + { + Stream_Free(remdesk->data_in, TRUE); + remdesk->data_in = NULL; + } + + return rc; +} + +static void remdesk_virtual_channel_event_terminated(remdeskPlugin* remdesk) +{ + remdesk->InitHandle = 0; + free(remdesk->context); + free(remdesk); +} + +static VOID VCAPITYPE remdesk_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + remdeskPlugin* remdesk = (remdeskPlugin*) lpUserParam; + + if (!remdesk || (remdesk->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = remdesk_virtual_channel_event_connected(remdesk, pData, + dataLength))) + WLog_ERR(TAG, "remdesk_virtual_channel_event_connected failed with error %"PRIu32"", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = remdesk_virtual_channel_event_disconnected(remdesk))) + WLog_ERR(TAG, + "remdesk_virtual_channel_event_disconnected failed with error %"PRIu32"", error); + + break; + + case CHANNEL_EVENT_TERMINATED: + remdesk_virtual_channel_event_terminated(remdesk); + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + break; + } + + if (error && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_init_event reported an error"); +} + +/* remdesk is always built-in */ +#define VirtualChannelEntryEx remdesk_VirtualChannelEntryEx + +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + remdeskPlugin* remdesk; + RemdeskClientContext* context = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + + if (!pEntryPoints) + { + return FALSE; + } + + remdesk = (remdeskPlugin*) calloc(1, sizeof(remdeskPlugin)); + + if (!remdesk) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + remdesk->channelDef.options = + CHANNEL_OPTION_INITIALIZED | + CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | + CHANNEL_OPTION_SHOW_PROTOCOL; + sprintf_s(remdesk->channelDef.name, ARRAYSIZE(remdesk->channelDef.name), "remdesk"); + remdesk->Version = 2; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*) pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (RemdeskClientContext*) calloc(1, sizeof(RemdeskClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + context->handle = (void*) remdesk; + remdesk->context = context; + remdesk->rdpcontext = pEntryPointsEx->context; + } + + CopyMemory(&(remdesk->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + remdesk->InitHandle = pInitHandle; + rc = remdesk->channelEntryPoints.pVirtualChannelInitEx(remdesk, context, pInitHandle, + &remdesk->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + remdesk_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelInitEx failed with %s [%08"PRIX32"]", + WTSErrorToString(rc), rc); + goto error_out; + } + + remdesk->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + free(remdesk); + free(context); + return FALSE; +} diff --git a/channels/remdesk/client/remdesk_main.h b/channels/remdesk/client/remdesk_main.h new file mode 100644 index 0000000..13ae5c7 --- /dev/null +++ b/channels/remdesk/client/remdesk_main.h @@ -0,0 +1,63 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H +#define FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#define TAG CHANNELS_TAG("remdesk.client") + +struct remdesk_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + RemdeskClientContext* context; + + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + rdpSettings* settings; + wMessageQueue* queue; + + UINT32 Version; + char* ExpertBlob; + BYTE* EncryptedPassStub; + int EncryptedPassStubSize; + rdpContext* rdpcontext; +}; +typedef struct remdesk_plugin remdeskPlugin; + +#endif /* FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H */ diff --git a/channels/remdesk/server/CMakeLists.txt b/channels/remdesk/server/CMakeLists.txt new file mode 100644 index 0000000..dc59a11 --- /dev/null +++ b/channels/remdesk/server/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("remdesk") + +set(${MODULE_PREFIX}_SRCS + remdesk_main.c + remdesk_main.h) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/remdesk/server/remdesk_main.c b/channels/remdesk/server/remdesk_main.c new file mode 100644 index 0000000..447ede4 --- /dev/null +++ b/channels/remdesk/server/remdesk_main.c @@ -0,0 +1,796 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "remdesk_main.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_write(RemdeskServerContext* context, + wStream* s) +{ + BOOL status; + ULONG BytesWritten = 0; + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, + (PCHAR) Stream_Buffer(s), Stream_Length(s), &BytesWritten); + return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_read_channel_header(wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + int status; + UINT32 ChannelNameLen; + char* pChannelName = NULL; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Read_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Read_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + + if (ChannelNameLen > 64) + { + WLog_ERR(TAG, "ChannelNameLen > 64!"); + return ERROR_INVALID_DATA; + } + + if ((ChannelNameLen % 2) != 0) + { + WLog_ERR(TAG, "(ChannelNameLen %% 2) != 0!"); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < ChannelNameLen) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + ZeroMemory(header->ChannelName, sizeof(header->ChannelName)); + pChannelName = (char*) header->ChannelName; + status = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(s), + ChannelNameLen / 2, &pChannelName, 32, NULL, NULL); + Stream_Seek(s, ChannelNameLen); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_write_channel_header(wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + int index; + UINT32 ChannelNameLen; + WCHAR ChannelNameW[32]; + ZeroMemory(ChannelNameW, sizeof(ChannelNameW)); + + for (index = 0; index < 32; index++) + { + ChannelNameW[index] = (WCHAR) header->ChannelName[index]; + } + + ChannelNameLen = (strlen(header->ChannelName) + 1) * 2; + Stream_Write_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Write_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + Stream_Write(s, ChannelNameW, ChannelNameLen); /* ChannelName (variable) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_write_ctl_header(wStream* s, REMDESK_CTL_HEADER* ctlHeader) +{ + UINT error; + + if ((error = remdesk_write_channel_header(s, + (REMDESK_CHANNEL_HEADER*) ctlHeader))) + { + WLog_ERR(TAG, "remdesk_write_channel_header failed with error %"PRIu32"!", error); + return error; + } + + Stream_Write_UINT32(s, ctlHeader->msgType); /* msgType (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, + UINT32 msgType, UINT32 msgSize) +{ + ctlHeader->msgType = msgType; + sprintf_s(ctlHeader->ChannelName, ARRAYSIZE(ctlHeader->ChannelName), REMDESK_CHANNEL_CTL_NAME); + ctlHeader->DataLength = 4 + msgSize; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_result_pdu(RemdeskServerContext* context, + UINT32 result) +{ + wStream* s; + REMDESK_CTL_RESULT_PDU pdu; + UINT error; + pdu.result = result; + + if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_RESULT, + 4))) + { + WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %"PRIu32"!", error); + return error; + } + + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)))) + { + WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %"PRIu32"!", error); + goto out; + } + + Stream_Write_UINT32(s, pdu.result); /* result (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(context, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_version_info_pdu(RemdeskServerContext* context) +{ + wStream* s; + REMDESK_CTL_VERSION_INFO_PDU pdu; + UINT error; + + if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), + REMDESK_CTL_VERSIONINFO, 8))) + { + WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %"PRIu32"!", error); + return error; + } + + pdu.versionMajor = 1; + pdu.versionMinor = 2; + s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)))) + { + WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %"PRIu32"!", error); + goto out; + } + + Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */ + Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(context, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %"PRIu32"!", error); + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_version_info_pdu(RemdeskServerContext* context, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + UINT32 versionMajor; + UINT32 versionMinor; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, versionMajor); /* versionMajor (4 bytes) */ + Stream_Read_UINT32(s, versionMinor); /* versionMinor (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_remote_control_desktop_pdu( + RemdeskServerContext* context, wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + int status; + int cchStringW; + WCHAR* pStringW; + UINT32 msgLength; + int cbRaConnectionStringW = 0; + WCHAR* raConnectionStringW = NULL; + REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU pdu; + UINT error; + msgLength = header->DataLength - 4; + pStringW = (WCHAR*) Stream_Pointer(s); + raConnectionStringW = pStringW; + cchStringW = 0; + + while ((msgLength > 0) && pStringW[cchStringW]) + { + msgLength -= 2; + cchStringW++; + } + + if (pStringW[cchStringW] || !cchStringW) + return ERROR_INVALID_DATA; + + cchStringW++; + cbRaConnectionStringW = cchStringW * 2; + pdu.raConnectionString = NULL; + status = ConvertFromUnicode(CP_UTF8, 0, raConnectionStringW, + cbRaConnectionStringW / 2, &pdu.raConnectionString, 0, NULL, NULL); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_INFO(TAG, "RaConnectionString: %s", + pdu.raConnectionString); + free(pdu.raConnectionString); + + if ((error = remdesk_send_ctl_result_pdu(context, 0))) + WLog_ERR(TAG, "remdesk_send_ctl_result_pdu failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_authenticate_pdu(RemdeskServerContext* context, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + int status; + int cchStringW; + WCHAR* pStringW; + UINT32 msgLength; + int cbExpertBlobW = 0; + WCHAR* expertBlobW = NULL; + int cbRaConnectionStringW = 0; + WCHAR* raConnectionStringW = NULL; + REMDESK_CTL_AUTHENTICATE_PDU pdu; + msgLength = header->DataLength - 4; + pStringW = (WCHAR*) Stream_Pointer(s); + raConnectionStringW = pStringW; + cchStringW = 0; + + while ((msgLength > 0) && pStringW[cchStringW]) + { + msgLength -= 2; + cchStringW++; + } + + if (pStringW[cchStringW] || !cchStringW) + return ERROR_INVALID_DATA; + + cchStringW++; + cbRaConnectionStringW = cchStringW * 2; + pStringW += cchStringW; + expertBlobW = pStringW; + cchStringW = 0; + + while ((msgLength > 0) && pStringW[cchStringW]) + { + msgLength -= 2; + cchStringW++; + } + + if (pStringW[cchStringW] || !cchStringW) + return ERROR_INVALID_DATA; + + cchStringW++; + cbExpertBlobW = cchStringW * 2; + pdu.raConnectionString = NULL; + status = ConvertFromUnicode(CP_UTF8, 0, raConnectionStringW, + cbRaConnectionStringW / 2, &pdu.raConnectionString, 0, NULL, NULL); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + pdu.expertBlob = NULL; + status = ConvertFromUnicode(CP_UTF8, 0, expertBlobW, + cbExpertBlobW / 2, &pdu.expertBlob, 0, NULL, NULL); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + free(pdu.raConnectionString); + return ERROR_INTERNAL_ERROR; + } + + WLog_INFO(TAG, "RaConnectionString: %s ExpertBlob: %s", + pdu.raConnectionString, pdu.expertBlob); + free(pdu.raConnectionString); + free(pdu.expertBlob); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_verify_password_pdu(RemdeskServerContext* context, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + int status; + int cbExpertBlobW = 0; + WCHAR* expertBlobW = NULL; + REMDESK_CTL_VERIFY_PASSWORD_PDU pdu; + UINT error; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + pdu.expertBlob = NULL; + expertBlobW = (WCHAR*) Stream_Pointer(s); + cbExpertBlobW = header->DataLength - 4; + status = ConvertFromUnicode(CP_UTF8, 0, expertBlobW, cbExpertBlobW / 2, + &pdu.expertBlob, 0, NULL, NULL); + + if (status <= 0) + { + WLog_ERR(TAG, "ConvertFromUnicode failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_INFO(TAG, "ExpertBlob: %s", pdu.expertBlob); + + if ((error = remdesk_send_ctl_result_pdu(context, 0))) + WLog_ERR(TAG, "remdesk_send_ctl_result_pdu failed with error %"PRIu32"!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_pdu(RemdeskServerContext* context, wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + UINT32 msgType = 0; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */ + WLog_INFO(TAG, "msgType: %"PRIu32"", msgType); + + switch (msgType) + { + case REMDESK_CTL_REMOTE_CONTROL_DESKTOP: + if ((error = remdesk_recv_ctl_remote_control_desktop_pdu(context, s, header))) + { + WLog_ERR(TAG, + "remdesk_recv_ctl_remote_control_desktop_pdu failed with error %"PRIu32"!", error); + return error; + } + + break; + + case REMDESK_CTL_AUTHENTICATE: + if ((error = remdesk_recv_ctl_authenticate_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_authenticate_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case REMDESK_CTL_DISCONNECT: + break; + + case REMDESK_CTL_VERSIONINFO: + if ((error = remdesk_recv_ctl_version_info_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case REMDESK_CTL_ISCONNECTED: + break; + + case REMDESK_CTL_VERIFY_PASSWORD: + if ((error = remdesk_recv_ctl_verify_password_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_verify_password_pdu failed with error %"PRIu32"!", + error); + return error; + } + + break; + + case REMDESK_CTL_EXPERT_ON_VISTA: + break; + + case REMDESK_CTL_RANOVICE_NAME: + break; + + case REMDESK_CTL_RAEXPERT_NAME: + break; + + case REMDESK_CTL_TOKEN: + break; + + default: + WLog_ERR(TAG, "remdesk_recv_control_pdu: unknown msgType: %"PRIu32"", msgType); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_receive_pdu(RemdeskServerContext* context, + wStream* s) +{ + UINT error = CHANNEL_RC_OK; + REMDESK_CHANNEL_HEADER header; +#if 0 + WLog_INFO(TAG, "RemdeskReceive: %"PRIuz"", Stream_GetRemainingLength(s)); + winpr_HexDump(Stream_Pointer(s), Stream_GetRemainingLength(s)); +#endif + + if ((error = remdesk_read_channel_header(s, &header))) + { + WLog_ERR(TAG, "remdesk_read_channel_header failed with error %"PRIu32"!", error); + return error; + } + + if (strcmp(header.ChannelName, "RC_CTL") == 0) + { + if ((error = remdesk_recv_ctl_pdu(context, s, &header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_pdu failed with error %"PRIu32"!", error); + return error; + } + } + else if (strcmp(header.ChannelName, "70") == 0) + { + } + else if (strcmp(header.ChannelName, "71") == 0) + { + } + else if (strcmp(header.ChannelName, ".") == 0) + { + } + else if (strcmp(header.ChannelName, "1000.") == 0) + { + } + else if (strcmp(header.ChannelName, "RA_FX") == 0) + { + } + else + { + } + + return error; +} + +static DWORD WINAPI remdesk_server_thread(LPVOID arg) +{ + wStream* s; + DWORD status; + DWORD nCount; + void* buffer; + UINT32* pHeader; + UINT32 PduLength; + HANDLE events[8]; + HANDLE ChannelEvent; + DWORD BytesReturned; + RemdeskServerContext* context; + UINT error; + context = (RemdeskServerContext*) arg; + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + else + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + if ((error = remdesk_send_ctl_version_info_pdu(context))) + { + WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %"PRIu32"!", + error); + goto out; + } + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + break; + } + + if (WTSVirtualChannelRead(context->priv->ChannelHandle, 0, + (PCHAR) Stream_Buffer(s), Stream_Capacity(s), &BytesReturned)) + { + if (BytesReturned) + Stream_Seek(s, BytesReturned); + } + else + { + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + } + + if (Stream_GetPosition(s) >= 8) + { + pHeader = (UINT32*) Stream_Buffer(s); + PduLength = pHeader[0] + pHeader[1] + 8; + + if (PduLength >= Stream_GetPosition(s)) + { + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if ((error = remdesk_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, "remdesk_server_receive_pdu failed with error %"PRIu32"!", error); + break; + } + + Stream_SetPosition(s, 0); + } + } + } + + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, + "remdesk_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_start(RemdeskServerContext* context) +{ + context->priv->ChannelHandle = WTSVirtualChannelOpen(context->vcm, + WTS_CURRENT_SESSION, "remdesk"); + + if (!context->priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = CreateThread(NULL, 0, + remdesk_server_thread, (void*) context, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_stop(RemdeskServerContext* context) +{ + UINT error; + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(context->priv->Thread); + CloseHandle(context->priv->StopEvent); + return CHANNEL_RC_OK; +} + +RemdeskServerContext* remdesk_server_context_new(HANDLE vcm) +{ + RemdeskServerContext* context; + context = (RemdeskServerContext*) calloc(1, sizeof(RemdeskServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = remdesk_server_start; + context->Stop = remdesk_server_stop; + context->priv = (RemdeskServerPrivate*) calloc(1, sizeof(RemdeskServerPrivate)); + + if (!context->priv) + { + free(context); + return NULL; + } + + context->priv->Version = 1; + } + + return context; +} + +void remdesk_server_context_free(RemdeskServerContext* context) +{ + if (context) + { + if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE) + WTSVirtualChannelClose(context->priv->ChannelHandle); + + free(context->priv); + free(context); + } +} diff --git a/channels/remdesk/server/remdesk_main.h b/channels/remdesk/server/remdesk_main.h new file mode 100644 index 0000000..857376a --- /dev/null +++ b/channels/remdesk/server/remdesk_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H +#define FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H + +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("remdesk.server") + +struct _remdesk_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + + UINT32 Version; +}; + +#endif /* FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H */ + diff --git a/channels/serial/CMakeLists.txt b/channels/serial/CMakeLists.txt new file mode 100644 index 0000000..aa5cd85 --- /dev/null +++ b/channels/serial/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("serial") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + diff --git a/channels/serial/ChannelOptions.cmake b/channels/serial/ChannelOptions.cmake new file mode 100644 index 0000000..add3443 --- /dev/null +++ b/channels/serial/ChannelOptions.cmake @@ -0,0 +1,23 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "serial" TYPE "device" + DESCRIPTION "Serial Port Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPESP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/serial/client/CMakeLists.txt b/channels/serial/client/CMakeLists.txt new file mode 100644 index 0000000..f16995b --- /dev/null +++ b/channels/serial/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("serial") + +set(${MODULE_PREFIX}_SRCS + serial_main.c) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/serial/client/serial_main.c b/channels/serial/client/serial_main.c new file mode 100644 index 0000000..5d6286b --- /dev/null +++ b/channels/serial/client/serial_main.c @@ -0,0 +1,980 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Hewlett-Packard 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("serial.client") + +/* TODO: all #ifdef __linux__ could be removed once only some generic + * functions will be used. Replace CommReadFile by ReadFile, + * CommWriteFile by WriteFile etc.. */ +#if defined __linux__ && !defined ANDROID + +#define MAX_IRP_THREADS 5 + +typedef struct _SERIAL_DEVICE SERIAL_DEVICE; + +struct _SERIAL_DEVICE +{ + DEVICE device; + BOOL permissive; + SERIAL_DRIVER_ID ServerSerialDriverId; + HANDLE* hComm; + + wLog* log; + HANDLE MainThread; + wMessageQueue* MainIrpQueue; + + /* one thread per pending IRP and indexed according their CompletionId */ + wListDictionary* IrpThreads; + UINT32 IrpThreadToBeTerminatedCount; + CRITICAL_SECTION TerminatingIrpThreadsLock; + rdpContext* rdpcontext; +}; + +typedef struct _IRP_THREAD_DATA IRP_THREAD_DATA; + +struct _IRP_THREAD_DATA +{ + SERIAL_DEVICE* serial; + IRP* irp; +}; + +static UINT32 _GetLastErrorToIoStatus(SERIAL_DEVICE* serial) +{ + /* http://msdn.microsoft.com/en-us/library/ff547466%28v=vs.85%29.aspx#generic_status_values_for_serial_device_control_requests */ + switch (GetLastError()) + { + case ERROR_BAD_DEVICE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_CALL_NOT_IMPLEMENTED: + return STATUS_NOT_IMPLEMENTED; + + case ERROR_CANCELLED: + return STATUS_CANCELLED; + + case ERROR_INSUFFICIENT_BUFFER: + return STATUS_BUFFER_TOO_SMALL; /* NB: STATUS_BUFFER_SIZE_TOO_SMALL not defined */ + + case ERROR_INVALID_DEVICE_OBJECT_PARAMETER: /* eg: SerCx2.sys' _purge() */ + return STATUS_INVALID_DEVICE_STATE; + + case ERROR_INVALID_HANDLE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_INVALID_PARAMETER: + return STATUS_INVALID_PARAMETER; + + case ERROR_IO_DEVICE: + return STATUS_IO_DEVICE_ERROR; + + case ERROR_IO_PENDING: + return STATUS_PENDING; + + case ERROR_NOT_SUPPORTED: + return STATUS_NOT_SUPPORTED; + + case ERROR_TIMEOUT: + return STATUS_TIMEOUT; + /* no default */ + } + + WLog_Print(serial->log, WLOG_DEBUG, "unexpected last-error: 0x%08"PRIX32"", + GetLastError()); + return STATUS_UNSUCCESSFUL; +} + +static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) +{ + DWORD DesiredAccess; + DWORD SharedAccess; + DWORD CreateDisposition; + UINT32 PathLength; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, DesiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */ + Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(irp->input, SharedAccess); /* SharedAccess (4 bytes) */ + Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */ + Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */ + Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */ + + if (Stream_GetRemainingLength(irp->input) < PathLength) + return ERROR_INVALID_DATA; + + Stream_Seek(irp->input, PathLength); /* Path (variable) */ + assert(PathLength == 0); /* MS-RDPESP 2.2.2.2 */ +#ifndef _WIN32 + /* Windows 2012 server sends on a first call : + * DesiredAccess = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES + * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ + * CreateDisposition = 0x00000001: CREATE_NEW + * + * then Windows 2012 sends : + * DesiredAccess = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA + * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ + * CreateDisposition = 0x00000001: CREATE_NEW + * + * assert(DesiredAccess == (GENERIC_READ | GENERIC_WRITE)); + * assert(SharedAccess == 0); + * assert(CreateDisposition == OPEN_EXISTING); + * + */ + WLog_Print(serial->log, WLOG_DEBUG, + "DesiredAccess: 0x%"PRIX32", SharedAccess: 0x%"PRIX32", CreateDisposition: 0x%"PRIX32"", + DesiredAccess, SharedAccess, CreateDisposition); + /* FIXME: As of today only the flags below are supported by CommCreateFileA: */ + DesiredAccess = GENERIC_READ | GENERIC_WRITE; + SharedAccess = 0; + CreateDisposition = OPEN_EXISTING; +#endif + serial->hComm = CreateFile(serial->device.name, + DesiredAccess, + SharedAccess, + NULL, /* SecurityAttributes */ + CreateDisposition, + 0, /* FlagsAndAttributes */ + NULL); /* TemplateFile */ + + if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE)) + { + WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08"PRIX32"", + serial->device.name, GetLastError()); + irp->IoStatus = STATUS_UNSUCCESSFUL; + goto error_handle; + } + + _comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId); + _comm_set_permissive(serial->hComm, serial->permissive); + /* NOTE: binary mode/raw mode required for the redirection. On + * Linux, CommCreateFileA forces this setting. + */ + /* ZeroMemory(&dcb, sizeof(DCB)); */ + /* dcb.DCBlength = sizeof(DCB); */ + /* GetCommState(serial->hComm, &dcb); */ + /* dcb.fBinary = TRUE; */ + /* SetCommState(serial->hComm, &dcb); */ + assert(irp->FileId == 0); + irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */ + irp->IoStatus = STATUS_SUCCESS; + WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %"PRIu32", FileId: %"PRIu32") created.", + serial->device.name, irp->device->id, irp->FileId); +error_handle: + Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */ + Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */ + return CHANNEL_RC_OK; +} + +static UINT serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp) +{ + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Seek(irp->input, 32); /* Padding (32 bytes) */ + + if (!CloseHandle(serial->hComm)) + { + WLog_Print(serial->log, WLOG_WARN, "CloseHandle failure: %s (%"PRIu32") closed.", + serial->device.name, irp->device->id); + irp->IoStatus = STATUS_UNSUCCESSFUL; + goto error_handle; + } + + WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %"PRIu32", FileId: %"PRIu32") closed.", + serial->device.name, irp->device->id, irp->FileId); + serial->hComm = NULL; + irp->IoStatus = STATUS_SUCCESS; +error_handle: + Stream_Zero(irp->output, 5); /* Padding (5 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 Length; + UINT64 Offset; + BYTE* buffer = NULL; + DWORD nbRead = 0; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ + Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + + if (buffer == NULL) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + /* MS-RDPESP 3.2.5.1.4: If the Offset field is not set to 0, the value MUST be ignored + * assert(Offset == 0); + */ + WLog_Print(serial->log, WLOG_DEBUG, "reading %"PRIu32" bytes from %s", Length, + serial->device.name); + + /* FIXME: CommReadFile to be replaced by ReadFile */ + if (CommReadFile(serial->hComm, buffer, Length, &nbRead, NULL)) + { + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "read failure to %s, nbRead=%"PRIu32", last-error: 0x%08"PRIX32"", serial->device.name, + nbRead, GetLastError()); + irp->IoStatus = _GetLastErrorToIoStatus(serial); + } + + WLog_Print(serial->log, WLOG_DEBUG, "%"PRIu32" bytes read from %s", nbRead, + serial->device.name); +error_handle: + Stream_Write_UINT32(irp->output, nbRead); /* Length (4 bytes) */ + + if (nbRead > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, nbRead)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + free(buffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, buffer, nbRead); /* ReadData */ + } + + free(buffer); + return CHANNEL_RC_OK; +} + +static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 Length; + UINT64 Offset; + DWORD nbWritten = 0; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ + Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + /* MS-RDPESP 3.2.5.1.5: The Offset field is ignored + * assert(Offset == 0); + * + * Using a serial printer, noticed though this field could be + * set. + */ + WLog_Print(serial->log, WLOG_DEBUG, "writing %"PRIu32" bytes to %s", Length, + serial->device.name); + + /* FIXME: CommWriteFile to be replaced by WriteFile */ + if (CommWriteFile(serial->hComm, Stream_Pointer(irp->input), Length, &nbWritten, + NULL)) + { + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "write failure to %s, nbWritten=%"PRIu32", last-error: 0x%08"PRIX32"", serial->device.name, + nbWritten, GetLastError()); + irp->IoStatus = _GetLastErrorToIoStatus(serial); + } + + WLog_Print(serial->log, WLOG_DEBUG, "%"PRIu32" bytes written to %s", nbWritten, + serial->device.name); + Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */ + Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 IoControlCode; + UINT32 InputBufferLength; + BYTE* InputBuffer = NULL; + UINT32 OutputBufferLength; + BYTE* OutputBuffer = NULL; + DWORD BytesReturned = 0; + + if (Stream_GetRemainingLength(irp->input) < 32) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, OutputBufferLength); /* OutputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, InputBufferLength); /* InputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, IoControlCode); /* IoControlCode (4 bytes) */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + + if (Stream_GetRemainingLength(irp->input) < InputBufferLength) + return ERROR_INVALID_DATA; + + OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE)); + + if (OutputBuffer == NULL) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + InputBuffer = (BYTE*)calloc(InputBufferLength, sizeof(BYTE)); + + if (InputBuffer == NULL) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + Stream_Read(irp->input, InputBuffer, InputBufferLength); + WLog_Print(serial->log, WLOG_DEBUG, + "CommDeviceIoControl: CompletionId=%"PRIu32", IoControlCode=[0x%"PRIX32"] %s", + irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode)); + + /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */ + if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, + InputBufferLength, OutputBuffer, OutputBufferLength, &BytesReturned, NULL)) + { + /* WLog_Print(serial->log, WLOG_DEBUG, "CommDeviceIoControl: CompletionId=%"PRIu32", IoControlCode=[0x%"PRIX32"] %s done", irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode)); */ + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "CommDeviceIoControl failure: IoControlCode=[0x%"PRIX32"] %s, last-error: 0x%08"PRIX32"", + IoControlCode, _comm_serial_ioctl_name(IoControlCode), GetLastError()); + irp->IoStatus = _GetLastErrorToIoStatus(serial); + } + +error_handle: + /* FIXME: find out whether it's required or not to get + * BytesReturned == OutputBufferLength when + * CommDeviceIoControl returns FALSE */ + assert(OutputBufferLength == BytesReturned); + Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */ + + if (BytesReturned > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + free(InputBuffer); + free(OutputBuffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */ + } + + /* FIXME: Why at least Windows 2008R2 gets lost with this + * extra byte and likely on a IOCTL_SERIAL_SET_BAUD_RATE? The + * extra byte is well required according MS-RDPEFS + * 2.2.1.5.5 */ + /* else */ + /* { */ + /* Stream_Write_UINT8(irp->output, 0); /\* Padding (1 byte) *\/ */ + /* } */ + free(InputBuffer); + free(OutputBuffer); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT error = CHANNEL_RC_OK; + WLog_Print(serial->log, WLOG_DEBUG, + "IRP MajorFunction: 0x%08"PRIX32" MinorFunction: 0x%08"PRIX32"\n", + irp->MajorFunction, irp->MinorFunction); + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = serial_process_irp_create(serial, irp); + break; + + case IRP_MJ_CLOSE: + error = serial_process_irp_close(serial, irp); + break; + + case IRP_MJ_READ: + if ((error = serial_process_irp_read(serial, irp))) + WLog_ERR(TAG, "serial_process_irp_read failed with error %"PRIu32"!", error); + + break; + + case IRP_MJ_WRITE: + error = serial_process_irp_write(serial, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + if ((error = serial_process_irp_device_control(serial, irp))) + WLog_ERR(TAG, "serial_process_irp_device_control failed with error %"PRIu32"!", + error); + + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + } + + return error; +} + + +static DWORD WINAPI irp_thread_func(LPVOID arg) +{ + IRP_THREAD_DATA* data = (IRP_THREAD_DATA*)arg; + UINT error; + + /* blocks until the end of the request */ + if ((error = serial_process_irp(data->serial, data->irp))) + { + WLog_ERR(TAG, "serial_process_irp failed with error %"PRIu32"", error); + goto error_out; + } + + EnterCriticalSection(&data->serial->TerminatingIrpThreadsLock); + data->serial->IrpThreadToBeTerminatedCount++; + error = data->irp->Complete(data->irp); + LeaveCriticalSection(&data->serial->TerminatingIrpThreadsLock); +error_out: + + if (error && data->serial->rdpcontext) + setChannelError(data->serial->rdpcontext, error, + "irp_thread_func reported an error"); + + /* NB: At this point, the server might already being reusing + * the CompletionId whereas the thread is not yet + * terminated */ + free(data); + ExitThread(error); + return error; +} + + +static void create_irp_thread(SERIAL_DEVICE* serial, IRP* irp) +{ + IRP_THREAD_DATA* data = NULL; + HANDLE irpThread; + HANDLE previousIrpThread; + uintptr_t key; + /* for a test/debug purpose, uncomment the code below to get a + * single thread for all IRPs. NB: two IRPs could not be + * processed at the same time, typically two concurent + * Read/Write operations could block each other. */ + /* serial_process_irp(serial, irp); */ + /* irp->Complete(irp); */ + /* return; */ + /* NOTE: for good or bad, this implementation relies on the + * server to avoid a flooding of requests. see also _purge(). + */ + EnterCriticalSection(&serial->TerminatingIrpThreadsLock); + + while (serial->IrpThreadToBeTerminatedCount > 0) + { + /* Cleaning up termitating and pending irp + * threads. See also: irp_thread_func() */ + HANDLE irpThread; + ULONG_PTR* ids; + int i, nbIds; + nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids); + + for (i = 0; i < nbIds; i++) + { + /* Checking if ids[i] is terminating or pending */ + DWORD waitResult; + ULONG_PTR id = ids[i]; + irpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)id); + /* FIXME: not quite sure a zero timeout is a good thing to check whether a thread is stil alived or not */ + waitResult = WaitForSingleObject(irpThread, 0); + + if (waitResult == WAIT_OBJECT_0) + { + /* terminating thread */ + /* WLog_Print(serial->log, WLOG_DEBUG, "IRP thread with CompletionId=%"PRIuz" naturally died", id); */ + CloseHandle(irpThread); + ListDictionary_Remove(serial->IrpThreads, (void*)id); + serial->IrpThreadToBeTerminatedCount--; + } + else if (waitResult != WAIT_TIMEOUT) + { + /* unexpected thread state */ + WLog_Print(serial->log, WLOG_WARN, + "WaitForSingleObject, got an unexpected result=0x%"PRIX32"\n", waitResult); + assert(FALSE); + } + + /* pending thread (but not yet terminating thread) if waitResult == WAIT_TIMEOUT */ + } + + if (serial->IrpThreadToBeTerminatedCount > 0) + { + WLog_Print(serial->log, WLOG_DEBUG, "%"PRIu32" IRP thread(s) not yet terminated", + serial->IrpThreadToBeTerminatedCount); + Sleep(1); /* 1 ms */ + } + + free(ids); + } + + LeaveCriticalSection(&serial->TerminatingIrpThreadsLock); + /* NB: At this point and thanks to the synchronization we're + * sure that the incoming IRP uses well a recycled + * CompletionId or the server sent again an IRP already posted + * which didn't get yet a response (this later server behavior + * at least observed with IOCTL_SERIAL_WAIT_ON_MASK and + * mstsc.exe). + * + * FIXME: behavior documented somewhere? behavior not yet + * observed with FreeRDP). + */ + key = irp->CompletionId; + previousIrpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)key); + + if (previousIrpThread) + { + /* Thread still alived <=> Request still pending */ + WLog_Print(serial->log, WLOG_DEBUG, + "IRP recall: IRP with the CompletionId=%"PRIu32" not yet completed!", + irp->CompletionId); + assert(FALSE); /* unimplemented */ + /* TODO: asserts that previousIrpThread handles well + * the same request by checking more details. Need an + * access to the IRP object used by previousIrpThread + */ + /* TODO: taking over the pending IRP or sending a kind + * of wake up signal to accelerate the pending + * request + * + * To be considered: + * if (IoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) { + * pComm->PendingEvents |= SERIAL_EV_FREERDP_*; + * } + */ + irp->Discard(irp); + return; + } + + if (ListDictionary_Count(serial->IrpThreads) >= MAX_IRP_THREADS) + { + WLog_Print(serial->log, WLOG_WARN, + "Number of IRP threads threshold reached: %d, keep on anyway", + ListDictionary_Count(serial->IrpThreads)); + assert(FALSE); /* unimplemented */ + /* TODO: MAX_IRP_THREADS has been thought to avoid a + * flooding of pending requests. Use + * WaitForMultipleObjects() when available in winpr + * for threads. + */ + } + + /* error_handle to be used ... */ + data = (IRP_THREAD_DATA*)calloc(1, sizeof(IRP_THREAD_DATA)); + + if (data == NULL) + { + WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP_THREAD_DATA."); + goto error_handle; + } + + data->serial = serial; + data->irp = irp; + /* data freed by irp_thread_func */ + irpThread = CreateThread(NULL, + 0, + irp_thread_func, + (void*)data, + 0, + NULL); + + if (irpThread == INVALID_HANDLE_VALUE) + { + WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP thread."); + goto error_handle; + } + + key = irp->CompletionId; + + if (!ListDictionary_Add(serial->IrpThreads, (void*)key, irpThread)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + goto error_handle; + } + + return; +error_handle: + irp->IoStatus = STATUS_NO_MEMORY; + irp->Complete(irp); + free(data); +} + + +static void terminate_pending_irp_threads(SERIAL_DEVICE* serial) +{ + ULONG_PTR* ids; + int i, nbIds; + nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids); + WLog_Print(serial->log, WLOG_DEBUG, "Terminating %d IRP thread(s)", nbIds); + + for (i = 0; i < nbIds; i++) + { + HANDLE irpThread; + ULONG_PTR id = ids[i]; + irpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)id); + TerminateThread(irpThread, 0); + + if (WaitForSingleObject(irpThread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed!"); + continue; + } + + CloseHandle(irpThread); + WLog_Print(serial->log, WLOG_DEBUG, "IRP thread terminated, CompletionId %p", (void*) id); + } + + ListDictionary_Clear(serial->IrpThreads); + free(ids); +} + + +static DWORD WINAPI serial_thread_func(LPVOID arg) +{ + IRP* irp; + wMessage message; + SERIAL_DEVICE* serial = (SERIAL_DEVICE*) arg; + UINT error = CHANNEL_RC_OK; + + while (1) + { + if (!MessageQueue_Wait(serial->MainIrpQueue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(serial->MainIrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + { + terminate_pending_irp_threads(serial); + break; + } + + irp = (IRP*) message.wParam; + + if (irp) + create_irp_thread(serial, irp); + } + + if (error && serial->rdpcontext) + setChannelError(serial->rdpcontext, error, + "serial_thread_func reported an error"); + + ExitThread(error); + return error; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_irp_request(DEVICE* device, IRP* irp) +{ + SERIAL_DEVICE* serial = (SERIAL_DEVICE*) device; + assert(irp != NULL); + + if (irp == NULL) + return CHANNEL_RC_OK; + + /* NB: ENABLE_ASYNCIO is set, (MS-RDPEFS 2.2.2.7.2) this + * allows the server to send multiple simultaneous read or + * write requests. + */ + + if (!MessageQueue_Post(serial->MainIrpQueue, NULL, 0, (void*) irp, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_free(DEVICE* device) +{ + UINT error; + SERIAL_DEVICE* serial = (SERIAL_DEVICE*) device; + WLog_Print(serial->log, WLOG_DEBUG, "freeing"); + MessageQueue_PostQuit(serial->MainIrpQueue, 0); + + if (WaitForSingleObject(serial->MainThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(serial->MainThread); + + if (serial->hComm) + CloseHandle(serial->hComm); + + /* Clean up resources */ + Stream_Free(serial->device.data, TRUE); + MessageQueue_Free(serial->MainIrpQueue); + ListDictionary_Free(serial->IrpThreads); + DeleteCriticalSection(&serial->TerminatingIrpThreadsLock); + free(serial); + return CHANNEL_RC_OK; +} + +#endif /* __linux__ */ + +#ifdef BUILTIN_CHANNELS +#define DeviceServiceEntry serial_DeviceServiceEntry +#else +#define DeviceServiceEntry FREERDP_API DeviceServiceEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + char* name; + char* path; + char* driver; + RDPDR_SERIAL* device; +#if defined __linux__ && !defined ANDROID + int i, len; + SERIAL_DEVICE* serial; +#endif /* __linux__ */ + UINT error = CHANNEL_RC_OK; + device = (RDPDR_SERIAL*) pEntryPoints->device; + name = device->Name; + path = device->Path; + driver = device->Driver; + + if (!name || (name[0] == '*')) + { + /* TODO: implement auto detection of serial ports */ + return CHANNEL_RC_OK; + } + + if ((name && name[0]) && (path && path[0])) + { + wLog* log; + log = WLog_Get("com.freerdp.channel.serial.client"); + WLog_Print(log, WLOG_DEBUG, "initializing"); +#ifndef __linux__ /* to be removed */ + WLog_Print(log, WLOG_WARN, + "Serial ports redirection not supported on this platform."); + return CHANNEL_RC_INITIALIZATION_ERROR; +#else /* __linux __ */ + WLog_Print(log, WLOG_DEBUG, "Defining %s as %s", name, path); + + if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */)) + { + DWORD status = GetLastError(); + WLog_ERR(TAG, "DefineCommDevice failed with %08"PRIx32, status); + return ERROR_INTERNAL_ERROR; + } + + serial = (SERIAL_DEVICE*) calloc(1, sizeof(SERIAL_DEVICE)); + + if (!serial) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + serial->log = log; + serial->device.type = RDPDR_DTYP_SERIAL; + serial->device.name = name; + serial->device.IRPRequest = serial_irp_request; + serial->device.Free = serial_free; + serial->rdpcontext = pEntryPoints->rdpcontext; + len = strlen(name); + serial->device.data = Stream_New(NULL, len + 1); + + if (!serial->device.data) + { + WLog_ERR(TAG, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + for (i = 0; i <= len; i++) + Stream_Write_UINT8(serial->device.data, name[i] < 0 ? '_' : name[i]); + + if (driver != NULL) + { + if (_stricmp(driver, "Serial") == 0) + serial->ServerSerialDriverId = SerialDriverSerialSys; + else if (_stricmp(driver, "SerCx") == 0) + serial->ServerSerialDriverId = SerialDriverSerCxSys; + else if (_stricmp(driver, "SerCx2") == 0) + serial->ServerSerialDriverId = SerialDriverSerCx2Sys; + else + { + assert(FALSE); + WLog_Print(serial->log, WLOG_DEBUG, + "Unknown server's serial driver: %s. SerCx2 will be used", driver); + serial->ServerSerialDriverId = SerialDriverSerialSys; + } + } + else + { + /* default driver */ + serial->ServerSerialDriverId = SerialDriverSerialSys; + } + + if (device->Permissive != NULL) + { + if (_stricmp(device->Permissive, "permissive") == 0) + { + serial->permissive = TRUE; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, "Unknown flag: %s", device->Permissive); + assert(FALSE); + } + } + + WLog_Print(serial->log, WLOG_DEBUG, "Server's serial driver: %s (id: %d)", + driver, serial->ServerSerialDriverId); + /* TODO: implement auto detection of the server's serial driver */ + serial->MainIrpQueue = MessageQueue_New(NULL); + + if (!serial->MainIrpQueue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + /* IrpThreads content only modified by create_irp_thread() */ + serial->IrpThreads = ListDictionary_New(FALSE); + + if (!serial->IrpThreads) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + serial->IrpThreadToBeTerminatedCount = 0; + InitializeCriticalSection(&serial->TerminatingIrpThreadsLock); + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, + (DEVICE*) serial))) + { + WLog_ERR(TAG, "EntryPoints->RegisterDevice failed with error %"PRIu32"!", error); + goto error_out; + } + + if (!(serial->MainThread = CreateThread(NULL, + 0, + serial_thread_func, + (void*) serial, + 0, + NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + +#endif /* __linux __ */ + } + + return error; +error_out: +#ifdef __linux__ /* to be removed */ + ListDictionary_Free(serial->IrpThreads); + MessageQueue_Free(serial->MainIrpQueue); + Stream_Free(serial->device.data, TRUE); + free(serial); +#endif /* __linux __ */ + return error; +} diff --git a/channels/server/CMakeLists.txt b/channels/server/CMakeLists.txt new file mode 100644 index 0000000..1f49c3b --- /dev/null +++ b/channels/server/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "freerdp-channels-server") +set(MODULE_PREFIX "FREERDP_CHANNELS_SERVER") + +set(${MODULE_PREFIX}_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/channels.c + ${CMAKE_CURRENT_SOURCE_DIR}/channels.h) + +foreach(STATIC_MODULE ${CHANNEL_STATIC_SERVER_MODULES}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_SERVER_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_SERVER_CHANNEL}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME}) +endforeach() + +add_library(${MODULE_NAME} STATIC ${${MODULE_PREFIX}_SRCS}) + +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common") diff --git a/channels/server/channels.c b/channels/server/channels.c new file mode 100644 index 0000000..8efff3f --- /dev/null +++ b/channels/server/channels.c @@ -0,0 +1,90 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Channels + * + * Copyright 2011-2012 Vic Lee + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "channels.h" + +/** + * this is a workaround to force importing symbols + * will need to fix that later on cleanly + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void freerdp_channels_dummy() +{ + audin_server_context* audin; + RdpsndServerContext* rdpsnd; + CliprdrServerContext* cliprdr; + echo_server_context* echo; + RdpdrServerContext* rdpdr; + DrdynvcServerContext* drdynvc; + RdpeiServerContext* rdpei; + RemdeskServerContext* remdesk; + EncomspServerContext* encomsp; + RdpgfxServerContext* rdpgfx; + audin = audin_server_context_new(NULL); + audin_server_context_free(audin); + rdpsnd = rdpsnd_server_context_new(NULL); + rdpsnd_server_context_free(rdpsnd); + cliprdr = cliprdr_server_context_new(NULL); + cliprdr_server_context_free(cliprdr); + echo = echo_server_context_new(NULL); + echo_server_context_free(echo); + rdpdr = rdpdr_server_context_new(NULL); + rdpdr_server_context_free(rdpdr); + drdynvc = drdynvc_server_context_new(NULL); + drdynvc_server_context_free(drdynvc); + rdpei = rdpei_server_context_new(NULL); + rdpei_server_context_free(rdpei); + remdesk = remdesk_server_context_new(NULL); + remdesk_server_context_free(remdesk); + encomsp = encomsp_server_context_new(NULL); + encomsp_server_context_free(encomsp); + rdpgfx = rdpgfx_server_context_new(NULL); + rdpgfx_server_context_free(rdpgfx); +} + +/** + * end of ugly symbols import workaround + */ diff --git a/channels/server/channels.h b/channels/server/channels.h new file mode 100644 index 0000000..f054c91 --- /dev/null +++ b/channels/server/channels.h @@ -0,0 +1,26 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Channels + * + * Copyright 2011-2012 Vic Lee + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SERVER_CHANNELS_H +#define FREERDP_CHANNEL_SERVER_CHANNELS_H + + + +#endif /* FREERDP_CHANNEL_SERVER_CHANNELS_H */ diff --git a/channels/smartcard/CMakeLists.txt b/channels/smartcard/CMakeLists.txt new file mode 100644 index 0000000..98c6c72 --- /dev/null +++ b/channels/smartcard/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("smartcard") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/smartcard/ChannelOptions.cmake b/channels/smartcard/ChannelOptions.cmake new file mode 100644 index 0000000..7af6e31 --- /dev/null +++ b/channels/smartcard/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "smartcard" TYPE "device" + DESCRIPTION "Smart Card Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPESC]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/smartcard/client/CMakeLists.txt b/channels/smartcard/client/CMakeLists.txt new file mode 100644 index 0000000..1af49ef --- /dev/null +++ b/channels/smartcard/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("smartcard") + +set(${MODULE_PREFIX}_SRCS + smartcard_main.c + smartcard_main.h + smartcard_pack.c + smartcard_pack.h + smartcard_operations.c) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DeviceServiceEntry") + + + +target_link_libraries(${MODULE_NAME} winpr freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/smartcard/client/smartcard_main.c b/channels/smartcard/client/smartcard_main.c new file mode 100644 index 0000000..134c021 --- /dev/null +++ b/channels/smartcard/client/smartcard_main.c @@ -0,0 +1,823 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2011 Anthony Tong + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "smartcard_main.h" + +#define CAST_FROM_DEVICE(device) cast_device_from(device, __FUNCTION__, __FILE__, __LINE__) + +static SMARTCARD_DEVICE* sSmartcard = NULL; + +static SMARTCARD_DEVICE* cast_device_from(DEVICE* device, const char* fkt, const char* file, + int line) +{ + if (!device) + { + WLog_ERR(TAG, "%s [%s:%d] Called smartcard channel with NULL device", fkt, file, line); + return NULL; + } + + if (device->type != RDPDR_DTYP_SMARTCARD) + { + WLog_ERR(TAG, "%s [%s:%d] Called smartcard channel with invalid device of type %"PRIx32, + fkt, file, line, device->type); + return NULL; + } + + return (SMARTCARD_DEVICE*)device; +} + +static DWORD WINAPI smartcard_context_thread(LPVOID arg) +{ + SMARTCARD_CONTEXT* pContext = (SMARTCARD_CONTEXT*)arg; + DWORD nCount; + LONG status = 0; + DWORD waitStatus; + HANDLE hEvents[2]; + wMessage message; + SMARTCARD_DEVICE* smartcard; + SMARTCARD_OPERATION* operation; + UINT error = CHANNEL_RC_OK; + smartcard = pContext->smartcard; + nCount = 0; + hEvents[nCount++] = MessageQueue_Event(pContext->IrpQueue); + + while (1) + { + waitStatus = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE); + + if (waitStatus == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + waitStatus = WaitForSingleObject(MessageQueue_Event(pContext->IrpQueue), 0); + + if (waitStatus == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + if (waitStatus == WAIT_OBJECT_0) + { + if (!MessageQueue_Peek(pContext->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + status = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + operation = (SMARTCARD_OPERATION*) message.wParam; + + if (operation) + { + if ((status = smartcard_irp_device_control_call(smartcard, operation))) + { + WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %"PRIu32"", + status); + break; + } + + if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*) operation->irp)) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + status = ERROR_INTERNAL_ERROR; + break; + } + + free(operation); + } + } + } + + if (status && smartcard->rdpcontext) + setChannelError(smartcard->rdpcontext, error, + "smartcard_context_thread reported an error"); + + ExitThread(status); + return error; +} + +SMARTCARD_CONTEXT* smartcard_context_new(SMARTCARD_DEVICE* smartcard, + SCARDCONTEXT hContext) +{ + SMARTCARD_CONTEXT* pContext; + pContext = (SMARTCARD_CONTEXT*) calloc(1, sizeof(SMARTCARD_CONTEXT)); + + if (!pContext) + { + WLog_ERR(TAG, "calloc failed!"); + return pContext; + } + + pContext->smartcard = smartcard; + pContext->hContext = hContext; + pContext->IrpQueue = MessageQueue_New(NULL); + + if (!pContext->IrpQueue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto error_irpqueue; + } + + pContext->thread = CreateThread(NULL, 0, + smartcard_context_thread, + pContext, 0, NULL); + + if (!pContext->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto error_thread; + } + + return pContext; +error_thread: + MessageQueue_Free(pContext->IrpQueue); +error_irpqueue: + free(pContext); + return NULL; +} + +void smartcard_context_free(void* pCtx) +{ + SMARTCARD_CONTEXT* pContext = pCtx; + + if (!pContext) + return; + + /* cancel blocking calls like SCardGetStatusChange */ + SCardCancel(pContext->hContext); + + if (MessageQueue_PostQuit(pContext->IrpQueue, 0) + && (WaitForSingleObject(pContext->thread, INFINITE) == WAIT_FAILED)) + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + + CloseHandle(pContext->thread); + MessageQueue_Free(pContext->IrpQueue); + free(pContext); +} + +static void smartcard_release_all_contexts(SMARTCARD_DEVICE* smartcard) +{ + int index; + int keyCount; + ULONG_PTR* pKeys; + SCARDCONTEXT hContext; + SMARTCARD_CONTEXT* pContext; + + /** + * On protocol termination, the following actions are performed: + * For each context in rgSCardContextList, SCardCancel is called causing all SCardGetStatusChange calls to be processed. + * After that, SCardReleaseContext is called on each context and the context MUST be removed from rgSCardContextList. + */ + + /** + * Call SCardCancel on existing contexts, unblocking all outstanding SCardGetStatusChange calls. + */ + + if (ListDictionary_Count(smartcard->rgSCardContextList) > 0) + { + pKeys = NULL; + keyCount = ListDictionary_GetKeys(smartcard->rgSCardContextList, &pKeys); + + for (index = 0; index < keyCount; index++) + { + pContext = (SMARTCARD_CONTEXT*) ListDictionary_GetItemValue( + smartcard->rgSCardContextList, (void*) pKeys[index]); + + if (!pContext) + continue; + + hContext = pContext->hContext; + + if (SCardIsValidContext(hContext) == SCARD_S_SUCCESS) + { + SCardCancel(hContext); + } + } + + free(pKeys); + } + + /** + * Call SCardReleaseContext on remaining contexts and remove them from rgSCardContextList. + */ + + if (ListDictionary_Count(smartcard->rgSCardContextList) > 0) + { + pKeys = NULL; + keyCount = ListDictionary_GetKeys(smartcard->rgSCardContextList, &pKeys); + + for (index = 0; index < keyCount; index++) + { + pContext = (SMARTCARD_CONTEXT*) ListDictionary_Remove( + smartcard->rgSCardContextList, (void*) pKeys[index]); + + if (!pContext) + continue; + + hContext = pContext->hContext; + + if (SCardIsValidContext(hContext) == SCARD_S_SUCCESS) + { + SCardReleaseContext(hContext); + + if (MessageQueue_PostQuit(pContext->IrpQueue, 0) + && (WaitForSingleObject(pContext->thread, INFINITE) == WAIT_FAILED)) + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + + CloseHandle(pContext->thread); + MessageQueue_Free(pContext->IrpQueue); + free(pContext); + } + } + + free(pKeys); + } +} + +static UINT smartcard_free_(SMARTCARD_DEVICE* smartcard) +{ + if (!smartcard) + return CHANNEL_RC_OK; + + if (smartcard->IrpQueue) + { + MessageQueue_Free(smartcard->IrpQueue); + CloseHandle(smartcard->thread); + } + + Stream_Free(smartcard->device.data, TRUE); + LinkedList_Free(smartcard->names); + ListDictionary_Free(smartcard->rgSCardContextList); + ListDictionary_Free(smartcard->rgOutstandingMessages); + Queue_Free(smartcard->CompletedIrpQueue); + + if (smartcard->StartedEvent) + SCardReleaseStartedEvent(); + + free(smartcard); + return CHANNEL_RC_OK; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_free(DEVICE* device) +{ + UINT error; + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + /** + * Calling smartcard_release_all_contexts to unblock all operations waiting for transactions + * to unlock. + */ + smartcard_release_all_contexts(smartcard); + + /* Stopping all threads and cancelling all IRPs */ + + if (smartcard->IrpQueue) + { + if (MessageQueue_PostQuit(smartcard->IrpQueue, 0) + && (WaitForSingleObject(smartcard->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + } + + if (sSmartcard == smartcard) + sSmartcard = NULL; + + return smartcard_free_(smartcard); +} + +/** + * Initialization occurs when the protocol server sends a device announce message. + * At that time, we need to cancel all outstanding IRPs. + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_init(DEVICE* device) +{ + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + smartcard_release_all_contexts(smartcard); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp) +{ + void* key; + key = (void*)(size_t) irp->CompletionId; + ListDictionary_Remove(smartcard->rgOutstandingMessages, key); + return irp->Complete(irp); +} + +/** + * Multiple threads and SCardGetStatusChange: + * http://musclecard.996296.n3.nabble.com/Multiple-threads-and-SCardGetStatusChange-td4430.html + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_process_irp(SMARTCARD_DEVICE* smartcard, IRP* irp) +{ + void* key; + LONG status; + BOOL asyncIrp = FALSE; + SMARTCARD_CONTEXT* pContext = NULL; + SMARTCARD_OPERATION* operation = NULL; + key = (void*)(size_t) irp->CompletionId; + + if (!ListDictionary_Add(smartcard->rgOutstandingMessages, key, irp)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL) + { + operation = (SMARTCARD_OPERATION*) calloc(1, sizeof(SMARTCARD_OPERATION)); + + if (!operation) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + operation->irp = irp; + status = smartcard_irp_device_control_decode(smartcard, operation); + + if (status != SCARD_S_SUCCESS) + { + irp->IoStatus = (UINT32)STATUS_UNSUCCESSFUL; + + if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*) irp)) + { + free(operation); + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return ERROR_INTERNAL_ERROR; + } + + free(operation); + return CHANNEL_RC_OK; + } + + asyncIrp = TRUE; + + switch (operation->ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + case SCARD_IOCTL_RELEASECONTEXT: + case SCARD_IOCTL_ISVALIDCONTEXT: + case SCARD_IOCTL_CANCEL: + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + case SCARD_IOCTL_RELEASESTARTEDEVENT: + asyncIrp = FALSE; + break; + + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + case SCARD_IOCTL_FORGETREADERGROUPA: + case SCARD_IOCTL_FORGETREADERGROUPW: + case SCARD_IOCTL_INTRODUCEREADERA: + case SCARD_IOCTL_INTRODUCEREADERW: + case SCARD_IOCTL_FORGETREADERA: + case SCARD_IOCTL_FORGETREADERW: + case SCARD_IOCTL_ADDREADERTOGROUPA: + case SCARD_IOCTL_ADDREADERTOGROUPW: + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_READCACHEA: + case SCARD_IOCTL_READCACHEW: + case SCARD_IOCTL_WRITECACHEA: + case SCARD_IOCTL_WRITECACHEW: + case SCARD_IOCTL_GETREADERICON: + case SCARD_IOCTL_GETDEVICETYPEID: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + case SCARD_IOCTL_GETTRANSMITCOUNT: + asyncIrp = TRUE; + break; + } + + pContext = ListDictionary_GetItemValue(smartcard->rgSCardContextList, + (void*) operation->hContext); + + if (!pContext) + asyncIrp = FALSE; + + if (!asyncIrp) + { + if ((status = smartcard_irp_device_control_call(smartcard, operation))) + { + WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %"PRId32"!", + status); + return (UINT32)status; + } + + if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*) irp)) + { + free(operation); + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return ERROR_INTERNAL_ERROR; + } + + free(operation); + } + else + { + if (pContext) + { + if (!MessageQueue_Post(pContext->IrpQueue, NULL, 0, (void*) operation, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + } + } + else + { + WLog_ERR(TAG, + "Unexpected SmartCard IRP: MajorFunction 0x%08"PRIX32" MinorFunction: 0x%08"PRIX32"", + irp->MajorFunction, irp->MinorFunction); + irp->IoStatus = (UINT32)STATUS_NOT_SUPPORTED; + + if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*) irp)) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI smartcard_thread_func(LPVOID arg) +{ + IRP* irp; + DWORD nCount; + DWORD status; + HANDLE hEvents[2]; + wMessage message; + UINT error = CHANNEL_RC_OK; + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(arg); + + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + nCount = 0; + hEvents[nCount++] = MessageQueue_Event(smartcard->IrpQueue); + hEvents[nCount++] = Queue_Event(smartcard->CompletedIrpQueue); + + while (1) + { + status = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + status = WaitForSingleObject(MessageQueue_Event(smartcard->IrpQueue), 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + if (!MessageQueue_Peek(smartcard->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + { + while (1) + { + status = WaitForSingleObject(Queue_Event(smartcard->CompletedIrpQueue), 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + goto out; + } + + if (status == WAIT_TIMEOUT) + break; + + irp = (IRP*) Queue_Dequeue(smartcard->CompletedIrpQueue); + + if (irp) + { + if (irp->thread) + { + status = WaitForSingleObject(irp->thread, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + goto out; + } + + CloseHandle(irp->thread); + irp->thread = NULL; + } + + if ((error = smartcard_complete_irp(smartcard, irp))) + { + WLog_ERR(TAG, "smartcard_complete_irp failed with error %"PRIu32"!", error); + goto out; + } + } + } + + break; + } + + irp = (IRP*) message.wParam; + + if (irp) + { + if ((error = smartcard_process_irp(smartcard, irp))) + { + WLog_ERR(TAG, "smartcard_process_irp failed with error %"PRIu32"!", error); + goto out; + } + } + } + + status = WaitForSingleObject(Queue_Event(smartcard->CompletedIrpQueue), 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + irp = (IRP*) Queue_Dequeue(smartcard->CompletedIrpQueue); + + if (irp) + { + if (irp->thread) + { + status = WaitForSingleObject(irp->thread, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + CloseHandle(irp->thread); + irp->thread = NULL; + } + + if ((error = smartcard_complete_irp(smartcard, irp))) + { + if (error == CHANNEL_RC_NOT_CONNECTED) + { + error = CHANNEL_RC_OK; + goto out; + } + + WLog_ERR(TAG, "smartcard_complete_irp failed with error %"PRIu32"!", error); + goto out; + } + } + } + } + +out: + + if (error && smartcard->rdpcontext) + setChannelError(smartcard->rdpcontext, error, + "smartcard_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_irp_request(DEVICE* device, IRP* irp) +{ + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + if (!MessageQueue_Post(smartcard->IrpQueue, NULL, 0, (void*) irp, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/* smartcard is always built-in */ +#define DeviceServiceEntry smartcard_DeviceServiceEntry + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + SMARTCARD_DEVICE* smartcard = NULL; + size_t length; + UINT error = CHANNEL_RC_NO_MEMORY; + + if (!sSmartcard) + { + wObject* obj; + smartcard = (SMARTCARD_DEVICE*) calloc(1, sizeof(SMARTCARD_DEVICE)); + + if (!smartcard) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + smartcard->device.type = RDPDR_DTYP_SMARTCARD; + smartcard->device.name = "SCARD"; + smartcard->device.IRPRequest = smartcard_irp_request; + smartcard->device.Init = smartcard_init; + smartcard->device.Free = smartcard_free; + smartcard->names = LinkedList_New(); + smartcard->rdpcontext = pEntryPoints->rdpcontext; + length = strlen(smartcard->device.name); + smartcard->device.data = Stream_New(NULL, length + 1); + + if (!smartcard->device.data || !smartcard->names) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + Stream_Write(smartcard->device.data, "SCARD", 6); + smartcard->IrpQueue = MessageQueue_New(NULL); + + if (!smartcard->IrpQueue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto fail; + } + + smartcard->CompletedIrpQueue = Queue_New(TRUE, -1, -1); + + if (!smartcard->CompletedIrpQueue) + { + WLog_ERR(TAG, "Queue_New failed!"); + goto fail; + } + + smartcard->rgSCardContextList = ListDictionary_New(TRUE); + + if (!smartcard->rgSCardContextList) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + goto fail; + } + + obj = ListDictionary_ValueObject(smartcard->rgSCardContextList); + obj->fnObjectFree = smartcard_context_free; + smartcard->rgOutstandingMessages = ListDictionary_New(TRUE); + + if (!smartcard->rgOutstandingMessages) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + goto fail; + } + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &smartcard->device))) + { + WLog_ERR(TAG, "RegisterDevice failed!"); + goto fail; + } + + smartcard->thread = CreateThread(NULL, 0, + smartcard_thread_func, + smartcard, CREATE_SUSPENDED, NULL); + + if (!smartcard->thread) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = ERROR_INTERNAL_ERROR; + goto fail; + } + + ResumeThread(smartcard->thread); + } + else + smartcard = sSmartcard; + + if (pEntryPoints->device->Name) + LinkedList_AddLast(smartcard->names, pEntryPoints->device->Name); + + sSmartcard = smartcard; + return CHANNEL_RC_OK; +fail: + smartcard_free_(smartcard); + return error; +} + diff --git a/channels/smartcard/client/smartcard_main.h b/channels/smartcard/client/smartcard_main.h new file mode 100644 index 0000000..a867e69 --- /dev/null +++ b/channels/smartcard/client/smartcard_main.h @@ -0,0 +1,132 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H +#define FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H + +#include +#include + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("smartcard.client") + +#define RDP_SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_FILE_SYSTEM, (code), METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define SCARD_IOCTL_ESTABLISHCONTEXT RDP_SCARD_CTL_CODE(5) /* SCardEstablishContext */ +#define SCARD_IOCTL_RELEASECONTEXT RDP_SCARD_CTL_CODE(6) /* SCardReleaseContext */ +#define SCARD_IOCTL_ISVALIDCONTEXT RDP_SCARD_CTL_CODE(7) /* SCardIsValidContext */ +#define SCARD_IOCTL_LISTREADERGROUPSA RDP_SCARD_CTL_CODE(8) /* SCardListReaderGroupsA */ +#define SCARD_IOCTL_LISTREADERGROUPSW RDP_SCARD_CTL_CODE(9) /* SCardListReaderGroupsW */ +#define SCARD_IOCTL_LISTREADERSA RDP_SCARD_CTL_CODE(10) /* SCardListReadersA */ +#define SCARD_IOCTL_LISTREADERSW RDP_SCARD_CTL_CODE(11) /* SCardListReadersW */ +#define SCARD_IOCTL_INTRODUCEREADERGROUPA RDP_SCARD_CTL_CODE(20) /* SCardIntroduceReaderGroupA */ +#define SCARD_IOCTL_INTRODUCEREADERGROUPW RDP_SCARD_CTL_CODE(21) /* SCardIntroduceReaderGroupW */ +#define SCARD_IOCTL_FORGETREADERGROUPA RDP_SCARD_CTL_CODE(22) /* SCardForgetReaderGroupA */ +#define SCARD_IOCTL_FORGETREADERGROUPW RDP_SCARD_CTL_CODE(23) /* SCardForgetReaderGroupW */ +#define SCARD_IOCTL_INTRODUCEREADERA RDP_SCARD_CTL_CODE(24) /* SCardIntroduceReaderA */ +#define SCARD_IOCTL_INTRODUCEREADERW RDP_SCARD_CTL_CODE(25) /* SCardIntroduceReaderW */ +#define SCARD_IOCTL_FORGETREADERA RDP_SCARD_CTL_CODE(26) /* SCardForgetReaderA */ +#define SCARD_IOCTL_FORGETREADERW RDP_SCARD_CTL_CODE(27) /* SCardForgetReaderW */ +#define SCARD_IOCTL_ADDREADERTOGROUPA RDP_SCARD_CTL_CODE(28) /* SCardAddReaderToGroupA */ +#define SCARD_IOCTL_ADDREADERTOGROUPW RDP_SCARD_CTL_CODE(29) /* SCardAddReaderToGroupW */ +#define SCARD_IOCTL_REMOVEREADERFROMGROUPA RDP_SCARD_CTL_CODE(30) /* SCardRemoveReaderFromGroupA */ +#define SCARD_IOCTL_REMOVEREADERFROMGROUPW RDP_SCARD_CTL_CODE(31) /* SCardRemoveReaderFromGroupW */ +#define SCARD_IOCTL_LOCATECARDSA RDP_SCARD_CTL_CODE(38) /* SCardLocateCardsA */ +#define SCARD_IOCTL_LOCATECARDSW RDP_SCARD_CTL_CODE(39) /* SCardLocateCardsW */ +#define SCARD_IOCTL_GETSTATUSCHANGEA RDP_SCARD_CTL_CODE(40) /* SCardGetStatusChangeA */ +#define SCARD_IOCTL_GETSTATUSCHANGEW RDP_SCARD_CTL_CODE(41) /* SCardGetStatusChangeW */ +#define SCARD_IOCTL_CANCEL RDP_SCARD_CTL_CODE(42) /* SCardCancel */ +#define SCARD_IOCTL_CONNECTA RDP_SCARD_CTL_CODE(43) /* SCardConnectA */ +#define SCARD_IOCTL_CONNECTW RDP_SCARD_CTL_CODE(44) /* SCardConnectW */ +#define SCARD_IOCTL_RECONNECT RDP_SCARD_CTL_CODE(45) /* SCardReconnect */ +#define SCARD_IOCTL_DISCONNECT RDP_SCARD_CTL_CODE(46) /* SCardDisconnect */ +#define SCARD_IOCTL_BEGINTRANSACTION RDP_SCARD_CTL_CODE(47) /* SCardBeginTransaction */ +#define SCARD_IOCTL_ENDTRANSACTION RDP_SCARD_CTL_CODE(48) /* SCardEndTransaction */ +#define SCARD_IOCTL_STATE RDP_SCARD_CTL_CODE(49) /* SCardState */ +#define SCARD_IOCTL_STATUSA RDP_SCARD_CTL_CODE(50) /* SCardStatusA */ +#define SCARD_IOCTL_STATUSW RDP_SCARD_CTL_CODE(51) /* SCardStatusW */ +#define SCARD_IOCTL_TRANSMIT RDP_SCARD_CTL_CODE(52) /* SCardTransmit */ +#define SCARD_IOCTL_CONTROL RDP_SCARD_CTL_CODE(53) /* SCardControl */ +#define SCARD_IOCTL_GETATTRIB RDP_SCARD_CTL_CODE(54) /* SCardGetAttrib */ +#define SCARD_IOCTL_SETATTRIB RDP_SCARD_CTL_CODE(55) /* SCardSetAttrib */ +#define SCARD_IOCTL_ACCESSSTARTEDEVENT RDP_SCARD_CTL_CODE(56) /* SCardAccessStartedEvent */ +#define SCARD_IOCTL_LOCATECARDSBYATRA RDP_SCARD_CTL_CODE(58) /* SCardLocateCardsByATRA */ +#define SCARD_IOCTL_LOCATECARDSBYATRW RDP_SCARD_CTL_CODE(59) /* SCardLocateCardsByATRW */ +#define SCARD_IOCTL_READCACHEA RDP_SCARD_CTL_CODE(60) /* SCardReadCacheA */ +#define SCARD_IOCTL_READCACHEW RDP_SCARD_CTL_CODE(61) /* SCardReadCacheW */ +#define SCARD_IOCTL_WRITECACHEA RDP_SCARD_CTL_CODE(62) /* SCardWriteCacheA */ +#define SCARD_IOCTL_WRITECACHEW RDP_SCARD_CTL_CODE(63) /* SCardWriteCacheW */ +#define SCARD_IOCTL_GETTRANSMITCOUNT RDP_SCARD_CTL_CODE(64) /* SCardGetTransmitCount */ +#define SCARD_IOCTL_RELEASESTARTEDEVENT RDP_SCARD_CTL_CODE(66) /* SCardReleaseStartedEvent */ +#define SCARD_IOCTL_GETREADERICON RDP_SCARD_CTL_CODE(67) /* SCardGetReaderIconA */ +#define SCARD_IOCTL_GETDEVICETYPEID RDP_SCARD_CTL_CODE(68) /* SCardGetDeviceTypeIdA */ + +typedef struct _SMARTCARD_DEVICE SMARTCARD_DEVICE; + +struct _SMARTCARD_OPERATION +{ + IRP* irp; + void* call; + UINT32 ioControlCode; + SCARDCONTEXT hContext; + SCARDHANDLE hCard; +}; +typedef struct _SMARTCARD_OPERATION SMARTCARD_OPERATION; + +struct _SMARTCARD_CONTEXT +{ + HANDLE thread; + SCARDCONTEXT hContext; + wMessageQueue* IrpQueue; + SMARTCARD_DEVICE* smartcard; +}; +typedef struct _SMARTCARD_CONTEXT SMARTCARD_CONTEXT; + +struct _SMARTCARD_DEVICE +{ + DEVICE device; + + HANDLE thread; + HANDLE StartedEvent; + wMessageQueue* IrpQueue; + wQueue* CompletedIrpQueue; + wListDictionary* rgSCardContextList; + wListDictionary* rgOutstandingMessages; + rdpContext* rdpcontext; + wLinkedList* names; +}; + +SMARTCARD_CONTEXT* smartcard_context_new(SMARTCARD_DEVICE* smartcard, SCARDCONTEXT hContext); +void smartcard_context_free(void* pContext); + +LONG smartcard_irp_device_control_decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation); +LONG smartcard_irp_device_control_call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation); + +#include "smartcard_pack.h" + +#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H */ diff --git a/channels/smartcard/client/smartcard_operations.c b/channels/smartcard/client/smartcard_operations.c new file mode 100644 index 0000000..4009b9f --- /dev/null +++ b/channels/smartcard/client/smartcard_operations.c @@ -0,0 +1,2141 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright (C) Alexi Volkov 2006 + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Anthony Tong + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Armin Novak + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include + +#include +#include + +#include "smartcard_main.h" + +const char* smartcard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName) +{ + switch (ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + return funcName ? "SCardEstablishContext" : "SCARD_IOCTL_ESTABLISHCONTEXT"; + + case SCARD_IOCTL_RELEASECONTEXT: + return funcName ? "SCardReleaseContext" : "SCARD_IOCTL_RELEASECONTEXT"; + + case SCARD_IOCTL_ISVALIDCONTEXT: + return funcName ? "SCardIsValidContext" : "SCARD_IOCTL_ISVALIDCONTEXT"; + + case SCARD_IOCTL_LISTREADERGROUPSA: + return funcName ? "SCardListReaderGroupsA" : "SCARD_IOCTL_LISTREADERGROUPSA"; + + case SCARD_IOCTL_LISTREADERGROUPSW: + return funcName ? "SCardListReaderGroupsW" : "SCARD_IOCTL_LISTREADERGROUPSW"; + + case SCARD_IOCTL_LISTREADERSA: + return funcName ? "SCardListReadersA" : "SCARD_IOCTL_LISTREADERSA"; + + case SCARD_IOCTL_LISTREADERSW: + return funcName ? "SCardListReadersW" : "SCARD_IOCTL_LISTREADERSW"; + + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + return funcName ? "SCardIntroduceReaderGroupA" : "SCARD_IOCTL_INTRODUCEREADERGROUPA"; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + return funcName ? "SCardIntroduceReaderGroupW" : "SCARD_IOCTL_INTRODUCEREADERGROUPW"; + + case SCARD_IOCTL_FORGETREADERGROUPA: + return funcName ? "SCardForgetReaderGroupA" : "SCARD_IOCTL_FORGETREADERGROUPA"; + + case SCARD_IOCTL_FORGETREADERGROUPW: + return funcName ? "SCardForgetReaderGroupW" : "SCARD_IOCTL_FORGETREADERGROUPW"; + + case SCARD_IOCTL_INTRODUCEREADERA: + return funcName ? "SCardIntroduceReaderA" : "SCARD_IOCTL_INTRODUCEREADERA"; + + case SCARD_IOCTL_INTRODUCEREADERW: + return funcName ? "SCardIntroduceReaderW" : "SCARD_IOCTL_INTRODUCEREADERW"; + + case SCARD_IOCTL_FORGETREADERA: + return funcName ? "SCardForgetReaderA" : "SCARD_IOCTL_FORGETREADERA"; + + case SCARD_IOCTL_FORGETREADERW: + return funcName ? "SCardForgetReaderW" : "SCARD_IOCTL_FORGETREADERW"; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + return funcName ? "SCardAddReaderToGroupA" : "SCARD_IOCTL_ADDREADERTOGROUPA"; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + return funcName ? "SCardAddReaderToGroupW" : "SCARD_IOCTL_ADDREADERTOGROUPW"; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + return funcName ? "SCardRemoveReaderFromGroupA" : "SCARD_IOCTL_REMOVEREADERFROMGROUPA"; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + return funcName ? "SCardRemoveReaderFromGroupW" : "SCARD_IOCTL_REMOVEREADERFROMGROUPW"; + + case SCARD_IOCTL_LOCATECARDSA: + return funcName ? "SCardLocateCardsA" : "SCARD_IOCTL_LOCATECARDSA"; + + case SCARD_IOCTL_LOCATECARDSW: + return funcName ? "SCardLocateCardsW" : "SCARD_IOCTL_LOCATECARDSW"; + + case SCARD_IOCTL_GETSTATUSCHANGEA: + return funcName ? "SCardGetStatusChangeA" : "SCARD_IOCTL_GETSTATUSCHANGEA"; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + return funcName ? "SCardGetStatusChangeW" : "SCARD_IOCTL_GETSTATUSCHANGEW"; + + case SCARD_IOCTL_CANCEL: + return funcName ? "SCardCancel" : "SCARD_IOCTL_CANCEL"; + + case SCARD_IOCTL_CONNECTA: + return funcName ? "SCardConnectA" : "SCARD_IOCTL_CONNECTA"; + + case SCARD_IOCTL_CONNECTW: + return funcName ? "SCardConnectW" : "SCARD_IOCTL_CONNECTW"; + + case SCARD_IOCTL_RECONNECT: + return funcName ? "SCardReconnect" : "SCARD_IOCTL_RECONNECT"; + + case SCARD_IOCTL_DISCONNECT: + return funcName ? "SCardDisconnect" : "SCARD_IOCTL_DISCONNECT"; + + case SCARD_IOCTL_BEGINTRANSACTION: + return funcName ? "SCardBeginTransaction" : "SCARD_IOCTL_BEGINTRANSACTION"; + + case SCARD_IOCTL_ENDTRANSACTION: + return funcName ? "SCardEndTransaction" : "SCARD_IOCTL_ENDTRANSACTION"; + + case SCARD_IOCTL_STATE: + return funcName ? "SCardState" : "SCARD_IOCTL_STATE"; + + case SCARD_IOCTL_STATUSA: + return funcName ? "SCardStatusA" : "SCARD_IOCTL_STATUSA"; + + case SCARD_IOCTL_STATUSW: + return funcName ? "SCardStatusW" : "SCARD_IOCTL_STATUSW"; + + case SCARD_IOCTL_TRANSMIT: + return funcName ? "SCardTransmit" : "SCARD_IOCTL_TRANSMIT"; + + case SCARD_IOCTL_CONTROL: + return funcName ? "SCardControl" : "SCARD_IOCTL_CONTROL"; + + case SCARD_IOCTL_GETATTRIB: + return funcName ? "SCardGetAttrib" : "SCARD_IOCTL_GETATTRIB"; + + case SCARD_IOCTL_SETATTRIB: + return funcName ? "SCardSetAttrib" : "SCARD_IOCTL_SETATTRIB"; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + return funcName ? "SCardAccessStartedEvent" : "SCARD_IOCTL_ACCESSSTARTEDEVENT"; + + case SCARD_IOCTL_LOCATECARDSBYATRA: + return funcName ? "SCardLocateCardsByATRA" : "SCARD_IOCTL_LOCATECARDSBYATRA"; + + case SCARD_IOCTL_LOCATECARDSBYATRW: + return funcName ? "SCardLocateCardsByATRB" : "SCARD_IOCTL_LOCATECARDSBYATRW"; + + case SCARD_IOCTL_READCACHEA: + return funcName ? "SCardReadCacheA" : "SCARD_IOCTL_READCACHEA"; + + case SCARD_IOCTL_READCACHEW: + return funcName ? "SCardReadCacheW" : "SCARD_IOCTL_READCACHEW"; + + case SCARD_IOCTL_WRITECACHEA: + return funcName ? "SCardWriteCacheA" : "SCARD_IOCTL_WRITECACHEA"; + + case SCARD_IOCTL_WRITECACHEW: + return funcName ? "SCardWriteCacheW" : "SCARD_IOCTL_WRITECACHEW"; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + return funcName ? "SCardGetTransmitCount" : "SCARD_IOCTL_GETTRANSMITCOUNT"; + + case SCARD_IOCTL_RELEASESTARTEDEVENT: + return funcName ? "SCardReleaseStartedEvent" : "SCARD_IOCTL_RELEASESTARTEDEVENT"; + + case SCARD_IOCTL_GETREADERICON: + return funcName ? "SCardGetReaderIcon" : "SCARD_IOCTL_GETREADERICON"; + + case SCARD_IOCTL_GETDEVICETYPEID: + return funcName ? "SCardGetDeviceTypeId" : "SCARD_IOCTL_GETDEVICETYPEID"; + + default: + return funcName ? "SCardUnknown" : "SCARD_IOCTL_UNKNOWN"; + } + + return funcName ? "SCardUnknown" : "SCARD_IOCTL_UNKNOWN"; +} + +static LONG smartcard_EstablishContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + EstablishContext_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(EstablishContext_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_establish_context_call(smartcard, irp->input, call))) + { + WLog_ERR(TAG, "smartcard_unpack_establish_context_call failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_establish_context_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +static LONG smartcard_EstablishContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + SCARDCONTEXT hContext = -1; + EstablishContext_Return ret; + IRP* irp = operation->irp; + EstablishContext_Call* call = operation->call; + status = ret.ReturnCode = SCardEstablishContext(call->dwScope, NULL, NULL, &hContext); + + if (ret.ReturnCode == SCARD_S_SUCCESS) + { + SMARTCARD_CONTEXT* pContext; + void* key = (void*)(size_t) hContext; + // TODO: handle return values + pContext = smartcard_context_new(smartcard, hContext); + + if (!pContext) + { + WLog_ERR(TAG, "smartcard_context_new failed!"); + return STATUS_NO_MEMORY; + } + + if (!ListDictionary_Add(smartcard->rgSCardContextList, key, (void*) pContext)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return STATUS_INTERNAL_ERROR; + } + } + else + { + WLog_ERR(TAG, "SCardEstablishContext failed with error %"PRId32"", status); + return status; + } + + smartcard_scard_context_native_to_redir(smartcard, &(ret.hContext), hContext); + smartcard_trace_establish_context_return(smartcard, &ret); + + if ((status = smartcard_pack_establish_context_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_establish_context_return failed with error %"PRId32"", status); + return status; + } + + return ret.ReturnCode; +} + +static LONG smartcard_ReleaseContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + Context_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Context_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_context_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_context_call failed with error %"PRId32"", status); + + smartcard_trace_context_call(smartcard, call, "ReleaseContext"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_ReleaseContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + Long_Return ret; + status = ret.ReturnCode = SCardReleaseContext(operation->hContext); + + if (ret.ReturnCode == SCARD_S_SUCCESS) + { + SMARTCARD_CONTEXT* pContext; + void* key = (void*)(size_t) operation->hContext; + pContext = (SMARTCARD_CONTEXT*) ListDictionary_Remove(smartcard->rgSCardContextList, key); + smartcard_context_free(pContext); + } + else + { + WLog_ERR(TAG, "SCardReleaseContext failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_long_return(smartcard, &ret, "ReleaseContext"); + return ret.ReturnCode; +} + +static LONG smartcard_IsValidContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + Context_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Context_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_context_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_context_call failed with error %"PRId32"", status); + + smartcard_trace_context_call(smartcard, call, "IsValidContext"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_IsValidContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + Long_Return ret; + + if ((status = ret.ReturnCode = SCardIsValidContext(operation->hContext))) + { + WLog_ERR(TAG, "SCardIsValidContext failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_long_return(smartcard, &ret, "IsValidContext"); + return ret.ReturnCode; +} + +static LONG smartcard_ListReaderGroupsA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ListReaderGroups_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + status = smartcard_unpack_list_reader_groups_call(smartcard, irp->input, call); + smartcard_trace_list_reader_groups_call(smartcard, call, FALSE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_ListReaderGroupsA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Return ret; + LPSTR mszGroups = NULL; + DWORD cchGroups = 0; + IRP* irp = operation->irp; + cchGroups = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = SCardListReaderGroupsA(operation->hContext, (LPSTR) &mszGroups, + &cchGroups); + ret.msz = (BYTE*) mszGroups; + ret.cBytes = cchGroups; + + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_list_reader_groups_return(smartcard, &ret, FALSE); + status = smartcard_pack_list_reader_groups_return(smartcard, irp->output, &ret); + + if (status != SCARD_S_SUCCESS) + return status; + + if (mszGroups) + SCardFreeMemory(operation->hContext, mszGroups); + + return ret.ReturnCode; +} + +static LONG smartcard_ListReaderGroupsW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ListReaderGroups_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + status = smartcard_unpack_list_reader_groups_call(smartcard, irp->input, call); + smartcard_trace_list_reader_groups_call(smartcard, call, TRUE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_ListReaderGroupsW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Return ret; + LPWSTR mszGroups = NULL; + DWORD cchGroups = 0; + IRP* irp = operation->irp; + cchGroups = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = SCardListReaderGroupsW(operation->hContext, (LPWSTR) &mszGroups, + &cchGroups); + ret.msz = (BYTE*) mszGroups; + ret.cBytes = cchGroups; + + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_list_reader_groups_return(smartcard, &ret, TRUE); + status = smartcard_pack_list_reader_groups_return(smartcard, irp->output, &ret); + + if (status != SCARD_S_SUCCESS) + return status; + + if (mszGroups) + SCardFreeMemory(operation->hContext, mszGroups); + + return ret.ReturnCode; +} + +static BOOL filter_match(wLinkedList* list, LPCSTR reader, size_t readerLen) +{ + if (readerLen < 1) + return FALSE; + + LinkedList_Enumerator_Reset(list); + + while (LinkedList_Enumerator_MoveNext(list)) + { + const char* filter = LinkedList_Enumerator_Current(list); + + if (filter) + { + if (strstr(reader, filter) != NULL) + return TRUE; + } + } + + return FALSE; +} + +static DWORD filter_device_by_name_a(wLinkedList* list, LPSTR* mszReaders, DWORD cchReaders) +{ + size_t rpos = 0, wpos = 0; + + if (LinkedList_Count(list) < 1) + return cchReaders; + + do + { + LPCSTR rreader = &(*mszReaders)[rpos]; + LPSTR wreader = &(*mszReaders)[wpos]; + size_t readerLen = strnlen(rreader, cchReaders - rpos); + rpos += readerLen + 1; + + if (filter_match(list, rreader, readerLen)) + { + if (rreader != wreader) + memmove(wreader, rreader, readerLen); + + wpos += readerLen + 1; + } + } + while (rpos < cchReaders); + + /* this string must be double 0 terminated */ + if (rpos != wpos) + { + if (wpos >= cchReaders) + return 0; + + (*mszReaders)[wpos++] = '\0'; + } + + return wpos; +} + +static DWORD filter_device_by_name_w(wLinkedList* list, LPWSTR* mszReaders, DWORD cchReaders) +{ + DWORD rc; + LPSTR readers; + + if (LinkedList_Count(list) < 1) + return cchReaders; + + if (ConvertFromUnicode(CP_UTF8, 0, *mszReaders, (int)cchReaders, &readers, 0, NULL, + NULL) != cchReaders) + return 0; + + free(*mszReaders); + *mszReaders = NULL; + rc = filter_device_by_name_a(list, &readers, cchReaders); + + if (ConvertToUnicode(CP_UTF8, 0, readers, (int)rc, mszReaders, 0) != rc) + rc = 0; + + free(readers); + return rc; +} + +static LONG smartcard_ListReadersA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ListReaders_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_list_readers_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_list_readers_call failed with error %"PRId32"", status); + + smartcard_trace_list_readers_call(smartcard, call, FALSE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_ListReadersA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Return ret; + LPSTR mszReaders = NULL; + DWORD cchReaders = 0; + IRP* irp = operation->irp; + ListReaders_Call* call = operation->call; + cchReaders = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = SCardListReadersA(operation->hContext, (LPCSTR) call->mszGroups, + (LPSTR) &mszReaders, &cchReaders); + cchReaders = filter_device_by_name_a(smartcard->names, &mszReaders, cchReaders); + ret.msz = (BYTE*) mszReaders; + ret.cBytes = cchReaders; + + if (call->mszGroups) + { + free(call->mszGroups); + call->mszGroups = NULL; + } + + if (status) + { + WLog_ERR(TAG, "SCardListReadersA failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_list_readers_return(smartcard, &ret, FALSE); + + if ((status = smartcard_pack_list_readers_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_list_readers_return failed with error %"PRId32"", status); + return status; + } + + if (mszReaders) + SCardFreeMemory(operation->hContext, mszReaders); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_ListReadersW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ListReaders_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_list_readers_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_list_readers_call failed with error %"PRId32"", status); + + smartcard_trace_list_readers_call(smartcard, call, TRUE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_ListReadersW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Return ret; + LPWSTR mszReaders = NULL; + DWORD cchReaders = 0; + IRP* irp = operation->irp; + ListReaders_Call* call = operation->call; + cchReaders = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = SCardListReadersW(operation->hContext, + (LPCWSTR) call->mszGroups, (LPWSTR) &mszReaders, &cchReaders); + cchReaders = filter_device_by_name_w(smartcard->names, &mszReaders, cchReaders); + ret.msz = (BYTE*) mszReaders; + ret.cBytes = cchReaders * 2; + + if (call->mszGroups) + { + free(call->mszGroups); + call->mszGroups = NULL; + } + + if (status != SCARD_S_SUCCESS) + { + WLog_ERR(TAG, "SCardListReadersW failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_list_readers_return(smartcard, &ret, TRUE); + + if ((status = smartcard_pack_list_readers_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_list_readers_return failed with error %"PRId32"", status); + return status; + } + + if (mszReaders) + SCardFreeMemory(operation->hContext, mszReaders); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_GetStatusChangeA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + GetStatusChangeA_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(GetStatusChangeA_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_get_status_change_a_call(smartcard, irp->input, call))) + { + WLog_ERR(TAG, "smartcard_unpack_get_status_change_a_call failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_get_status_change_a_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_GetStatusChangeA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + UINT32 index; + GetStatusChange_Return ret; + LPSCARD_READERSTATEA rgReaderState = NULL; + IRP* irp = operation->irp; + GetStatusChangeA_Call* call = operation->call; + ret.ReturnCode = SCardGetStatusChangeA(operation->hContext, call->dwTimeOut, call->rgReaderStates, + call->cReaders); + ret.cReaders = call->cReaders; + ret.rgReaderStates = NULL; + + if (ret.cReaders > 0) + { + ret.rgReaderStates = (ReaderState_Return*) calloc(ret.cReaders, sizeof(ReaderState_Return)); + + if (!ret.rgReaderStates) + return STATUS_NO_MEMORY; + } + + for (index = 0; index < ret.cReaders; index++) + { + ret.rgReaderStates[index].dwCurrentState = call->rgReaderStates[index].dwCurrentState; + ret.rgReaderStates[index].dwEventState = call->rgReaderStates[index].dwEventState; + ret.rgReaderStates[index].cbAtr = call->rgReaderStates[index].cbAtr; + CopyMemory(&(ret.rgReaderStates[index].rgbAtr), &(call->rgReaderStates[index].rgbAtr), 32); + } + + smartcard_trace_get_status_change_return(smartcard, &ret, FALSE); + smartcard_pack_get_status_change_return(smartcard, irp->output, &ret); + + if (call->rgReaderStates) + { + for (index = 0; index < call->cReaders; index++) + { + rgReaderState = &call->rgReaderStates[index]; + free((void*)rgReaderState->szReader); + } + + free(call->rgReaderStates); + } + + free(ret.rgReaderStates); + return ret.ReturnCode; +} + +static LONG smartcard_GetStatusChangeW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + GetStatusChangeW_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(GetStatusChangeW_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_get_status_change_w_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_get_status_change_w_call failed with error %"PRId32"", status); + + smartcard_trace_get_status_change_w_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_GetStatusChangeW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + UINT32 index; + GetStatusChange_Return ret; + LPSCARD_READERSTATEW rgReaderState = NULL; + IRP* irp = operation->irp; + GetStatusChangeW_Call* call = operation->call; + ret.ReturnCode = SCardGetStatusChangeW(operation->hContext, call->dwTimeOut, + call->rgReaderStates, call->cReaders); + ret.cReaders = call->cReaders; + ret.rgReaderStates = NULL; + + if (ret.cReaders > 0) + { + ret.rgReaderStates = (ReaderState_Return*) calloc(ret.cReaders, sizeof(ReaderState_Return)); + + if (!ret.rgReaderStates) + return STATUS_NO_MEMORY; + } + + for (index = 0; index < ret.cReaders; index++) + { + ret.rgReaderStates[index].dwCurrentState = call->rgReaderStates[index].dwCurrentState; + ret.rgReaderStates[index].dwEventState = call->rgReaderStates[index].dwEventState; + ret.rgReaderStates[index].cbAtr = call->rgReaderStates[index].cbAtr; + CopyMemory(&(ret.rgReaderStates[index].rgbAtr), &(call->rgReaderStates[index].rgbAtr), 32); + } + + smartcard_trace_get_status_change_return(smartcard, &ret, TRUE); + smartcard_pack_get_status_change_return(smartcard, irp->output, &ret); + + if (call->rgReaderStates) + { + for (index = 0; index < call->cReaders; index++) + { + rgReaderState = &call->rgReaderStates[index]; + free((void*)rgReaderState->szReader); + } + + free(call->rgReaderStates); + } + + free(ret.rgReaderStates); + return ret.ReturnCode; +} + +static LONG smartcard_Cancel_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Context_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Context_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_context_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_context_call failed with error %"PRId32"", status); + + smartcard_trace_context_call(smartcard, call, "Cancel"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_Cancel_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Long_Return ret; + + if ((status = ret.ReturnCode = SCardCancel(operation->hContext))) + { + WLog_ERR(TAG, "SCardCancel failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_long_return(smartcard, &ret, "Cancel"); + return ret.ReturnCode; +} + +static LONG smartcard_ConnectA_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ConnectA_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ConnectA_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_connect_a_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_connect_a_call failed with error %"PRId32"", status); + + smartcard_trace_connect_a_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, + &(call->Common.hContext)); + return status; +} + +static LONG smartcard_ConnectA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + SCARDHANDLE hCard = 0; + Connect_Return ret = { 0 }; + IRP* irp = operation->irp; + ConnectA_Call* call = operation->call; + + if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) && + (call->Common.dwShareMode != SCARD_SHARE_DIRECT)) + { + call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx; + } + + status = ret.ReturnCode = SCardConnectA(operation->hContext, (char*) call->szReader, + call->Common.dwShareMode, + call->Common.dwPreferredProtocols, &hCard, &ret.dwActiveProtocol); + smartcard_scard_context_native_to_redir(smartcard, &(ret.hContext), operation->hContext); + smartcard_scard_handle_native_to_redir(smartcard, &(ret.hCard), hCard); + smartcard_trace_connect_return(smartcard, &ret); + + if (status) + { + WLog_ERR(TAG, "SCardConnectA failed with error %"PRId32"", status); + goto out_fail; + } + + if ((status = smartcard_pack_connect_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_connect_return failed with error %"PRId32"", status); + goto out_fail; + } + + status = ret.ReturnCode; +out_fail: + free(call->szReader); + return status; +} + +static LONG smartcard_ConnectW_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ConnectW_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(ConnectW_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_connect_w_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_connect_w_call failed with error %"PRId32"", status); + + smartcard_trace_connect_w_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, + &(call->Common.hContext)); + return status; +} + +static LONG smartcard_ConnectW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + SCARDHANDLE hCard = 0; + Connect_Return ret = { 0 }; + IRP* irp = operation->irp; + ConnectW_Call* call = operation->call; + + if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) && + (call->Common.dwShareMode != SCARD_SHARE_DIRECT)) + { + call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx; + } + + status = ret.ReturnCode = SCardConnectW(operation->hContext, (WCHAR*) call->szReader, + call->Common.dwShareMode, + call->Common.dwPreferredProtocols, &hCard, &ret.dwActiveProtocol); + smartcard_scard_context_native_to_redir(smartcard, &(ret.hContext), operation->hContext); + smartcard_scard_handle_native_to_redir(smartcard, &(ret.hCard), hCard); + smartcard_trace_connect_return(smartcard, &ret); + + if (status) + { + WLog_ERR(TAG, "SCardConnectW failed with error %"PRId32"", status); + goto out_fail; + } + + if ((status = smartcard_pack_connect_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_connect_return failed with error %"PRId32"", status); + goto out_fail; + } + + status = ret.ReturnCode; +out_fail: + free(call->szReader); + return status; +} + +static LONG smartcard_Reconnect_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Reconnect_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Reconnect_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_reconnect_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_reconnect_call failed with error %"PRId32"", status); + + smartcard_trace_reconnect_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_Reconnect_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Reconnect_Return ret; + IRP* irp = operation->irp; + Reconnect_Call* call = operation->call; + ret.ReturnCode = SCardReconnect(operation->hCard, call->dwShareMode, + call->dwPreferredProtocols, call->dwInitialization, &ret.dwActiveProtocol); + smartcard_trace_reconnect_return(smartcard, &ret); + + if ((status = smartcard_pack_reconnect_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_reconnect_return failed with error %"PRId32"", status); + return status; + } + + return ret.ReturnCode; +} + +static LONG smartcard_Disconnect_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + HCardAndDisposition_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(HCardAndDisposition_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_hcard_and_disposition_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_hcard_and_disposition_call failed with error %"PRId32"", status); + + smartcard_trace_hcard_and_disposition_call(smartcard, call, "Disconnect"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_Disconnect_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Long_Return ret; + HCardAndDisposition_Call* call = operation->call; + + if ((status = ret.ReturnCode = SCardDisconnect(operation->hCard, call->dwDisposition))) + { + WLog_ERR(TAG, "SCardDisconnect failed with error %"PRId32"", status); + return status; + } + + smartcard_trace_long_return(smartcard, &ret, "Disconnect"); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_BeginTransaction_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + HCardAndDisposition_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(HCardAndDisposition_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_hcard_and_disposition_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_hcard_and_disposition_call failed with error %"PRId32"", status); + + smartcard_trace_hcard_and_disposition_call(smartcard, call, "BeginTransaction"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_BeginTransaction_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret; + + if ((ret.ReturnCode = SCardBeginTransaction(operation->hCard))) + { + WLog_ERR(TAG, "SCardBeginTransaction failed with error %"PRId32"", ret.ReturnCode); + return ret.ReturnCode; + } + + smartcard_trace_long_return(smartcard, &ret, "BeginTransaction"); + return ret.ReturnCode; +} + +static LONG smartcard_EndTransaction_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + HCardAndDisposition_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(HCardAndDisposition_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_hcard_and_disposition_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_hcard_and_disposition_call failed with error %"PRId32"", status); + + smartcard_trace_hcard_and_disposition_call(smartcard, call, "EndTransaction"); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_EndTransaction_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret; + HCardAndDisposition_Call* call = operation->call; + + if ((ret.ReturnCode = SCardEndTransaction(operation->hCard, call->dwDisposition))) + { + WLog_ERR(TAG, "SCardEndTransaction failed with error %"PRId32"", ret.ReturnCode); + return ret.ReturnCode; + } + + smartcard_trace_long_return(smartcard, &ret, "EndTransaction"); + return ret.ReturnCode; +} + +static LONG smartcard_State_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + State_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(State_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_state_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_state_call failed with error %"PRId32"", status); + + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_State_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + State_Return ret; + IRP* irp = operation->irp; + ret.cbAtrLen = SCARD_ATR_LENGTH; + ret.ReturnCode = SCardState(operation->hCard, &ret.dwState, &ret.dwProtocol, (BYTE*) &ret.rgAtr, + &ret.cbAtrLen); + + if ((status = smartcard_pack_state_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_state_return failed with error %"PRId32"", status); + return status; + } + + return ret.ReturnCode; +} + +static LONG smartcard_StatusA_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Status_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Status_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_status_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_status_call failed with error %"PRId32"", status); + + smartcard_trace_status_call(smartcard, call, FALSE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_StatusA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Status_Return ret = { 0 }; + DWORD cchReaderLen = 0; + DWORD cbAtrLen = 0; + LPSTR mszReaderNames = NULL; + IRP* irp = operation->irp; + Status_Call* call = operation->call; + ZeroMemory(ret.pbAtr, 32); + call->cbAtrLen = 32; + cbAtrLen = call->cbAtrLen; + + if (call->fmszReaderNamesIsNULL) + cchReaderLen = 0; + else + cchReaderLen = SCARD_AUTOALLOCATE; + + status = ret.ReturnCode = SCardStatusA(operation->hCard, + call->fmszReaderNamesIsNULL ? NULL : (LPSTR) &mszReaderNames, + &cchReaderLen, &ret.dwState, &ret.dwProtocol, + cbAtrLen ? (BYTE*) &ret.pbAtr : NULL, &cbAtrLen); + + if (status == SCARD_S_SUCCESS) + { + if (!call->fmszReaderNamesIsNULL) + ret.mszReaderNames = (BYTE*) mszReaderNames; + + ret.cBytes = cchReaderLen; + + if (call->cbAtrLen) + ret.cbAtrLen = cbAtrLen; + } + + smartcard_trace_status_return(smartcard, &ret, FALSE); + + if ((status = smartcard_pack_status_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_status_return failed with error %"PRId32"", status); + return status; + } + + if (mszReaderNames) + { + SCardFreeMemory(operation->hContext, mszReaderNames); + } + + return ret.ReturnCode; +} + +static LONG smartcard_StatusW_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Status_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Status_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_status_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_status_call failed with error %"PRId32"", status); + + smartcard_trace_status_call(smartcard, call, TRUE); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_StatusW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Status_Return ret; + DWORD cchReaderLen = 0; + LPWSTR mszReaderNames = NULL; + IRP* irp = operation->irp; + Status_Call* call = operation->call; + DWORD cbAtrLen; + + if (call->cbAtrLen > 32) + call->cbAtrLen = 32; + + if (call->fmszReaderNamesIsNULL) + cchReaderLen = 0; + else + cchReaderLen = SCARD_AUTOALLOCATE; + + cbAtrLen = call->cbAtrLen; + ZeroMemory(ret.pbAtr, 32); + status = ret.ReturnCode = SCardStatusW(operation->hCard, + call->fmszReaderNamesIsNULL ? NULL : (LPWSTR) &mszReaderNames, + &cchReaderLen, &ret.dwState, &ret.dwProtocol, (BYTE*) &ret.pbAtr, &cbAtrLen); + + if (status == SCARD_S_SUCCESS) + { + if (!call->fmszReaderNamesIsNULL) + ret.mszReaderNames = (BYTE*) mszReaderNames; + + // WinScard returns the number of CHARACTERS whereas pcsc-lite returns the + // number of BYTES. +#ifdef _WIN32 + ret.cBytes = cchReaderLen * 2; +#else + ret.cBytes = cchReaderLen; +#endif + + if (call->cbAtrLen) + ret.cbAtrLen = cbAtrLen; + } + + smartcard_trace_status_return(smartcard, &ret, TRUE); + + if ((status = smartcard_pack_status_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_status_return failed with error %"PRId32"", status); + return status; + } + + if (mszReaderNames) + SCardFreeMemory(operation->hContext, mszReaderNames); + + return ret.ReturnCode; +} + +static LONG smartcard_Transmit_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Transmit_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Transmit_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_transmit_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_transmit_call failed with error %"PRId32"", status); + + smartcard_trace_transmit_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_Transmit_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Transmit_Return ret; + IRP* irp = operation->irp; + Transmit_Call* call = operation->call; + ret.cbRecvLength = 0; + ret.pbRecvBuffer = NULL; + + if (call->cbRecvLength && !call->fpbRecvBufferIsNULL) + { + if (call->cbRecvLength >= 66560) + call->cbRecvLength = 66560; + + ret.cbRecvLength = call->cbRecvLength; + ret.pbRecvBuffer = (BYTE*) malloc(ret.cbRecvLength); + + if (!ret.pbRecvBuffer) + return STATUS_NO_MEMORY; + } + + ret.pioRecvPci = call->pioRecvPci; + ret.ReturnCode = SCardTransmit(operation->hCard, call->pioSendPci, call->pbSendBuffer, + call->cbSendLength, ret.pioRecvPci, ret.pbRecvBuffer, &(ret.cbRecvLength)); + smartcard_trace_transmit_return(smartcard, &ret); + + if ((status = smartcard_pack_transmit_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_transmit_return failed with error %"PRId32"", status); + return status; + } + + free(call->pbSendBuffer); + free(ret.pbRecvBuffer); + free(call->pioSendPci); + free(call->pioRecvPci); + return ret.ReturnCode; +} + +static LONG smartcard_Control_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Control_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Control_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_control_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_control_call failed with error %"PRId32"", status); + + smartcard_trace_control_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_Control_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Control_Return ret; + IRP* irp = operation->irp; + Control_Call* call = operation->call; + ret.cbOutBufferSize = call->cbOutBufferSize; + ret.pvOutBuffer = (BYTE*) malloc(call->cbOutBufferSize); + + if (!ret.pvOutBuffer) + return SCARD_E_NO_MEMORY; + + ret.ReturnCode = SCardControl(operation->hCard, + call->dwControlCode, call->pvInBuffer, call->cbInBufferSize, + ret.pvOutBuffer, call->cbOutBufferSize, &ret.cbOutBufferSize); + smartcard_trace_control_return(smartcard, &ret); + + if ((status = smartcard_pack_control_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_control_return failed with error %"PRId32"", status); + return status; + } + + free(call->pvInBuffer); + free(ret.pvOutBuffer); + return ret.ReturnCode; +} + +static LONG smartcard_GetAttrib_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + GetAttrib_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(GetAttrib_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_get_attrib_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_get_attrib_call failed with error %"PRId32"", status); + + smartcard_trace_get_attrib_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + operation->hCard = smartcard_scard_handle_native_from_redir(smartcard, &(call->hCard)); + return status; +} + +static LONG smartcard_GetAttrib_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + DWORD cbAttrLen; + BOOL autoAllocate; + GetAttrib_Return ret; + IRP* irp = operation->irp; + GetAttrib_Call* call = operation->call; + ret.pbAttr = NULL; + + if (call->fpbAttrIsNULL) + call->cbAttrLen = 0; + + autoAllocate = (call->cbAttrLen == SCARD_AUTOALLOCATE) ? TRUE : FALSE; + + if (call->cbAttrLen && !autoAllocate) + { + ret.pbAttr = (BYTE*) malloc(call->cbAttrLen); + + if (!ret.pbAttr) + return SCARD_E_NO_MEMORY; + } + + cbAttrLen = call->cbAttrLen; + ret.ReturnCode = SCardGetAttrib(operation->hCard, call->dwAttrId, + autoAllocate ? (LPBYTE) & (ret.pbAttr) : ret.pbAttr, &cbAttrLen); + ret.cbAttrLen = cbAttrLen; + smartcard_trace_get_attrib_return(smartcard, &ret, call->dwAttrId); + + if (ret.ReturnCode) + { + WLog_WARN(TAG, "SCardGetAttrib: %s (0x%08"PRIX32") cbAttrLen: %"PRIu32"", + SCardGetAttributeString(call->dwAttrId), call->dwAttrId, call->cbAttrLen); + Stream_Zero(irp->output, 256); + free(ret.pbAttr); + return ret.ReturnCode; + } + + if ((status = smartcard_pack_get_attrib_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_get_attrib_return failed with error %"PRId32"", status); + return status; + } + + free(ret.pbAttr); + return ret.ReturnCode; +} + +static LONG smartcard_AccessStartedEvent_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(Long_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if (Stream_GetRemainingLength(irp->input) < 4) + { + WLog_WARN(TAG, "AccessStartedEvent is too short: %"PRIuz"", + Stream_GetRemainingLength(irp->input)); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Read_UINT32(irp->input, call->LongValue); /* Unused (4 bytes) */ + return SCARD_S_SUCCESS; +} + +static LONG smartcard_AccessStartedEvent_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status = SCARD_S_SUCCESS; + + if (!smartcard->StartedEvent) + smartcard->StartedEvent = SCardAccessStartedEvent(); + + if (!smartcard->StartedEvent) + status = SCARD_E_NO_SERVICE; + + return status; +} + +static LONG smartcard_LocateCardsByATRA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + LocateCardsByATRA_Call* call; + IRP* irp = operation->irp; + operation->call = call = calloc(1, sizeof(LocateCardsByATRA_Call)); + + if (!call) + return STATUS_NO_MEMORY; + + if ((status = smartcard_unpack_locate_cards_by_atr_a_call(smartcard, irp->input, call))) + WLog_ERR(TAG, "smartcard_unpack_locate_cards_by_atr_a_call failed with error %"PRId32"", status); + + smartcard_trace_locate_cards_by_atr_a_call(smartcard, call); + operation->hContext = smartcard_scard_context_native_from_redir(smartcard, &(call->hContext)); + return status; +} + +static LONG smartcard_LocateCardsByATRA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + DWORD i, j, k; + GetStatusChange_Return ret; + LPSCARD_READERSTATEA state = NULL; + LPSCARD_READERSTATEA states = NULL; + IRP* irp = operation->irp; + LocateCardsByATRA_Call* call = operation->call; + states = (LPSCARD_READERSTATEA) calloc(call->cReaders, sizeof(SCARD_READERSTATEA)); + + if (!states) + return STATUS_NO_MEMORY; + + for (i = 0; i < call->cReaders; i++) + { + states[i].szReader = (LPCSTR) call->rgReaderStates[i].szReader; + states[i].dwCurrentState = call->rgReaderStates[i].Common.dwCurrentState; + states[i].dwEventState = call->rgReaderStates[i].Common.dwEventState; + states[i].cbAtr = call->rgReaderStates[i].Common.cbAtr; + CopyMemory(&(states[i].rgbAtr), &(call->rgReaderStates[i].Common.rgbAtr), 36); + } + + status = ret.ReturnCode = SCardGetStatusChangeA(operation->hContext, 0x000001F4, states, + call->cReaders); + + if (status && (status != SCARD_E_TIMEOUT) && (status != SCARD_E_CANCELLED)) + { + call->cReaders = 0; + } + + for (i = 0; i < call->cAtrs; i++) + { + for (j = 0; j < call->cReaders; j++) + { + for (k = 0; k < call->rgAtrMasks[i].cbAtr; k++) + { + if ((call->rgAtrMasks[i].rgbAtr[k] & call->rgAtrMasks[i].rgbMask[k]) != + (states[j].rgbAtr[k] & call->rgAtrMasks[i].rgbMask[k])) + { + break; + } + + states[j].dwEventState |= SCARD_STATE_ATRMATCH; + } + } + } + + ret.cReaders = call->cReaders; + ret.rgReaderStates = NULL; + + if (ret.cReaders > 0) + ret.rgReaderStates = (ReaderState_Return*) calloc(ret.cReaders, sizeof(ReaderState_Return)); + + if (!ret.rgReaderStates) + return STATUS_NO_MEMORY; + + for (i = 0; i < ret.cReaders; i++) + { + state = &states[i]; + ret.rgReaderStates[i].dwCurrentState = state->dwCurrentState; + ret.rgReaderStates[i].dwEventState = state->dwEventState; + ret.rgReaderStates[i].cbAtr = state->cbAtr; + CopyMemory(&(ret.rgReaderStates[i].rgbAtr), &(state->rgbAtr), 32); + } + + free(states); + smartcard_trace_get_status_change_return(smartcard, &ret, FALSE); + + if ((status = smartcard_pack_get_status_change_return(smartcard, irp->output, &ret))) + { + WLog_ERR(TAG, "smartcard_pack_get_status_change_return failed with error %"PRId32"", status); + return status; + } + + if (call->rgReaderStates) + { + for (i = 0; i < call->cReaders; i++) + { + state = (LPSCARD_READERSTATEA) &call->rgReaderStates[i]; + + if (state->szReader) + { + free((void*) state->szReader); + state->szReader = NULL; + } + } + + free(call->rgReaderStates); + call->rgReaderStates = NULL; + } + + free(ret.rgReaderStates); + return ret.ReturnCode; +} + +LONG smartcard_irp_device_control_decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + UINT32 offset; + UINT32 ioControlCode; + UINT32 outputBufferLength; + UINT32 inputBufferLength; + IRP* irp = operation->irp; + + /* Device Control Request */ + + if (Stream_GetRemainingLength(irp->input) < 32) + { + WLog_WARN(TAG, "Device Control Request is too short: %"PRIuz"", + Stream_GetRemainingLength(irp->input)); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Read_UINT32(irp->input, outputBufferLength); /* OutputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, inputBufferLength); /* InputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, ioControlCode); /* IoControlCode (4 bytes) */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + operation->ioControlCode = ioControlCode; + + if (Stream_Length(irp->input) != (Stream_GetPosition(irp->input) + inputBufferLength)) + { + WLog_WARN(TAG, "InputBufferLength mismatch: Actual: %"PRIuz" Expected: %"PRIuz"", + Stream_Length(irp->input), + Stream_GetPosition(irp->input) + inputBufferLength); + return SCARD_F_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "%s (0x%08"PRIX32") FileId: %"PRIu32" CompletionId: %"PRIu32"", + smartcard_get_ioctl_string(ioControlCode, TRUE), + ioControlCode, irp->FileId, irp->CompletionId); + + if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioControlCode != SCARD_IOCTL_RELEASESTARTEDEVENT)) + { + if ((status = smartcard_unpack_common_type_header(smartcard, irp->input))) + { + WLog_ERR(TAG, "smartcard_unpack_common_type_header failed with error %"PRId32"", status); + return SCARD_F_INTERNAL_ERROR; + } + + if ((status = smartcard_unpack_private_type_header(smartcard, irp->input))) + { + WLog_ERR(TAG, "smartcard_unpack_common_type_header failed with error %"PRId32"", status); + return SCARD_F_INTERNAL_ERROR; + } + } + + /* Decode */ + operation->call = NULL; + + switch (ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + status = smartcard_EstablishContext_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_RELEASECONTEXT: + status = smartcard_ReleaseContext_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_ISVALIDCONTEXT: + status = smartcard_IsValidContext_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERGROUPSA: + status = smartcard_ListReaderGroupsA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERGROUPSW: + status = smartcard_ListReaderGroupsW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERSA: + status = smartcard_ListReadersA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERSW: + status = smartcard_ListReadersW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERGROUPA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERGROUPW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_LOCATECARDSA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_LOCATECARDSW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETSTATUSCHANGEA: + status = smartcard_GetStatusChangeA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + status = smartcard_GetStatusChangeW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_CANCEL: + status = smartcard_Cancel_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_CONNECTA: + status = smartcard_ConnectA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_CONNECTW: + status = smartcard_ConnectW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_RECONNECT: + status = smartcard_Reconnect_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_DISCONNECT: + status = smartcard_Disconnect_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_BEGINTRANSACTION: + status = smartcard_BeginTransaction_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_ENDTRANSACTION: + status = smartcard_EndTransaction_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_STATE: + status = smartcard_State_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_STATUSA: + status = smartcard_StatusA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_STATUSW: + status = smartcard_StatusW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_TRANSMIT: + status = smartcard_Transmit_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_CONTROL: + status = smartcard_Control_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_GETATTRIB: + status = smartcard_GetAttrib_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_SETATTRIB: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + status = smartcard_AccessStartedEvent_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSBYATRA: + status = smartcard_LocateCardsByATRA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSBYATRW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_READCACHEA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_READCACHEW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_WRITECACHEA: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_WRITECACHEW: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_RELEASESTARTEDEVENT: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETREADERICON: + status = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETDEVICETYPEID: + status = SCARD_F_INTERNAL_ERROR; + break; + + default: + status = SCARD_F_INTERNAL_ERROR; + break; + } + + if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioControlCode != SCARD_IOCTL_RELEASESTARTEDEVENT)) + { + offset = (RDPDR_DEVICE_IO_REQUEST_LENGTH + RDPDR_DEVICE_IO_CONTROL_REQ_HDR_LENGTH); + smartcard_unpack_read_size_align(smartcard, irp->input, + Stream_GetPosition(irp->input) - offset, 8); + } + + if (Stream_GetPosition(irp->input) < Stream_Length(irp->input)) + { + SIZE_T difference; + difference = Stream_Length(irp->input) - Stream_GetPosition(irp->input); + WLog_WARN(TAG, + "IRP was not fully parsed %s (0x%08"PRIX32"): Actual: %"PRIuz", Expected: %"PRIuz", Difference: %"PRIuz"", + smartcard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, + Stream_GetPosition(irp->input), Stream_Length(irp->input), difference); + winpr_HexDump(TAG, WLOG_WARN, Stream_Pointer(irp->input), difference); + } + + if (Stream_GetPosition(irp->input) > Stream_Length(irp->input)) + { + SIZE_T difference; + difference = Stream_GetPosition(irp->input) - Stream_Length(irp->input); + WLog_WARN(TAG, + "IRP was parsed beyond its end %s (0x%08"PRIX32"): Actual: %"PRIuz", Expected: %"PRIuz", Difference: %"PRIuz"", + smartcard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, + Stream_GetPosition(irp->input), Stream_Length(irp->input), difference); + } + + if (status != SCARD_S_SUCCESS) + { + free(operation->call); + operation->call = NULL; + } + + return status; +} + +LONG smartcard_irp_device_control_call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + IRP* irp; + LONG result; + UINT32 offset; + UINT32 ioControlCode; + UINT32 outputBufferLength; + UINT32 objectBufferLength; + irp = operation->irp; + ioControlCode = operation->ioControlCode; + /** + * [MS-RDPESC] 3.2.5.1: Sending Outgoing Messages: + * the output buffer length SHOULD be set to 2048 + * + * Since it's a SHOULD and not a MUST, we don't care + * about it, but we still reserve at least 2048 bytes. + */ + Stream_EnsureRemainingCapacity(irp->output, 2048); + /* Device Control Response */ + Stream_Seek_UINT32(irp->output); /* OutputBufferLength (4 bytes) */ + Stream_Seek(irp->output, SMARTCARD_COMMON_TYPE_HEADER_LENGTH); /* CommonTypeHeader (8 bytes) */ + Stream_Seek(irp->output, SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH); /* PrivateTypeHeader (8 bytes) */ + Stream_Seek_UINT32(irp->output); /* Result (4 bytes) */ + + /* Call */ + + switch (ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + result = smartcard_EstablishContext_Call(smartcard, operation); + break; + + case SCARD_IOCTL_RELEASECONTEXT: + result = smartcard_ReleaseContext_Call(smartcard, operation); + break; + + case SCARD_IOCTL_ISVALIDCONTEXT: + result = smartcard_IsValidContext_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERGROUPSA: + result = smartcard_ListReaderGroupsA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERGROUPSW: + result = smartcard_ListReaderGroupsW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERSA: + result = smartcard_ListReadersA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LISTREADERSW: + result = smartcard_ListReadersW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERGROUPA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERGROUPW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_INTRODUCEREADERW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_FORGETREADERW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_LOCATECARDSA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_LOCATECARDSW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETSTATUSCHANGEA: + result = smartcard_GetStatusChangeA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + result = smartcard_GetStatusChangeW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_CANCEL: + result = smartcard_Cancel_Call(smartcard, operation); + break; + + case SCARD_IOCTL_CONNECTA: + result = smartcard_ConnectA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_CONNECTW: + result = smartcard_ConnectW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_RECONNECT: + result = smartcard_Reconnect_Call(smartcard, operation); + break; + + case SCARD_IOCTL_DISCONNECT: + result = smartcard_Disconnect_Call(smartcard, operation); + break; + + case SCARD_IOCTL_BEGINTRANSACTION: + result = smartcard_BeginTransaction_Call(smartcard, operation); + break; + + case SCARD_IOCTL_ENDTRANSACTION: + result = smartcard_EndTransaction_Call(smartcard, operation); + break; + + case SCARD_IOCTL_STATE: + result = smartcard_State_Call(smartcard, operation); + break; + + case SCARD_IOCTL_STATUSA: + result = smartcard_StatusA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_STATUSW: + result = smartcard_StatusW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_TRANSMIT: + result = smartcard_Transmit_Call(smartcard, operation); + break; + + case SCARD_IOCTL_CONTROL: + result = smartcard_Control_Call(smartcard, operation); + break; + + case SCARD_IOCTL_GETATTRIB: + result = smartcard_GetAttrib_Call(smartcard, operation); + break; + + case SCARD_IOCTL_SETATTRIB: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + result = smartcard_AccessStartedEvent_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSBYATRA: + result = smartcard_LocateCardsByATRA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSBYATRW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_READCACHEA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_READCACHEW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_WRITECACHEA: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_WRITECACHEW: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_RELEASESTARTEDEVENT: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETREADERICON: + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GETDEVICETYPEID: + result = SCARD_F_INTERNAL_ERROR; + break; + + default: + result = STATUS_UNSUCCESSFUL; + break; + } + + free(operation->call); + operation->call = NULL; + + /** + * [MS-RPCE] 2.2.6.3 Primitive Type Serialization + * The type MUST be aligned on an 8-byte boundary. If the size of the + * primitive type is not a multiple of 8 bytes, the data MUST be padded. + */ + + if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioControlCode != SCARD_IOCTL_RELEASESTARTEDEVENT)) + { + offset = (RDPDR_DEVICE_IO_RESPONSE_LENGTH + RDPDR_DEVICE_IO_CONTROL_RSP_HDR_LENGTH); + smartcard_pack_write_size_align(smartcard, irp->output, Stream_GetPosition(irp->output) - offset, + 8); + } + + if ((result != SCARD_S_SUCCESS) && (result != SCARD_E_TIMEOUT) && + (result != SCARD_E_NO_READERS_AVAILABLE) && (result != SCARD_E_NO_SERVICE)) + { + WLog_WARN(TAG, "IRP failure: %s (0x%08"PRIX32"), status: %s (0x%08"PRIX32")", + smartcard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, + SCardGetErrorString(result), result); + } + + irp->IoStatus = STATUS_SUCCESS; + + if ((result & 0xC0000000) == 0xC0000000) + { + /* NTSTATUS error */ + irp->IoStatus = (UINT32)result; + WLog_WARN(TAG, "IRP failure: %s (0x%08"PRIX32"), ntstatus: 0x%08"PRIX32"", + smartcard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, result); + } + + Stream_SealLength(irp->output); + outputBufferLength = Stream_Length(irp->output) - RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4; + objectBufferLength = outputBufferLength - RDPDR_DEVICE_IO_RESPONSE_LENGTH; + Stream_SetPosition(irp->output, RDPDR_DEVICE_IO_RESPONSE_LENGTH); + /* Device Control Response */ + Stream_Write_UINT32(irp->output, outputBufferLength); /* OutputBufferLength (4 bytes) */ + smartcard_pack_common_type_header(smartcard, irp->output); /* CommonTypeHeader (8 bytes) */ + smartcard_pack_private_type_header(smartcard, irp->output, + objectBufferLength); /* PrivateTypeHeader (8 bytes) */ + Stream_Write_UINT32(irp->output, result); /* Result (4 bytes) */ + Stream_SetPosition(irp->output, Stream_Length(irp->output)); + return SCARD_S_SUCCESS; +} + diff --git a/channels/smartcard/client/smartcard_pack.c b/channels/smartcard/client/smartcard_pack.c new file mode 100644 index 0000000..b2f3563 --- /dev/null +++ b/channels/smartcard/client/smartcard_pack.c @@ -0,0 +1,2922 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smart Card Structure Packing + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "smartcard_pack.h" + +LONG smartcard_unpack_common_type_header(SMARTCARD_DEVICE* smartcard, wStream* s) +{ + UINT8 version; + UINT32 filler; + UINT8 endianness; + UINT16 commonHeaderLength; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "CommonTypeHeader is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + /* Process CommonTypeHeader */ + Stream_Read_UINT8(s, version); /* Version (1 byte) */ + Stream_Read_UINT8(s, endianness); /* Endianness (1 byte) */ + Stream_Read_UINT16(s, commonHeaderLength); /* CommonHeaderLength (2 bytes) */ + Stream_Read_UINT32(s, filler); /* Filler (4 bytes), should be 0xCCCCCCCC */ + + if (version != 1) + { + WLog_WARN(TAG, "Unsupported CommonTypeHeader Version %"PRIu8"", version); + return STATUS_INVALID_PARAMETER; + } + + if (endianness != 0x10) + { + WLog_WARN(TAG, "Unsupported CommonTypeHeader Endianness %"PRIu8"", endianness); + return STATUS_INVALID_PARAMETER; + } + + if (commonHeaderLength != 8) + { + WLog_WARN(TAG, "Unsupported CommonTypeHeader CommonHeaderLength %"PRIu16"", commonHeaderLength); + return STATUS_INVALID_PARAMETER; + } + + if (filler != 0xCCCCCCCC) + { + WLog_WARN(TAG, "Unexpected CommonTypeHeader Filler 0x%08"PRIX32"", filler); + return STATUS_INVALID_PARAMETER; + } + + return SCARD_S_SUCCESS; +} + +void smartcard_pack_common_type_header(SMARTCARD_DEVICE* smartcard, wStream* s) +{ + Stream_Write_UINT8(s, 1); /* Version (1 byte) */ + Stream_Write_UINT8(s, 0x10); /* Endianness (1 byte) */ + Stream_Write_UINT16(s, 8); /* CommonHeaderLength (2 bytes) */ + Stream_Write_UINT32(s, 0xCCCCCCCC); /* Filler (4 bytes), should be 0xCCCCCCCC */ +} + +LONG smartcard_unpack_private_type_header(SMARTCARD_DEVICE* smartcard, wStream* s) +{ + UINT32 filler; + UINT32 objectBufferLength; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "PrivateTypeHeader is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */ + Stream_Read_UINT32(s, filler); /* Filler (4 bytes), should be 0x00000000 */ + + if (filler != 0x00000000) + { + WLog_WARN(TAG, "Unexpected PrivateTypeHeader Filler 0x%08"PRIX32"", filler); + return STATUS_INVALID_PARAMETER; + } + + if (objectBufferLength != Stream_GetRemainingLength(s)) + { + WLog_WARN(TAG, + "PrivateTypeHeader ObjectBufferLength mismatch: Actual: %"PRIu32", Expected: %"PRIuz"", + objectBufferLength, Stream_GetRemainingLength(s)); + return STATUS_INVALID_PARAMETER; + } + + return SCARD_S_SUCCESS; +} + +void smartcard_pack_private_type_header(SMARTCARD_DEVICE* smartcard, wStream* s, + UINT32 objectBufferLength) +{ + Stream_Write_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */ + Stream_Write_UINT32(s, 0x00000000); /* Filler (4 bytes), should be 0x00000000 */ +} + +LONG smartcard_unpack_read_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, UINT32 size, + UINT32 alignment) +{ + UINT32 pad; + pad = size; + size = (size + alignment - 1) & ~(alignment - 1); + pad = size - pad; + + if (pad) + Stream_Seek(s, pad); + + return pad; +} + +LONG smartcard_pack_write_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, UINT32 size, + UINT32 alignment) +{ + UINT32 pad; + pad = size; + size = (size + alignment - 1) & ~(alignment - 1); + pad = size - pad; + + if (pad) + { + if (!Stream_EnsureRemainingCapacity(s, pad)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Zero(s, pad); + } + + return SCARD_S_SUCCESS; +} + +SCARDCONTEXT smartcard_scard_context_native_from_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDCONTEXT* context) +{ + SCARDCONTEXT hContext = 0; + + if ((context->cbContext != sizeof(ULONG_PTR)) && (context->cbContext != 0)) + { + WLog_WARN(TAG, + "REDIR_SCARDCONTEXT does not match native size: Actual: %"PRIu32", Expected: %"PRIuz"", + context->cbContext, sizeof(ULONG_PTR)); + return 0; + } + + if (context->cbContext) + CopyMemory(&hContext, &(context->pbContext), context->cbContext); + else + ZeroMemory(&hContext, sizeof(ULONG_PTR)); + + return hContext; +} + +void smartcard_scard_context_native_to_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDCONTEXT* context, SCARDCONTEXT hContext) +{ + ZeroMemory(context, sizeof(REDIR_SCARDCONTEXT)); + context->cbContext = sizeof(ULONG_PTR); + CopyMemory(&(context->pbContext), &hContext, context->cbContext); +} + +SCARDHANDLE smartcard_scard_handle_native_from_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDHANDLE* handle) +{ + SCARDHANDLE hCard = 0; + + if (handle->cbHandle != sizeof(ULONG_PTR)) + { + WLog_WARN(TAG, + "REDIR_SCARDHANDLE does not match native size: Actual: %"PRIu32", Expected: %"PRIuz"", + handle->cbHandle, sizeof(ULONG_PTR)); + return 0; + } + + if (handle->cbHandle) + CopyMemory(&hCard, &(handle->pbHandle), handle->cbHandle); + + return hCard; +} + +void smartcard_scard_handle_native_to_redir(SMARTCARD_DEVICE* smartcard, REDIR_SCARDHANDLE* handle, + SCARDHANDLE hCard) +{ + ZeroMemory(handle, sizeof(REDIR_SCARDHANDLE)); + handle->cbHandle = sizeof(ULONG_PTR); + CopyMemory(&(handle->pbHandle), &hCard, handle->cbHandle); +} + +LONG smartcard_unpack_redir_scard_context(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context) +{ + UINT32 pbContextNdrPtr; + ZeroMemory(context, sizeof(REDIR_SCARDCONTEXT)); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, context->cbContext); /* cbContext (4 bytes) */ + + if (Stream_GetRemainingLength(s) < context->cbContext) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), context->cbContext); + return STATUS_BUFFER_TOO_SMALL; + } + + if ((context->cbContext != 0) && (context->cbContext != 4) && (context->cbContext != 8)) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT length is not 0, 4 or 8: %"PRIu32"", context->cbContext); + return STATUS_INVALID_PARAMETER; + } + + Stream_Read_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */ + + if (((context->cbContext == 0) && pbContextNdrPtr) || ((context->cbContext != 0) && + !pbContextNdrPtr)) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT cbContext (%"PRIu32") pbContextNdrPtr (%"PRIu32") inconsistency", + context->cbContext, pbContextNdrPtr); + return STATUS_INVALID_PARAMETER; + } + + if (context->cbContext > Stream_GetRemainingLength(s)) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT is too long: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), context->cbContext); + return STATUS_INVALID_PARAMETER; + } + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_context(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context) +{ + UINT32 pbContextNdrPtr; + pbContextNdrPtr = (context->cbContext) ? 0x00020001 : 0; + Stream_Write_UINT32(s, context->cbContext); /* cbContext (4 bytes) */ + Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */ + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context) +{ + UINT32 length; + + if (context->cbContext == 0) + return SCARD_S_SUCCESS; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT is too short: Actual: %"PRIuz", Expected: 4", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length != context->cbContext) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT length (%"PRIu32") cbContext (%"PRIu32") mismatch", + length, context->cbContext); + return STATUS_INVALID_PARAMETER; + } + + if ((context->cbContext != 0) && (context->cbContext != 4) && (context->cbContext != 8)) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT length is not 4 or 8: %"PRIu32"", context->cbContext); + return STATUS_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < context->cbContext) + { + WLog_WARN(TAG, "REDIR_SCARDCONTEXT is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), context->cbContext); + return STATUS_BUFFER_TOO_SMALL; + } + + if (context->cbContext) + Stream_Read(s, &(context->pbContext), context->cbContext); + else + ZeroMemory(&(context->pbContext), sizeof(context->pbContext)); + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context) +{ + Stream_Write_UINT32(s, context->cbContext); /* Length (4 bytes) */ + + if (context->cbContext) + { + Stream_Write(s, &(context->pbContext), context->cbContext); + } + + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle) +{ + UINT32 pbHandleNdrPtr; + ZeroMemory(handle, sizeof(REDIR_SCARDHANDLE)); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "SCARDHANDLE is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, handle->cbHandle); /* Length (4 bytes) */ + + if ((Stream_GetRemainingLength(s) < handle->cbHandle) || (!handle->cbHandle)) + { + WLog_WARN(TAG, "SCARDHANDLE is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), handle->cbHandle); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, pbHandleNdrPtr); /* NdrPtr (4 bytes) */ + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle) +{ + UINT32 pbHandleNdrPtr; + pbHandleNdrPtr = (handle->cbHandle) ? 0x00020002 : 0; + Stream_Write_UINT32(s, handle->cbHandle); /* cbHandle (4 bytes) */ + Stream_Write_UINT32(s, pbHandleNdrPtr); /* pbHandleNdrPtr (4 bytes) */ + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle) +{ + UINT32 length; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "REDIR_SCARDHANDLE is too short: Actual: %"PRIuz", Expected: 4", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length != handle->cbHandle) + { + WLog_WARN(TAG, "REDIR_SCARDHANDLE length (%"PRIu32") cbHandle (%"PRIu32") mismatch", + length, handle->cbHandle); + return STATUS_INVALID_PARAMETER; + } + + if ((handle->cbHandle != 4) && (handle->cbHandle != 8)) + { + WLog_WARN(TAG, "REDIR_SCARDHANDLE length is not 4 or 8: %"PRIu32"", handle->cbHandle); + return STATUS_INVALID_PARAMETER; + } + + if ((Stream_GetRemainingLength(s) < handle->cbHandle) || (!handle->cbHandle)) + { + WLog_WARN(TAG, "REDIR_SCARDHANDLE is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), handle->cbHandle); + return STATUS_BUFFER_TOO_SMALL; + } + + if (handle->cbHandle) + Stream_Read(s, &(handle->pbHandle), handle->cbHandle); + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle) +{ + Stream_Write_UINT32(s, handle->cbHandle); /* Length (4 bytes) */ + + if (handle->cbHandle) + Stream_Write(s, &(handle->pbHandle), handle->cbHandle); + + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_establish_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, + EstablishContext_Call* call) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "EstablishContext_Call is too short: Actual: %"PRIuz", Expected: 4", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwScope); /* dwScope (4 bytes) */ + return SCARD_S_SUCCESS; +} + +void smartcard_trace_establish_context_call(SMARTCARD_DEVICE* smartcard, + EstablishContext_Call* call) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "EstablishContext_Call {"); + WLog_DBG(TAG, "dwScope: %s (0x%08"PRIX32")", + SCardGetScopeString(call->dwScope), call->dwScope); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_establish_context_return(SMARTCARD_DEVICE* smartcard, wStream* s, + EstablishContext_Return* ret) +{ + LONG status; + + if ((status = smartcard_pack_redir_scard_context(smartcard, s, &(ret->hContext)))) + { + WLog_ERR(TAG, "smartcard_pack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_pack_redir_scard_context_ref(smartcard, s, &(ret->hContext)))) + WLog_ERR(TAG, "smartcard_pack_redir_scard_context_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_establish_context_return(SMARTCARD_DEVICE* smartcard, + EstablishContext_Return* ret) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "EstablishContext_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + pb = (BYTE*) & (ret->hContext.pbContext); + + if (ret->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], ret->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], ret->hContext.cbContext); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, Context_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_context_call(SMARTCARD_DEVICE* smartcard, Context_Call* call, const char* name) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "%s_Call {", name); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + WLog_DBG(TAG, "}"); +} + +void smartcard_trace_long_return(SMARTCARD_DEVICE* smartcard, Long_Return* ret, const char* name) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "%s_Return {", name); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Call* call) +{ + LONG status; + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)); + + if (status) + return status; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "ListReaderGroups_Call is too short: %d", + (int) Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->fmszGroupsIsNULL); /* fmszGroupsIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cchGroups); /* cchGroups (4 bytes) */ + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)); + + if (status) + return status; + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, + ListReaderGroups_Call* call, BOOL unicode) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "ListReaderGroups%S_Call {", unicode ? "W" : "A"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + WLog_DBG(TAG, "fmszGroupsIsNULL: %"PRId32" cchGroups: 0x%08"PRIx32, + call->fmszGroupsIsNULL, call->cchGroups); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Return* ret) +{ + UINT32 mszNdrPtr; + mszNdrPtr = (ret->cBytes) ? 0x00020008 : 0; + Stream_EnsureRemainingCapacity(s, ret->cBytes + 32); + Stream_Write_UINT32(s, ret->cBytes); /* cBytes (4 bytes) */ + Stream_Write_UINT32(s, mszNdrPtr); /* mszNdrPtr (4 bytes) */ + + if (mszNdrPtr) + { + Stream_Write_UINT32(s, ret->cBytes); /* mszNdrLen (4 bytes) */ + + if (ret->msz) + Stream_Write(s, ret->msz, ret->cBytes); + else + Stream_Zero(s, ret->cBytes); + + smartcard_pack_write_size_align(smartcard, s, ret->cBytes, 4); + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, + ListReaderGroups_Return* ret, BOOL unicode) +{ + int index; + int length; + char* mszA = NULL; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + if (unicode) + { + length = ret->cBytes / 2; + ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) ret->msz, length, &mszA, 0, NULL, NULL); + } + else + { + length = ret->cBytes; + mszA = (char*) malloc(length); + CopyMemory(mszA, ret->msz, ret->cBytes); + } + + for (index = 0; index < length - 2; index++) + { + if (mszA[index] == '\0') + mszA[index] = ','; + } + + WLog_DBG(TAG, "ListReaderGroups%s_Return {", unicode ? "W" : "A"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIx32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "cBytes: %"PRIu32" msz: %s", ret->cBytes, mszA); + WLog_DBG(TAG, "}"); + free(mszA); +} + +LONG smartcard_unpack_list_readers_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Call* call) +{ + LONG status; + UINT32 count; + UINT32 mszGroupsNdrPtr; + call->mszGroups = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_WARN(TAG, "ListReaders_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->cBytes); /* cBytes (4 bytes) */ + Stream_Read_UINT32(s, mszGroupsNdrPtr); /* mszGroupsNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, call->fmszReadersIsNULL); /* fmszReadersIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cchReaders); /* cchReaders (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((mszGroupsNdrPtr && !call->cBytes) || (!mszGroupsNdrPtr && call->cBytes)) + { + WLog_WARN(TAG, + "ListReaders_Call mszGroupsNdrPtr (0x%08"PRIX32") and cBytes (0x%08"PRIX32") inconsistency", + mszGroupsNdrPtr, call->cBytes); + return STATUS_INVALID_PARAMETER; + } + + if (mszGroupsNdrPtr) + { + Stream_Read_UINT32(s, count); /* NdrCount (4 bytes) */ + + if (count != call->cBytes) + { + WLog_WARN(TAG, "ListReaders_Call NdrCount (0x%08"PRIX32") and cBytes (0x%08"PRIX32") inconsistency", + count, call->cBytes); + return STATUS_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < call->cBytes) + { + WLog_WARN(TAG, "ListReaders_Call is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), call->cBytes); + return STATUS_BUFFER_TOO_SMALL; + } + + call->mszGroups = (BYTE*) calloc(1, call->cBytes + 4); + + if (!call->mszGroups) + { + WLog_WARN(TAG, "ListReaders_Call out of memory error (mszGroups)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, call->mszGroups, call->cBytes); + smartcard_unpack_read_size_align(smartcard, s, call->cBytes, 4); + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_list_readers_call(SMARTCARD_DEVICE* smartcard, ListReaders_Call* call, + BOOL unicode) +{ + BYTE* pb; + char* mszGroupsA = NULL; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + if (unicode) + ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) call->mszGroups, call->cBytes / 2, &mszGroupsA, 0, NULL, + NULL); + + WLog_DBG(TAG, "ListReaders%s_Call {", unicode ? "W" : "A"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + WLog_DBG(TAG, + "cBytes: %"PRIu32" mszGroups: %s fmszReadersIsNULL: %"PRId32" cchReaders: 0x%08"PRIX32"", + call->cBytes, mszGroupsA, call->fmszReadersIsNULL, call->cchReaders); + WLog_DBG(TAG, "}"); + + if (unicode) + free(mszGroupsA); +} + +LONG smartcard_pack_list_readers_return(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Return* ret) +{ + UINT32 mszNdrPtr; + LONG error; + + if (ret->ReturnCode != SCARD_S_SUCCESS) + return ret->ReturnCode; + + mszNdrPtr = (ret->cBytes) ? 0x00020008 : 0; + + if (!Stream_EnsureRemainingCapacity(s, ret->cBytes + 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cBytes); /* cBytes (4 bytes) */ + Stream_Write_UINT32(s, mszNdrPtr); /* mszNdrPtr (4 bytes) */ + + if (mszNdrPtr) + { + Stream_Write_UINT32(s, ret->cBytes); /* mszNdrLen (4 bytes) */ + + if (ret->msz) + Stream_Write(s, ret->msz, ret->cBytes); + else + Stream_Zero(s, ret->cBytes); + + if ((error = smartcard_pack_write_size_align(smartcard, s, ret->cBytes, 4))) + { + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"", error); + return error; + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_list_readers_return(SMARTCARD_DEVICE* smartcard, ListReaders_Return* ret, + BOOL unicode) +{ + int index; + size_t length; + char* mszA = NULL; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "ListReaders%s_Return {", unicode ? "W" : "A"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode != SCARD_S_SUCCESS) + { + WLog_DBG(TAG, "}"); + return; + } + + if (unicode) + { + length = ret->cBytes / 2; + + if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) ret->msz, (int)length, + &mszA, 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "ConvertFromUnicode failed"); + return; + } + } + else + { + length = ret->cBytes; + mszA = (char*) malloc(length); + + if (!mszA) + { + WLog_ERR(TAG, "malloc failed!"); + return; + } + + CopyMemory(mszA, ret->msz, ret->cBytes); + } + + for (index = 0; index < length - 1; index++) + { + if (mszA[index] == '\0') + mszA[index] = ','; + } + + WLog_DBG(TAG, "cBytes: %"PRIu32" msz: %s", ret->cBytes, mszA); + WLog_DBG(TAG, "}"); + free(mszA); +} + +LONG smartcard_unpack_connect_common(SMARTCARD_DEVICE* smartcard, wStream* s, + Connect_Common* common) +{ + LONG status; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "Connect_Common is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(common->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + Stream_Read_UINT32(s, common->dwShareMode); /* dwShareMode (4 bytes) */ + Stream_Read_UINT32(s, common->dwPreferredProtocols); /* dwPreferredProtocols (4 bytes) */ + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_connect_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectA_Call* call) +{ + LONG status; + UINT32 count; + call->szReader = NULL; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "ConnectA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Seek_UINT32(s); /* szReaderNdrPtr (4 bytes) */ + + if ((status = smartcard_unpack_connect_common(smartcard, s, &(call->Common)))) + { + WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %"PRId32"", status); + return status; + } + + /* szReader */ + Stream_Seek_UINT32(s); /* NdrMaxCount (4 bytes) */ + Stream_Seek_UINT32(s); /* NdrOffset (4 bytes) */ + Stream_Read_UINT32(s, count); /* NdrActualCount (4 bytes) */ + call->szReader = (unsigned char*) malloc(count + 1); + + if (!call->szReader) + { + WLog_WARN(TAG, "ConnectA_Call out of memory error (call->szReader)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, call->szReader, count); + smartcard_unpack_read_size_align(smartcard, s, count, 4); + call->szReader[count] = '\0'; + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->Common.hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_connect_a_call(SMARTCARD_DEVICE* smartcard, ConnectA_Call* call) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "ConnectA_Call {"); + pb = (BYTE*) & (call->Common.hContext.pbContext); + + if (call->Common.hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->Common.hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->Common.hContext.cbContext); + } + + WLog_DBG(TAG, + "szReader: %s dwShareMode: %s (0x%08"PRIX32") dwPreferredProtocols: %s (0x%08"PRIX32")", + call->szReader, SCardGetShareModeString(call->Common.dwShareMode), call->Common.dwShareMode, + SCardGetProtocolString(call->Common.dwPreferredProtocols), call->Common.dwPreferredProtocols); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_connect_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectW_Call* call) +{ + LONG status; + UINT32 count; + call->szReader = NULL; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "ConnectW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Seek_UINT32(s); /* szReaderNdrPtr (4 bytes) */ + + if ((status = smartcard_unpack_connect_common(smartcard, s, &(call->Common)))) + { + WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %"PRId32"", status); + return status; + } + + /* szReader */ + Stream_Seek_UINT32(s); /* NdrMaxCount (4 bytes) */ + Stream_Seek_UINT32(s); /* NdrOffset (4 bytes) */ + Stream_Read_UINT32(s, count); /* NdrActualCount (4 bytes) */ + call->szReader = (WCHAR*) calloc((count + 1), 2); + + if (!call->szReader) + { + WLog_WARN(TAG, "ConnectW_Call out of memory error (call->szReader)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, call->szReader, (count * 2)); + smartcard_unpack_read_size_align(smartcard, s, (count * 2), 4); + call->szReader[count] = '\0'; + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->Common.hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_connect_w_call(SMARTCARD_DEVICE* smartcard, ConnectW_Call* call) +{ + BYTE* pb; + char* szReaderA = NULL; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + ConvertFromUnicode(CP_UTF8, 0, call->szReader, -1, &szReaderA, 0, NULL, NULL); + WLog_DBG(TAG, "ConnectW_Call {"); + pb = (BYTE*) & (call->Common.hContext.pbContext); + + if (call->Common.hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->Common.hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->Common.hContext.cbContext); + } + + WLog_DBG(TAG, + "szReader: %s dwShareMode: %s (0x%08"PRIX32") dwPreferredProtocols: %s (0x%08"PRIX32")", + szReaderA, SCardGetShareModeString(call->Common.dwShareMode), call->Common.dwShareMode, + SCardGetProtocolString(call->Common.dwPreferredProtocols), call->Common.dwPreferredProtocols); + WLog_DBG(TAG, "}"); + free(szReaderA); +} + +LONG smartcard_pack_connect_return(SMARTCARD_DEVICE* smartcard, wStream* s, Connect_Return* ret) +{ + LONG status; + + if ((status = smartcard_pack_redir_scard_context(smartcard, s, &(ret->hContext)))) + { + WLog_ERR(TAG, "smartcard_pack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_pack_redir_scard_handle(smartcard, s, &(ret->hCard)))) + { + WLog_ERR(TAG, "smartcard_pack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */ + + if ((status = smartcard_pack_redir_scard_context_ref(smartcard, s, &(ret->hContext)))) + { + WLog_ERR(TAG, "smartcard_pack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_pack_redir_scard_handle_ref(smartcard, s, &(ret->hCard)))) + WLog_ERR(TAG, "smartcard_pack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_connect_return(SMARTCARD_DEVICE* smartcard, Connect_Return* ret) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Connect_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + pb = (BYTE*) & (ret->hContext.pbContext); + + if (ret->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], ret->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], ret->hContext.cbContext); + } + + pb = (BYTE*) & (ret->hCard.pbHandle); + + if (ret->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], ret->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], ret->hCard.cbHandle); + } + + WLog_DBG(TAG, "dwActiveProtocol: %s (0x%08"PRIX32")", + SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_reconnect_call(SMARTCARD_DEVICE* smartcard, wStream* s, Reconnect_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "Reconnect_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwShareMode); /* dwShareMode (4 bytes) */ + Stream_Read_UINT32(s, call->dwPreferredProtocols); /* dwPreferredProtocols (4 bytes) */ + Stream_Read_UINT32(s, call->dwInitialization); /* dwInitialization (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_reconnect_call(SMARTCARD_DEVICE* smartcard, Reconnect_Call* call) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Reconnect_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + WLog_DBG(TAG, + "dwShareMode: %s (0x%08"PRIX32") dwPreferredProtocols: %s (0x%08"PRIX32") dwInitialization: %s (0x%08"PRIX32")", + SCardGetShareModeString(call->dwShareMode), call->dwShareMode, + SCardGetProtocolString(call->dwPreferredProtocols), call->dwPreferredProtocols, + SCardGetDispositionString(call->dwInitialization), call->dwInitialization); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_reconnect_return(SMARTCARD_DEVICE* smartcard, wStream* s, Reconnect_Return* ret) +{ + Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */ + return SCARD_S_SUCCESS; +} + +void smartcard_trace_reconnect_return(SMARTCARD_DEVICE* smartcard, Reconnect_Return* ret) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Reconnect_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "dwActiveProtocol: %s (0x%08"PRIX32")", + SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, wStream* s, + HCardAndDisposition_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "HCardAndDisposition_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwDisposition); /* dwDisposition (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, + HCardAndDisposition_Call* call, const char* name) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "%s_Call {", name); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + WLog_DBG(TAG, "dwDisposition: %s (0x%08"PRIX32")", + SCardGetDispositionString(call->dwDisposition), call->dwDisposition); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeA_Call* call) +{ + UINT32 index; + UINT32 count; + LONG status; + UINT32 offset; + UINT32 maxCount; + UINT32 szReaderNdrPtr; + UINT32 rgReaderStatesNdrPtr; + LPSCARD_READERSTATEA readerState; + call->rgReaderStates = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwTimeOut); /* dwTimeOut (4 bytes) */ + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + Stream_Read_UINT32(s, rgReaderStatesNdrPtr); /* rgReaderStatesNdrPtr (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, count); /* NdrCount (4 bytes) */ + + if (count != call->cReaders) + { + WLog_WARN(TAG, + "GetStatusChangeA_Call unexpected reader count: Actual: %"PRIu32", Expected: %"PRIu32"", + count, call->cReaders); + return STATUS_INVALID_PARAMETER; + } + + if (call->cReaders > 0) + { + call->rgReaderStates = (LPSCARD_READERSTATEA) calloc(call->cReaders, sizeof(SCARD_READERSTATEA)); + + if (!call->rgReaderStates) + { + WLog_WARN(TAG, "GetStatusChangeA_Call out of memory error (call->rgReaderStates)"); + return STATUS_NO_MEMORY; + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 52) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, szReaderNdrPtr); /* szReaderNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */ + Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */ + Stream_Read(s, readerState->rgbAtr, 32); /* rgbAtr [0..32] (32 bytes) */ + Stream_Seek(s, 4); /* rgbAtr [32..36] (4 bytes) */ + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, maxCount); /* NdrMaxCount (4 bytes) */ + Stream_Read_UINT32(s, offset); /* NdrOffset (4 bytes) */ + Stream_Read_UINT32(s, count); /* NdrActualCount (4 bytes) */ + + if (Stream_GetRemainingLength(s) < count) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + readerState->szReader = (LPCSTR) malloc(count + 1); + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeA_Call out of memory error (readerState->szReader)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, (void*) readerState->szReader, count); + smartcard_unpack_read_size_align(smartcard, s, count, 4); + ((char*) readerState->szReader)[count] = '\0'; + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeA_Call null reader name"); + return STATUS_INVALID_PARAMETER; + } + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, + GetStatusChangeA_Call* call) +{ + BYTE* pb; + UINT32 index; + char* szEventState; + char* szCurrentState; + LPSCARD_READERSTATEA readerState; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "GetStatusChangeA_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + WLog_DBG(TAG, "dwTimeOut: 0x%08"PRIX32" cReaders: %"PRIu32"", + call->dwTimeOut, call->cReaders); + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + WLog_DBG(TAG, "\t[%"PRIu32"]: szReader: %s cbAtr: %"PRIu32"", + index, readerState->szReader, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwCurrentState: %s (0x%08"PRIX32")", + index, szCurrentState, readerState->dwCurrentState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwEventState: %s (0x%08"PRIX32")", + index, szEventState, readerState->dwEventState); + free(szCurrentState); + free(szEventState); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeW_Call* call) +{ + UINT32 index; + UINT32 count; + LONG status; + UINT32 offset; + UINT32 maxCount; + UINT32 szReaderNdrPtr; + UINT32 rgReaderStatesNdrPtr; + LPSCARD_READERSTATEW readerState; + call->rgReaderStates = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetStatusChangeW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwTimeOut); /* dwTimeOut (4 bytes) */ + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + Stream_Read_UINT32(s, rgReaderStatesNdrPtr); /* rgReaderStatesNdrPtr (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "GetStatusChangeW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Seek_UINT32(s); /* NdrConformant (4 bytes) */ + + if (call->cReaders > 0) + { + call->rgReaderStates = (LPSCARD_READERSTATEW) calloc(call->cReaders, sizeof(SCARD_READERSTATEW)); + + if (!call->rgReaderStates) + { + WLog_WARN(TAG, "GetStatusChangeW_Call out of memory error (call->rgReaderStates)"); + return STATUS_NO_MEMORY; + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 52) + { + WLog_WARN(TAG, "GetStatusChangeW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, szReaderNdrPtr); /* (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */ + Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */ + Stream_Read(s, readerState->rgbAtr, 32); /* rgbAtr [0..32] (32 bytes) */ + Stream_Seek(s, 4); /* rgbAtr [32..36] (4 bytes) */ + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetStatusChangeW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, maxCount); /* NdrMaxCount (4 bytes) */ + Stream_Read_UINT32(s, offset); /* NdrOffset (4 bytes) */ + Stream_Read_UINT32(s, count); /* NdrActualCount (4 bytes) */ + + if (Stream_GetRemainingLength(s) < (count * 2)) + { + WLog_WARN(TAG, "GetStatusChangeW_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + readerState->szReader = (WCHAR*) calloc((count + 1), 2); + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeW_Call out of memory error (readerState->szReader)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, (void*) readerState->szReader, (count * 2)); + smartcard_unpack_read_size_align(smartcard, s, (count * 2), 4); + ((WCHAR*) readerState->szReader)[count] = '\0'; + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeW_Call null reader name"); + return STATUS_INVALID_PARAMETER; + } + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, + GetStatusChangeW_Call* call) +{ + BYTE* pb; + UINT32 index; + char* szEventState; + char* szCurrentState; + LPSCARD_READERSTATEW readerState; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "GetStatusChangeW_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + WLog_DBG(TAG, "dwTimeOut: 0x%08"PRIX32" cReaders: %"PRIu32"", + call->dwTimeOut, call->cReaders); + + for (index = 0; index < call->cReaders; index++) + { + char* szReaderA = NULL; + readerState = &call->rgReaderStates[index]; + ConvertFromUnicode(CP_UTF8, 0, readerState->szReader, -1, &szReaderA, 0, NULL, NULL); + WLog_DBG(TAG, "\t[%"PRIu32"]: szReader: %s cbAtr: %"PRIu32"", + index, szReaderA, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwCurrentState: %s (0x%08"PRIX32")", + index, szCurrentState, readerState->dwCurrentState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwEventState: %s (0x%08"PRIX32")", + index, szEventState, readerState->dwEventState); + free(szCurrentState); + free(szEventState); + free(szReaderA); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_get_status_change_return(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChange_Return* ret) +{ + UINT32 index; + ReaderState_Return* rgReaderState; + Stream_Write_UINT32(s, ret->cReaders); /* cReaders (4 bytes) */ + Stream_Write_UINT32(s, 0x00020100); /* rgReaderStatesNdrPtr (4 bytes) */ + Stream_Write_UINT32(s, ret->cReaders); /* rgReaderStatesNdrCount (4 bytes) */ + + for (index = 0; index < ret->cReaders; index++) + { + rgReaderState = &(ret->rgReaderStates[index]); + Stream_Write_UINT32(s, rgReaderState->dwCurrentState); /* dwCurrentState (4 bytes) */ + Stream_Write_UINT32(s, rgReaderState->dwEventState); /* dwEventState (4 bytes) */ + Stream_Write_UINT32(s, rgReaderState->cbAtr); /* cbAtr (4 bytes) */ + Stream_Write(s, rgReaderState->rgbAtr, 32); /* rgbAtr [0..32] (32 bytes) */ + Stream_Zero(s, 4); /* rgbAtr [32..36] (32 bytes) */ + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_get_status_change_return(SMARTCARD_DEVICE* smartcard, + GetStatusChange_Return* ret, BOOL unicode) +{ + UINT32 index; + char* rgbAtr; + char* szEventState; + char* szCurrentState; + ReaderState_Return* rgReaderState; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "GetStatusChange%s_Return {", unicode ? "W" : "A"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "cReaders: %"PRIu32"", ret->cReaders); + + for (index = 0; index < ret->cReaders; index++) + { + rgReaderState = &(ret->rgReaderStates[index]); + szCurrentState = SCardGetReaderStateString(rgReaderState->dwCurrentState); + szEventState = SCardGetReaderStateString(rgReaderState->dwEventState); + rgbAtr = winpr_BinToHexString((BYTE*) & (rgReaderState->rgbAtr), rgReaderState->cbAtr, FALSE); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwCurrentState: %s (0x%08"PRIX32")", + index, szCurrentState, rgReaderState->dwCurrentState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwEventState: %s (0x%08"PRIX32")", + index, szEventState, rgReaderState->dwEventState); + WLog_DBG(TAG, "\t[%"PRIu32"]: cbAtr: %"PRIu32" rgbAtr: %s", + index, rgReaderState->cbAtr, rgbAtr); + free(szCurrentState); + free(szEventState); + free(rgbAtr); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_state_call(SMARTCARD_DEVICE* smartcard, wStream* s, State_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "State_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->fpbAtrIsNULL); /* fpbAtrIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cbAtrLen); /* cbAtrLen (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +LONG smartcard_pack_state_return(SMARTCARD_DEVICE* smartcard, wStream* s, State_Return* ret) +{ + LONG status; + Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */ + Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Write_UINT32(s, ret->cbAtrLen); /* cbAtrLen (4 bytes) */ + Stream_Write_UINT32(s, 0x00020020); /* rgAtrNdrPtr (4 bytes) */ + Stream_Write_UINT32(s, ret->cbAtrLen); /* rgAtrLength (4 bytes) */ + Stream_Write(s, ret->rgAtr, ret->cbAtrLen); /* rgAtr */ + + if ((status = smartcard_pack_write_size_align(smartcard, s, ret->cbAtrLen, 4))) + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"", status); + + return status; +} + +LONG smartcard_unpack_status_call(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "Status_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->fmszReaderNamesIsNULL); /* fmszReaderNamesIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cchReaderLen); /* cchReaderLen (4 bytes) */ + Stream_Read_UINT32(s, call->cbAtrLen); /* cbAtrLen (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_status_call(SMARTCARD_DEVICE* smartcard, Status_Call* call, BOOL unicode) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Status%s_Call {", unicode ? "W" : "A"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + WLog_DBG(TAG, "fmszReaderNamesIsNULL: %"PRId32" cchReaderLen: %"PRIu32" cbAtrLen: %"PRIu32"", + call->fmszReaderNamesIsNULL, call->cchReaderLen, call->cbAtrLen); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_status_return(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Return* ret) +{ + LONG status; + + if (!Stream_EnsureRemainingCapacity(s, ret->cBytes + 64)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cBytes); /* cBytes (4 bytes) */ + Stream_Write_UINT32(s, 0x00020010); /* mszReaderNamesNdrPtr (4 bytes) */ + Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */ + Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Write(s, ret->pbAtr, 32); /* pbAtr (32 bytes) */ + Stream_Write_UINT32(s, ret->cbAtrLen); /* cbAtrLen (4 bytes) */ + Stream_Write_UINT32(s, ret->cBytes); /* mszReaderNamesNdrLen (4 bytes) */ + + if (ret->mszReaderNames) + Stream_Write(s, ret->mszReaderNames, ret->cBytes); + else + Stream_Zero(s, ret->cBytes); + + if ((status = smartcard_pack_write_size_align(smartcard, s, ret->cBytes, 4))) + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_status_return(SMARTCARD_DEVICE* smartcard, Status_Return* ret, BOOL unicode) +{ + int index; + size_t length; + char* pbAtr = NULL; + char* mszReaderNamesA = NULL; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + if (ret->mszReaderNames) + { + if (unicode) + { + length = ret->cBytes / 2; + + if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) ret->mszReaderNames, (int) length, + &mszReaderNamesA, 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "ConvertFromUnicode failed"); + return; + } + } + else + { + length = (int) ret->cBytes; + mszReaderNamesA = (char*) malloc(length); + + if (!mszReaderNamesA) + { + WLog_ERR(TAG, "malloc failed!"); + return; + } + + CopyMemory(mszReaderNamesA, ret->mszReaderNames, ret->cBytes); + } + } + else + length = 0; + + if (length > 2) + { + for (index = 0; index < length - 2; index++) + { + if (mszReaderNamesA[index] == '\0') + mszReaderNamesA[index] = ','; + } + } + + pbAtr = winpr_BinToHexString(ret->pbAtr, ret->cbAtrLen, FALSE); + WLog_DBG(TAG, "Status%s_Return {", unicode ? "W" : "A"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "dwState: %s (0x%08"PRIX32") dwProtocol: %s (0x%08"PRIX32")", + SCardGetCardStateString(ret->dwState), ret->dwState, + SCardGetProtocolString(ret->dwProtocol), ret->dwProtocol); + + if (mszReaderNamesA) + { + WLog_DBG(TAG, "cBytes: %"PRIu32" mszReaderNames: %s", + ret->cBytes, mszReaderNamesA); + } + + WLog_DBG(TAG, "cbAtrLen: %"PRIu32" pbAtr: %s", ret->cbAtrLen, pbAtr); + WLog_DBG(TAG, "}"); + free(mszReaderNamesA); + free(pbAtr); +} + +LONG smartcard_unpack_get_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, GetAttrib_Call* call) +{ + LONG status; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetAttrib_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwAttrId); /* dwAttrId (4 bytes) */ + Stream_Read_UINT32(s, call->fpbAttrIsNULL); /* fpbAttrIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cbAttrLen); /* cbAttrLen (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_get_attrib_call(SMARTCARD_DEVICE* smartcard, GetAttrib_Call* call) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "GetAttrib_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + WLog_DBG(TAG, "dwAttrId: %s (0x%08"PRIX32") fpbAttrIsNULL: %"PRId32" cbAttrLen: 0x%08"PRIX32"", + SCardGetAttributeString(call->dwAttrId), call->dwAttrId, call->fpbAttrIsNULL, call->cbAttrLen); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_get_attrib_return(SMARTCARD_DEVICE* smartcard, wStream* s, + GetAttrib_Return* ret) +{ + LONG status; + + if (!Stream_EnsureRemainingCapacity(s, ret->cbAttrLen + 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cbAttrLen); /* cbAttrLen (4 bytes) */ + Stream_Write_UINT32(s, 0x00020080); /* pbAttrNdrPtr (4 bytes) */ + Stream_Write_UINT32(s, ret->cbAttrLen); /* pbAttrNdrCount (4 bytes) */ + + if (!ret->pbAttr) + Stream_Zero(s, ret->cbAttrLen); /* pbAttr */ + else + Stream_Write(s, ret->pbAttr, ret->cbAttrLen); /* pbAttr */ + + if ((status = smartcard_pack_write_size_align(smartcard, s, ret->cbAttrLen, 4))) + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"", status); + + return status; +} + +void smartcard_trace_get_attrib_return(SMARTCARD_DEVICE* smartcard, GetAttrib_Return* ret, + DWORD dwAttrId) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "GetAttrib_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "dwAttrId: %s (0x%08"PRIX32") cbAttrLen: 0x%08"PRIX32"", + SCardGetAttributeString(dwAttrId), dwAttrId, ret->cbAttrLen); + + if (dwAttrId == SCARD_ATTR_VENDOR_NAME) + { + WLog_DBG(TAG, "pbAttr: %.*s", ret->cbAttrLen, (char*) ret->pbAttr); + } + else if (dwAttrId == SCARD_ATTR_CURRENT_PROTOCOL_TYPE) + { + UINT32 dwProtocolType = *((UINT32*) ret->pbAttr); + WLog_DBG(TAG, "dwProtocolType: %s (0x%08"PRIX32")", + SCardGetProtocolString(dwProtocolType), dwProtocolType); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_control_call(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Call* call) +{ + LONG status; + UINT32 length; + call->pvInBuffer = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_WARN(TAG, "Control_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->dwControlCode); /* dwControlCode (4 bytes) */ + Stream_Read_UINT32(s, call->cbInBufferSize); /* cbInBufferSize (4 bytes) */ + Stream_Seek_UINT32(s); /* pvInBufferNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, call->fpvOutBufferIsNULL); /* fpvOutBufferIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cbOutBufferSize); /* cbOutBufferSize (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if (call->cbInBufferSize) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "Control_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (Stream_GetRemainingLength(s) < length) + { + WLog_WARN(TAG, "Control_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + call->pvInBuffer = (BYTE*) malloc(length); + + if (!call->pvInBuffer) + { + WLog_WARN(TAG, "Control_Call out of memory error (call->pvInBuffer)"); + return STATUS_NO_MEMORY; + } + + call->cbInBufferSize = length; + Stream_Read(s, call->pvInBuffer, length); + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_control_call(SMARTCARD_DEVICE* smartcard, Control_Call* call) +{ + BYTE* pb; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Control_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + WLog_DBG(TAG, + "dwControlCode: 0x%08"PRIX32" cbInBufferSize: %"PRIu32" fpvOutBufferIsNULL: %"PRId32" cbOutBufferSize: %"PRIu32"", + call->dwControlCode, call->cbInBufferSize, call->fpvOutBufferIsNULL, call->cbOutBufferSize); + + if (call->pvInBuffer) + { + char* szInBuffer = winpr_BinToHexString(call->pvInBuffer, call->cbInBufferSize, TRUE); + WLog_DBG(TAG, "pbInBuffer: %s", szInBuffer); + free(szInBuffer); + } + else + { + WLog_DBG(TAG, "pvInBuffer: null"); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_control_return(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Return* ret) +{ + LONG error; + + if (!Stream_EnsureRemainingCapacity(s, ret->cbOutBufferSize + 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cbOutBufferSize); /* cbOutBufferSize (4 bytes) */ + Stream_Write_UINT32(s, 0x00020040); /* pvOutBufferPointer (4 bytes) */ + Stream_Write_UINT32(s, ret->cbOutBufferSize); /* pvOutBufferLength (4 bytes) */ + + if (ret->cbOutBufferSize > 0) + { + Stream_Write(s, ret->pvOutBuffer, ret->cbOutBufferSize); /* pvOutBuffer */ + + if ((error = smartcard_pack_write_size_align(smartcard, s, ret->cbOutBufferSize, 4))) + { + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"", error); + return error; + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_control_return(SMARTCARD_DEVICE* smartcard, Control_Return* ret) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Control_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_DBG(TAG, "cbOutBufferSize: %"PRIu32"", ret->cbOutBufferSize); + + if (ret->pvOutBuffer) + { + char* szOutBuffer = winpr_BinToHexString(ret->pvOutBuffer, ret->cbOutBufferSize, TRUE); + WLog_DBG(TAG, "pvOutBuffer: %s", szOutBuffer); + free(szOutBuffer); + } + else + { + WLog_DBG(TAG, "pvOutBuffer: null"); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_transmit_call(SMARTCARD_DEVICE* smartcard, wStream* s, Transmit_Call* call) +{ + LONG status; + UINT32 length; + BYTE* pbExtraBytes; + UINT32 pbExtraBytesNdrPtr; + UINT32 pbSendBufferNdrPtr; + UINT32 pioRecvPciNdrPtr; + SCardIO_Request ioSendPci; + SCardIO_Request ioRecvPci; + call->pioSendPci = NULL; + call->pioRecvPci = NULL; + call->pbSendBuffer = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 32) + { + WLog_WARN(TAG, "Transmit_Call is too short: Actual: %"PRIuz", Expected: 32", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, ioSendPci.dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Read_UINT32(s, ioSendPci.cbExtraBytes); /* cbExtraBytes (4 bytes) */ + Stream_Read_UINT32(s, pbExtraBytesNdrPtr); /* pbExtraBytesNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, call->cbSendLength); /* cbSendLength (4 bytes) */ + Stream_Read_UINT32(s, pbSendBufferNdrPtr); /* pbSendBufferNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, pioRecvPciNdrPtr); /* pioRecvPciNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, call->fpbRecvBufferIsNULL); /* fpbRecvBufferIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cbRecvLength); /* cbRecvLength (4 bytes) */ + + if (ioSendPci.cbExtraBytes > 1024) + { + WLog_WARN(TAG, "Transmit_Call ioSendPci.cbExtraBytes is out of bounds: %"PRIu32" (max: 1024)", + ioSendPci.cbExtraBytes); + return STATUS_INVALID_PARAMETER; + } + + if (call->cbSendLength > 66560) + { + WLog_WARN(TAG, "Transmit_Call cbSendLength is out of bounds: %"PRIu32" (max: 66560)", + ioSendPci.cbExtraBytes); + return STATUS_INVALID_PARAMETER; + } + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->hCard)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %"PRId32"", status); + return status; + } + + if (ioSendPci.cbExtraBytes && !pbExtraBytesNdrPtr) + { + WLog_WARN(TAG, "Transmit_Call ioSendPci.cbExtraBytes is non-zero but pbExtraBytesNdrPtr is null"); + return STATUS_INVALID_PARAMETER; + } + + if (pbExtraBytesNdrPtr) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "Transmit_Call is too short: %"PRIuz" (ioSendPci.pbExtraBytes)", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (Stream_GetRemainingLength(s) < ioSendPci.cbExtraBytes) + { + WLog_WARN(TAG, + "Transmit_Call is too short: Actual: %"PRIuz", Expected: %"PRIu32" (ioSendPci.cbExtraBytes)", + Stream_GetRemainingLength(s), ioSendPci.cbExtraBytes); + return STATUS_BUFFER_TOO_SMALL; + } + + ioSendPci.pbExtraBytes = Stream_Pointer(s); + call->pioSendPci = (LPSCARD_IO_REQUEST) malloc(sizeof(SCARD_IO_REQUEST) + ioSendPci.cbExtraBytes); + + if (!call->pioSendPci) + { + WLog_WARN(TAG, "Transmit_Call out of memory error (pioSendPci)"); + return STATUS_NO_MEMORY; + } + + call->pioSendPci->dwProtocol = ioSendPci.dwProtocol; + call->pioSendPci->cbPciLength = (DWORD)(ioSendPci.cbExtraBytes + sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) call->pioSendPci)[sizeof(SCARD_IO_REQUEST)]; + Stream_Read(s, pbExtraBytes, ioSendPci.cbExtraBytes); + smartcard_unpack_read_size_align(smartcard, s, ioSendPci.cbExtraBytes, 4); + } + else + { + call->pioSendPci = (LPSCARD_IO_REQUEST) calloc(1, sizeof(SCARD_IO_REQUEST)); + + if (!call->pioSendPci) + { + WLog_WARN(TAG, "Transmit_Call out of memory error (pioSendPci)"); + return STATUS_NO_MEMORY; + } + + call->pioSendPci->dwProtocol = ioSendPci.dwProtocol; + call->pioSendPci->cbPciLength = sizeof(SCARD_IO_REQUEST); + } + + if (pbSendBufferNdrPtr) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "Transmit_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length != call->cbSendLength) + { + WLog_WARN(TAG, + "Transmit_Call unexpected length: Actual: %"PRIu32", Expected: %"PRIu32" (cbSendLength)", + length, call->cbSendLength); + return STATUS_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < call->cbSendLength) + { + WLog_WARN(TAG, "Transmit_Call is too short: Actual: %"PRIuz", Expected: %"PRIu32" (cbSendLength)", + Stream_GetRemainingLength(s), call->cbSendLength); + return STATUS_BUFFER_TOO_SMALL; + } + + call->pbSendBuffer = (BYTE*) malloc(call->cbSendLength); + + if (!call->pbSendBuffer) + { + WLog_WARN(TAG, "Transmit_Call out of memory error (pbSendBuffer)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, call->pbSendBuffer, call->cbSendLength); + smartcard_unpack_read_size_align(smartcard, s, call->cbSendLength, 4); + } + + if (pioRecvPciNdrPtr) + { + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "Transmit_Call is too short: Actual: %"PRIuz", Expected: 12", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, ioRecvPci.dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Read_UINT32(s, ioRecvPci.cbExtraBytes); /* cbExtraBytes (4 bytes) */ + Stream_Read_UINT32(s, pbExtraBytesNdrPtr); /* pbExtraBytesNdrPtr (4 bytes) */ + + if (ioRecvPci.cbExtraBytes && !pbExtraBytesNdrPtr) + { + WLog_WARN(TAG, "Transmit_Call ioRecvPci.cbExtraBytes is non-zero but pbExtraBytesNdrPtr is null"); + return STATUS_INVALID_PARAMETER; + } + + if (pbExtraBytesNdrPtr) + { + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "Transmit_Call is too short: %"PRIuz" (ioRecvPci.pbExtraBytes)", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (ioRecvPci.cbExtraBytes > 1024) + { + WLog_WARN(TAG, "Transmit_Call ioRecvPci.cbExtraBytes is out of bounds: %"PRIu32" (max: 1024)", + ioRecvPci.cbExtraBytes); + return STATUS_INVALID_PARAMETER; + } + + if (length != ioRecvPci.cbExtraBytes) + { + WLog_WARN(TAG, + "Transmit_Call unexpected length: Actual: %"PRIu32", Expected: %"PRIu32" (ioRecvPci.cbExtraBytes)", + length, ioRecvPci.cbExtraBytes); + return STATUS_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < ioRecvPci.cbExtraBytes) + { + WLog_WARN(TAG, + "Transmit_Call is too short: Actual: %"PRIuz", Expected: %"PRIu32" (ioRecvPci.cbExtraBytes)", + Stream_GetRemainingLength(s), ioRecvPci.cbExtraBytes); + return STATUS_BUFFER_TOO_SMALL; + } + + ioRecvPci.pbExtraBytes = Stream_Pointer(s); + call->pioRecvPci = (LPSCARD_IO_REQUEST) malloc(sizeof(SCARD_IO_REQUEST) + ioRecvPci.cbExtraBytes); + + if (!call->pioRecvPci) + { + WLog_WARN(TAG, "Transmit_Call out of memory error (pioRecvPci)"); + return STATUS_NO_MEMORY; + } + + call->pioRecvPci->dwProtocol = ioRecvPci.dwProtocol; + call->pioRecvPci->cbPciLength = (DWORD)(ioRecvPci.cbExtraBytes + sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) call->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + Stream_Read(s, pbExtraBytes, ioRecvPci.cbExtraBytes); + smartcard_unpack_read_size_align(smartcard, s, ioRecvPci.cbExtraBytes, 4); + } + else + { + call->pioRecvPci = (LPSCARD_IO_REQUEST) calloc(1, sizeof(SCARD_IO_REQUEST)); + + if (!call->pioRecvPci) + { + WLog_WARN(TAG, "Transmit_Call out of memory error (pioRecvPci)"); + return STATUS_NO_MEMORY; + } + + call->pioRecvPci->dwProtocol = ioRecvPci.dwProtocol; + call->pioRecvPci->cbPciLength = sizeof(SCARD_IO_REQUEST); + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_transmit_call(SMARTCARD_DEVICE* smartcard, Transmit_Call* call) +{ + BYTE* pb; + UINT32 cbExtraBytes; + BYTE* pbExtraBytes; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Transmit_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + pb = (BYTE*) & (call->hCard.pbHandle); + + if (call->hCard.cbHandle > 4) + { + WLog_DBG(TAG, + "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hCard.cbHandle); + } + else + { + WLog_DBG(TAG, "hCard: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hCard.cbHandle); + } + + if (call->pioSendPci) + { + cbExtraBytes = (UINT32)(call->pioSendPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) call->pioSendPci)[sizeof(SCARD_IO_REQUEST)]; + WLog_DBG(TAG, "pioSendPci: dwProtocol: %"PRIu32" cbExtraBytes: %"PRIu32"", + call->pioSendPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char* szExtraBytes = winpr_BinToHexString(pbExtraBytes, cbExtraBytes, TRUE); + WLog_DBG(TAG, "pbExtraBytes: %s", szExtraBytes); + free(szExtraBytes); + } + } + else + { + WLog_DBG(TAG, "pioSendPci: null"); + } + + WLog_DBG(TAG, "cbSendLength: %"PRIu32"", call->cbSendLength); + + if (call->pbSendBuffer) + { + char* szSendBuffer = winpr_BinToHexString(call->pbSendBuffer, call->cbSendLength, TRUE); + WLog_DBG(TAG, "pbSendBuffer: %s", szSendBuffer); + free(szSendBuffer); + } + else + { + WLog_DBG(TAG, "pbSendBuffer: null"); + } + + if (call->pioRecvPci) + { + cbExtraBytes = (UINT32)(call->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) call->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + WLog_DBG(TAG, "pioRecvPci: dwProtocol: %"PRIu32" cbExtraBytes: %"PRIu32"", + call->pioRecvPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char* szExtraBytes = winpr_BinToHexString(pbExtraBytes, cbExtraBytes, TRUE); + WLog_DBG(TAG, "pbExtraBytes: %s", szExtraBytes); + free(szExtraBytes); + } + } + else + { + WLog_DBG(TAG, "pioRecvPci: null"); + } + + WLog_DBG(TAG, "fpbRecvBufferIsNULL: %"PRId32" cbRecvLength: %"PRIu32"", + call->fpbRecvBufferIsNULL, call->cbRecvLength); + WLog_DBG(TAG, "}"); +} + +LONG smartcard_pack_transmit_return(SMARTCARD_DEVICE* smartcard, wStream* s, Transmit_Return* ret) +{ + UINT32 cbExtraBytes; + BYTE* pbExtraBytes; + UINT32 pioRecvPciNdrPtr; + UINT32 pbRecvBufferNdrPtr; + UINT32 pbExtraBytesNdrPtr; + LONG error; + + if (!ret->pbRecvBuffer) + ret->cbRecvLength = 0; + + pioRecvPciNdrPtr = (ret->pioRecvPci) ? 0x00020000 : 0; + pbRecvBufferNdrPtr = (ret->pbRecvBuffer) ? 0x00020004 : 0; + Stream_Write_UINT32(s, pioRecvPciNdrPtr); /* pioRecvPciNdrPtr (4 bytes) */ + Stream_Write_UINT32(s, ret->cbRecvLength); /* cbRecvLength (4 bytes) */ + Stream_Write_UINT32(s, pbRecvBufferNdrPtr); /* pbRecvBufferNdrPtr (4 bytes) */ + + if (pioRecvPciNdrPtr) + { + cbExtraBytes = (UINT32)(ret->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) ret->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + pbExtraBytesNdrPtr = cbExtraBytes ? 0x00020008 : 0; + + if (!Stream_EnsureRemainingCapacity(s, cbExtraBytes + 16)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->pioRecvPci->dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Write_UINT32(s, cbExtraBytes); /* cbExtraBytes (4 bytes) */ + Stream_Write_UINT32(s, pbExtraBytesNdrPtr); /* pbExtraBytesNdrPtr (4 bytes) */ + + if (pbExtraBytesNdrPtr) + { + Stream_Write_UINT32(s, cbExtraBytes); /* Length (4 bytes) */ + Stream_Write(s, pbExtraBytes, cbExtraBytes); + + if ((error = smartcard_pack_write_size_align(smartcard, s, cbExtraBytes, 4))) + { + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"!", error); + return error; + } + } + } + + if (pbRecvBufferNdrPtr) + { + if (!Stream_EnsureRemainingCapacity(s, ret->cbRecvLength + 16)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cbRecvLength); /* pbRecvBufferNdrLen (4 bytes) */ + Stream_Write(s, ret->pbRecvBuffer, ret->cbRecvLength); + + if ((error = smartcard_pack_write_size_align(smartcard, s, ret->cbRecvLength, 4))) + { + WLog_ERR(TAG, "smartcard_pack_write_size_align failed with error %"PRId32"!", error); + return error; + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_transmit_return(SMARTCARD_DEVICE* smartcard, Transmit_Return* ret) +{ + UINT32 cbExtraBytes; + BYTE* pbExtraBytes; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "Transmit_Return {"); + WLog_DBG(TAG, "ReturnCode: %s (0x%08"PRIX32")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->pioRecvPci) + { + cbExtraBytes = (UINT32)(ret->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*) ret->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + WLog_DBG(TAG, "pioRecvPci: dwProtocol: %"PRIu32" cbExtraBytes: %"PRIu32"", + ret->pioRecvPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char* szExtraBytes = winpr_BinToHexString(pbExtraBytes, cbExtraBytes, TRUE); + WLog_DBG(TAG, "pbExtraBytes: %s", szExtraBytes); + free(szExtraBytes); + } + } + else + { + WLog_DBG(TAG, "pioRecvPci: null"); + } + + WLog_DBG(TAG, "cbRecvLength: %"PRIu32"", ret->cbRecvLength); + + if (ret->pbRecvBuffer) + { + char* szRecvBuffer = winpr_BinToHexString(ret->pbRecvBuffer, ret->cbRecvLength, TRUE); + WLog_DBG(TAG, "pbRecvBuffer: %s", szRecvBuffer); + free(szRecvBuffer); + } + else + { + WLog_DBG(TAG, "pbRecvBuffer: null"); + } + + WLog_DBG(TAG, "}"); +} + +LONG smartcard_unpack_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRA_Call* call) +{ + UINT32 index; + UINT32 count; + LONG status; + UINT32 offset; + UINT32 maxCount; + UINT32 szReaderNdrPtr; + UINT32 rgReaderStatesNdrPtr; + UINT32 rgAtrMasksNdrPtr; + LPSCARD_READERSTATEA readerState; + call->rgReaderStates = NULL; + + if ((status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->cAtrs); + Stream_Read_UINT32(s, rgAtrMasksNdrPtr); + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + Stream_Read_UINT32(s, rgReaderStatesNdrPtr); /* rgReaderStatesNdrPtr (4 bytes) */ + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->hContext)))) + { + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %"PRId32"", status); + return status; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + if ((rgAtrMasksNdrPtr && !call->cAtrs) || (!rgAtrMasksNdrPtr && call->cAtrs)) + { + WLog_WARN(TAG, + "LocateCardsByATRA_Call rgAtrMasksNdrPtr (0x%08"PRIX32") and cAtrs (0x%08"PRIX32") inconsistency", + rgAtrMasksNdrPtr, call->cAtrs); + return STATUS_INVALID_PARAMETER; + } + + if (rgAtrMasksNdrPtr) + { + Stream_Read_UINT32(s, count); + + if (count != call->cAtrs) + { + WLog_WARN(TAG, + "LocateCardsByATRA_Call NdrCount (0x%08"PRIX32") and cAtrs (0x%08"PRIX32") inconsistency", + count, call->cAtrs); + return STATUS_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < call->cAtrs) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call is too short: Actual: %"PRIuz", Expected: %"PRIu32"", + Stream_GetRemainingLength(s), call->cAtrs); + return STATUS_BUFFER_TOO_SMALL; + } + + call->rgAtrMasks = (LocateCards_ATRMask*)calloc(call->cAtrs, sizeof(LocateCards_ATRMask)); + + if (!call->rgAtrMasks) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call out of memory error (call->rgAtrMasks)"); + return STATUS_NO_MEMORY; + } + + for (index = 0; index < call->cAtrs; index++) + { + Stream_Read_UINT32(s, call->rgAtrMasks[index].cbAtr); + Stream_Read(s, call->rgAtrMasks[index].rgbAtr, 36); + Stream_Read(s, call->rgAtrMasks[index].rgbMask, 36); + } + } + + Stream_Read_UINT32(s, count); + + if (count != call->cReaders) + { + WLog_WARN(TAG, + "GetStatusChangeA_Call unexpected reader count: Actual: %"PRIu32", Expected: %"PRIu32"", + count, call->cReaders); + return STATUS_INVALID_PARAMETER; + } + + if (call->cReaders > 0) + { + call->rgReaderStates = (ReaderStateA*)calloc(call->cReaders, sizeof(ReaderStateA)); + + if (!call->rgReaderStates) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call out of memory error (call->rgReaderStates)"); + return STATUS_NO_MEMORY; + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = (LPSCARD_READERSTATEA) &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 52) + { + WLog_WARN(TAG, "LocateCardsByATRA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, szReaderNdrPtr); /* szReaderNdrPtr (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */ + Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */ + Stream_Read(s, readerState->rgbAtr, 32); /* rgbAtr [0..32] (32 bytes) */ + Stream_Seek(s, 4); /* rgbAtr [32..36] (4 bytes) */ + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = (LPSCARD_READERSTATEA) &call->rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, maxCount); /* NdrMaxCount (4 bytes) */ + Stream_Read_UINT32(s, offset); /* NdrOffset (4 bytes) */ + Stream_Read_UINT32(s, count); /* NdrActualCount (4 bytes) */ + + if (Stream_GetRemainingLength(s) < count) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %"PRIuz"", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + readerState->szReader = (LPCSTR) malloc(count + 1); + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeA_Call out of memory error (readerState->szReader)"); + return STATUS_NO_MEMORY; + } + + Stream_Read(s, (void*) readerState->szReader, count); + smartcard_unpack_read_size_align(smartcard, s, count, 4); + ((char*) readerState->szReader)[count] = '\0'; + + if (!readerState->szReader) + { + WLog_WARN(TAG, "GetStatusChangeA_Call null reader name"); + return STATUS_INVALID_PARAMETER; + } + } + } + + return SCARD_S_SUCCESS; +} + +void smartcard_trace_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, + LocateCardsByATRA_Call* call) +{ + BYTE* pb; + UINT32 index; + char* szEventState; + char* szCurrentState; + char* rgbAtr; + LPSCARD_READERSTATEA readerState; + + if (!WLog_IsLevelActive(WLog_Get(TAG), WLOG_DEBUG)) + return; + + WLog_DBG(TAG, "LocateCardsByATRA_Call {"); + pb = (BYTE*) & (call->hContext.pbContext); + + if (call->hContext.cbContext > 4) + { + WLog_DBG(TAG, + "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7], call->hContext.cbContext); + } + else + { + WLog_DBG(TAG, "hContext: 0x%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8" (%"PRIu32")", + pb[0], pb[1], pb[2], pb[3], call->hContext.cbContext); + } + + for (index = 0; index < call->cReaders; index++) + { + readerState = (LPSCARD_READERSTATEA) &call->rgReaderStates[index]; + WLog_DBG(TAG, "\t[%"PRIu32"]: szReader: %s cbAtr: %"PRIu32"", + index, readerState->szReader, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + rgbAtr = winpr_BinToHexString((BYTE*) & (readerState->rgbAtr), readerState->cbAtr, FALSE); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwCurrentState: %s (0x%08"PRIX32")", + index, szCurrentState, readerState->dwCurrentState); + WLog_DBG(TAG, "\t[%"PRIu32"]: dwEventState: %s (0x%08"PRIX32")", + index, szEventState, readerState->dwEventState); + + if (rgbAtr) + { + WLog_DBG(TAG, "\t[%"PRIu32"]: cbAtr: %"PRIu32" rgbAtr: %s", + index, readerState->cbAtr, rgbAtr); + } + else + { + WLog_DBG(TAG, "\t[%"PRIu32"]: cbAtr: 0 rgbAtr: n/a", + index); + } + + free(szCurrentState); + free(szEventState); + free(rgbAtr); + } + + WLog_DBG(TAG, "}"); +} diff --git a/channels/smartcard/client/smartcard_pack.h b/channels/smartcard/client/smartcard_pack.h new file mode 100644 index 0000000..f7bc083 --- /dev/null +++ b/channels/smartcard/client/smartcard_pack.h @@ -0,0 +1,589 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smart Card Structure Packing + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H +#define FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H + +#include +#include +#include + +/* interface type_scard_pack */ +/* [unique][version][uuid] */ + +typedef struct _REDIR_SCARDCONTEXT +{ + /* [range] */ DWORD cbContext; + /* [size_is][unique] */ BYTE pbContext[8]; +} REDIR_SCARDCONTEXT; + +typedef struct _REDIR_SCARDHANDLE +{ + /* [range] */ DWORD cbHandle; + /* [size_is] */ BYTE pbHandle[8]; +} REDIR_SCARDHANDLE; + +typedef struct _Long_Call +{ + LONG LongValue; +} Long_Call; + +typedef struct _Long_Return +{ + LONG ReturnCode; +} Long_Return; + +typedef struct _longAndMultiString_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* msz; +} ListReaderGroups_Return; + +typedef struct _longAndMultiString_Return ListReaders_Return; + +typedef struct _Context_Call +{ + REDIR_SCARDCONTEXT hContext; +} Context_Call; + +typedef struct _ContextAndStringA_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [string] */ unsigned char* sz; +} ContextAndStringA_Call; + +typedef struct _ContextAndStringW_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [string] */ WCHAR* sz; +} ContextAndStringW_Call; + +typedef struct _ContextAndTwoStringA_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [string] */ unsigned char* sz1; + /* [string] */ unsigned char* sz2; +} ContextAndTwoStringA_Call; + +typedef struct _ContextAndTwoStringW_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [string] */ WCHAR* sz1; + /* [string] */ WCHAR* sz2; +} ContextAndTwoStringW_Call; + +typedef struct _EstablishContext_Call +{ + DWORD dwScope; +} EstablishContext_Call; + +typedef struct _EstablishContext_Return +{ + LONG ReturnCode; + REDIR_SCARDCONTEXT hContext; +} EstablishContext_Return; + +typedef struct _ListReaderGroups_Call +{ + REDIR_SCARDCONTEXT hContext; + LONG fmszGroupsIsNULL; + DWORD cchGroups; +} ListReaderGroups_Call; + +typedef struct _ListReaders_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* mszGroups; + LONG fmszReadersIsNULL; + DWORD cchReaders; +} ListReaders_Call; + +typedef struct _ReaderState_Common_Call +{ + DWORD dwCurrentState; + DWORD dwEventState; + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; +} ReaderState_Common_Call; + +typedef struct _ReaderStateA +{ + /* [string] */ unsigned char* szReader; + ReaderState_Common_Call Common; +} ReaderStateA; + +typedef struct _ReaderStateW +{ + /* [string] */ WCHAR* szReader; + ReaderState_Common_Call Common; +} ReaderStateW; + +typedef struct _ReaderState_Return +{ + DWORD dwCurrentState; + DWORD dwEventState; + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; +} ReaderState_Return; + +typedef struct _GetStatusChangeA_Call +{ + REDIR_SCARDCONTEXT hContext; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} GetStatusChangeA_Call; + +typedef struct _LocateCardsA_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [range] */ DWORD cBytes; + /* [size_is] */ BYTE* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderStateA* rgReaderStates; +} LocateCardsA_Call; + +typedef struct _LocateCardsW_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [range] */ DWORD cBytes; + /* [size_is] */ BYTE* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderStateW* rgReaderStates; +} LocateCardsW_Call; + +typedef struct _LocateCards_ATRMask +{ + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[ 36 ]; + BYTE rgbMask[ 36 ]; +} LocateCards_ATRMask; + +typedef struct _LocateCardsByATRA_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderStateA* rgReaderStates; +} LocateCardsByATRA_Call; + +typedef struct _LocateCardsByATRW_Call +{ + REDIR_SCARDCONTEXT hContext; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderStateW* rgReaderStates; +} LocateCardsByATRW_Call; + +typedef struct _GetStatusChange_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderState_Return* rgReaderStates; +} LocateCards_Return; + +typedef struct _GetStatusChange_Return GetStatusChange_Return; + +typedef struct _GetStatusChangeW_Call +{ + REDIR_SCARDCONTEXT hContext; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} GetStatusChangeW_Call; + +typedef struct _Connect_Common +{ + REDIR_SCARDCONTEXT hContext; + DWORD dwShareMode; + DWORD dwPreferredProtocols; +} Connect_Common; + +typedef struct _ConnectA_Call +{ + /* [string] */ unsigned char* szReader; + Connect_Common Common; +} ConnectA_Call; + +typedef struct _ConnectW_Call +{ + /* [string] */ WCHAR* szReader; + Connect_Common Common; +} ConnectW_Call; + +typedef struct _Connect_Return +{ + LONG ReturnCode; + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwActiveProtocol; +} Connect_Return; + +typedef struct _Reconnect_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwShareMode; + DWORD dwPreferredProtocols; + DWORD dwInitialization; +} Reconnect_Call; + +typedef struct Reconnect_Return +{ + LONG ReturnCode; + DWORD dwActiveProtocol; +} Reconnect_Return; + +typedef struct _HCardAndDisposition_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwDisposition; +} HCardAndDisposition_Call; + +typedef struct _State_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + LONG fpbAtrIsNULL; + DWORD cbAtrLen; +} State_Call; + +typedef struct _State_Return +{ + LONG ReturnCode; + DWORD dwState; + DWORD dwProtocol; + /* [range] */ DWORD cbAtrLen; + /* [size_is][unique] */ BYTE rgAtr[36]; +} State_Return; + +typedef struct _Status_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + LONG fmszReaderNamesIsNULL; + DWORD cchReaderLen; + DWORD cbAtrLen; +} Status_Call; + +typedef struct _Status_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* mszReaderNames; + DWORD dwState; + DWORD dwProtocol; + BYTE pbAtr[32]; + /* [range] */ DWORD cbAtrLen; +} Status_Return; + +typedef struct _SCardIO_Request +{ + DWORD dwProtocol; + /* [range] */ DWORD cbExtraBytes; + /* [size_is][unique] */ BYTE* pbExtraBytes; +} SCardIO_Request; + +typedef struct _Transmit_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + LPSCARD_IO_REQUEST pioSendPci; + /* [range] */ DWORD cbSendLength; + /* [size_is] */ BYTE* pbSendBuffer; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + LONG fpbRecvBufferIsNULL; + DWORD cbRecvLength; +} Transmit_Call; + +typedef struct _Transmit_Return +{ + LONG ReturnCode; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + /* [range] */ DWORD cbRecvLength; + /* [size_is][unique] */ BYTE* pbRecvBuffer; +} Transmit_Return; + +typedef struct _GetTransmitCount_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; +} GetTransmitCount_Call; + +typedef struct _GetTransmitCount_Return +{ + LONG ReturnCode; + DWORD cTransmitCount; +} GetTransmitCount_Return; + +typedef struct _Control_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwControlCode; + /* [range] */ DWORD cbInBufferSize; + /* [size_is][unique] */ BYTE* pvInBuffer; + LONG fpvOutBufferIsNULL; + DWORD cbOutBufferSize; +} Control_Call; + +typedef struct _Control_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbOutBufferSize; + /* [size_is][unique] */ BYTE* pvOutBuffer; +} Control_Return; + +typedef struct _GetAttrib_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwAttrId; + LONG fpbAttrIsNULL; + DWORD cbAttrLen; +} GetAttrib_Call; + +typedef struct _GetAttrib_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbAttrLen; + /* [size_is][unique] */ BYTE* pbAttr; +} GetAttrib_Return; + +typedef struct _SetAttrib_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwAttrId; + /* [range] */ DWORD cbAttrLen; + /* [size_is] */ BYTE* pbAttr; +} SetAttrib_Call; + +typedef struct _ReadCache_Common +{ + REDIR_SCARDCONTEXT hContext; + UUID* CardIdentifier; + DWORD FreshnessCounter; + LONG fPbDataIsNULL; + DWORD cbDataLen; +} ReadCache_Common; + +typedef struct _ReadCacheA_Call +{ + /* [string] */ unsigned char* szLookupName; + ReadCache_Common Common; +} ReadCacheA_Call; + +typedef struct _ReadCacheW_Call +{ + /* [string] */ WCHAR* szLookupName; + ReadCache_Common Common; +} ReadCacheW_Call; + +typedef struct _ReadCache_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} ReadCache_Return; + +typedef struct _WriteCache_Common +{ + REDIR_SCARDCONTEXT hContext; + UUID* CardIdentifier; + DWORD FreshnessCounter; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} WriteCache_Common; + +typedef struct _WriteCacheA_Call +{ + /* [string] */ unsigned char* szLookupName; + WriteCache_Common Common; +} WriteCacheA_Call; + +typedef struct _WriteCacheW_Call +{ + /* [string] */ WCHAR* szLookupName; + WriteCache_Common Common; +} WriteCacheW_Call; + +#define SMARTCARD_COMMON_TYPE_HEADER_LENGTH 8 +#define SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH 8 + +#include "smartcard_main.h" + +LONG smartcard_pack_write_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, UINT32 size, + UINT32 alignment); +LONG smartcard_unpack_read_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, UINT32 size, + UINT32 alignment); + +SCARDCONTEXT smartcard_scard_context_native_from_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDCONTEXT* context); +void smartcard_scard_context_native_to_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDCONTEXT* context, SCARDCONTEXT hContext); + +SCARDHANDLE smartcard_scard_handle_native_from_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDHANDLE* handle); +void smartcard_scard_handle_native_to_redir(SMARTCARD_DEVICE* smartcard, REDIR_SCARDHANDLE* handle, + SCARDHANDLE hCard); + +LONG smartcard_unpack_common_type_header(SMARTCARD_DEVICE* smartcard, wStream* s); +void smartcard_pack_common_type_header(SMARTCARD_DEVICE* smartcard, wStream* s); + +LONG smartcard_unpack_private_type_header(SMARTCARD_DEVICE* smartcard, wStream* s); +void smartcard_pack_private_type_header(SMARTCARD_DEVICE* smartcard, wStream* s, + UINT32 objectBufferLength); + +LONG smartcard_unpack_redir_scard_context(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context); +LONG smartcard_pack_redir_scard_context(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context); + +LONG smartcard_unpack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context); +LONG smartcard_pack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context); + +LONG smartcard_unpack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle); +LONG smartcard_pack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle); + +LONG smartcard_unpack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle); +LONG smartcard_pack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle); + +LONG smartcard_unpack_establish_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, + EstablishContext_Call* call); +void smartcard_trace_establish_context_call(SMARTCARD_DEVICE* smartcard, + EstablishContext_Call* call); + +LONG smartcard_pack_establish_context_return(SMARTCARD_DEVICE* smartcard, wStream* s, + EstablishContext_Return* ret); +void smartcard_trace_establish_context_return(SMARTCARD_DEVICE* smartcard, + EstablishContext_Return* ret); + +LONG smartcard_unpack_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, Context_Call* call); +void smartcard_trace_context_call(SMARTCARD_DEVICE* smartcard, Context_Call* call, + const char* name); + +void smartcard_trace_long_return(SMARTCARD_DEVICE* smartcard, Long_Return* ret, const char* name); + +LONG smartcard_unpack_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Call* call); +void smartcard_trace_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, + ListReaderGroups_Call* call, BOOL unicode); + +LONG smartcard_pack_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Return* ret); +void smartcard_trace_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, + ListReaderGroups_Return* ret, BOOL unicode); + +LONG smartcard_unpack_list_readers_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Call* call); +void smartcard_trace_list_readers_call(SMARTCARD_DEVICE* smartcard, ListReaders_Call* call, + BOOL unicode); + +LONG smartcard_pack_list_readers_return(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Return* ret); +void smartcard_trace_list_readers_return(SMARTCARD_DEVICE* smartcard, ListReaders_Return* ret, + BOOL unicode); + +LONG smartcard_unpack_connect_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectA_Call* call); +void smartcard_trace_connect_a_call(SMARTCARD_DEVICE* smartcard, ConnectA_Call* call); + +LONG smartcard_unpack_connect_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectW_Call* call); +void smartcard_trace_connect_w_call(SMARTCARD_DEVICE* smartcard, ConnectW_Call* call); + +LONG smartcard_pack_connect_return(SMARTCARD_DEVICE* smartcard, wStream* s, Connect_Return* ret); +void smartcard_trace_connect_return(SMARTCARD_DEVICE* smartcard, Connect_Return* ret); + +LONG smartcard_unpack_reconnect_call(SMARTCARD_DEVICE* smartcard, wStream* s, Reconnect_Call* call); +void smartcard_trace_reconnect_call(SMARTCARD_DEVICE* smartcard, Reconnect_Call* call); + +LONG smartcard_pack_reconnect_return(SMARTCARD_DEVICE* smartcard, wStream* s, + Reconnect_Return* ret); +void smartcard_trace_reconnect_return(SMARTCARD_DEVICE* smartcard, Reconnect_Return* ret); + +LONG smartcard_unpack_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, wStream* s, + HCardAndDisposition_Call* call); +void smartcard_trace_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, + HCardAndDisposition_Call* call, const char* name); + +LONG smartcard_unpack_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeA_Call* call); +void smartcard_trace_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, + GetStatusChangeA_Call* call); + +LONG smartcard_unpack_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeW_Call* call); +void smartcard_trace_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, + GetStatusChangeW_Call* call); + +LONG smartcard_pack_get_status_change_return(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChange_Return* ret); +void smartcard_trace_get_status_change_return(SMARTCARD_DEVICE* smartcard, + GetStatusChange_Return* ret, BOOL unicode); + +LONG smartcard_unpack_state_call(SMARTCARD_DEVICE* smartcard, wStream* s, State_Call* call); +LONG smartcard_pack_state_return(SMARTCARD_DEVICE* smartcard, wStream* s, State_Return* ret); + +LONG smartcard_unpack_status_call(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Call* call); +void smartcard_trace_status_call(SMARTCARD_DEVICE* smartcard, Status_Call* call, BOOL unicode); + +LONG smartcard_pack_status_return(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Return* ret); +void smartcard_trace_status_return(SMARTCARD_DEVICE* smartcard, Status_Return* ret, BOOL unicode); + +LONG smartcard_unpack_get_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetAttrib_Call* call); +void smartcard_trace_get_attrib_call(SMARTCARD_DEVICE* smartcard, GetAttrib_Call* call); + +LONG smartcard_pack_get_attrib_return(SMARTCARD_DEVICE* smartcard, wStream* s, + GetAttrib_Return* ret); +void smartcard_trace_get_attrib_return(SMARTCARD_DEVICE* smartcard, GetAttrib_Return* ret, + DWORD dwAttrId); + +LONG smartcard_unpack_control_call(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Call* call); +void smartcard_trace_control_call(SMARTCARD_DEVICE* smartcard, Control_Call* call); + +LONG smartcard_pack_control_return(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Return* ret); +void smartcard_trace_control_return(SMARTCARD_DEVICE* smartcard, Control_Return* ret); + +LONG smartcard_unpack_transmit_call(SMARTCARD_DEVICE* smartcard, wStream* s, Transmit_Call* call); +void smartcard_trace_transmit_call(SMARTCARD_DEVICE* smartcard, Transmit_Call* call); + +LONG smartcard_pack_transmit_return(SMARTCARD_DEVICE* smartcard, wStream* s, Transmit_Return* ret); +void smartcard_trace_transmit_return(SMARTCARD_DEVICE* smartcard, Transmit_Return* ret); + +LONG smartcard_unpack_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRA_Call* call); +void smartcard_trace_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, + LocateCardsByATRA_Call* call); + + +#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H */ diff --git a/channels/sshagent/CMakeLists.txt b/channels/sshagent/CMakeLists.txt new file mode 100644 index 0000000..f3fa34e --- /dev/null +++ b/channels/sshagent/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("sshagent") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/sshagent/ChannelOptions.cmake b/channels/sshagent/ChannelOptions.cmake new file mode 100644 index 0000000..083d8d5 --- /dev/null +++ b/channels/sshagent/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "sshagent" TYPE "dynamic" + DESCRIPTION "SSH Agent Forwarding Extension" + SPECIFICATIONS "" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/sshagent/client/CMakeLists.txt b/channels/sshagent/client/CMakeLists.txt new file mode 100644 index 0000000..7feea96 --- /dev/null +++ b/channels/sshagent/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("sshagent") + +set(${MODULE_PREFIX}_SRCS + sshagent_main.c + sshagent_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +target_link_libraries(${MODULE_NAME} winpr) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/sshagent/client/sshagent_main.c b/channels/sshagent/client/sshagent_main.c new file mode 100644 index 0000000..39d81aa --- /dev/null +++ b/channels/sshagent/client/sshagent_main.c @@ -0,0 +1,408 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent + * + * This relays data to and from an ssh-agent program equivalent running on the + * RDP server to an ssh-agent running locally. Unlike the normal ssh-agent, + * which sends data over an SSH channel, the data is send over an RDP dynamic + * virtual channel. + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sshagent_main.h" +#include + +#define TAG CHANNELS_TAG("sshagent.client") + +typedef struct _SSHAGENT_LISTENER_CALLBACK SSHAGENT_LISTENER_CALLBACK; +struct _SSHAGENT_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + + rdpContext* rdpcontext; + const char* agent_uds_path; +}; + +typedef struct _SSHAGENT_CHANNEL_CALLBACK SSHAGENT_CHANNEL_CALLBACK; +struct _SSHAGENT_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + rdpContext* rdpcontext; + int agent_fd; + HANDLE thread; + CRITICAL_SECTION lock; +}; + +typedef struct _SSHAGENT_PLUGIN SSHAGENT_PLUGIN; +struct _SSHAGENT_PLUGIN +{ + IWTSPlugin iface; + + SSHAGENT_LISTENER_CALLBACK* listener_callback; + + rdpContext* rdpcontext; +}; + + +/** + * Function to open the connection to the sshagent + * + * @return The fd on success, otherwise -1 + */ +static int connect_to_sshagent(const char* udspath) +{ + int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (agent_fd == -1) + { + WLog_ERR(TAG, "Can't open Unix domain socket!"); + return -1; + } + + struct sockaddr_un addr; + + memset(&addr, 0, sizeof(addr)); + + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1); + + int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr)); + + if (rc != 0) + { + WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", + udspath); + close(agent_fd); + return -1; + } + + return agent_fd; +} + + +/** + * Entry point for thread to read from the ssh-agent socket and forward + * the data to RDP + * + * @return NULL + */ +static DWORD WINAPI sshagent_read_thread(LPVOID data) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data; + BYTE buffer[4096]; + int going = 1; + UINT status = CHANNEL_RC_OK; + + while (going) + { + int bytes_read = read(callback->agent_fd, + buffer, + sizeof(buffer)); + + if (bytes_read == 0) + { + /* Socket closed cleanly at other end */ + going = 0; + } + else if (bytes_read < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, + "Error reading from sshagent, errno=%d", + errno); + status = ERROR_READ_FAULT; + going = 0; + } + } + else + { + /* Something read: forward to virtual channel */ + status = callback->channel->Write(callback->channel, + bytes_read, + buffer, + NULL); + + if (status != CHANNEL_RC_OK) + { + going = 0; + } + } + } + + close(callback->agent_fd); + + if (status != CHANNEL_RC_OK) + setChannelError(callback->rdpcontext, status, + "sshagent_read_thread reported an error"); + + ExitThread(status); + return status; +} + +/** + * Callback for data received from the RDP server; forward this to ssh-agent + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*) pChannelCallback; + BYTE* pBuffer = Stream_Pointer(data); + UINT32 cbSize = Stream_GetRemainingLength(data); + BYTE* pos = pBuffer; + /* Forward what we have received to the ssh agent */ + UINT32 bytes_to_write = cbSize; + errno = 0; + + while (bytes_to_write > 0) + { + int bytes_written = write(callback->agent_fd, pos, + bytes_to_write); + + if (bytes_written < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, + "Error writing to sshagent, errno=%d", + errno); + return ERROR_WRITE_FAULT; + } + } + else + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + } + + /* Consume stream */ + Stream_Seek(data, cbSize); + return CHANNEL_RC_OK; +} + +/** + * Callback for when the virtual channel is closed + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*) pChannelCallback; + /* Call shutdown() to wake up the read() in sshagent_read_thread(). */ + shutdown(callback->agent_fd, SHUT_RDWR); + EnterCriticalSection(&callback->lock); + + if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED) + { + UINT error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(callback->thread); + LeaveCriticalSection(&callback->lock); + DeleteCriticalSection(&callback->lock); + free(callback); + return CHANNEL_RC_OK; +} + + +/** + * Callback for when a new virtual channel is opened + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + SSHAGENT_CHANNEL_CALLBACK* callback; + SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*) pListenerCallback; + callback = (SSHAGENT_CHANNEL_CALLBACK*) calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Now open a connection to the local ssh-agent. Do this for each + * connection to the plugin in case we mess up the agent session. */ + callback->agent_fd + = connect_to_sshagent(listener_callback->agent_uds_path); + + if (callback->agent_fd == -1) + { + free(callback); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + InitializeCriticalSection(&callback->lock); + callback->iface.OnDataReceived = sshagent_on_data_received; + callback->iface.OnClose = sshagent_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + callback->rdpcontext = listener_callback->rdpcontext; + callback->thread + = CreateThread(NULL, + 0, + sshagent_read_thread, + (void*) callback, + 0, + NULL); + + if (!callback->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + DeleteCriticalSection(&callback->lock); + free(callback); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Callback for when the plugin is initialised + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*) pPlugin; + sshagent->listener_callback = (SSHAGENT_LISTENER_CALLBACK*) calloc(1, + sizeof(SSHAGENT_LISTENER_CALLBACK)); + + if (!sshagent->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->listener_callback->rdpcontext = sshagent->rdpcontext; + sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection; + sshagent->listener_callback->plugin = pPlugin; + sshagent->listener_callback->channel_mgr = pChannelMgr; + sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK"); + + if (sshagent->listener_callback->agent_uds_path == NULL) + { + WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!"); + free(sshagent->listener_callback); + sshagent->listener_callback = NULL; + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0, + (IWTSListenerCallback*) sshagent->listener_callback, NULL); +} + +/** + * Callback for when the plugin is terminated + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*) pPlugin; + free(sshagent); + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry sshagent_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Main entry point for sshagent DVC plugin + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = CHANNEL_RC_OK; + SSHAGENT_PLUGIN* sshagent; + sshagent = (SSHAGENT_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "sshagent"); + + if (!sshagent) + { + sshagent = (SSHAGENT_PLUGIN*) calloc(1, sizeof(SSHAGENT_PLUGIN)); + + if (!sshagent) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->iface.Initialize = sshagent_plugin_initialize; + sshagent->iface.Connected = NULL; + sshagent->iface.Disconnected = NULL; + sshagent->iface.Terminated = sshagent_plugin_terminated; + sshagent->rdpcontext = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( + pEntryPoints))->instance)->context; + status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", (IWTSPlugin*) sshagent); + } + + return status; +} + +/* vim: set sw=8:ts=8:noet: */ diff --git a/channels/sshagent/client/sshagent_main.h b/channels/sshagent/client/sshagent_main.h new file mode 100644 index 0000000..fc1ac15 --- /dev/null +++ b/channels/sshagent/client/sshagent_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 SSHAGENT_MAIN_H +#define SSHAGENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("sshagent.client") +#ifdef WITH_DEBUG_SSHAGENT +#define DEBUG_SSHAGENT(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_SSHAGENT(...) do { } while (0) +#endif + +#endif /* SSHAGENT_MAIN_H */ + diff --git a/channels/sshagent/server/CMakeLists.txt b/channels/sshagent/server/CMakeLists.txt new file mode 100644 index 0000000..9d62995 --- /dev/null +++ b/channels/sshagent/server/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("sshagent") + +set(${MODULE_PREFIX}_SRCS + sshagent_main.c) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/sshagent/server/sshagent_main.c b/channels/sshagent/server/sshagent_main.c new file mode 100644 index 0000000..dce713c --- /dev/null +++ b/channels/sshagent/server/sshagent_main.c @@ -0,0 +1,422 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2012-2013 Jay Sorg + * Copyright 2012-2013 Laxmikant Rashinkar + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Portions are from OpenSSH, under the following license: + * + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * The authentication agent program. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * xrdp-ssh-agent.c: program to forward ssh-agent protocol from xrdp session + * + * This performs the equivalent function of ssh-agent on a server you connect + * to via ssh, but the ssh-agent protocol is over an RDP dynamic virtual + * channel and not an SSH channel. + * + * This will print out variables to set in your environment (specifically, + * $SSH_AUTH_SOCK) for ssh clients to find the agent's socket, then it will + * run in the background. This is suitable to run just as you would run the + * normal ssh-agent, e.g. in your Xsession or /etc/xrdp/startwm.sh. + * + * Your RDP client needs to be running a compatible client-side plugin + * that can see a local ssh-agent. + * + * usage (from within an xrdp session): + * xrdp-ssh-agent + * + * build instructions: + * gcc xrdp-ssh-agent.c -o xrdp-ssh-agent -L./.libs -lxrdpapi -Wall + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#ifdef __WIN32__ +#include +#endif + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _PATH_DEVNULL "/dev/null" + +static char socket_name[PATH_MAX]; +static char socket_dir[PATH_MAX]; +static int sa_uds_fd = -1; +static int is_going = 1; + + +/* Make a template filename for mk[sd]temp() */ +/* This is from mktemp_proto() in misc.c from openssh */ +void +mktemp_proto(char* s, size_t len) +{ + const char* tmpdir; + int r; + + if ((tmpdir = getenv("TMPDIR")) != NULL) + { + r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir); + + if (r > 0 && (size_t)r < len) + return; + } + + r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX"); + + if (r < 0 || (size_t)r >= len) + { + fprintf(stderr, "%s: template string too short", __func__); + exit(1); + } +} + + +/* This uses parts of main() in ssh-agent.c from openssh */ +static void +setup_ssh_agent(struct sockaddr_un* addr) +{ + int rc; + /* Create private directory for agent socket */ + mktemp_proto(socket_dir, sizeof(socket_dir)); + + if (mkdtemp(socket_dir) == NULL) + { + perror("mkdtemp: private socket dir"); + exit(1); + } + + snprintf(socket_name, sizeof(socket_name), "%s/agent.%ld", socket_dir, + (long)getpid()); + /* Create unix domain socket */ + unlink(socket_name); + sa_uds_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (sa_uds_fd == -1) + { + fprintf(stderr, "sshagent: socket creation failed"); + exit(2); + } + + memset(addr, 0, sizeof(struct sockaddr_un)); + addr->sun_family = AF_UNIX; + strncpy(addr->sun_path, socket_name, sizeof(addr->sun_path)); + addr->sun_path[sizeof(addr->sun_path) - 1] = 0; + /* Create with privileges rw------- so other users can't access the UDS */ + mode_t umask_sav = umask(0177); + rc = bind(sa_uds_fd, (struct sockaddr*)addr, sizeof(struct sockaddr_un)); + + if (rc != 0) + { + fprintf(stderr, "sshagent: bind failed"); + close(sa_uds_fd); + unlink(socket_name); + exit(3); + } + + umask(umask_sav); + rc = listen(sa_uds_fd, /* backlog = */ 5); + + if (rc != 0) + { + fprintf(stderr, "listen failed\n"); + close(sa_uds_fd); + unlink(socket_name); + exit(1); + } + + /* Now fork: the child becomes the ssh-agent daemon and the parent prints + * out the pid and socket name. */ + pid_t pid = fork(); + + if (pid == -1) + { + perror("fork"); + exit(1); + } + else if (pid != 0) + { + /* Parent */ + close(sa_uds_fd); + printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n", socket_name); + printf("SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", pid); + printf("echo Agent pid %d;\n", pid); + exit(0); + } + + /* Child */ + + if (setsid() == -1) + { + fprintf(stderr, "setsid failed"); + exit(1); + } + + (void)chdir("/"); + int devnullfd; + + if ((devnullfd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) + { + /* XXX might close listen socket */ + (void)dup2(devnullfd, STDIN_FILENO); + (void)dup2(devnullfd, STDOUT_FILENO); + (void)dup2(devnullfd, STDERR_FILENO); + + if (devnullfd > 2) + close(devnullfd); + } + + /* deny core dumps, since memory contains unencrypted private keys */ + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + { + fprintf(stderr, "setrlimit RLIMIT_CORE: %s", strerror(errno)); + exit(1); + } +} + + +static void +handle_connection(int client_fd) +{ + int rdp_fd = -1; + int rc; + void* channel = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, + "SSHAGENT", + WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED); + + if (channel == NULL) + { + fprintf(stderr, "WTSVirtualChannelOpenEx() failed\n"); + } + + unsigned int retlen; + int* retdata; + rc = WTSVirtualChannelQuery(channel, + WTSVirtualFileHandle, + (void**)&retdata, + &retlen); + + if (!rc) + { + fprintf(stderr, "WTSVirtualChannelQuery() failed\n"); + } + + if (retlen != sizeof(rdp_fd)) + { + fprintf(stderr, "WTSVirtualChannelQuery() returned wrong length %d\n", + retlen); + } + + rdp_fd = *retdata; + int client_going = 1; + + while (client_going) + { + /* Wait for data from RDP or the client */ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(client_fd, &readfds); + FD_SET(rdp_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + if (FD_ISSET(rdp_fd, &readfds)) + { + /* Read from RDP and write to the client */ + char buffer[4096]; + unsigned int bytes_to_write; + rc = WTSVirtualChannelRead(channel, + /* TimeOut = */ 5000, + buffer, + sizeof(buffer), + &bytes_to_write); + + if (rc == 1) + { + char* pos = buffer; + errno = 0; + + while (bytes_to_write > 0) + { + int bytes_written = send(client_fd, pos, bytes_to_write, 0); + + if (bytes_written > 0) + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + else if (bytes_written == 0) + { + fprintf(stderr, "send() returned 0!\n"); + } + else if (errno != EINTR) + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + else + { + /* Error */ + fprintf(stderr, "WTSVirtualChannelRead() failed: %d\n", errno); + client_going = 0; + } + } + + if (FD_ISSET(client_fd, &readfds)) + { + /* Read from the client and write to RDP */ + char buffer[4096]; + ssize_t bytes_to_write = recv(client_fd, buffer, sizeof(buffer), 0); + + if (bytes_to_write > 0) + { + char* pos = buffer; + + while (bytes_to_write > 0) + { + unsigned int bytes_written; + int rc = WTSVirtualChannelWrite(channel, + pos, + bytes_to_write, + &bytes_written); + + if (rc == 0) + { + fprintf(stderr, "WTSVirtualChannelWrite() failed: %d\n", + errno); + client_going = 0; + } + else + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + } + } + else if (bytes_to_write == 0) + { + /* Client has closed connection */ + client_going = 0; + } + else + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + + WTSVirtualChannelClose(channel); +} + + +int +main(int argc, char** argv) +{ + /* Setup the Unix domain socket and daemon process */ + struct sockaddr_un addr; + setup_ssh_agent(&addr); + + /* Wait for a client to connect to the socket */ + while (is_going) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sa_uds_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + /* If something connected then get it... + * (You can test this using "socat - UNIX-CONNECT:".) */ + if (FD_ISSET(sa_uds_fd, &readfds)) + { + socklen_t addrsize = sizeof(addr); + int client_fd = accept(sa_uds_fd, + (struct sockaddr*)&addr, + &addrsize); + handle_connection(client_fd); + close(client_fd); + } + } + + close(sa_uds_fd); + unlink(socket_name); + return 0; +} + +/* vim: set sw=4:ts=4:et: */ diff --git a/channels/tsmf/CMakeLists.txt b/channels/tsmf/CMakeLists.txt new file mode 100644 index 0000000..8b4073e --- /dev/null +++ b/channels/tsmf/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("tsmf") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/tsmf/ChannelOptions.cmake b/channels/tsmf/ChannelOptions.cmake new file mode 100644 index 0000000..b59578f --- /dev/null +++ b/channels/tsmf/ChannelOptions.cmake @@ -0,0 +1,23 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options(NAME "tsmf" TYPE "dynamic" + DESCRIPTION "Video Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEV]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/tsmf/client/CMakeLists.txt b/channels/tsmf/client/CMakeLists.txt new file mode 100644 index 0000000..ee172fc --- /dev/null +++ b/channels/tsmf/client/CMakeLists.txt @@ -0,0 +1,77 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2012 Hewlett-Packard 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. + +define_channel_client("tsmf") + +set(${MODULE_PREFIX}_SRCS + tsmf_audio.c + tsmf_audio.h + tsmf_codec.c + tsmf_codec.h + tsmf_constants.h + tsmf_decoder.c + tsmf_decoder.h + tsmf_ifman.c + tsmf_ifman.h + tsmf_main.c + tsmf_main.h + tsmf_media.c + tsmf_media.h + tsmf_types.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp winpr) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + +if(WITH_FFMPEG) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ffmpeg" "decoder") +endif() + +if(WITH_GSTREAMER_0_10 OR WITH_GSTREAMER_1_0) + set(XRANDR_FEATURE_TYPE "REQUIRED") + set(XRANDR_FEATURE_PURPOSE "X11 randr") + set(XRANDR_FEATURE_DESCRIPTION "X11 randr extension") + find_feature(XRandR ${XRANDR_FEATURE_TYPE} ${XRANDR_FEATURE_PURPOSE} ${XRANDR_FEATURE_DESCRIPTION}) + if (WITH_XRANDR) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "gstreamer" "decoder") + else() + message(WARNING "Disabling tsmf gstreamer because XRandR wasn't found") + endif() +endif() + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "audio") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "audio") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "audio") +endif() diff --git a/channels/tsmf/client/alsa/CMakeLists.txt b/channels/tsmf/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..a3938ea --- /dev/null +++ b/channels/tsmf/client/alsa/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("tsmf" "alsa" "audio") + +set(${MODULE_PREFIX}_SRCS + tsmf_alsa.c) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + +target_link_libraries(${MODULE_NAME} ${ALSA_LIBRARIES} winpr freerdp) diff --git a/channels/tsmf/client/alsa/tsmf_alsa.c b/channels/tsmf/client/alsa/tsmf_alsa.c new file mode 100644 index 0000000..3cf821b --- /dev/null +++ b/channels/tsmf/client/alsa/tsmf_alsa.c @@ -0,0 +1,254 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - ALSA Audio Device + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFALSAAudioDevice +{ + ITSMFAudioDevice iface; + + char device[32]; + snd_pcm_t* out_handle; + UINT32 source_rate; + UINT32 actual_rate; + UINT32 source_channels; + UINT32 actual_channels; + UINT32 bytes_per_sample; +} TSMFAlsaAudioDevice; + +static BOOL tsmf_alsa_open_device(TSMFAlsaAudioDevice* alsa) +{ + int error; + error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0); + + if (error < 0) + { + WLog_ERR(TAG, "failed to open device %s", alsa->device); + return FALSE; + } + + DEBUG_TSMF("open device %s", alsa->device); + return TRUE; +} + +static BOOL tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*) audio; + + if (!device) + { + strncpy(alsa->device, "default", sizeof(alsa->device)); + } + else + { + strncpy(alsa->device, device, sizeof(alsa->device) - 1); + } + + return tsmf_alsa_open_device(alsa); +} + +static BOOL tsmf_alsa_set_format(ITSMFAudioDevice* audio, + UINT32 sample_rate, UINT32 channels, UINT32 bits_per_sample) +{ + int error; + snd_pcm_uframes_t frames; + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*) audio; + + if (!alsa->out_handle) + return FALSE; + + snd_pcm_drop(alsa->out_handle); + alsa->actual_rate = alsa->source_rate = sample_rate; + alsa->actual_channels = alsa->source_channels = channels; + alsa->bytes_per_sample = bits_per_sample / 8; + error = snd_pcm_hw_params_malloc(&hw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_hw_params_malloc failed"); + return FALSE; + } + + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, + SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, + &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, + &alsa->actual_channels); + frames = sample_rate; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, + &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + error = snd_pcm_sw_params_malloc(&sw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_sw_params_malloc"); + return FALSE; + } + + snd_pcm_sw_params_current(alsa->out_handle, sw_params); + snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, + frames / 2); + snd_pcm_sw_params(alsa->out_handle, sw_params); + snd_pcm_sw_params_free(sw_params); + snd_pcm_prepare(alsa->out_handle); + DEBUG_TSMF("sample_rate %"PRIu32" channels %"PRIu32" bits_per_sample %"PRIu32"", + sample_rate, channels, bits_per_sample); + DEBUG_TSMF("hardware buffer %lu frames", frames); + + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_TSMF("actual rate %"PRIu32" / channel %"PRIu32" is different " + "from source rate %"PRIu32" / channel %"PRIu32", resampling required.", + alsa->actual_rate, alsa->actual_channels, + alsa->source_rate, alsa->source_channels); + } + + return TRUE; +} + +static BOOL tsmf_alsa_play(ITSMFAudioDevice* audio, const BYTE* src, UINT32 data_size) +{ + int len; + int error; + int frames; + const BYTE* end; + const BYTE* pindex; + int rbytes_per_frame; + int sbytes_per_frame; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*) audio; + DEBUG_TSMF("data_size %"PRIu32"", data_size); + + if (alsa->out_handle) + { + sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample; + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample; + pindex = src; + end = pindex + data_size; + + while (pindex < end) + { + len = end - pindex; + frames = len / rbytes_per_frame; + error = snd_pcm_writei(alsa->out_handle, pindex, frames); + + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, error, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_TSMF("error len %d", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + tsmf_alsa_open_device(alsa); + break; + } + + DEBUG_TSMF("%d frames played.", error); + + if (error == 0) + break; + + pindex += error * rbytes_per_frame; + } + } + + return TRUE; +} + +static UINT64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + snd_pcm_sframes_t frames = 0; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*) audio; + + if (alsa->out_handle && alsa->actual_rate > 0 && + snd_pcm_delay(alsa->out_handle, &frames) == 0 && + frames > 0) + { + latency = ((UINT64)frames) * 10000000LL / (UINT64) alsa->actual_rate; + } + + return latency; +} + +static BOOL tsmf_alsa_flush(ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_alsa_free(ITSMFAudioDevice* audio) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*) audio; + DEBUG_TSMF(""); + + if (alsa->out_handle) + { + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + } + + free(alsa); +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_tsmf_client_audio_subsystem_entry alsa_freerdp_tsmf_client_audio_subsystem_entry +#else +#define freerdp_tsmf_client_audio_subsystem_entry FREERDP_API freerdp_tsmf_client_audio_subsystem_entry +#endif + +ITSMFAudioDevice* freerdp_tsmf_client_audio_subsystem_entry(void) +{ + TSMFAlsaAudioDevice* alsa; + alsa = (TSMFAlsaAudioDevice*) malloc(sizeof(TSMFAlsaAudioDevice)); + ZeroMemory(alsa, sizeof(TSMFAlsaAudioDevice)); + alsa->iface.Open = tsmf_alsa_open; + alsa->iface.SetFormat = tsmf_alsa_set_format; + alsa->iface.Play = tsmf_alsa_play; + alsa->iface.GetLatency = tsmf_alsa_get_latency; + alsa->iface.Flush = tsmf_alsa_flush; + alsa->iface.Free = tsmf_alsa_free; + return (ITSMFAudioDevice*) alsa; +} diff --git a/channels/tsmf/client/ffmpeg/CMakeLists.txt b/channels/tsmf/client/ffmpeg/CMakeLists.txt new file mode 100644 index 0000000..cda0bdf --- /dev/null +++ b/channels/tsmf/client/ffmpeg/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("tsmf" "ffmpeg" "decoder") + +set(${MODULE_PREFIX}_SRCS + tsmf_ffmpeg.c) + +include_directories(..) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +if(APPLE) + # For this to work on apple, we need to add some frameworks + FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation) + FIND_LIBRARY(COREVIDEO_LIBRARY CoreVideo) + FIND_LIBRARY(COREVIDEODECODE_LIBRARY VideoDecodeAcceleration) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${FFMPEG_LIBRARIES} ${COREFOUNDATION_LIBRARY} ${COREVIDEO_LIBRARY} ${COREVIDEODECODE_LIBRARY}) + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} freerdp) +else() + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${FFMPEG_LIBRARIES}) + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) +endif() + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + diff --git a/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c new file mode 100644 index 0000000..5522eb7 --- /dev/null +++ b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,642 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - FFmpeg Decoder + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +/* Compatibility with older FFmpeg */ +#if LIBAVUTIL_VERSION_MAJOR < 50 +#define AVMEDIA_TYPE_VIDEO 0 +#define AVMEDIA_TYPE_AUDIO 1 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 54 +#define MAX_AUDIO_FRAME_SIZE AVCODEC_MAX_AUDIO_FRAME_SIZE +#else +#define MAX_AUDIO_FRAME_SIZE 192000 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 55 +#define AV_CODEC_ID_VC1 CODEC_ID_VC1 +#define AV_CODEC_ID_WMAV2 CODEC_ID_WMAV2 +#define AV_CODEC_ID_WMAPRO CODEC_ID_WMAPRO +#define AV_CODEC_ID_MP3 CODEC_ID_MP3 +#define AV_CODEC_ID_MP2 CODEC_ID_MP2 +#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO +#define AV_CODEC_ID_WMV3 CODEC_ID_WMV3 +#define AV_CODEC_ID_AAC CODEC_ID_AAC +#define AV_CODEC_ID_H264 CODEC_ID_H264 +#define AV_CODEC_ID_AC3 CODEC_ID_AC3 +#endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2) +#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED +#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED +#endif + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + +typedef struct _TSMFFFmpegDecoder +{ + ITSMFDecoder iface; + + int media_type; +#if LIBAVCODEC_VERSION_MAJOR < 55 + enum CodecID codec_id; +#else + enum AVCodecID codec_id; +#endif + AVCodecContext* codec_context; + AVCodec* codec; + AVFrame* frame; + int prepared; + + BYTE* decoded_data; + UINT32 decoded_size; + UINT32 decoded_size_max; +} TSMFFFmpegDecoder; + +static BOOL tsmf_ffmpeg_init_context(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + mdecoder->codec_context = avcodec_alloc_context3(NULL); + + if (!mdecoder->codec_context) + { + WLog_ERR(TAG, "avcodec_alloc_context failed."); + return FALSE; + } + + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + mdecoder->codec_context->width = media_type->Width; + mdecoder->codec_context->height = media_type->Height; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->time_base.den = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->time_base.num = media_type->SamplesPerSecond.Denominator; +#if LIBAVCODEC_VERSION_MAJOR < 55 + mdecoder->frame = avcodec_alloc_frame(); +#else + mdecoder->frame = av_frame_alloc(); +#endif + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + mdecoder->codec_context->sample_rate = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->channels = media_type->Channels; + mdecoder->codec_context->block_align = media_type->BlockAlign; +#if LIBAVCODEC_VERSION_MAJOR < 55 +#ifdef AV_CPU_FLAG_SSE2 + mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2; +#else +#if LIBAVCODEC_VERSION_MAJOR < 53 + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMXEXT; +#else + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2; +#endif +#endif +#else /* LIBAVCODEC_VERSION_MAJOR < 55 */ +#ifdef AV_CPU_FLAG_SSE2 + av_set_cpu_flags_mask(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT); +#else + av_set_cpu_flags_mask(FF_MM_SSE2 | FF_MM_MMX2); +#endif +#endif /* LIBAVCODEC_VERSION_MAJOR < 55 */ + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + BYTE* p; + UINT32 size; + const BYTE* s; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id); + + if (!mdecoder->codec) + { + WLog_ERR(TAG, "avcodec_find_decoder failed."); + return FALSE; + } + + mdecoder->codec_context->codec_id = mdecoder->codec_id; + mdecoder->codec_context->codec_type = mdecoder->media_type; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + if (!tsmf_ffmpeg_init_video_stream(decoder, media_type)) + return FALSE; + + break; + + case AVMEDIA_TYPE_AUDIO: + if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type)) + return FALSE; + + break; + + default: + WLog_ERR(TAG, "unknown media_type %d", mdecoder->media_type); + break; + } + + if (media_type->ExtraData) + { + /* Add a padding to avoid invalid memory read in some codec */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = calloc(1, mdecoder->codec_context->extradata_size); + + if (!mdecoder->codec_context->extradata) + return FALSE; + + if (media_type->SubType == TSMF_SUB_TYPE_AVC1 && + media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO) + { + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + p = mdecoder->codec_context->extradata; + *p++ = 1; /* Reserved? */ + *p++ = media_type->ExtraData[8]; /* Profile */ + *p++ = 0; /* Profile */ + *p++ = media_type->ExtraData[12]; /* Level */ + *p++ = 0xff; /* Flag? */ + *p++ = 0xe0 | 0x01; /* Reserved | #sps */ + s = media_type->ExtraData + 20; + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + *p++ = 1; /* #pps */ + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + memcpy(p, s, size + 2); + } + else + { + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, media_type->ExtraDataSize); + memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8); + } + } + + if (mdecoder->codec->capabilities & AV_CODEC_CAP_TRUNCATED) + mdecoder->codec_context->flags |= AV_CODEC_FLAG_TRUNCATED; + + return TRUE; +} + +static BOOL tsmf_ffmpeg_prepare(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (avcodec_open2(mdecoder->codec_context, mdecoder->codec, NULL) < 0) + { + WLog_ERR(TAG, "avcodec_open2 failed."); + return FALSE; + } + + mdecoder->prepared = 1; + return TRUE; +} + +static BOOL tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = AVMEDIA_TYPE_VIDEO; + break; + + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = AVMEDIA_TYPE_AUDIO; + break; + + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->codec_id = AV_CODEC_ID_VC1; + break; + + case TSMF_SUB_TYPE_WMA2: + mdecoder->codec_id = AV_CODEC_ID_WMAV2; + break; + + case TSMF_SUB_TYPE_WMA9: + mdecoder->codec_id = AV_CODEC_ID_WMAPRO; + break; + + case TSMF_SUB_TYPE_MP3: + mdecoder->codec_id = AV_CODEC_ID_MP3; + break; + + case TSMF_SUB_TYPE_MP2A: + mdecoder->codec_id = AV_CODEC_ID_MP2; + break; + + case TSMF_SUB_TYPE_MP2V: + mdecoder->codec_id = AV_CODEC_ID_MPEG2VIDEO; + break; + + case TSMF_SUB_TYPE_WMV3: + mdecoder->codec_id = AV_CODEC_ID_WMV3; + break; + + case TSMF_SUB_TYPE_AAC: + mdecoder->codec_id = AV_CODEC_ID_AAC; + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + break; + + case TSMF_SUB_TYPE_H264: + case TSMF_SUB_TYPE_AVC1: + mdecoder->codec_id = AV_CODEC_ID_H264; + break; + + case TSMF_SUB_TYPE_AC3: + mdecoder->codec_id = AV_CODEC_ID_AC3; + break; + + default: + return FALSE; + } + + if (!tsmf_ffmpeg_init_context(decoder)) + return FALSE; + + if (!tsmf_ffmpeg_init_stream(decoder, media_type)) + return FALSE; + + if (!tsmf_ffmpeg_prepare(decoder)) + return FALSE; + + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int decoded; + int len; + AVFrame* frame; + BOOL ret = TRUE; +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (BYTE*) data; + pkt.size = data_size; + + if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT) + pkt.flags |= AV_PKT_FLAG_KEY; + + len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt); + } +#endif + + if (len < 0) + { + WLog_ERR(TAG, "data_size %"PRIu32", avcodec_decode_video failed (%d)", data_size, len); + ret = FALSE; + } + else if (!decoded) + { + WLog_ERR(TAG, "data_size %"PRIu32", no frame is decoded.", data_size); + ret = FALSE; + } + else + { + DEBUG_TSMF("linesize[0] %d linesize[1] %d linesize[2] %d linesize[3] %d " + "pix_fmt %d width %d height %d", + mdecoder->frame->linesize[0], mdecoder->frame->linesize[1], + mdecoder->frame->linesize[2], mdecoder->frame->linesize[3], + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + mdecoder->decoded_size = avpicture_get_size(mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size); + + if (!mdecoder->decoded_data) + return FALSE; + +#if LIBAVCODEC_VERSION_MAJOR < 55 + frame = avcodec_alloc_frame(); +#else + frame = av_frame_alloc(); +#endif + avpicture_fill((AVPicture*) frame, mdecoder->decoded_data, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + av_picture_copy((AVPicture*) frame, (AVPicture*) mdecoder->frame, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + av_free(frame); + } + + return ret; +} + +static BOOL tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int len; + int frame_size; + UINT32 src_size; + const BYTE* src; + BYTE* dst; + int dst_offset; +#if 0 + WLog_DBG(TAG, ("tsmf_ffmpeg_decode_audio: data_size %"PRIu32"", data_size)); + int i; + + for (i = 0; i < data_size; i++) + { + WLog_DBG(TAG, ("%02"PRIX8"", data[i])); + + if (i % 16 == 15) + WLog_DBG(TAG, ("\n")); + } + +#endif + + if (mdecoder->decoded_size_max == 0) + mdecoder->decoded_size_max = MAX_AUDIO_FRAME_SIZE + 16; + + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size_max); + + if (!mdecoder->decoded_data) + return FALSE; + + /* align the memory for SSE2 needs */ + dst = (BYTE*)(((uintptr_t) mdecoder->decoded_data + 15) & ~ 0x0F); + dst_offset = dst - mdecoder->decoded_data; + src = data; + src_size = data_size; + + while (src_size > 0) + { + /* Ensure enough space for decoding */ + if (mdecoder->decoded_size_max - mdecoder->decoded_size < MAX_AUDIO_FRAME_SIZE) + { + BYTE* tmp_data; + tmp_data = realloc(mdecoder->decoded_data, mdecoder->decoded_size_max * 2 + 16); + + if (!tmp_data) + return FALSE; + + mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2 + 16; + mdecoder->decoded_data = tmp_data; + dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); + + if (dst - mdecoder->decoded_data != dst_offset) + { + /* re-align the memory if the alignment has changed after realloc */ + memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + dst_offset = dst - mdecoder->decoded_data; + } + + dst += mdecoder->decoded_size; + } + + frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size; +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_audio2(mdecoder->codec_context, + (int16_t*) dst, &frame_size, src, src_size); +#else + { +#if LIBAVCODEC_VERSION_MAJOR < 55 + AVFrame* decoded_frame = avcodec_alloc_frame(); +#else + AVFrame* decoded_frame = av_frame_alloc(); +#endif + int got_frame = 0; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (BYTE*) src; + pkt.size = src_size; + len = avcodec_decode_audio4(mdecoder->codec_context, decoded_frame, &got_frame, &pkt); + + if (len >= 0 && got_frame) + { + frame_size = av_samples_get_buffer_size(NULL, mdecoder->codec_context->channels, + decoded_frame->nb_samples, mdecoder->codec_context->sample_fmt, 1); + memcpy(dst, decoded_frame->data[0], frame_size); + } + else + { + frame_size = 0; + } + + av_free(decoded_frame); + } +#endif + + if (len > 0) + { + src += len; + src_size -= len; + } + + if (frame_size > 0) + { + mdecoder->decoded_size += frame_size; + dst += frame_size; + } + } + + if (mdecoder->decoded_size == 0) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + else if (dst_offset) + { + /* move the aligned decoded data to original place */ + memmove(mdecoder->decoded_data, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + } + + DEBUG_TSMF("data_size %"PRIu32" decoded_size %"PRIu32"", + data_size, mdecoder->decoded_size); + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->decoded_data) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + + mdecoder->decoded_size = 0; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions); + + case AVMEDIA_TYPE_AUDIO: + return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions); + + default: + WLog_ERR(TAG, "unknown media type."); + return FALSE; + } +} + +static BYTE* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, UINT32* size) +{ + BYTE* buf; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + *size = mdecoder->decoded_size; + buf = mdecoder->decoded_data; + mdecoder->decoded_data = NULL; + mdecoder->decoded_size = 0; + return buf; +} + +static UINT32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + switch (mdecoder->codec_context->pix_fmt) + { + case AV_PIX_FMT_YUV420P: + return RDP_PIXFMT_I420; + + default: + WLog_ERR(TAG, "unsupported pixel format %u", + mdecoder->codec_context->pix_fmt); + return (UINT32) - 1; + } +} + +static BOOL tsmf_ffmpeg_get_decoded_dimension(ITSMFDecoder* decoder, UINT32* width, UINT32* height) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->codec_context->width > 0 && mdecoder->codec_context->height > 0) + { + *width = mdecoder->codec_context->width; + *height = mdecoder->codec_context->height; + return TRUE; + } + else + { + return FALSE; + } +} + +static void tsmf_ffmpeg_free(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->frame) + av_free(mdecoder->frame); + + free(mdecoder->decoded_data); + + if (mdecoder->codec_context) + { + if (mdecoder->prepared) + avcodec_close(mdecoder->codec_context); + + free(mdecoder->codec_context->extradata); + av_free(mdecoder->codec_context); + } + + free(decoder); +} + +static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT; +static BOOL CALLBACK InitializeAvCodecs(PINIT_ONCE once, PVOID param, PVOID* context) +{ + avcodec_register_all(); + return TRUE; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_tsmf_client_subsystem_entry ffmpeg_freerdp_tsmf_client_decoder_subsystem_entry +#else +#define freerdp_tsmf_client_subsystem_entry FREERDP_API freerdp_tsmf_client_decoder_subsystem_entry +#endif + +ITSMFDecoder* freerdp_tsmf_client_subsystem_entry(void) +{ + TSMFFFmpegDecoder* decoder; + InitOnceExecuteOnce(&g_Initialized, InitializeAvCodecs, NULL, NULL); + WLog_DBG(TAG, "TSMFDecoderEntry FFMPEG"); + decoder = (TSMFFFmpegDecoder*) calloc(1, sizeof(TSMFFFmpegDecoder)); + + if (!decoder) + return NULL; + + decoder->iface.SetFormat = tsmf_ffmpeg_set_format; + decoder->iface.Decode = tsmf_ffmpeg_decode; + decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data; + decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format; + decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension; + decoder->iface.Free = tsmf_ffmpeg_free; + return (ITSMFDecoder*) decoder; +} diff --git a/channels/tsmf/client/gstreamer/CMakeLists.txt b/channels/tsmf/client/gstreamer/CMakeLists.txt new file mode 100644 index 0000000..fff688c --- /dev/null +++ b/channels/tsmf/client/gstreamer/CMakeLists.txt @@ -0,0 +1,65 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script for gstreamer subsystem +# +# (C) Copyright 2012 Hewlett-Packard 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. + +define_channel_client_subsystem("tsmf" "gstreamer" "decoder") + +if(NOT GSTREAMER_0_10_FOUND AND NOT GSTREAMER_1_0_FOUND) + message(FATAL_ERROR "GStreamer library not found, but required for TSMF module.") +elseif (GSTREAMER_0_10_FOUND AND GSTREAMER_1_0_FOUND) + message(FATAL_ERROR "GStreamer 0.10 and GStreamer 1.0 support are mutually exclusive!") +endif() + +set(SRC "tsmf_gstreamer.c") + +if (GSTREAMER_1_0_FOUND) + set(LIBS ${GSTREAMER_1_0_LIBRARIES}) + include_directories(${GSTREAMER_1_0_INCLUDE_DIRS}) +elseif (GSTREAMER_0_10_FOUND) + set(LIBS ${GSTREAMER_0_10_LIBRARIES}) + include_directories(${GSTREAMER_0_10_INCLUDE_DIRS}) +endif() + +if(ANDROID) + set(SRC ${SRC} + tsmf_android.c) + set(LIBS ${LIBS}) +else() + set(XEXT_FEATURE_TYPE "RECOMMENDED") + set(XEXT_FEATURE_PURPOSE "X11 extension") + set(XEXT_FEATURE_DESCRIPTION "X11 core extensions") + + find_feature(Xext ${XEXT_FEATURE_TYPE} ${XEXT_FEATURE_PURPOSE} ${XEXT_FEATURE_DESCRIPTION}) + + set(SRC ${SRC} + tsmf_X11.c) + set(LIBS ${LIBS} ${X11_LIBRARIES} ${XEXT_LIBRARIES}) + if (NOT APPLE) + list(APPEND LIBS rt) + endif() + + if(XEXT_FOUND) + add_definitions(-DWITH_XEXT=1) + endif() + +endif() + +set(${MODULE_PREFIX}_SRCS "${SRC}") + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") +target_link_libraries(${MODULE_NAME} ${LIBS} winpr) diff --git a/channels/tsmf/client/gstreamer/tsmf_X11.c b/channels/tsmf/client/gstreamer/tsmf_X11.c new file mode 100644 index 0000000..b57b927 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_X11.c @@ -0,0 +1,515 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder X11 specifics + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#ifndef __CYGWIN__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wparentheses-equality" +#endif /* __clang__ */ +#include +#if __clang__ +#pragma clang diagnostic pop +#endif /* __clang__ */ + +#if GST_VERSION_MAJOR > 0 +#include +#else +#include +#endif + +#include +#include +#include + +#include + +#include "tsmf_platform.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +#if !defined(WITH_XEXT) +#warning "Building TSMF without shape extension support" +#endif + +struct X11Handle +{ + int shmid; + int *xfwin; +#if defined(WITH_XEXT) + BOOL has_shape; +#endif + Display *disp; + Window subwin; + BOOL subwinMapped; +#if GST_VERSION_MAJOR > 0 + GstVideoOverlay *overlay; +#else + GstXOverlay *overlay; +#endif + int subwinWidth; + int subwinHeight; + int subwinX; + int subwinY; +}; + +static const char* get_shm_id() +{ + static char shm_id[128]; + sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId()); + return shm_id; +} + +static GstBusSyncReply tsmf_platform_bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data) +{ + struct X11Handle* hdl; + + TSMFGstreamerDecoder* decoder = user_data; + + if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT) + return GST_BUS_PASS; + +#if GST_VERSION_MAJOR > 0 + if (!gst_is_video_overlay_prepare_window_handle_message (message)) + return GST_BUS_PASS; +#else + if (!gst_structure_has_name (message->structure, "prepare-xwindow-id")) + return GST_BUS_PASS; +#endif + + hdl = (struct X11Handle*) decoder->platform; + + if (hdl->subwin) + { +#if GST_VERSION_MAJOR > 0 + hdl->overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)); + gst_video_overlay_set_window_handle(hdl->overlay, hdl->subwin); + gst_video_overlay_handle_events(hdl->overlay, TRUE); +#else + hdl->overlay = GST_X_OVERLAY (GST_MESSAGE_SRC (message)); +#if GST_CHECK_VERSION(0,10,31) + gst_x_overlay_set_window_handle(hdl->overlay, hdl->subwin); +#else + gst_x_overlay_set_xwindow_id(hdl->overlay, hdl->subwin); +#endif + gst_x_overlay_handle_events(hdl->overlay, TRUE); +#endif + + if (hdl->subwinWidth != -1 && hdl->subwinHeight != -1 && hdl->subwinX != -1 && hdl->subwinY != -1) + { +#if GST_VERSION_MAJOR > 0 + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, hdl->subwinHeight); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + } else { + g_warning ("Window was not available before retrieving the overlay!"); + } + + gst_message_unref (message); + + return GST_BUS_DROP; +} + +const char* tsmf_platform_get_video_sink(void) +{ + return "autovideosink"; +} + +const char* tsmf_platform_get_audio_sink(void) +{ + return "autoaudiosink"; +} + +int tsmf_platform_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->platform) + return -1; + + hdl = calloc(1, sizeof(struct X11Handle)); + if (!hdl) + { + WLog_ERR(TAG, "Could not allocate handle."); + return -1; + } + + decoder->platform = hdl; + hdl->shmid = shm_open(get_shm_id(), (O_RDWR | O_CREAT), (PROT_READ | PROT_WRITE)); + if (hdl->shmid == -1) + { + WLog_ERR(TAG, "failed to get access to shared memory - shmget(%s): %i - %s", get_shm_id(), errno, strerror(errno)); + return -2; + } + + hdl->xfwin = mmap(0, sizeof(void *), PROT_READ | PROT_WRITE, MAP_SHARED, hdl->shmid, 0); + if (hdl->xfwin == MAP_FAILED) + { + WLog_ERR(TAG, "shmat failed!"); + return -3; + } + + hdl->disp = XOpenDisplay(NULL); + if (!hdl->disp) + { + WLog_ERR(TAG, "Failed to open display"); + return -4; + } + + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + + return 0; +} + +int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder) +{ + if (!decoder) + return -1; + + if (decoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + + } + + return 0; +} + +int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder) +{ + GstBus* bus; + + if (!decoder) + return -1; + + if (!decoder->pipe) + return -1; + + bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipe)); + +#if GST_VERSION_MAJOR > 0 + gst_bus_set_sync_handler (bus, (GstBusSyncHandler) tsmf_platform_bus_sync_handler, decoder, NULL); +#else + gst_bus_set_sync_handler (bus, (GstBusSyncHandler) tsmf_platform_bus_sync_handler, decoder); +#endif + + if (!bus) + { + WLog_ERR(TAG, "gst_pipeline_get_bus failed!"); + return 1; + } + + gst_object_unref (bus); + + return 0; +} + +int tsmf_platform_free(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl = decoder->platform; + + if (!hdl) + return -1; + + if (hdl->disp) + XCloseDisplay(hdl->disp); + + if (hdl->xfwin) + munmap(0, sizeof(void*)); + + if (hdl->shmid >= 0) + close(hdl->shmid); + + free(hdl); + decoder->platform = NULL; + + return 0; +} + +int tsmf_window_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + decoder->ready = TRUE; + return -3; + } + else + { + if (!decoder) + return -1; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*) decoder->platform; + + if (!hdl->subwin) + { + XLockDisplay(hdl->disp); + hdl->subwin = XCreateSimpleWindow(hdl->disp, *(int *)hdl->xfwin, 0, 0, 1, 1, 0, 0, 0); + XUnlockDisplay(hdl->disp); + + if (!hdl->subwin) + { + WLog_ERR(TAG, "Could not create subwindow!"); + } + } + + tsmf_window_map(decoder); + + decoder->ready = TRUE; +#if defined(WITH_XEXT) + int event, error; + XLockDisplay(hdl->disp); + hdl->has_shape = XShapeQueryExtension(hdl->disp, &event, &error); + XUnlockDisplay(hdl->disp); +#endif + } + + return 0; +} + +int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, + int height, int nr_rects, RDP_RECT *rects) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + return -3; + } + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*) decoder->platform; + DEBUG_TSMF("resize: x=%d, y=%d, w=%d, h=%d", x, y, width, height); + + if (hdl->overlay) + { +#if GST_VERSION_MAJOR > 0 + + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + } + + if (hdl->subwin) + { + hdl->subwinX = x; + hdl->subwinY = y; + hdl->subwinWidth = width; + hdl->subwinHeight = height; + + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, hdl->subwinHeight); + + /* Unmap the window if there are no visibility rects */ + if (nr_rects == 0) + tsmf_window_unmap(decoder); + else + tsmf_window_map(decoder); + +#if defined(WITH_XEXT) + if (hdl->has_shape) + { + int i; + XRectangle *xrects = NULL; + + if (nr_rects == 0) + { + xrects = calloc(1, sizeof(XRectangle)); + xrects->x = x; + xrects->y = y; + xrects->width = width; + xrects->height = height; + } + else + { + xrects = calloc(nr_rects, sizeof(XRectangle)); + } + + if (xrects) + { + for (i = 0; i < nr_rects; i++) + { + xrects[i].x = rects[i].x - x; + xrects[i].y = rects[i].y - y; + xrects[i].width = rects[i].width; + xrects[i].height = rects[i].height; + } + + XShapeCombineRectangles(hdl->disp, hdl->subwin, ShapeBounding, x, y, xrects, nr_rects, ShapeSet, 0); + free(xrects); + } + } +#endif + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_pause(TSMFGstreamerDecoder* decoder) +{ + if (!decoder) + return -1; + + return 0; +} + +int tsmf_window_resume(TSMFGstreamerDecoder* decoder) +{ + if (!decoder) + return -1; + + return 0; +} + +int tsmf_window_map(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*) decoder->platform; + + /* Only need to map the window if it is not currently mapped */ + if ((hdl->subwin) && (!hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XMapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = TRUE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_unmap(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*) decoder->platform; + + /* only need to unmap window if it is currently mapped */ + if ((hdl->subwin) && (hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XUnmapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = FALSE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + + +int tsmf_window_destroy(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + decoder->ready = FALSE; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + return -3; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*) decoder->platform; + + if (hdl->subwin) + { + XLockDisplay(hdl->disp); + XDestroyWindow(hdl->disp, hdl->subwin); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + hdl->overlay = NULL; + hdl->subwin = 0; + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + return 0; +} + diff --git a/channels/tsmf/client/gstreamer/tsmf_gstreamer.c b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c new file mode 100644 index 0000000..0addbe7 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c @@ -0,0 +1,1075 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * + * (C) Copyright 2012 HP Development Company, LLC + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wparentheses-equality" +#endif /* __clang__ */ +#include +#if __clang__ +#pragma clang diagnostic pop +#endif /* __clang__ */ + +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" +#include "tsmf_platform.h" + +#ifdef HAVE_INTTYPES_H +#include +#endif + +/* 1 second = 10,000,000 100ns units*/ +#define SEEK_TOLERANCE 10*1000*1000 + +static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder); +static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder); +static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, + GstState desired_state); +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder); + +const char* get_type(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder) + return NULL; + + switch (mdecoder->media_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + return "VIDEO"; + case TSMF_MAJOR_TYPE_AUDIO: + return "AUDIO"; + default: + return "UNKNOWN"; + } +} + +static void cb_child_added(GstChildProxy *child_proxy, GObject *object, TSMFGstreamerDecoder* mdecoder) +{ + DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object)); + + if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") || !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") || !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink *) object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */ + } + + else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") || !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink *) object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "slave-method", 1, NULL); + g_object_set(G_OBJECT(object), "buffer-time", (gint64) 20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "drift-tolerance", (gint64) 20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "latency-time", (gint64) 10000, NULL); /* microseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, NULL); /* no async state changes */ + } +} + +static void tsmf_gstreamer_enough_data(GstAppSrc *src, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void) mdecoder; + DEBUG_TSMF("%s", get_type(mdecoder)); +} + +static void tsmf_gstreamer_need_data(GstAppSrc *src, guint length, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void) mdecoder; + DEBUG_TSMF("%s length=%u", get_type(mdecoder), length); +} + +static gboolean tsmf_gstreamer_seek_data(GstAppSrc *src, guint64 offset, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void) mdecoder; + DEBUG_TSMF("%s offset=%"PRIu64"", get_type(mdecoder), offset); + + return TRUE; +} + +static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + + if (!mdecoder || !mdecoder->pipe) + return TRUE; + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + return TRUE; + + mdecoder->gstMuted = (BOOL) muted; + DEBUG_TSMF("mute=[%"PRId32"]", mdecoder->gstMuted); + mdecoder->gstVolume = (double) newVolume / (double) 10000; + DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume); + + if (!mdecoder->volume) + return TRUE; + + if (!G_IS_OBJECT(mdecoder->volume)) + return TRUE; + + g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, NULL); + g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, NULL); + + return TRUE; +} + +#ifdef __OpenBSD__ +static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp) +#else +static inline const GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp) +#endif +{ + /* + * Convert Microsoft 100ns timestamps to Gstreamer 1ns units. + */ + return (GstClockTime)(ms_timestamp * 100); +} + +int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state) +{ + GstStateChangeReturn state_change; + const char* name; + const char* sname = get_type(mdecoder); + + if (!mdecoder) + return 0; + + if (!mdecoder->pipe) + return 0; /* Just in case this is called during startup or shutdown when we don't expect it */ + + if (desired_state == mdecoder->state) + return 0; /* Redundant request - Nothing to do */ + + name = gst_element_state_get_name(desired_state); /* For debug */ + DEBUG_TSMF("%s to %s", sname, name); + state_change = gst_element_set_state(mdecoder->pipe, desired_state); + + if (state_change == GST_STATE_CHANGE_FAILURE) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name); + } + else if (state_change == GST_STATE_CHANGE_ASYNC) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name); + mdecoder->state = desired_state; + } + else + { + mdecoder->state = desired_state; + } + + return 0; +} + +static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size) +{ + GstBuffer* buffer; + gpointer data; + + if (!raw_data) + return NULL; + + if (size < 1) + return NULL; + + data = g_malloc(size); + + if (!data) + { + WLog_ERR(TAG, "Could not allocate %"G_GSIZE_FORMAT" bytes of data.", size); + return NULL; + } + + CopyMemory(data, raw_data, size); + +#if GST_VERSION_MAJOR > 0 + buffer = gst_buffer_new_wrapped(data, size); +#else + buffer = gst_buffer_new(); + + if (!buffer) + { + WLog_ERR(TAG, "Could not create GstBuffer"); + free(data); + return NULL; + } + + GST_BUFFER_MALLOCDATA(buffer) = data; + GST_BUFFER_SIZE(buffer) = size; + GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer); +#endif + + return buffer; +} + +static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*) decoder; + + if (!mdecoder) + return FALSE; + + DEBUG_TSMF(""); + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO; + break; + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO; + break; + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->gst_caps = gst_caps_new_simple("video/x-wmv", + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "wmvversion", G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WVC1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'V', 'C', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1 , 1, + NULL); + break; + case TSMF_SUB_TYPE_MP4S: + mdecoder->gst_caps = gst_caps_new_simple("video/x-divx", + "divxversion", G_TYPE_INT, 5, + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); + break; + case TSMF_SUB_TYPE_MP42: + mdecoder->gst_caps = gst_caps_new_simple("video/x-msmpeg", + "msmpegversion", G_TYPE_INT, 42, + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); + break; + case TSMF_SUB_TYPE_MP43: + mdecoder->gst_caps = gst_caps_new_simple("video/x-msmpeg", + "msmpegversion", G_TYPE_INT, 43, + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP43", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('M', 'P', '4', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); + break; + case TSMF_SUB_TYPE_M4S2: + mdecoder->gst_caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 4, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "M4S2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('M', '4', 'S', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); + break; + case TSMF_SUB_TYPE_WMA9: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-wma", + "wmaversion", G_TYPE_INT, 3, + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + "bitrate", G_TYPE_INT, media_type->BitRate, + "depth", G_TYPE_INT, media_type->BitsPerSample, + "width", G_TYPE_INT, media_type->BitsPerSample, + "block_align", G_TYPE_INT, media_type->BlockAlign, + NULL); + break; + case TSMF_SUB_TYPE_WMA1: + mdecoder->gst_caps = gst_caps_new_simple ("audio/x-wma", + "wmaversion", G_TYPE_INT, 1, + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + "bitrate", G_TYPE_INT, media_type->BitRate, + "depth", G_TYPE_INT, media_type->BitsPerSample, + "width", G_TYPE_INT, media_type->BitsPerSample, + "block_align", G_TYPE_INT, media_type->BlockAlign, + NULL); + break; + case TSMF_SUB_TYPE_WMA2: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-wma", + "wmaversion", G_TYPE_INT, 2, + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + "bitrate", G_TYPE_INT, media_type->BitRate, + "depth", G_TYPE_INT, media_type->BitsPerSample, + "width", G_TYPE_INT, media_type->BitsPerSample, + "block_align", G_TYPE_INT, media_type->BlockAlign, + NULL); + break; + case TSMF_SUB_TYPE_MP3: + mdecoder->gst_caps = gst_caps_new_simple("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, + "layer", G_TYPE_INT, 3, + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + NULL); + break; + case TSMF_SUB_TYPE_WMV1: + mdecoder->gst_caps = gst_caps_new_simple("video/x-wmv", + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "wmvversion", G_TYPE_INT, 1, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'M', 'V', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); + break; + case TSMF_SUB_TYPE_WMV2: + mdecoder->gst_caps = gst_caps_new_simple("video/x-wmv", + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "wmvversion", G_TYPE_INT, 2, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'M', 'V', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1 , 1, + NULL); + break; + case TSMF_SUB_TYPE_WMV3: + mdecoder->gst_caps = gst_caps_new_simple("video/x-wmv", + "bitrate", G_TYPE_UINT, media_type->BitRate, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "wmvversion", G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV3", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('W', 'M', 'V', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1 , 1, + NULL); + break; + case TSMF_SUB_TYPE_AVC1: + case TSMF_SUB_TYPE_H264: + mdecoder->gst_caps = gst_caps_new_simple("video/x-h264", + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1 , 1, + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "nal", + NULL); + break; + case TSMF_SUB_TYPE_AC3: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-ac3", + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + NULL); + break; + case TSMF_SUB_TYPE_AAC: + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + mdecoder->gst_caps = gst_caps_new_simple("audio/mpeg", + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + "mpegversion", G_TYPE_INT, 4, + "framed", G_TYPE_BOOLEAN, TRUE, + "stream-format", G_TYPE_STRING, "raw", + NULL); + break; + case TSMF_SUB_TYPE_MP1A: + mdecoder->gst_caps = gst_caps_new_simple("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, + "channels", G_TYPE_INT, media_type->Channels, + NULL); + break; + case TSMF_SUB_TYPE_MP1V: + mdecoder->gst_caps = gst_caps_new_simple("video/mpeg", + "mpegversion", G_TYPE_INT, 1, + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "systemstream", G_TYPE_BOOLEAN, FALSE, + NULL); + break; + case TSMF_SUB_TYPE_YUY2: +#if GST_VERSION_MAJOR > 0 + mdecoder->gst_caps = gst_caps_new_simple("video/x-raw", + "format", G_TYPE_STRING, "YUY2", + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + NULL); +#else + mdecoder->gst_caps = gst_caps_new_simple("video/x-raw-yuv", + "format", G_TYPE_STRING, "YUY2", + "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + NULL); +#endif + break; + case TSMF_SUB_TYPE_MP2V: + mdecoder->gst_caps = gst_caps_new_simple("video/mpeg", + "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, + NULL); + break; + case TSMF_SUB_TYPE_MP2A: + mdecoder->gst_caps = gst_caps_new_simple("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, + "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, + NULL); + break; + case TSMF_SUB_TYPE_FLAC: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", NULL); + break; + default: + WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType); + return FALSE; + } + + if (media_type->ExtraDataSize > 0) + { + GstBuffer *buffer; + DEBUG_TSMF("Extra data available (%"PRIu32")", media_type->ExtraDataSize); + buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize); + + if (!buffer) + { + WLog_ERR(TAG, "could not allocate GstBuffer!"); + return FALSE; + } + + gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL); + } + + DEBUG_TSMF("%p format '%s'", (void*) mdecoder, gst_caps_to_string(mdecoder->gst_caps)); + tsmf_platform_set_format(mdecoder); + + /* Create the pipeline... */ + if (!tsmf_gstreamer_pipeline_build(mdecoder)) + return FALSE; + + return TRUE; +} + +void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder || !mdecoder->pipe) + return; + + if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + gst_object_unref(mdecoder->pipe); + } + + mdecoder->ready = FALSE; + mdecoder->paused = FALSE; + + mdecoder->pipe = NULL; + mdecoder->src = NULL; + mdecoder->queue = NULL; +} + +BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder) +{ +#if GST_VERSION_MAJOR > 0 + const char* video = "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !"; + const char* audio = "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#else + const char* video = "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !"; + const char* audio = "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#endif + char pipeline[1024]; + + if (!mdecoder) + return FALSE; + + /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments. + * The only fixed elements necessary are appsrc and the volume element for audio streams. + * The rest could easily be provided in gstreamer pipeline notation from command line. */ + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video, tsmf_platform_get_video_sink()); + else + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio, tsmf_platform_get_audio_sink()); + + DEBUG_TSMF("pipeline=%s", pipeline); + mdecoder->pipe = gst_parse_launch(pipeline, NULL); + + if (!mdecoder->pipe) + { + WLog_ERR(TAG, "Failed to create new pipe"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource"); + else + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource"); + + if (!mdecoder->src) + { + WLog_ERR(TAG, "Failed to get appsrc"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue"); + else + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue"); + + if (!mdecoder->queue) + { + WLog_ERR(TAG, "Failed to get queue"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink"); + else + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink"); + + if (!mdecoder->outsink) + { + WLog_ERR(TAG, "Failed to get sink"); + return FALSE; + } + + g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO) + { + mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume"); + + if (!mdecoder->volume) + { + WLog_ERR(TAG, "Failed to get volume"); + return FALSE; + } + + tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume*((double) 10000), mdecoder->gstMuted); + } + + tsmf_platform_register_handler(mdecoder); + /* AppSrc settings */ + GstAppSrcCallbacks callbacks = + { + tsmf_gstreamer_need_data, + tsmf_gstreamer_enough_data, + tsmf_gstreamer_seek_data + }; + g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, NULL); + g_object_set(mdecoder->src, "is-live", FALSE, NULL); + g_object_set(mdecoder->src, "block", FALSE, NULL); + g_object_set(mdecoder->src, "blocksize", 1024, NULL); + gst_app_src_set_caps((GstAppSrc *) mdecoder->src, mdecoder->gst_caps); + gst_app_src_set_callbacks((GstAppSrc *)mdecoder->src, &callbacks, mdecoder, NULL); + gst_app_src_set_stream_type((GstAppSrc *) mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE); + gst_app_src_set_latency((GstAppSrc *) mdecoder->src, 0, -1); + gst_app_src_set_max_bytes((GstAppSrc *) mdecoder->src, (guint64) 0);//unlimited + g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, NULL); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64) 0, NULL); + + /* Only set these properties if not an autosink, otherwise we will set properties when real sinks are added */ + if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") && !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink")) + { + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + gst_base_sink_set_max_lateness((GstBaseSink *) mdecoder->outsink, 10000000); /* nanoseconds */ + } + else + { + gst_base_sink_set_max_lateness((GstBaseSink *) mdecoder->outsink, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64) 20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64) 20000, NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64) 10000, NULL); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, NULL); + } + g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE, NULL); /* synchronize on the clock */ + g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE, NULL); /* no async state changes */ + } + + tsmf_window_create(mdecoder); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL, get_type(mdecoder)); + + return TRUE; +} + +static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE *data, UINT32 data_size, UINT32 extensions, + UINT64 start_time, UINT64 end_time, UINT64 duration) +{ + GstBuffer *gst_buf; + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time); + BOOL useTimestamps = TRUE; + + if (!mdecoder) + { + WLog_ERR(TAG, "Decoder not initialized!"); + return FALSE; + } + + /* + * This function is always called from a stream-specific thread. + * It should be alright to block here if necessary. + * We don't expect to block here often, since the pipeline should + * have more than enough buffering. + */ + DEBUG_TSMF("%s. Start:(%"PRIu64") End:(%"PRIu64") Duration:(%"PRIu64") Last Start:(%"PRIu64")", + get_type(mdecoder), start_time, end_time, duration, + mdecoder->last_sample_start_time); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "decodeEx called on shutdown decoder"); + return TRUE; + } + + if (mdecoder->gst_caps == NULL) + { + WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format."); + return FALSE; + } + + if (!mdecoder->pipe) + tsmf_gstreamer_pipeline_build(mdecoder); + + if (!mdecoder->src) + { + WLog_ERR(TAG, "failed to construct pipeline correctly. Unable to push buffer to source element."); + return FALSE; + } + + gst_buf = tsmf_get_buffer_from_data(data, data_size); + + if (gst_buf == NULL) + { + WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %"PRIu32") failed.", (void*) data, data_size); + return FALSE; + } + + /* Relative timestamping will sometimes be set to 0 + * so we ignore these timestamps just to be safe(bit 8) + */ + if (extensions & 0x00000080) + { + DEBUG_TSMF("Ignoring the timestamps - relative - bit 8"); + useTimestamps = FALSE; + } + + /* If no timestamps exist then we dont want to look at the timestamp values (bit 7) */ + if (extensions & 0x00000040) + { + DEBUG_TSMF("Ignoring the timestamps - none - bit 7"); + useTimestamps = FALSE; + } + + /* If performing a seek */ + if (mdecoder->seeking) + { + mdecoder->seeking = FALSE; + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->pipeline_start_time_valid = 0; + } + + if (mdecoder->pipeline_start_time_valid) + { + DEBUG_TSMF("%s start time %"PRIu64"", get_type(mdecoder), start_time); + + /* Adjusted the condition for a seek to be based on start time only + * WMV1 and WMV2 files in particular have bad end time and duration values + * there seems to be no real side effects of just using the start time instead + */ + UINT64 minTime = mdecoder->last_sample_start_time - (UINT64) SEEK_TOLERANCE; + UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64) SEEK_TOLERANCE; + + /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */ + if (mdecoder->last_sample_start_time < (UINT64) SEEK_TOLERANCE) + minTime = 0; + + /* If the start_time is valid and different from the previous start time by more than the seek tolerance, then we have a seek condition */ + if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps) + { + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%"PRIu64"] > last_sample_start_time=[%"PRIu64"] OR ", start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%"PRIu64"] < last_sample_start_time=[%"PRIu64"] with", start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF("tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample", SEEK_TOLERANCE); + DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%"PRIu64"] maxTime=[%"PRIu64"]", minTime, maxTime); + + mdecoder->seeking = TRUE; + + /* since we cant make the gstreamer pipeline jump to the new start time after a seek - we just maintain + * a offset between realtime and gstreamer time + */ + mdecoder->seek_offset = start_time; + } + } + else + { + DEBUG_TSMF("%s start time %"PRIu64"", get_type(mdecoder), start_time); + /* Always set base/start time to 0. Will use seek offset to translate real buffer times + * back to 0. This allows the video to be started from anywhere and the ability to handle seeks + * without rebuilding the pipeline, etc. since that is costly + */ + gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + mdecoder->pipeline_start_time_valid = 1; + + /* Set the seek offset if buffer has valid timestamps. */ + if (useTimestamps) + mdecoder->seek_offset = start_time; + + if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + { + WLog_ERR(TAG, "seek failed"); + } + } + +#if GST_VERSION_MAJOR > 0 + if (useTimestamps) + GST_BUFFER_PTS(gst_buf) = sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE; +#else + if (useTimestamps) + GST_BUFFER_TIMESTAMP(gst_buf) = sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE; +#endif + GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE; +#if GST_VERSION_MAJOR > 0 +#else + gst_buffer_set_caps(gst_buf, mdecoder->gst_caps); +#endif + gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf); + + /* Should only update the last timestamps if the current ones are valid */ + if (useTimestamps) + { + mdecoder->last_sample_start_time = start_time; + mdecoder->last_sample_end_time = end_time; + } + + if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING)) + { + DEBUG_TSMF("%s: state=%s", get_type(mdecoder), gst_element_state_get_name(GST_STATE(mdecoder->pipe))); + + DEBUG_TSMF("%s Paused: %"PRIi32" Shutdown: %i Ready: %"PRIi32"", get_type(mdecoder), mdecoder->paused, mdecoder->shutdown, mdecoder->ready); + if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + + return TRUE; +} + +static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32 *arg) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + + if (!mdecoder) + { + WLog_ERR(TAG, "Control called with no decoder!"); + return TRUE; + } + + if (control_msg == Control_Pause) + { + DEBUG_TSMF("Control_Pause %s", get_type(mdecoder)); + + if (mdecoder->paused) + { + WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder)); + return TRUE; + } + + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->shutdown = 0; + mdecoder->paused = TRUE; + } + else if (control_msg == Control_Resume) + { + DEBUG_TSMF("Control_Resume %s", get_type(mdecoder)); + + if (!mdecoder->paused && !mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder)); + return TRUE; + } + + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + } + else if (control_msg == Control_Stop) + { + DEBUG_TSMF("Control_Stop %s", get_type(mdecoder)); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder)); + return TRUE; + } + + /* Reset stamps, flush buffers, etc */ + if (mdecoder->pipe) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + } + mdecoder->seek_offset = 0; + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 1; + } + else if (control_msg == Control_Restart) + { + DEBUG_TSMF("Control_Restart %s", get_type(mdecoder)); + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + if (mdecoder->pipeline_start_time_valid) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + else + WLog_ERR(TAG, "Unknown control message %08x", control_msg); + + return TRUE; +} + +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + DEBUG_TSMF(""); + + if (!mdecoder) + return FALSE; + + guint clbuff = 0; + + if (G_IS_OBJECT(mdecoder->queue)) + g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, NULL); + + DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff); + return clbuff; +} + +static void tsmf_gstreamer_free(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + DEBUG_TSMF("%s", get_type(mdecoder)); + + if (mdecoder) + { + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + + if (mdecoder->gst_caps) + gst_caps_unref(mdecoder->gst_caps); + + tsmf_platform_free(mdecoder); + ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder)); + free(mdecoder); + mdecoder = NULL; + } +} + +static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + + if (!mdecoder) + return 0; + + if (!mdecoder->outsink) + return mdecoder->last_sample_start_time; + + if (!mdecoder->pipe) + return 0; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 pos = 0; +#if GST_VERSION_MAJOR > 0 + gst_element_query_position(mdecoder->pipe, fmt, &pos); +#else + gst_element_query_position(mdecoder->pipe, &fmt, &pos); +#endif + return (UINT64) (pos/100 + mdecoder->seek_offset); +} + +static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, + int newX, int newY, int newWidth, int newHeight, int numRectangles, + RDP_RECT *rectangles) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, + newHeight, numRectangles); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, + numRectangles, rectangles) == 0; + } + + return TRUE; +} + +BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void *, BOOL), void *stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + DEBUG_TSMF(""); + mdecoder->ack_cb = NULL; + mdecoder->stream = stream; + return TRUE; +} + +BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void *), void *stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder *) decoder; + DEBUG_TSMF(""); + mdecoder->sync_cb = NULL; + mdecoder->stream = stream; + return TRUE; +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_tsmf_client_subsystem_entry gstreamer_freerdp_tsmf_client_decoder_subsystem_entry +#else +#define freerdp_tsmf_client_subsystem_entry FREERDP_API freerdp_tsmf_client_decoder_subsystem_entry +#endif + +ITSMFDecoder* freerdp_tsmf_client_subsystem_entry(void) +{ + TSMFGstreamerDecoder *decoder; + +#if GST_CHECK_VERSION(0,10,31) + if (!gst_is_initialized()) + { + gst_init(NULL, NULL); + } +#else + gst_init(NULL, NULL); +#endif + + decoder = calloc(1, sizeof(TSMFGstreamerDecoder)); + + if (!decoder) + return NULL; + + decoder->iface.SetFormat = tsmf_gstreamer_set_format; + decoder->iface.Decode = NULL; + decoder->iface.GetDecodedData = NULL; + decoder->iface.GetDecodedFormat = NULL; + decoder->iface.GetDecodedDimension = NULL; + decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time; + decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area; + decoder->iface.Free = tsmf_gstreamer_free; + decoder->iface.Control = tsmf_gstreamer_control; + decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx; + decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume; + decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level; + decoder->iface.SetAckFunc = tsmf_gstreamer_ack; + decoder->iface.SetSyncFunc = tsmf_gstreamer_sync; + decoder->paused = FALSE; + decoder->gstVolume = 0.5; + decoder->gstMuted = FALSE; + decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */ + decoder->last_sample_start_time = 0; + decoder->last_sample_end_time = 0; + decoder->seek_offset = 0; + decoder->seeking = FALSE; + + if (tsmf_platform_create(decoder) < 0) + { + free(decoder); + return NULL; + } + + return (ITSMFDecoder*) decoder; +} diff --git a/channels/tsmf/client/gstreamer/tsmf_platform.h b/channels/tsmf/client/gstreamer/tsmf_platform.h new file mode 100644 index 0000000..7fb6be1 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_platform.h @@ -0,0 +1,87 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * platform specific functions + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H +#define FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H + +#include +#include + +typedef struct _TSMFGstreamerDecoder +{ + ITSMFDecoder iface; + + int media_type; /* TSMF_MAJOR_TYPE_AUDIO or TSMF_MAJOR_TYPE_VIDEO */ + + gint64 duration; + + GstState state; + GstCaps *gst_caps; + + GstElement *pipe; + GstElement *src; + GstElement *queue; + GstElement *outsink; + GstElement *volume; + + BOOL ready; + BOOL paused; + UINT64 last_sample_start_time; + UINT64 last_sample_end_time; + BOOL seeking; + UINT64 seek_offset; + + double gstVolume; + BOOL gstMuted; + + int pipeline_start_time_valid; /* We've set the start time and have not reset the pipeline */ + int shutdown; /* The decoder stream is shutting down */ + + void *platform; + + BOOL (*ack_cb)(void *,BOOL); + void (*sync_cb)(void *); + void *stream; + +} TSMFGstreamerDecoder; + +const char* get_type(TSMFGstreamerDecoder* mdecoder); + +const char* tsmf_platform_get_video_sink(void); +const char* tsmf_platform_get_audio_sink(void); + +int tsmf_platform_create(TSMFGstreamerDecoder* decoder); +int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder); +int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder); +int tsmf_platform_free(TSMFGstreamerDecoder* decoder); + +int tsmf_window_create(TSMFGstreamerDecoder* decoder); +int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, + int width, int height, int nr_rect, RDP_RECT *visible); +int tsmf_window_destroy(TSMFGstreamerDecoder* decoder); + +int tsmf_window_map(TSMFGstreamerDecoder* decoder); +int tsmf_window_unmap(TSMFGstreamerDecoder* decoder); + +BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder); +void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */ diff --git a/channels/tsmf/client/oss/CMakeLists.txt b/channels/tsmf/client/oss/CMakeLists.txt new file mode 100644 index 0000000..8f9e627 --- /dev/null +++ b/channels/tsmf/client/oss/CMakeLists.txt @@ -0,0 +1,28 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("tsmf" "oss" "audio") + +set(${MODULE_PREFIX}_SRCS + tsmf_oss.c) + +include_directories(..) +include_directories(${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") +target_link_libraries(${MODULE_NAME} winpr) + diff --git a/channels/tsmf/client/oss/tsmf_oss.c b/channels/tsmf/client/oss/tsmf_oss.c new file mode 100644 index 0000000..67a7927 --- /dev/null +++ b/channels/tsmf/client/oss/tsmf_oss.c @@ -0,0 +1,254 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - OSS Audio Device + * + * Copyright (c) 2015 Rozhuk Ivan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#if defined(__OpenBSD__) +#include +#else +#include +#endif +#include + +#include +#include + +#include "tsmf_audio.h" + + +typedef struct _TSMFOSSAudioDevice +{ + ITSMFAudioDevice iface; + + char dev_name[PATH_MAX]; + int pcm_handle; + + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + UINT32 data_size_last; +} TSMFOssAudioDevice; + + +#define OSS_LOG_ERR(_text, _error) \ + if (_error != 0) \ + WLog_ERR(TAG, "%s: %i - %s", _text, _error, strerror(_error)); + + +static BOOL tsmf_oss_open(ITSMFAudioDevice* audio, const char* device) +{ + int tmp; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL || oss->pcm_handle != -1) + return FALSE; + + if (device == NULL) /* Default device. */ + { + strncpy(oss->dev_name, "/dev/dsp", sizeof(oss->dev_name)); + } + else + { + strncpy(oss->dev_name, device, sizeof(oss->dev_name) - 1); + } + + if ((oss->pcm_handle = open(oss->dev_name, O_WRONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return FALSE; + } + +#if 0 /* FreeBSD OSS implementation at this moment (2015.03) does not set PCM_CAP_OUTPUT flag. */ + tmp = 0; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETCAPS, &mask) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETCAPS failed, try ignory", errno); + } + else if ((mask & PCM_CAP_OUTPUT) == 0) + { + OSS_LOG_ERR("Device does not supports playback", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + +#endif + tmp = 0; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + if ((AFMT_S16_LE & tmp) == 0) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS - AFMT_S16_LE", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + WLog_INFO(TAG, "open: %s", oss->dev_name); + return TRUE; +} + +static BOOL tsmf_oss_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + int tmp; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL || oss->pcm_handle == -1) + return FALSE; + + oss->sample_rate = sample_rate; + oss->channels = channels; + oss->bits_per_sample = bits_per_sample; + tmp = AFMT_S16_LE; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + + tmp = channels; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + + tmp = sample_rate; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + + tmp = ((bits_per_sample / 8) * channels * sample_rate); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + + DEBUG_TSMF("sample_rate %"PRIu32" channels %"PRIu32" bits_per_sample %"PRIu32"", + sample_rate, channels, bits_per_sample); + return TRUE; +} + +static BOOL tsmf_oss_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + int status; + UINT32 offset; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + DEBUG_TSMF("tsmf_oss_play: data_size %"PRIu32"", data_size); + + if (oss == NULL || oss->pcm_handle == -1) + return FALSE; + + if (data == NULL || data_size == 0) + return TRUE; + + offset = 0; + oss->data_size_last = data_size; + + while (offset < data_size) + { + status = write(oss->pcm_handle, &data[offset], (data_size - offset)); + + if (status < 0) + { + OSS_LOG_ERR("write fail", errno); + return FALSE; + } + + offset += status; + } + + return TRUE; +} + +static UINT64 tsmf_oss_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL) + return 0; + + //latency = ((oss->data_size_last / (oss->bits_per_sample / 8)) * oss->sample_rate); + //WLog_INFO(TAG, "latency: %zu", latency); + return latency; +} + +static BOOL tsmf_oss_flush(ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_oss_free(ITSMFAudioDevice* audio) +{ + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == NULL) + return; + + if (oss->pcm_handle != -1) + { + WLog_INFO(TAG, "close: %s", oss->dev_name); + close(oss->pcm_handle); + } + + free(oss); +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_tsmf_client_audio_subsystem_entry oss_freerdp_tsmf_client_audio_subsystem_entry +#else +#define freerdp_tsmf_client_audio_subsystem_entry FREERDP_API freerdp_tsmf_client_audio_subsystem_entry +#endif + +ITSMFAudioDevice* freerdp_tsmf_client_audio_subsystem_entry(void) +{ + TSMFOssAudioDevice* oss; + oss = (TSMFOssAudioDevice*)malloc(sizeof(TSMFOssAudioDevice)); + ZeroMemory(oss, sizeof(TSMFOssAudioDevice)); + oss->iface.Open = tsmf_oss_open; + oss->iface.SetFormat = tsmf_oss_set_format; + oss->iface.Play = tsmf_oss_play; + oss->iface.GetLatency = tsmf_oss_get_latency; + oss->iface.Flush = tsmf_oss_flush; + oss->iface.Free = tsmf_oss_free; + oss->pcm_handle = -1; + return (ITSMFAudioDevice*)oss; +} diff --git a/channels/tsmf/client/pulse/CMakeLists.txt b/channels/tsmf/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..9f78ca2 --- /dev/null +++ b/channels/tsmf/client/pulse/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("tsmf" "pulse" "audio") + +set(${MODULE_PREFIX}_SRCS + tsmf_pulse.c) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") +target_link_libraries(${MODULE_NAME} winpr ${PULSE_LIBRARY}) diff --git a/channels/tsmf/client/pulse/tsmf_pulse.c b/channels/tsmf/client/pulse/tsmf_pulse.c new file mode 100644 index 0000000..e63477c --- /dev/null +++ b/channels/tsmf/client/pulse/tsmf_pulse.c @@ -0,0 +1,433 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - PulseAudio Device + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFPulseAudioDevice +{ + ITSMFAudioDevice iface; + + char device[32]; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; +} TSMFPulseAudioDevice; + +static void tsmf_pulse_context_state_callback(pa_context* context, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_context_state_t state; + state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_TSMF("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static BOOL tsmf_pulse_connect(TSMFPulseAudioDevice* pulse) +{ + pa_context_state_t state; + + if (!pulse->context) + return FALSE; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + WLog_ERR(TAG, "pa_context_connect failed (%d)", + pa_context_errno(pulse->context)); + return FALSE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_threaded_mainloop_start failed (%d)", + pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_TSMF("bad context state (%d)", + pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_CONTEXT_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + pa_context_disconnect(pulse->context); + return FALSE; + } +} + +static BOOL tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (device) + { + strncpy(pulse->device, device, sizeof(pulse->device) - 1); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + { + WLog_ERR(TAG, "pa_threaded_mainloop_new failed"); + return FALSE; + } + + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + + if (!pulse->context) + { + WLog_ERR(TAG, "pa_context_new failed"); + return FALSE; + } + + pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse); + + if (!tsmf_pulse_connect(pulse)) + { + WLog_ERR(TAG, "tsmf_pulse_connect failed"); + return FALSE; + } + + DEBUG_TSMF("open device %s", pulse->device); + return TRUE; +} + +static void tsmf_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation) +{ + if (operation == NULL) + return; + + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_operation_unref(operation); +} + +static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_stream_state_t state; + state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + DEBUG_TSMF("PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static void tsmf_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + DEBUG_TSMF("%"PRIdz"", length); + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static BOOL tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse) +{ + if (!pulse->context || !pulse->stream) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_set_write_callback(pulse->stream, NULL, NULL); + tsmf_pulse_wait_for_operation(pulse, + pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static BOOL tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse) +{ + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + + if (!pulse->context) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", + &pulse->sample_spec, NULL); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return FALSE; + } + + pa_stream_set_state_callback(pulse->stream, + tsmf_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, + tsmf_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = pa_usec_to_bytes(500000, &pulse->sample_spec); + buffer_attr.tlength = pa_usec_to_bytes(250000, &pulse->sample_spec); + buffer_attr.prebuf = (UINT32) - 1; + buffer_attr.minreq = (UINT32) - 1; + buffer_attr.fragsize = (UINT32) - 1; + + if (pa_stream_connect_playback(pulse->stream, + pulse->device[0] ? pulse->device : NULL, &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, + NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + WLog_ERR(TAG, "bad stream state (%d)", + pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_STREAM_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + tsmf_pulse_close_stream(pulse); + return FALSE; + } +} + +static BOOL tsmf_pulse_set_format(ITSMFAudioDevice* audio, + UINT32 sample_rate, UINT32 channels, UINT32 bits_per_sample) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + DEBUG_TSMF("sample_rate %"PRIu32" channels %"PRIu32" bits_per_sample %"PRIu32"", + sample_rate, channels, bits_per_sample); + pulse->sample_spec.rate = sample_rate; + pulse->sample_spec.channels = channels; + pulse->sample_spec.format = PA_SAMPLE_S16LE; + return tsmf_pulse_open_stream(pulse); +} + +static BOOL tsmf_pulse_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + const BYTE* src; + size_t len; + int ret; + DEBUG_TSMF("data_size %"PRIu32"", data_size); + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + src = data; + + while (data_size > 0) + { + while ((len = pa_stream_writable_size(pulse->stream)) == 0) + { + DEBUG_TSMF("waiting"); + pa_threaded_mainloop_wait(pulse->mainloop); + } + + if (len == (size_t) -1) + break; + + if (len > data_size) + len = data_size; + + ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE); + + if (ret < 0) + { + DEBUG_TSMF("pa_stream_write failed (%d)", + pa_context_errno(pulse->context)); + break; + } + + src += len; + data_size -= len; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + } + + return TRUE; +} + +static UINT64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio) +{ + pa_usec_t usec; + UINT64 latency = 0; + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, NULL) == 0) + { + latency = ((UINT64)usec) * 10LL; + } + + return latency; +} + +static BOOL tsmf_pulse_flush(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + pa_threaded_mainloop_lock(pulse->mainloop); + tsmf_pulse_wait_for_operation(pulse, + pa_stream_flush(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static void tsmf_pulse_free(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + DEBUG_TSMF(""); + tsmf_pulse_close_stream(pulse); + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + + free(pulse); +} + +#ifdef BUILTIN_CHANNELS +ITSMFAudioDevice* pulse_freerdp_tsmf_client_audio_subsystem_entry(void) +#else +FREERDP_API ITSMFAudioDevice* freerdp_tsmf_client_audio_subsystem_entry(void) +#endif +{ + TSMFPulseAudioDevice* pulse; + pulse = (TSMFPulseAudioDevice*) calloc(1, sizeof(TSMFPulseAudioDevice)); + + if (!pulse) + return NULL; + + pulse->iface.Open = tsmf_pulse_open; + pulse->iface.SetFormat = tsmf_pulse_set_format; + pulse->iface.Play = tsmf_pulse_play; + pulse->iface.GetLatency = tsmf_pulse_get_latency; + pulse->iface.Flush = tsmf_pulse_flush; + pulse->iface.Free = tsmf_pulse_free; + return (ITSMFAudioDevice*) pulse; +} + diff --git a/channels/tsmf/client/tsmf_audio.c b/channels/tsmf/client/tsmf_audio.c new file mode 100644 index 0000000..a71b016 --- /dev/null +++ b/channels/tsmf/client/tsmf_audio.c @@ -0,0 +1,99 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "tsmf_audio.h" + +static ITSMFAudioDevice* tsmf_load_audio_device_by_name(const char* name, const char* device) +{ + ITSMFAudioDevice* audio; + TSMF_AUDIO_DEVICE_ENTRY entry; + + entry = (TSMF_AUDIO_DEVICE_ENTRY) freerdp_load_channel_addin_entry("tsmf", (LPSTR) name, "audio", 0); + + if (!entry) + return NULL; + + audio = entry(); + + if (!audio) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return NULL; + } + + if (!audio->Open(audio, device)) + { + audio->Free(audio); + audio = NULL; + WLog_ERR(TAG, "failed to open, name: %s, device: %s", name, device); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device) +{ + ITSMFAudioDevice* audio = NULL; + + if (name) + { + audio = tsmf_load_audio_device_by_name(name, device); + } + else + { +#if defined(WITH_PULSE) + if (!audio) + audio = tsmf_load_audio_device_by_name("pulse", device); +#endif + +#if defined(WITH_OSS) + if (!audio) + audio = tsmf_load_audio_device_by_name("oss", device); +#endif + +#if defined(WITH_ALSA) + if (!audio) + audio = tsmf_load_audio_device_by_name("alsa", device); +#endif + } + + if (audio == NULL) + { + WLog_ERR(TAG, "no sound device."); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} + diff --git a/channels/tsmf/client/tsmf_audio.h b/channels/tsmf/client/tsmf_audio.h new file mode 100644 index 0000000..d589d54 --- /dev/null +++ b/channels/tsmf/client/tsmf_audio.h @@ -0,0 +1,51 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * 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_CHANNEL_TSMF_CLIENT_AUDIO_H +#define FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H + +#include "tsmf_types.h" + +typedef struct _ITSMFAudioDevice ITSMFAudioDevice; + +struct _ITSMFAudioDevice +{ + /* Open the audio device. */ + BOOL (*Open)(ITSMFAudioDevice* audio, const char* device); + /* Set the audio data format. */ + BOOL (*SetFormat)(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample); + /* Play audio data. */ + BOOL (*Play)(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size); + /* Get the latency of the last written sample, in 100ns */ + UINT64(*GetLatency)(ITSMFAudioDevice* audio); + /* Change the playback volume level */ + BOOL (*ChangeVolume)(ITSMFAudioDevice* audio, UINT32 newVolume, UINT32 muted); + /* Flush queued audio data */ + BOOL (*Flush)(ITSMFAudioDevice* audio); + /* Free the audio device */ + void (*Free)(ITSMFAudioDevice* audio); +}; + +#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry" +typedef ITSMFAudioDevice* (*TSMF_AUDIO_DEVICE_ENTRY)(void); + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H */ diff --git a/channels/tsmf/client/tsmf_codec.c b/channels/tsmf/client/tsmf_codec.c new file mode 100644 index 0000000..0a4f41d --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.c @@ -0,0 +1,656 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "tsmf_decoder.h" +#include "tsmf_constants.h" +#include "tsmf_types.h" + +#include "tsmf_codec.h" + +#include + +#define TAG CHANNELS_TAG("tsmf.client") + +typedef struct _TSMFMediaTypeMap +{ + BYTE guid[16]; + const char* name; + int type; +} TSMFMediaTypeMap; + +static const TSMFMediaTypeMap tsmf_major_type_map[] = +{ + /* 73646976-0000-0010-8000-00AA00389B71 */ + { + { 0x76, 0x69, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIATYPE_Video", + TSMF_MAJOR_TYPE_VIDEO + }, + + /* 73647561-0000-0010-8000-00AA00389B71 */ + { + { 0x61, 0x75, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIATYPE_Audio", + TSMF_MAJOR_TYPE_AUDIO + }, + + { + { 0 }, + "Unknown", + TSMF_MAJOR_TYPE_UNKNOWN + } +}; + +static const TSMFMediaTypeMap tsmf_sub_type_map[] = +{ + /* 31435657-0000-0010-8000-00AA00389B71 */ + { + { 0x57, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WVC1", + TSMF_SUB_TYPE_WVC1 + }, + + /* 00000160-0000-0010-8000-00AA00389B71 */ + { + { 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMAudioV1", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA1 + }, + + /* 00000161-0000-0010-8000-00AA00389B71 */ + { + { 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMAudioV2", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA2 + }, + + /* 00000162-0000-0010-8000-00AA00389B71 */ + { + { 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMAudioV9", + TSMF_SUB_TYPE_WMA9 + }, + + /* 00000055-0000-0010-8000-00AA00389B71 */ + { + { 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP3", + TSMF_SUB_TYPE_MP3 + }, + + /* E06D802B-DB46-11CF-B4D1-00805F6CBBEA */ + { + { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA }, + "MEDIASUBTYPE_MPEG2_AUDIO", + TSMF_SUB_TYPE_MP2A + }, + + /* E06D8026-DB46-11CF-B4D1-00805F6CBBEA */ + { + { 0x26, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA }, + "MEDIASUBTYPE_MPEG2_VIDEO", + TSMF_SUB_TYPE_MP2V + }, + + /* 31564D57-0000-0010-8000-00AA00389B71 */ + { + { 0x57, 0x4D, 0x56, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMV1", + TSMF_SUB_TYPE_WMV1 + }, + + /* 32564D57-0000-0010-8000-00AA00389B71 */ + { + { 0x57, 0x4D, 0x56, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMV2", + TSMF_SUB_TYPE_WMV2 + }, + + /* 33564D57-0000-0010-8000-00AA00389B71 */ + { + { 0x57, 0x4D, 0x56, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_WMV3", + TSMF_SUB_TYPE_WMV3 + }, + + /* 00001610-0000-0010-8000-00AA00389B71 */ + { + { 0x10, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MPEG_HEAAC", + TSMF_SUB_TYPE_AAC + }, + + /* 34363248-0000-0010-8000-00AA00389B71 */ + { + { 0x48, 0x32, 0x36, 0x34, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_H264", + TSMF_SUB_TYPE_H264 + }, + + /* 31435641-0000-0010-8000-00AA00389B71 */ + { + { 0x41, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_AVC1", + TSMF_SUB_TYPE_AVC1 + }, + + /* 3334504D-0000-0010-8000-00AA00389B71 */ + { + { 0x4D, 0x50, 0x34, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP43", + TSMF_SUB_TYPE_MP43 + }, + + /* 5634504D-0000-0010-8000-00AA00389B71 */ + { + { 0x4D, 0x50, 0x34, 0x56, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP4S", + TSMF_SUB_TYPE_MP4S + }, + + /* 3234504D-0000-0010-8000-00AA00389B71 */ + { + { 0x4D, 0x50, 0x34, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_MP42 + }, + + /* 3253344D-0000-0010-8000-00AA00389B71 */ + { + { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_M4S2 + }, + + /* E436EB81-524F-11CE-9F53-0020AF0BA770 */ + { + { 0x81, 0xEB, 0x36, 0xE4, 0x4F, 0x52, 0xCE, 0x11, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, 0x70 }, + "MEDIASUBTYPE_MP1V", + TSMF_SUB_TYPE_MP1V + }, + + /* 00000050-0000-0010-8000-00AA00389B71 */ + { + { 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_MP1A", + TSMF_SUB_TYPE_MP1A + }, + + /* E06D802C-DB46-11CF-B4D1-00805F6CBBEA */ + { + { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA }, + "MEDIASUBTYPE_DOLBY_AC3", + TSMF_SUB_TYPE_AC3 + }, + + /* 32595559-0000-0010-8000-00AA00389B71 */ + { + { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }, + "MEDIASUBTYPE_YUY2", + TSMF_SUB_TYPE_YUY2 + }, + + /* Opencodec IDS */ + { + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + "MEDIASUBTYPE_FLAC", + TSMF_SUB_TYPE_FLAC + }, + + { + {0x61, 0x34, 0x70, 0x6D, 0x7A, 0x76, 0x4D, 0x49, 0xB4, 0x78, 0xF2, 0x9D, 0x25, 0xDC, 0x90, 0x37}, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG + }, + + { + {0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + "MEDIASUBTYPE_H263", + TSMF_SUB_TYPE_H263 + }, + + /* WebMMF codec IDS */ + { + {0x56, 0x50, 0x38, 0x30, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}, + "MEDIASUBTYPE_VP8", + TSMF_SUB_TYPE_VP8 + }, + + { + {0x0B, 0xD1, 0x2F, 0x8D, 0x41, 0x58, 0x6B, 0x4A, 0x89, 0x05, 0x58, 0x8F, 0xEC, 0x1A, 0xDE, 0xD9}, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG + }, + + { + { 0 }, + "Unknown", + TSMF_SUB_TYPE_UNKNOWN + } + +}; + +static const TSMFMediaTypeMap tsmf_format_type_map[] = +{ + /* AED4AB2D-7326-43CB-9464-C879CAB9C43D */ + { + { 0x2D, 0xAB, 0xD4, 0xAE, 0x26, 0x73, 0xCB, 0x43, 0x94, 0x64, 0xC8, 0x79, 0xCA, 0xB9, 0xC4, 0x3D }, + "FORMAT_MFVideoFormat", + TSMF_FORMAT_TYPE_MFVIDEOFORMAT + }, + + /* 05589F81-C356-11CE-BF01-00AA0055595A */ + { + { 0x81, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, 0x5A }, + "FORMAT_WaveFormatEx", + TSMF_FORMAT_TYPE_WAVEFORMATEX + }, + + /* E06D80E3-DB46-11CF-B4D1-00805F6CBBEA */ + { + { 0xE3, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, 0xEA }, + "FORMAT_MPEG2_VIDEO", + TSMF_FORMAT_TYPE_MPEG2VIDEOINFO + }, + + /* F72A76A0-EB0A-11D0-ACE4-0000C0CC16BA */ + { + { 0xA0, 0x76, 0x2A, 0xF7, 0x0A, 0xEB, 0xD0, 0x11, 0xAC, 0xE4, 0x00, 0x00, 0xC0, 0xCC, 0x16, 0xBA }, + "FORMAT_VideoInfo2", + TSMF_FORMAT_TYPE_VIDEOINFO2 + }, + + /* 05589F82-C356-11CE-BF01-00AA0055595A */ + { + { 0x82, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, 0x5A }, + "FORMAT_MPEG1_VIDEO", + TSMF_FORMAT_TYPE_MPEG1VIDEOINFO + }, + + { + { 0 }, + "Unknown", + TSMF_FORMAT_TYPE_UNKNOWN + } +}; + +static void tsmf_print_guid(const BYTE* guid) +{ +#ifdef WITH_DEBUG_TSMF + char guidString[37]; + + snprintf(guidString, sizeof(guidString), "%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"-%02"PRIX8"%02"PRIX8"-%02"PRIX8"%02"PRIX8"-%02"PRIX8"%02"PRIX8"-%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"%02"PRIX8"", + guid[3], guid[2], guid[1], guid[0], + guid[5], guid[4], + guid[7], guid[6], + guid[8], guid[9], + guid[10], guid[11], guid[12], guid[13], guid[14], guid[15] + ); + + + WLog_INFO(TAG, "%s", guidString); +#endif +} + +/* http://msdn.microsoft.com/en-us/library/dd318229.aspx */ +static UINT32 tsmf_codec_parse_BITMAPINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s, BOOL bypass) +{ + UINT32 biSize; + UINT32 biWidth; + UINT32 biHeight; + + if (Stream_GetRemainingLength(s) < 40) + return 0; + Stream_Read_UINT32(s, biSize); + Stream_Read_UINT32(s, biWidth); + Stream_Read_UINT32(s, biHeight); + Stream_Seek(s, 28); + + if (mediatype->Width == 0) + mediatype->Width = biWidth; + + if (mediatype->Height == 0) + mediatype->Height = biHeight; + + /* Assume there will be no color table for video? */ + if ((biSize < 40) || (Stream_GetRemainingLength(s) < (biSize-40))) + return 0; + + if (bypass && biSize > 40) + Stream_Seek(s, biSize - 40); + + return (bypass ? biSize : 40); +} + +/* http://msdn.microsoft.com/en-us/library/dd407326.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER2(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT64 AvgTimePerFrame; + + /* VIDEOINFOHEADER2.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + if (Stream_GetRemainingLength(s) < 72) + return 0; + + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER2.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER2.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER2.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER2.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL); + /* Remaining fields before bmiHeader */ + Stream_Seek(s, 24); + return 72; +} + +/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + /* + typedef struct tagVIDEOINFOHEADER { + RECT rcSource; //16 + RECT rcTarget; //16 32 + DWORD dwBitRate; //4 36 + DWORD dwBitErrorRate; //4 40 + REFERENCE_TIME AvgTimePerFrame; //8 48 + BITMAPINFOHEADER bmiHeader; + } VIDEOINFOHEADER; + */ + UINT64 AvgTimePerFrame; + + if (Stream_GetRemainingLength(s) < 48) + return 0; + + /* VIDEOINFOHEADER.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (int)(AvgTimePerFrame / 10LL); + return 48; +} + +static BOOL tsmf_read_format_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s, UINT32 cbFormat) +{ + int i, j; + + switch (mediatype->FormatType) + { + case TSMF_FORMAT_TYPE_MFVIDEOFORMAT: + /* http://msdn.microsoft.com/en-us/library/aa473808.aspx */ + if (Stream_GetRemainingLength(s) < 176) + return FALSE; + + Stream_Seek(s, 8); /* dwSize and ? */ + Stream_Read_UINT32(s, mediatype->Width); /* videoInfo.dwWidth */ + Stream_Read_UINT32(s, mediatype->Height); /* videoInfo.dwHeight */ + Stream_Seek(s, 32); + /* videoInfo.FramesPerSecond */ + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Denominator); + Stream_Seek(s, 80); + Stream_Read_UINT32(s, mediatype->BitRate); /* compressedInfo.AvgBitrate */ + Stream_Seek(s, 36); + + if (cbFormat > 176) + { + mediatype->ExtraDataSize = cbFormat - 176; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_WAVEFORMATEX: + /* http://msdn.microsoft.com/en-us/library/dd757720.aspx */ + if (Stream_GetRemainingLength(s) < 18) + return FALSE; + + Stream_Seek_UINT16(s); + Stream_Read_UINT16(s, mediatype->Channels); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + mediatype->SamplesPerSecond.Denominator = 1; + Stream_Read_UINT32(s, mediatype->BitRate); + mediatype->BitRate *= 8; + Stream_Read_UINT16(s, mediatype->BlockAlign); + Stream_Read_UINT16(s, mediatype->BitsPerSample); + Stream_Read_UINT16(s, mediatype->ExtraDataSize); + + if (mediatype->ExtraDataSize > 0) + { + if (Stream_GetRemainingLength(s) < mediatype->ExtraDataSize) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG1VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (Stream_GetRemainingLength(s) < mediatype->ExtraDataSize) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390707.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (Stream_GetRemainingLength(s) < mediatype->ExtraDataSize) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_VIDEOINFO2: + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, FALSE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (Stream_GetRemainingLength(s) < mediatype->ExtraDataSize) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + default: + WLog_INFO(TAG, "unhandled format type 0x%x", mediatype->FormatType); + break; + } + return TRUE; +} + +BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT32 cbFormat; + BOOL ret = TRUE; + int i; + + ZeroMemory(mediatype, sizeof(TS_AM_MEDIA_TYPE)); + + /* MajorType */ + DEBUG_TSMF("MediaMajorType:"); + if (Stream_GetRemainingLength(s) < 16) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_major_type_map[i].type != TSMF_MAJOR_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_major_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->MajorType = tsmf_major_type_map[i].type; + if (mediatype->MajorType == TSMF_MAJOR_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("MediaMajorType %s", tsmf_major_type_map[i].name); + Stream_Seek(s, 16); + + /* SubType */ + DEBUG_TSMF("MediaSubType:"); + if (Stream_GetRemainingLength(s) < 16) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_sub_type_map[i].type != TSMF_SUB_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_sub_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->SubType = tsmf_sub_type_map[i].type; + if (mediatype->SubType == TSMF_SUB_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("MediaSubType %s", tsmf_sub_type_map[i].name); + Stream_Seek(s, 16); + + /* bFixedSizeSamples, bTemporalCompression, SampleSize */ + if (Stream_GetRemainingLength(s) < 12) + return FALSE; + Stream_Seek(s, 12); + + /* FormatType */ + DEBUG_TSMF("FormatType:"); + if (Stream_GetRemainingLength(s) < 16) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_format_type_map[i].type != TSMF_FORMAT_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_format_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->FormatType = tsmf_format_type_map[i].type; + if (mediatype->FormatType == TSMF_FORMAT_TYPE_UNKNOWN) + ret = FALSE; + + DEBUG_TSMF("FormatType %s", tsmf_format_type_map[i].name); + Stream_Seek(s, 16); + + /* cbFormat */ + if (Stream_GetRemainingLength(s) < 4) + return FALSE; + Stream_Read_UINT32(s, cbFormat); + DEBUG_TSMF("cbFormat %"PRIu32"", cbFormat); +#ifdef WITH_DEBUG_TSMF + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(s), cbFormat); +#endif + + ret = tsmf_read_format_type(mediatype, s, cbFormat); + + if (mediatype->SamplesPerSecond.Numerator == 0) + mediatype->SamplesPerSecond.Numerator = 1; + + if (mediatype->SamplesPerSecond.Denominator == 0) + mediatype->SamplesPerSecond.Denominator = 1; + + return ret; +} + +BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s) +{ + BYTE* m; + BOOL ret = FALSE; + TS_AM_MEDIA_TYPE mediatype; + + static BOOL decoderAvailable = FALSE; + static BOOL firstRun = TRUE; + + if (firstRun) + { + firstRun =FALSE; + if (tsmf_check_decoder_available(decoder_name)) + decoderAvailable = TRUE; + } + + Stream_GetPointer(s, m); + if (decoderAvailable) + ret = tsmf_codec_parse_media_type(&mediatype, s); + Stream_SetPointer(s, m); + + if (ret) + { + ITSMFDecoder* decoder = tsmf_load_decoder(decoder_name, &mediatype); + + if (!decoder) + { + WLog_WARN(TAG, "Format not supported by decoder %s", decoder_name); + ret = FALSE; + } + else + { + decoder->Free(decoder); + } + } + + return ret; +} diff --git a/channels/tsmf/client/tsmf_codec.h b/channels/tsmf/client/tsmf_codec.h new file mode 100644 index 0000000..a678ea4 --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * 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_CHANNEL_TSMF_CLIENT_CODEC_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H + +#include "tsmf_types.h" + +BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s); +BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H */ + diff --git a/channels/tsmf/client/tsmf_constants.h b/channels/tsmf/client/tsmf_constants.h new file mode 100644 index 0000000..d84370a --- /dev/null +++ b/channels/tsmf/client/tsmf_constants.h @@ -0,0 +1,139 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Constants + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H + +#define GUID_SIZE 16 +#define TSMF_BUFFER_PADDING_SIZE 8 + +/* Interface IDs defined in [MS-RDPEV]. There's no constant names in the MS + documentation, so we create them on our own. */ +#define TSMF_INTERFACE_DEFAULT 0x00000000 +#define TSMF_INTERFACE_CLIENT_NOTIFICATIONS 0x00000001 +#define TSMF_INTERFACE_CAPABILITIES 0x00000002 + +/* Interface ID Mask */ +#define STREAM_ID_STUB 0x80000000 +#define STREAM_ID_PROXY 0x40000000 +#define STREAM_ID_NONE 0x00000000 + +/* Functon ID */ +/* Common IDs for all interfaces are as follows. */ +#define RIMCALL_RELEASE 0x00000001 +#define RIMCALL_QUERYINTERFACE 0x00000002 +/* Capabilities Negotiator Interface IDs are as follows. */ +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +/* The Client Notifications Interface ID is as follows. */ +#define PLAYBACK_ACK 0x00000100 +#define CLIENT_EVENT_NOTIFICATION 0x00000101 +/* Server Data Interface IDs are as follows. */ +#define EXCHANGE_CAPABILITIES_REQ 0x00000100 +#define SET_CHANNEL_PARAMS 0x00000101 +#define ADD_STREAM 0x00000102 +#define ON_SAMPLE 0x00000103 +#define SET_VIDEO_WINDOW 0x00000104 +#define ON_NEW_PRESENTATION 0x00000105 +#define SHUTDOWN_PRESENTATION_REQ 0x00000106 +#define SET_TOPOLOGY_REQ 0x00000107 +#define CHECK_FORMAT_SUPPORT_REQ 0x00000108 +#define ON_PLAYBACK_STARTED 0x00000109 +#define ON_PLAYBACK_PAUSED 0x0000010a +#define ON_PLAYBACK_STOPPED 0x0000010b +#define ON_PLAYBACK_RESTARTED 0x0000010c +#define ON_PLAYBACK_RATE_CHANGED 0x0000010d +#define ON_FLUSH 0x0000010e +#define ON_STREAM_VOLUME 0x0000010f +#define ON_CHANNEL_VOLUME 0x00000110 +#define ON_END_OF_STREAM 0x00000111 +#define SET_ALLOCATOR 0x00000112 +#define NOTIFY_PREROLL 0x00000113 +#define UPDATE_GEOMETRY_INFO 0x00000114 +#define REMOVE_STREAM 0x00000115 +#define SET_SOURCE_VIDEO_RECT 0x00000116 + +/* Supported platform */ +#define MMREDIR_CAPABILITY_PLATFORM_MF 0x00000001 +#define MMREDIR_CAPABILITY_PLATFORM_DSHOW 0x00000002 +#define MMREDIR_CAPABILITY_PLATFORM_OTHER 0x00000004 + +/* TSMM_CLIENT_EVENT Constants */ +#define TSMM_CLIENT_EVENT_ENDOFSTREAM 0x0064 +#define TSMM_CLIENT_EVENT_STOP_COMPLETED 0x00C8 +#define TSMM_CLIENT_EVENT_START_COMPLETED 0x00C9 +#define TSMM_CLIENT_EVENT_MONITORCHANGED 0x012C + +/* TS_MM_DATA_SAMPLE.SampleExtensions */ +#define TSMM_SAMPLE_EXT_CLEANPOINT 0x00000001 +#define TSMM_SAMPLE_EXT_DISCONTINUITY 0x00000002 +#define TSMM_SAMPLE_EXT_INTERLACED 0x00000004 +#define TSMM_SAMPLE_EXT_BOTTOMFIELDFIRST 0x00000008 +#define TSMM_SAMPLE_EXT_REPEATFIELDFIRST 0x00000010 +#define TSMM_SAMPLE_EXT_SINGLEFIELD 0x00000020 +#define TSMM_SAMPLE_EXT_DERIVEDFROMTOPFIELD 0x00000040 +#define TSMM_SAMPLE_EXT_HAS_NO_TIMESTAMPS 0x00000080 +#define TSMM_SAMPLE_EXT_RELATIVE_TIMESTAMPS 0x00000100 +#define TSMM_SAMPLE_EXT_ABSOLUTE_TIMESTAMPS 0x00000200 + +/* MajorType */ +#define TSMF_MAJOR_TYPE_UNKNOWN 0 +#define TSMF_MAJOR_TYPE_VIDEO 1 +#define TSMF_MAJOR_TYPE_AUDIO 2 + +/* SubType */ +#define TSMF_SUB_TYPE_UNKNOWN 0 +#define TSMF_SUB_TYPE_WVC1 1 +#define TSMF_SUB_TYPE_WMA2 2 +#define TSMF_SUB_TYPE_WMA9 3 +#define TSMF_SUB_TYPE_MP3 4 +#define TSMF_SUB_TYPE_MP2A 5 +#define TSMF_SUB_TYPE_MP2V 6 +#define TSMF_SUB_TYPE_WMV3 7 +#define TSMF_SUB_TYPE_AAC 8 +#define TSMF_SUB_TYPE_H264 9 +#define TSMF_SUB_TYPE_AVC1 10 +#define TSMF_SUB_TYPE_AC3 11 +#define TSMF_SUB_TYPE_WMV2 12 +#define TSMF_SUB_TYPE_WMV1 13 +#define TSMF_SUB_TYPE_MP1V 14 +#define TSMF_SUB_TYPE_MP1A 15 +#define TSMF_SUB_TYPE_YUY2 16 +#define TSMF_SUB_TYPE_MP43 17 +#define TSMF_SUB_TYPE_MP4S 18 +#define TSMF_SUB_TYPE_MP42 19 +#define TSMF_SUB_TYPE_OGG 20 +#define TSMF_SUB_TYPE_SPEEX 21 +#define TSMF_SUB_TYPE_THEORA 22 +#define TSMF_SUB_TYPE_FLAC 23 +#define TSMF_SUB_TYPE_VP8 24 +#define TSMF_SUB_TYPE_VP9 25 +#define TSMF_SUB_TYPE_H263 26 +#define TSMF_SUB_TYPE_M4S2 27 +#define TSMF_SUB_TYPE_WMA1 28 + +/* FormatType */ +#define TSMF_FORMAT_TYPE_UNKNOWN 0 +#define TSMF_FORMAT_TYPE_MFVIDEOFORMAT 1 +#define TSMF_FORMAT_TYPE_WAVEFORMATEX 2 +#define TSMF_FORMAT_TYPE_MPEG2VIDEOINFO 3 +#define TSMF_FORMAT_TYPE_VIDEOINFO2 4 +#define TSMF_FORMAT_TYPE_MPEG1VIDEOINFO 5 + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H */ diff --git a/channels/tsmf/client/tsmf_decoder.c b/channels/tsmf/client/tsmf_decoder.c new file mode 100644 index 0000000..319dbfc --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.c @@ -0,0 +1,123 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +static ITSMFDecoder* tsmf_load_decoder_by_name(const char *name) +{ + ITSMFDecoder* decoder; + TSMF_DECODER_ENTRY entry; + + entry = (TSMF_DECODER_ENTRY) freerdp_load_channel_addin_entry("tsmf", (LPSTR) name, "decoder", 0); + + if (!entry) + return NULL; + + decoder = entry(); + + if (!decoder) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return NULL; + } + + return decoder; +} + +static BOOL tsmf_decoder_set_format(ITSMFDecoder *decoder, TS_AM_MEDIA_TYPE* media_type) +{ + if (decoder->SetFormat(decoder, media_type)) + return TRUE; + else + return FALSE; +} + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type) +{ + ITSMFDecoder* decoder = NULL; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name); + } + +#if defined(WITH_GSTREAMER_1_0) || defined(WITH_GSTREAMER_0_10) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + if (!tsmf_decoder_set_format(decoder, media_type)) + { + decoder->Free(decoder); + decoder = NULL; + } + } + + return decoder; +} + +BOOL tsmf_check_decoder_available(const char* name) +{ + ITSMFDecoder* decoder = NULL; + BOOL retValue = FALSE; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name); + } +#if defined(WITH_GSTREAMER_1_0) || defined(WITH_GSTREAMER_0_10) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + decoder->Free(decoder); + decoder = NULL; + retValue = TRUE; + } + + return retValue; +} + diff --git a/channels/tsmf/client/tsmf_decoder.h b/channels/tsmf/client/tsmf_decoder.h new file mode 100644 index 0000000..a12e8ce --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.h @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard 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. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H +#define FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H + +#include "tsmf_types.h" + +typedef enum _ITSMFControlMsg +{ + Control_Pause, + Control_Resume, + Control_Restart, + Control_Stop +} ITSMFControlMsg; + +typedef struct _ITSMFDecoder ITSMFDecoder; + +struct _ITSMFDecoder +{ + /* Set the decoder format. Return true if supported. */ + BOOL (*SetFormat)(ITSMFDecoder *decoder, TS_AM_MEDIA_TYPE *media_type); + /* Decode a sample. */ + BOOL (*Decode)(ITSMFDecoder *decoder, const BYTE *data, UINT32 data_size, UINT32 extensions); + /* Get the decoded data */ + BYTE *(*GetDecodedData)(ITSMFDecoder *decoder, UINT32 *size); + /* Get the pixel format of decoded video frame */ + UINT32(*GetDecodedFormat)(ITSMFDecoder *decoder); + /* Get the width and height of decoded video frame */ + BOOL (*GetDecodedDimension)(ITSMFDecoder *decoder, UINT32 *width, UINT32 *height); + /* Free the decoder */ + void (*Free)(ITSMFDecoder *decoder); + /* Optional Contol function */ + BOOL (*Control)(ITSMFDecoder *decoder, ITSMFControlMsg control_msg, UINT32 *arg); + /* Decode a sample with extended interface. */ + BOOL (*DecodeEx)(ITSMFDecoder *decoder, const BYTE *data, UINT32 data_size, UINT32 extensions, + UINT64 start_time, UINT64 end_time, UINT64 duration); + /* Get current play time */ + UINT64(*GetRunningTime)(ITSMFDecoder *decoder); + /* Update Gstreamer Rendering Area */ + BOOL (*UpdateRenderingArea)(ITSMFDecoder *decoder, int newX, int newY, int newWidth, int newHeight, int numRectangles, RDP_RECT *rectangles); + /* Change Gstreamer Audio Volume */ + BOOL (*ChangeVolume)(ITSMFDecoder *decoder, UINT32 newVolume, UINT32 muted); + /* Check buffer level */ + BOOL (*BufferLevel)(ITSMFDecoder *decoder); + /* Register a callback for frame ack. */ + BOOL (*SetAckFunc)(ITSMFDecoder *decoder, BOOL (*cb)(void *,BOOL), void *stream); + /* Register a callback for stream seek detection. */ + BOOL (*SetSyncFunc)(ITSMFDecoder *decoder, void (*cb)(void *), void *stream); +}; + +#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry" +typedef ITSMFDecoder *(*TSMF_DECODER_ENTRY)(void); + +ITSMFDecoder *tsmf_load_decoder(const char *name, TS_AM_MEDIA_TYPE *media_type); +BOOL tsmf_check_decoder_available(const char* name); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H */ diff --git a/channels/tsmf/client/tsmf_ifman.c b/channels/tsmf/client/tsmf_ifman.c new file mode 100644 index 0000000..9fcc620 --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.c @@ -0,0 +1,834 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_media.h" +#include "tsmf_codec.h" + +#include "tsmf_ifman.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 CapabilityValue; + + if (Stream_GetRemainingLength(ifman->input) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, CapabilityValue); + DEBUG_TSMF("server CapabilityValue %"PRIu32"", CapabilityValue); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(ifman->output, 1); /* CapabilityValue */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 i; + UINT32 v; + UINT32 pos; + UINT32 CapabilityType; + UINT32 cbCapabilityLength; + UINT32 numHostCapabilities; + + if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4)) + return ERROR_OUTOFMEMORY; + + pos = Stream_GetPosition(ifman->output); + Stream_Copy(ifman->input, ifman->output, ifman->input_size); + Stream_SetPosition(ifman->output, pos); + + if (Stream_GetRemainingLength(ifman->output) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, numHostCapabilities); + + for (i = 0; i < numHostCapabilities; i++) + { + if (Stream_GetRemainingLength(ifman->output) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, CapabilityType); + Stream_Read_UINT32(ifman->output, cbCapabilityLength); + + if (Stream_GetRemainingLength(ifman->output) < cbCapabilityLength) + return ERROR_INVALID_DATA; + + pos = Stream_GetPosition(ifman->output); + + switch (CapabilityType) + { + case 1: /* Protocol version request */ + if (Stream_GetRemainingLength(ifman->output) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, v); + DEBUG_TSMF("server protocol version %"PRIu32"", v); + break; + + case 2: /* Supported platform */ + if (Stream_GetRemainingLength(ifman->output) < 4) + return ERROR_INVALID_DATA; + + Stream_Peek_UINT32(ifman->output, v); + DEBUG_TSMF("server supported platform %"PRIu32"", v); + /* Claim that we support both MF and DShow platforms. */ + Stream_Write_UINT32(ifman->output, + MMREDIR_CAPABILITY_PLATFORM_MF | MMREDIR_CAPABILITY_PLATFORM_DSHOW); + break; + + default: + WLog_ERR(TAG, "skipping unknown capability type %"PRIu32"", CapabilityType); + break; + } + + Stream_SetPosition(ifman->output, pos + cbCapabilityLength); + } + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman) +{ + UINT32 numMediaType; + UINT32 PlatformCookie; + UINT32 FormatSupported = 1; + + if (Stream_GetRemainingLength(ifman->input) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, PlatformCookie); + Stream_Seek_UINT32(ifman->input); /* NoRolloverFlags (4 bytes) */ + Stream_Read_UINT32(ifman->input, numMediaType); + DEBUG_TSMF("PlatformCookie %"PRIu32" numMediaType %"PRIu32"", PlatformCookie, numMediaType); + + if (!tsmf_codec_check_media_type(ifman->decoder_name, ifman->input)) + FormatSupported = 0; + + if (FormatSupported) + DEBUG_TSMF("format ok."); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 12)) + return -1; + + Stream_Write_UINT32(ifman->output, FormatSupported); + Stream_Write_UINT32(ifman->output, PlatformCookie); + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + DEBUG_TSMF("Presentation already exists"); + ifman->output_pending = FALSE; + return CHANNEL_RC_OK; + } + + presentation = tsmf_presentation_new(Stream_Pointer(ifman->input), ifman->channel_callback); + + if (!presentation) + status = ERROR_OUTOFMEMORY; + else + tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device); + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext) +{ + UINT32 StreamId; + UINT status = CHANNEL_RC_OK; + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE + 8) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numMediaType */ + stream = tsmf_stream_new(presentation, StreamId, rdpcontext); + + if (!stream) + { + WLog_ERR(TAG, "failed to create stream"); + return ERROR_OUTOFMEMORY; + } + + if (!tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input)) + { + WLog_ERR(TAG, "failed to set stream format"); + return ERROR_OUTOFMEMORY; + } + + tsmf_stream_start_threads(stream); + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 1); /* TopologyReady */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman) +{ + int status = CHANNEL_RC_OK; + UINT32 StreamId; + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < 20) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_free(stream); + else + status = ERROR_NOT_FOUND; + } + + ifman->output_pending = TRUE; + return status; +} + +float tsmf_stream_read_float(wStream* s) +{ + float fValue; + UINT32 iValue; + Stream_Read_UINT32(s, iValue); + CopyMemory(&fValue, &iValue, 4); + return fValue; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + float Left, Top; + float Right, Bottom; + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < 32) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { + Left = tsmf_stream_read_float(ifman->input); /* Left (4 bytes) */ + Top = tsmf_stream_read_float(ifman->input); /* Top (4 bytes) */ + Right = tsmf_stream_read_float(ifman->input); /* Right (4 bytes) */ + Bottom = tsmf_stream_read_float(ifman->input); /* Bottom (4 bytes) */ + DEBUG_TSMF("SetSourceVideoRect: Left: %f Top: %f Right: %f Bottom: %f", + Left, Top, Right, Bottom); + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_free(presentation); + else + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + if (!Stream_EnsureRemainingCapacity(ifman->output, 4)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + UINT32 newVolume; + UINT32 muted; + DEBUG_TSMF("on stream volume"); + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE + 8) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, newVolume); + DEBUG_TSMF("on stream volume: new volume=[%"PRIu32"]", newVolume); + Stream_Read_UINT32(ifman->input, muted); + DEBUG_TSMF("on stream volume: muted=[%"PRIu32"]", muted); + + if (!tsmf_presentation_volume_changed(presentation, newVolume, muted)) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF("on channel volume"); + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE + 8) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + UINT32 channelVolume; + UINT32 changedChannel; + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, channelVolume); + DEBUG_TSMF("on channel volume: channel volume=[%"PRIu32"]", channelVolume); + Stream_Read_UINT32(ifman->input, changedChannel); + DEBUG_TSMF("on stream volume: changed channel=[%"PRIu32"]", changedChannel); + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + UINT32 numGeometryInfo; + UINT32 Left; + UINT32 Top; + UINT32 Width; + UINT32 Height; + UINT32 cbVisibleRect; + RDP_RECT* rects = NULL; + int num_rects = 0; + UINT error = CHANNEL_RC_OK; + int i; + size_t pos; + + if (Stream_GetRemainingLength(ifman->input) < GUID_SIZE + 32) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + return ERROR_NOT_FOUND; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, numGeometryInfo); + pos = Stream_GetPosition(ifman->input); + Stream_Seek(ifman->input, 12); /* VideoWindowId (8 bytes), VideoWindowState (4 bytes) */ + Stream_Read_UINT32(ifman->input, Width); + Stream_Read_UINT32(ifman->input, Height); + Stream_Read_UINT32(ifman->input, Left); + Stream_Read_UINT32(ifman->input, Top); + Stream_SetPosition(ifman->input, pos + numGeometryInfo); + Stream_Read_UINT32(ifman->input, cbVisibleRect); + num_rects = cbVisibleRect / 16; + DEBUG_TSMF("numGeometryInfo %"PRIu32" Width %"PRIu32" Height %"PRIu32" Left %"PRIu32" Top %"PRIu32" cbVisibleRect %"PRIu32" num_rects %d", + numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects); + + if (num_rects > 0) + { + rects = (RDP_RECT*) calloc(num_rects, sizeof(RDP_RECT)); + + for (i = 0; i < num_rects; i++) + { + Stream_Read_UINT16(ifman->input, rects[i].y); /* Top */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].x); /* Left */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].height); /* Bottom */ + Stream_Seek_UINT16(ifman->input); + Stream_Read_UINT16(ifman->input, rects[i].width); /* Right */ + Stream_Seek_UINT16(ifman->input); + rects[i].width -= rects[i].x; + rects[i].height -= rects[i].y; + DEBUG_TSMF("rect %d: %"PRId16" %"PRId16" %"PRId16" %"PRId16"", i, + rects[i].x, rects[i].y, rects[i].width, rects[i].height); + } + } + + if (!tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, num_rects, rects)) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + tsmf_ifman_on_playback_paused(ifman); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + TSMF_STREAM* stream; + UINT32 StreamId; + UINT64 SampleStartTime; + UINT64 SampleEndTime; + UINT64 ThrottleDuration; + UINT32 SampleExtensions; + UINT32 cbData; + UINT error; + + if (Stream_GetRemainingLength(ifman->input) < 60) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numSample */ + Stream_Read_UINT64(ifman->input, SampleStartTime); + Stream_Read_UINT64(ifman->input, SampleEndTime); + Stream_Read_UINT64(ifman->input, ThrottleDuration); + Stream_Seek_UINT32(ifman->input); /* SampleFlags */ + Stream_Read_UINT32(ifman->input, SampleExtensions); + Stream_Read_UINT32(ifman->input, cbData); + + if (Stream_GetRemainingLength(ifman->input) < cbData) + return ERROR_INVALID_DATA; + + DEBUG_TSMF("MessageId %"PRIu32" StreamId %"PRIu32" SampleStartTime %"PRIu64" SampleEndTime %"PRIu64" " + "ThrottleDuration %"PRIu64" SampleExtensions %"PRIu32" cbData %"PRIu32"", + ifman->message_id, StreamId, SampleStartTime, SampleEndTime, + ThrottleDuration, SampleExtensions, cbData); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (!stream) + { + WLog_ERR(TAG, "unknown stream id"); + return ERROR_NOT_FOUND; + } + + if (!tsmf_stream_push_sample(stream, ifman->channel_callback, + ifman->message_id, SampleStartTime, SampleEndTime, + ThrottleDuration, SampleExtensions, cbData, Stream_Pointer(ifman->input))) + { + WLog_ERR(TAG, "unable to push sample"); + return ERROR_OUTOFMEMORY; + } + + if ((error = tsmf_presentation_sync(presentation))) + { + WLog_ERR(TAG, "tsmf_presentation_sync failed with error %"PRIu32"", error); + return error; + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman) +{ + UINT32 StreamId; + TSMF_PRESENTATION* presentation; + TSMF_STREAM* stream; + + if (Stream_GetRemainingLength(ifman->input) < 20) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + DEBUG_TSMF("StreamId %"PRIu32"", StreamId); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + /* Flush message is for a stream, not the entire presentation + * therefore we only flush the stream as intended per the MS-RDPEV spec + */ + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + { + if (!tsmf_stream_flush(stream)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown stream id"); + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman) +{ + UINT32 StreamId; + TSMF_STREAM* stream = NULL; + TSMF_PRESENTATION* presentation; + + if (Stream_GetRemainingLength(ifman->input) < 20) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_end(stream, ifman->message_id, ifman->channel_callback); + } + + DEBUG_TSMF("StreamId %"PRIu32"", StreamId); + ifman->output_pending = TRUE; + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + + if (Stream_GetRemainingLength(ifman->input) < 16) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_start(presentation); + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_START_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added pause control so gstreamer pipeline can be paused accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_paused(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added restart control so gstreamer pipeline can be resumed accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_restarted(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation; + DEBUG_TSMF(""); + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_stop(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_STOP_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_MONITORCHANGED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} diff --git a/channels/tsmf/client/tsmf_ifman.h b/channels/tsmf/client/tsmf_ifman.h new file mode 100644 index 0000000..b698620 --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.h @@ -0,0 +1,70 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H + +#include + +typedef struct _TSMF_IFMAN TSMF_IFMAN; +struct _TSMF_IFMAN +{ + IWTSVirtualChannelCallback* channel_callback; + const char* decoder_name; + const char* audio_name; + const char* audio_device; + BYTE presentation_id[16]; + UINT32 stream_id; + UINT32 message_id; + + wStream* input; + UINT32 input_size; + wStream* output; + BOOL output_pending; + UINT32 output_interface_id; +}; + +UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman); +UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext); +UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman); +UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman); +UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman); +UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman); +UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman); +UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman); +UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H */ + diff --git a/channels/tsmf/client/tsmf_main.c b/channels/tsmf/client/tsmf_main.c new file mode 100644 index 0000000..9309235 --- /dev/null +++ b/channels/tsmf/client/tsmf_main.c @@ -0,0 +1,623 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_ifman.h" +#include "tsmf_media.h" + +#include "tsmf_main.h" + +BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id) +{ + wStream* s = NULL; + int status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + if (!callback) + { + DEBUG_TSMF("No callback reference - unable to send eos response!"); + return FALSE; + } + + if (callback && callback->stream_id && callback->channel && callback->channel->Write) + { + s = Stream_New(NULL, 24); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */ + Stream_Write_UINT32(s, 0); /* cbData */ + DEBUG_TSMF("EOS response size %"PRIuz"", Stream_GetPosition(s)); + status = callback->channel->Write(callback->channel, Stream_GetPosition(s), Stream_Buffer(s), NULL); + + if (status) + { + WLog_ERR(TAG, "response error %d", status); + } + + Stream_Free(s, TRUE); + } + + return (status == 0); +} + +BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, + UINT32 message_id, UINT64 duration, UINT32 data_size) +{ + wStream* s = NULL; + int status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + + if (!callback) + return FALSE; + + s = Stream_New(NULL, 32); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, PLAYBACK_ACK); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT64(s, duration); /* DataDuration */ + Stream_Write_UINT64(s, data_size); /* cbData */ + DEBUG_TSMF("ACK response size %"PRIuz"", Stream_GetPosition(s)); + + if (!callback->channel || !callback->channel->Write) + { + WLog_ERR(TAG, "callback=%p, channel=%p, write=%p", callback, + (callback ? callback->channel : NULL), + (callback && callback->channel ? callback->channel->Write : NULL)); + } + else + { + status = callback->channel->Write(callback->channel, + Stream_GetPosition(s), Stream_Buffer(s), NULL); + } + + if (status) + { + WLog_ERR(TAG, "response error %d", status); + } + + Stream_Free(s, TRUE); + return (status == 0); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + size_t length; + wStream* input; + wStream* output; + UINT error = CHANNEL_RC_OK; + BOOL processed = FALSE; + TSMF_IFMAN ifman; + UINT32 MessageId; + UINT32 FunctionId; + UINT32 InterfaceId; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + UINT32 cbSize = Stream_GetRemainingLength(data); + + /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ + if (cbSize < 12) + { + WLog_ERR(TAG, "invalid size. cbSize=%"PRIu32"", cbSize); + return ERROR_INVALID_DATA; + } + + input = data; + output = Stream_New(NULL, 256); + + if (!output) + return ERROR_OUTOFMEMORY; + + Stream_Seek(output, 8); + Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */ + Stream_Read_UINT32(input, MessageId); /* MessageId (4 bytes) */ + Stream_Read_UINT32(input, FunctionId); /* FunctionId (4 bytes) */ + DEBUG_TSMF("cbSize=%"PRIu32" InterfaceId=0x%"PRIX32" MessageId=0x%"PRIX32" FunctionId=0x%"PRIX32"", + cbSize, InterfaceId, MessageId, FunctionId); + ZeroMemory(&ifman, sizeof(TSMF_IFMAN)); + ifman.channel_callback = pChannelCallback; + ifman.decoder_name = ((TSMF_PLUGIN*) callback->plugin)->decoder_name; + ifman.audio_name = ((TSMF_PLUGIN*) callback->plugin)->audio_name; + ifman.audio_device = ((TSMF_PLUGIN*) callback->plugin)->audio_device; + CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE); + ifman.stream_id = callback->stream_id; + ifman.message_id = MessageId; + ifman.input = input; + ifman.input_size = cbSize - 12; + ifman.output = output; + ifman.output_pending = FALSE; + ifman.output_interface_id = InterfaceId; + + //fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId: 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId); + + switch (InterfaceId) + { + case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE: + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = tsmf_ifman_rim_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY: + switch (FunctionId) + { + case SET_CHANNEL_PARAMS: + if (Stream_GetRemainingLength(input) < GUID_SIZE + 4) + { + error = ERROR_INVALID_DATA; + goto out; + } + + CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE); + Stream_Seek(input, GUID_SIZE); + Stream_Read_UINT32(input, callback->stream_id); + DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%"PRIu32"", callback->stream_id); + ifman.output_pending = TRUE; + processed = TRUE; + break; + + case EXCHANGE_CAPABILITIES_REQ: + error = tsmf_ifman_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case CHECK_FORMAT_SUPPORT_REQ: + error = tsmf_ifman_check_format_support_request(&ifman); + processed = TRUE; + break; + + case ON_NEW_PRESENTATION: + error = tsmf_ifman_on_new_presentation(&ifman); + processed = TRUE; + break; + + case ADD_STREAM: + error = tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*) callback->plugin)->rdpcontext); + processed = TRUE; + break; + + case SET_TOPOLOGY_REQ: + error = tsmf_ifman_set_topology_request(&ifman); + processed = TRUE; + break; + + case REMOVE_STREAM: + error = tsmf_ifman_remove_stream(&ifman); + processed = TRUE; + break; + + case SET_SOURCE_VIDEO_RECT: + error = tsmf_ifman_set_source_video_rect(&ifman); + processed = TRUE; + break; + + case SHUTDOWN_PRESENTATION_REQ: + error = tsmf_ifman_shutdown_presentation(&ifman); + processed = TRUE; + break; + + case ON_STREAM_VOLUME: + error = tsmf_ifman_on_stream_volume(&ifman); + processed = TRUE; + break; + + case ON_CHANNEL_VOLUME: + error = tsmf_ifman_on_channel_volume(&ifman); + processed = TRUE; + break; + + case SET_VIDEO_WINDOW: + error = tsmf_ifman_set_video_window(&ifman); + processed = TRUE; + break; + + case UPDATE_GEOMETRY_INFO: + error = tsmf_ifman_update_geometry_info(&ifman); + processed = TRUE; + break; + + case SET_ALLOCATOR: + error = tsmf_ifman_set_allocator(&ifman); + processed = TRUE; + break; + + case NOTIFY_PREROLL: + error = tsmf_ifman_notify_preroll(&ifman); + processed = TRUE; + break; + + case ON_SAMPLE: + error = tsmf_ifman_on_sample(&ifman); + processed = TRUE; + break; + + case ON_FLUSH: + error = tsmf_ifman_on_flush(&ifman); + processed = TRUE; + break; + + case ON_END_OF_STREAM: + error = tsmf_ifman_on_end_of_stream(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STARTED: + error = tsmf_ifman_on_playback_started(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_PAUSED: + error = tsmf_ifman_on_playback_paused(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RESTARTED: + error = tsmf_ifman_on_playback_restarted(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STOPPED: + error = tsmf_ifman_on_playback_stopped(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RATE_CHANGED: + error = tsmf_ifman_on_playback_rate_changed(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + default: + break; + } + + input = NULL; + ifman.input = NULL; + + if (error) + { + WLog_ERR(TAG, "ifman data received processing error %"PRIu32"", error); + } + + if (!processed) + { + switch (FunctionId) + { + case RIMCALL_RELEASE: + /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE) + This message does not require a reply. */ + processed = TRUE; + ifman.output_pending = 1; + break; + + case RIMCALL_QUERYINTERFACE: + /* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP) + This message is not supported in this channel. */ + processed = TRUE; + break; + } + + if (!processed) + { + WLog_ERR(TAG, + "Unknown InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId: 0x%08"PRIX32"\n", + InterfaceId, MessageId, FunctionId); + /* When a request is not implemented we return empty response indicating error */ + } + + processed = TRUE; + } + + if (processed && !ifman.output_pending) + { + /* Response packet does not have FunctionId */ + length = Stream_GetPosition(output); + Stream_SetPosition(output, 0); + Stream_Write_UINT32(output, ifman.output_interface_id); + Stream_Write_UINT32(output, MessageId); + DEBUG_TSMF("response size %d", length); + error = callback->channel->Write(callback->channel, length, Stream_Buffer(output), NULL); + + if (error) + { + WLog_ERR(TAG, "response error %"PRIu32"", error); + } + } + +out: + Stream_Free(output, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_STREAM* stream; + TSMF_PRESENTATION* presentation; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) pChannelCallback; + DEBUG_TSMF(""); + + if (callback->stream_id) + { + presentation = tsmf_presentation_find_by_id(callback->presentation_id); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, callback->stream_id); + + if (stream) + tsmf_stream_free(stream); + } + } + + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + TSMF_CHANNEL_CALLBACK* callback; + TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*) pListenerCallback; + DEBUG_TSMF(""); + callback = (TSMF_CHANNEL_CALLBACK*) calloc(1, sizeof(TSMF_CHANNEL_CALLBACK)); + + if (!callback) + return CHANNEL_RC_NO_MEMORY; + + callback->iface.OnDataReceived = tsmf_on_data_received; + callback->iface.OnClose = tsmf_on_close; + callback->iface.OnOpen = NULL; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + DEBUG_TSMF(""); + tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*) calloc(1, sizeof(TSMF_LISTENER_CALLBACK)); + + if (!tsmf->listener_callback) + return CHANNEL_RC_NO_MEMORY; + + tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection; + tsmf->listener_callback->plugin = pPlugin; + tsmf->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener(pChannelMgr, "TSMF", 0, + (IWTSListenerCallback*) tsmf->listener_callback, &(tsmf->listener)); + tsmf->listener->pInterface = tsmf->iface.pInterface; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + DEBUG_TSMF(""); + free(tsmf->listener_callback); + free(tsmf); + return CHANNEL_RC_OK; +} + +COMMAND_LINE_ARGUMENT_A tsmf_args[] = +{ + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "audio device name" }, + { "decoder", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "decoder subsystem" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*) pPlugin; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, + tsmf_args, flags, tsmf, NULL, NULL); + + if (status != 0) + return ERROR_INVALID_DATA; + + arg = tsmf_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "sys") + { + tsmf->audio_name = _strdup(arg->Value); + + if (!tsmf->audio_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "dev") + { + tsmf->audio_device = _strdup(arg->Value); + + if (!tsmf->audio_device) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "decoder") + { + tsmf->decoder_name = _strdup(arg->Value); + + if (!tsmf->decoder_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry tsmf_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = 0; + TSMF_PLUGIN* tsmf; + TsmfClientContext* context; + UINT error = CHANNEL_RC_NO_MEMORY; + tsmf = (TSMF_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "tsmf"); + + if (!tsmf) + { + tsmf = (TSMF_PLUGIN*) calloc(1, sizeof(TSMF_PLUGIN)); + + if (!tsmf) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + tsmf->iface.Initialize = tsmf_plugin_initialize; + tsmf->iface.Connected = NULL; + tsmf->iface.Disconnected = NULL; + tsmf->iface.Terminated = tsmf_plugin_terminated; + tsmf->rdpcontext = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( + pEntryPoints))->instance)->context; + context = (TsmfClientContext*) calloc(1, sizeof(TsmfClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_context; + } + + context->handle = (void*) tsmf; + tsmf->iface.pInterface = (void*) context; + + if (!tsmf_media_init()) + { + error = ERROR_INVALID_OPERATION; + goto error_init; + } + + status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", (IWTSPlugin*) tsmf); + } + + if (status == CHANNEL_RC_OK) + { + status = tsmf_process_addin_args((IWTSPlugin*) tsmf, pEntryPoints->GetPluginData(pEntryPoints)); + } + + return status; +error_init: + free(context); +error_context: + free(tsmf); + return error; +} diff --git a/channels/tsmf/client/tsmf_main.h b/channels/tsmf/client/tsmf_main.h new file mode 100644 index 0000000..0143d29 --- /dev/null +++ b/channels/tsmf/client/tsmf_main.h @@ -0,0 +1,71 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H + +#include + +typedef struct _TSMF_LISTENER_CALLBACK TSMF_LISTENER_CALLBACK; + +typedef struct _TSMF_CHANNEL_CALLBACK TSMF_CHANNEL_CALLBACK; + +typedef struct _TSMF_PLUGIN TSMF_PLUGIN; + +struct _TSMF_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +struct _TSMF_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + BYTE presentation_id[GUID_SIZE]; + UINT32 stream_id; +}; + +struct _TSMF_PLUGIN +{ + IWTSPlugin iface; + + IWTSListener* listener; + TSMF_LISTENER_CALLBACK* listener_callback; + + const char* decoder_name; + const char* audio_name; + const char* audio_device; + + rdpContext* rdpcontext; +}; + +BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id); +BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, + UINT32 message_id, UINT64 duration, UINT32 data_size); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H */ diff --git a/channels/tsmf/client/tsmf_media.c b/channels/tsmf/client/tsmf_media.c new file mode 100644 index 0000000..05ca6c4 --- /dev/null +++ b/channels/tsmf/client/tsmf_media.c @@ -0,0 +1,1567 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "tsmf_constants.h" +#include "tsmf_types.h" +#include "tsmf_decoder.h" +#include "tsmf_audio.h" +#include "tsmf_main.h" +#include "tsmf_codec.h" +#include "tsmf_media.h" + +#define AUDIO_TOLERANCE 10000000LL + +/* 1 second = 10,000,000 100ns units*/ +#define VIDEO_ADJUST_MAX 10*1000*1000 + +#define MAX_ACK_TIME 666667 + +#define AUDIO_MIN_BUFFER_LEVEL 3 +#define AUDIO_MAX_BUFFER_LEVEL 6 + +#define VIDEO_MIN_BUFFER_LEVEL 10 +#define VIDEO_MAX_BUFFER_LEVEL 30 + +struct _TSMF_PRESENTATION +{ + BYTE presentation_id[GUID_SIZE]; + + const char* audio_name; + const char* audio_device; + + IWTSVirtualChannelCallback* channel_callback; + + UINT64 audio_start_time; + UINT64 audio_end_time; + + UINT32 volume; + UINT32 muted; + + wArrayList* stream_list; + + int x; + int y; + int width; + int height; + + int nr_rects; + void* rects; +}; + +struct _TSMF_STREAM +{ + UINT32 stream_id; + + TSMF_PRESENTATION* presentation; + + ITSMFDecoder* decoder; + + int major_type; + int eos; + UINT32 eos_message_id; + IWTSVirtualChannelCallback* eos_channel_callback; + int delayed_stop; + UINT32 width; + UINT32 height; + + ITSMFAudioDevice* audio; + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + /* The start time of last played sample */ + UINT64 last_start_time; + /* The end_time of last played sample */ + UINT64 last_end_time; + /* Next sample should not start before this system time. */ + UINT64 next_start_time; + + UINT32 minBufferLevel; + UINT32 maxBufferLevel; + UINT32 currentBufferLevel; + + HANDLE play_thread; + HANDLE ack_thread; + HANDLE stopEvent; + HANDLE ready; + + wQueue* sample_list; + wQueue* sample_ack_list; + rdpContext* rdpcontext; + + BOOL seeking; +}; + +struct _TSMF_SAMPLE +{ + UINT32 sample_id; + UINT64 start_time; + UINT64 end_time; + UINT64 duration; + UINT32 extensions; + UINT32 data_size; + BYTE* data; + UINT32 decoded_size; + UINT32 pixfmt; + + BOOL invalidTimestamps; + + TSMF_STREAM* stream; + IWTSVirtualChannelCallback* channel_callback; + UINT64 ack_time; +}; + +static wArrayList* presentation_list = NULL; +static int TERMINATING = 0; + +static void _tsmf_presentation_free(void* obj); +static void _tsmf_stream_free(void* obj); + +static UINT64 get_current_time(void) +{ + struct timeval tp; + gettimeofday(&tp, 0); + return ((UINT64)tp.tv_sec) * 10000000LL + ((UINT64)tp.tv_usec) * 10LL; +} + +static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* s; + TSMF_SAMPLE* sample; + BOOL pending = FALSE; + TSMF_PRESENTATION* presentation = NULL; + + if (!stream) + return NULL; + + presentation = stream->presentation; + + if (Queue_Count(stream->sample_list) < 1) + return NULL; + + if (sync) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + /* Check if some other stream has earlier sample that needs to be played first */ + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (stream->last_start_time > AUDIO_TOLERANCE) + { + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + s = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (s != stream && !s->eos && s->last_start_time && + s->last_start_time < stream->last_start_time - AUDIO_TOLERANCE) + { + DEBUG_TSMF("Pending due to audio tolerance"); + pending = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + } + else + { + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (stream->last_start_time > presentation->audio_start_time) + { + DEBUG_TSMF("Pending due to stream start time > audio start time"); + pending = TRUE; + } + } + } + } + } + + if (pending) + return NULL; + + sample = (TSMF_SAMPLE*) Queue_Dequeue(stream->sample_list); + + /* Only update stream last end time if the sample end time is valid and greater than the current stream end time */ + if (sample && (sample->end_time > stream->last_end_time) + && (!sample->invalidTimestamps)) + stream->last_end_time = sample->end_time; + + /* Only update stream last start time if the sample start time is valid and greater than the current stream start time */ + if (sample && (sample->start_time > stream->last_start_time) + && (!sample->invalidTimestamps)) + stream->last_start_time = sample->start_time; + + return sample; +} + +static void tsmf_sample_free(void* arg) +{ + TSMF_SAMPLE* sample = arg; + + if (!sample) + return; + + free(sample->data); + free(sample); +} + +static BOOL tsmf_sample_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + return tsmf_playback_ack(sample->channel_callback, sample->sample_id, + sample->duration, sample->data_size); +} + +static BOOL tsmf_sample_queue_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + if (!sample->stream) + return FALSE; + + return Queue_Enqueue(sample->stream->sample_ack_list, sample); +} + +/* Returns TRUE if no more samples are currently available + * Returns FALSE otherwise + */ +static BOOL tsmf_stream_process_ack(void* arg, BOOL force) +{ + TSMF_STREAM* stream = arg; + TSMF_SAMPLE* sample; + UINT64 ack_time; + BOOL rc = FALSE; + + if (!stream) + return TRUE; + + Queue_Lock(stream->sample_ack_list); + sample = (TSMF_SAMPLE*) Queue_Peek(stream->sample_ack_list); + + if (!sample) + { + rc = TRUE; + goto finally; + } + + if (!force) + { + /* Do some min/max ack limiting if we have access to Buffer level information */ + if (stream->decoder && stream->decoder->BufferLevel) + { + /* Try to keep buffer level below max by withholding acks */ + if (stream->currentBufferLevel > stream->maxBufferLevel) + goto finally; + /* Try to keep buffer level above min by pushing acks through quickly */ + else if (stream->currentBufferLevel < stream->minBufferLevel) + goto dequeue; + } + + /* Time based acks only */ + ack_time = get_current_time(); + + if (sample->ack_time > ack_time) + goto finally; + } + +dequeue: + sample = Queue_Dequeue(stream->sample_ack_list); + + if (sample) + { + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + } + +finally: + Queue_Unlock(stream->sample_ack_list); + return rc; +} + +TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid, + IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_PRESENTATION* presentation; + + if (!guid || !pChannelCallback) + return NULL; + + presentation = (TSMF_PRESENTATION*) calloc(1, sizeof(TSMF_PRESENTATION)); + + if (!presentation) + { + WLog_ERR(TAG, "calloc failed"); + return NULL; + } + + CopyMemory(presentation->presentation_id, guid, GUID_SIZE); + presentation->channel_callback = pChannelCallback; + presentation->volume = 5000; /* 50% */ + presentation->muted = 0; + + if (!(presentation->stream_list = ArrayList_New(TRUE))) + goto error_stream_list; + + ArrayList_Object(presentation->stream_list)->fnObjectFree = + _tsmf_stream_free; + + if (ArrayList_Add(presentation_list, presentation) < 0) + goto error_add; + + return presentation; +error_add: + ArrayList_Free(presentation->stream_list); +error_stream_list: + free(presentation); + return NULL; +} + +static char* guid_to_string(const BYTE* guid, char* str, size_t len) +{ + int i; + + if (!guid || !str) + return NULL; + + for (i = 0; i < GUID_SIZE && len > 2 * i; i++) + sprintf_s(str + (2 * i), len - 2 * i, "%02"PRIX8"", guid[i]); + + return str; +} + +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid) +{ + UINT32 index; + UINT32 count; + BOOL found = FALSE; + char guid_str[GUID_SIZE * 2 + 1]; + TSMF_PRESENTATION* presentation; + ArrayList_Lock(presentation_list); + count = ArrayList_Count(presentation_list); + + for (index = 0; index < count; index++) + { + presentation = (TSMF_PRESENTATION*) ArrayList_GetItem(presentation_list, index); + + if (memcmp(presentation->presentation_id, guid, GUID_SIZE) == 0) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation_list); + + if (!found) + WLog_WARN(TAG, "presentation id %s not found", guid_to_string(guid, guid_str, + sizeof(guid_str))); + + return (found) ? presentation : NULL; +} + +static BOOL tsmf_sample_playback_video(TSMF_SAMPLE* sample) +{ + UINT64 t; + TSMF_VIDEO_FRAME_EVENT event; + TSMF_STREAM* stream = sample->stream; + TSMF_PRESENTATION* presentation = stream->presentation; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*) + sample->channel_callback; + TsmfClientContext* tsmf = (TsmfClientContext*) callback->plugin->pInterface; + DEBUG_TSMF("MessageId %"PRIu32" EndTime %"PRIu64" data_size %"PRIu32" consumed.", + sample->sample_id, sample->end_time, sample->data_size); + + if (sample->data) + { + t = get_current_time(); + + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (stream->next_start_time > t && + ((sample->start_time >= presentation->audio_start_time) || + ((sample->start_time < stream->last_start_time) + && (!sample->invalidTimestamps)))) + { + USleep((stream->next_start_time - t) / 10); + } + + stream->next_start_time = t + sample->duration - 50000; + ZeroMemory(&event, sizeof(TSMF_VIDEO_FRAME_EVENT)); + event.frameData = sample->data; + event.frameSize = sample->decoded_size; + event.framePixFmt = sample->pixfmt; + event.frameWidth = sample->stream->width; + event.frameHeight = sample->stream->height; + event.x = presentation->x; + event.y = presentation->y; + event.width = presentation->width; + event.height = presentation->height; + + if (presentation->nr_rects > 0) + { + event.numVisibleRects = presentation->nr_rects; + event.visibleRects = (RECTANGLE_16*) calloc(event.numVisibleRects, sizeof(RECTANGLE_16)); + + if (!event.visibleRects) + { + WLog_ERR(TAG, "can't allocate memory for copy rectangles"); + return FALSE; + } + + memcpy(event.visibleRects, presentation->rects, presentation->nr_rects * sizeof(RDP_RECT)); + presentation->nr_rects = 0; + } + +#if 0 + /* Dump a .ppm image for every 30 frames. Assuming the frame is in YUV format, we + extract the Y values to create a grayscale image. */ + static int frame_id = 0; + char buf[100]; + FILE* fp; + + if ((frame_id % 30) == 0) + { + sprintf_s(buf, sizeof(buf), "/tmp/FreeRDP_Frame_%d.ppm", frame_id); + fp = fopen(buf, "wb"); + fwrite("P5\n", 1, 3, fp); + sprintf_s(buf, sizeof(buf), "%"PRIu32" %"PRIu32"\n", sample->stream->width, + sample->stream->height); + fwrite(buf, 1, strlen(buf), fp); + fwrite("255\n", 1, 4, fp); + fwrite(sample->data, 1, sample->stream->width * sample->stream->height, fp); + fflush(fp); + fclose(fp); + } + + frame_id++; +#endif + /* The frame data ownership is passed to the event object, and is freed after the event is processed. */ + sample->data = NULL; + sample->decoded_size = 0; + + if (tsmf->FrameEvent) + tsmf->FrameEvent(tsmf, &event); + + free(event.frameData); + + if (event.visibleRects != NULL) + free(event.visibleRects); + } + + return TRUE; +} + +static BOOL tsmf_sample_playback_audio(TSMF_SAMPLE* sample) +{ + UINT64 latency = 0; + TSMF_STREAM* stream = sample->stream; + BOOL ret; + DEBUG_TSMF("MessageId %"PRIu32" EndTime %"PRIu64" consumed.", + sample->sample_id, sample->end_time); + + if (stream->audio && sample->data) + { + ret = sample->stream->audio->Play(sample->stream->audio, sample->data, + sample->decoded_size); + free(sample->data); + sample->data = NULL; + sample->decoded_size = 0; + + if (stream->audio->GetLatency) + latency = stream->audio->GetLatency(stream->audio); + } + else + { + ret = TRUE; + latency = 0; + } + + sample->ack_time = latency + get_current_time(); + + /* Only update stream times if the sample timestamps are valid */ + if (!sample->invalidTimestamps) + { + stream->last_start_time = sample->start_time + latency; + stream->last_end_time = sample->end_time + latency; + stream->presentation->audio_start_time = sample->start_time + latency; + stream->presentation->audio_end_time = sample->end_time + latency; + } + + return ret; +} + +static BOOL tsmf_sample_playback(TSMF_SAMPLE* sample) +{ + BOOL ret = FALSE; + UINT32 width; + UINT32 height; + UINT32 pixfmt = 0; + TSMF_STREAM* stream = sample->stream; + + if (stream->decoder) + { + if (stream->decoder->DecodeEx) + { + /* Try to "sync" video buffers to audio buffers by looking at the running time for each stream + * The difference between the two running times causes an offset between audio and video actual + * render times. So, we try to adjust timestamps on the video buffer to match those on the audio buffer. + */ + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + TSMF_STREAM* temp_stream = NULL; + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Lock(presentation->stream_list); + int count = ArrayList_Count(presentation->stream_list); + int index = 0; + + for (index = 0; index < count; index++) + { + UINT64 time_diff; + temp_stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, + index); + + if (temp_stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + UINT64 video_time = (UINT64) stream->decoder->GetRunningTime(stream->decoder); + UINT64 audio_time = (UINT64) temp_stream->decoder->GetRunningTime( + temp_stream->decoder); + UINT64 max_adjust = VIDEO_ADJUST_MAX; + + if (video_time < audio_time) + max_adjust = -VIDEO_ADJUST_MAX; + + if (video_time > audio_time) + time_diff = video_time - audio_time; + else + time_diff = audio_time - video_time; + + time_diff = time_diff < VIDEO_ADJUST_MAX ? time_diff : max_adjust; + sample->start_time += time_diff; + sample->end_time += time_diff; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + + ret = stream->decoder->DecodeEx(stream->decoder, sample->data, + sample->data_size, sample->extensions, + sample->start_time, sample->end_time, sample->duration); + } + else + { + ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, + sample->extensions); + } + } + + if (!ret) + { + WLog_ERR(TAG, "decode error, queue ack anyways"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + return FALSE; + } + + return TRUE; + } + + free(sample->data); + sample->data = NULL; + + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + if (stream->decoder->GetDecodedFormat) + { + pixfmt = stream->decoder->GetDecodedFormat(stream->decoder); + + if (pixfmt == ((UINT32) - 1)) + { + WLog_ERR(TAG, "unable to decode video format"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + } + + return FALSE; + } + + sample->pixfmt = pixfmt; + } + + if (stream->decoder->GetDecodedDimension) + { + ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height); + + if (ret && (width != stream->width || height != stream->height)) + { + DEBUG_TSMF("video dimension changed to %"PRIu32" x %"PRIu32"", width, height); + stream->width = width; + stream->height = height; + } + } + } + + if (stream->decoder->GetDecodedData) + { + sample->data = stream->decoder->GetDecodedData(stream->decoder, + &sample->decoded_size); + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + ret = tsmf_sample_playback_video(sample) && + tsmf_sample_queue_ack(sample); + break; + + case TSMF_MAJOR_TYPE_AUDIO: + ret = tsmf_sample_playback_audio(sample) && + tsmf_sample_queue_ack(sample); + break; + } + } + else + { + TSMF_STREAM* stream = sample->stream; + UINT64 ack_anticipation_time = get_current_time(); + BOOL buffer_filled = TRUE; + + /* Classify the buffer as filled once it reaches minimum level */ + if (stream->decoder->BufferLevel) + { + if (stream->currentBufferLevel < stream->minBufferLevel) + buffer_filled = FALSE; + } + + if (buffer_filled) + { + ack_anticipation_time += (sample->duration / 2 < MAX_ACK_TIME) ? + sample->duration / 2 : MAX_ACK_TIME; + } + else + { + ack_anticipation_time += (sample->duration / 2 < MAX_ACK_TIME) ? + sample->duration / 2 : MAX_ACK_TIME; + } + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + { + break; + } + + case TSMF_MAJOR_TYPE_AUDIO: + { + break; + } + } + + sample->ack_time = ack_anticipation_time; + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + ret = FALSE; + } + } + + return ret; +} + +static DWORD WINAPI tsmf_stream_ack_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_STREAM* stream = (TSMF_STREAM*) arg; + UINT error = CHANNEL_RC_OK; + DEBUG_TSMF("in %"PRIu32"", stream->stream_id); + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_ack_list); + + while (1) + { + DWORD ev = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (ev == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + if (stream->eos) + { + while ((stream->currentBufferLevel > 0) + && !(tsmf_stream_process_ack(stream, TRUE))) + { + DEBUG_TSMF("END OF STREAM PROCESSING!"); + + if (stream->decoder && stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + else + stream->currentBufferLevel = 1; + + USleep(1000); + } + + tsmf_send_eos_response(stream->eos_channel_callback, stream->eos_message_id); + stream->eos = 0; + + if (stream->delayed_stop) + { + DEBUG_TSMF("Finishing delayed stream stop, now that eos has processed."); + tsmf_stream_flush(stream); + + if (stream->decoder && stream->decoder->Control) + stream->decoder->Control(stream->decoder, Control_Stop, NULL); + } + } + + /* Stream stopped force all of the acks to happen */ + if (ev == WAIT_OBJECT_0) + { + DEBUG_TSMF("ack: Stream stopped!"); + + while (1) + { + if (tsmf_stream_process_ack(stream, TRUE)) + break; + + USleep(1000); + } + + break; + } + + if (tsmf_stream_process_ack(stream, FALSE)) + continue; + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, + "tsmf_stream_ack_func reported an error"); + + DEBUG_TSMF("out %"PRIu32"", stream->stream_id); + ExitThread(error); + return error; +} + +static DWORD WINAPI tsmf_stream_playback_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_SAMPLE* sample = NULL; + TSMF_STREAM* stream = (TSMF_STREAM*) arg; + TSMF_PRESENTATION* presentation = stream->presentation; + UINT error = CHANNEL_RC_OK; + DWORD status; + DEBUG_TSMF("in %"PRIu32"", stream->stream_id); + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO && + stream->sample_rate && stream->channels && stream->bits_per_sample) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + stream->audio = tsmf_load_audio_device( + presentation->audio_name + && presentation->audio_name[0] ? presentation->audio_name : NULL, + presentation->audio_device + && presentation->audio_device[0] ? presentation->audio_device : NULL); + + if (stream->audio) + { + stream->audio->SetFormat(stream->audio, stream->sample_rate, stream->channels, + stream->bits_per_sample); + } + } + } + } + + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_list); + + while (1) + { + status = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", error); + break; + } + + status = WaitForSingleObject(stream->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + sample = tsmf_stream_pop_sample(stream, 0); + + if (sample && !tsmf_sample_playback(sample)) + { + WLog_ERR(TAG, "error playing sample"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (stream->audio) + { + stream->audio->Free(stream->audio); + stream->audio = NULL; + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, + "tsmf_stream_playback_func reported an error"); + + DEBUG_TSMF("out %"PRIu32"", stream->stream_id); + ExitThread(error); + return error; +} + +static BOOL tsmf_stream_start(TSMF_STREAM* stream) +{ + if (!stream || !stream->presentation || !stream->decoder + || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, NULL); +} + +static BOOL tsmf_stream_stop(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + /* If stopping after eos - we delay until the eos has been processed + * this allows us to process any buffers that have been acked even though + * they have not actually been completely processes by the decoder + */ + if (stream->eos) + { + DEBUG_TSMF("Setting up a delayed stop for once the eos has been processed."); + stream->delayed_stop = 1; + return TRUE; + } + /* Otherwise force stop immediately */ + else + { + DEBUG_TSMF("Stop with no pending eos response, so do it immediately."); + tsmf_stream_flush(stream); + return stream->decoder->Control(stream->decoder, Control_Stop, NULL); + } +} + +static BOOL tsmf_stream_pause(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + return stream->decoder->Control(stream->decoder, Control_Pause, NULL); +} + +static BOOL tsmf_stream_restart(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, NULL); +} + +static BOOL tsmf_stream_change_volume(TSMF_STREAM* stream, UINT32 newVolume, + UINT32 muted) +{ + if (!stream || !stream->decoder) + return TRUE; + + if (stream->decoder != NULL && stream->decoder->ChangeVolume) + { + return stream->decoder->ChangeVolume(stream->decoder, newVolume, muted); + } + else if (stream->audio != NULL && stream->audio->ChangeVolume) + { + return stream->audio->ChangeVolume(stream->audio, newVolume, muted); + } + + return TRUE; +} + +BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, + UINT32 newVolume, UINT32 muted) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + BOOL ret = TRUE; + presentation->volume = newVolume; + presentation->muted = muted; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_change_volume(stream, newVolume, muted); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_pause(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_restart(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_start(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation) +{ + UINT32 index; + UINT32 count; + UINT error; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + TSMF_STREAM* stream = (TSMF_STREAM*) ArrayList_GetItem( + presentation->stream_list, index); + + if (WaitForSingleObject(stream->ready, 500) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + } + + ArrayList_Unlock(presentation->stream_list); + return CHANNEL_RC_OK; +} + +BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_stop(stream); + } + + ArrayList_Unlock(presentation->stream_list); + presentation->audio_start_time = 0; + presentation->audio_end_time = 0; + return ret; +} + +BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, + UINT32 x, UINT32 y, UINT32 width, UINT32 height, int num_rects, RDP_RECT* rects) +{ + UINT32 index; + UINT32 count; + TSMF_STREAM* stream; + void* tmp_rects = NULL; + BOOL ret = TRUE; + + /* The server may send messages with invalid width / height. + * Ignore those messages. */ + if (!width || !height) + return TRUE; + + /* Streams can be added/removed from the presentation and the server will resend geometry info when a new stream is + * added to the presentation. Also, num_rects is used to indicate whether or not the window is visible. + * So, always process a valid message with unchanged position/size and/or no visibility rects. + */ + presentation->x = x; + presentation->y = y; + presentation->width = width; + presentation->height = height; + tmp_rects = realloc(presentation->rects, sizeof(RDP_RECT) * num_rects); + + if (!tmp_rects && num_rects) + return FALSE; + + presentation->nr_rects = num_rects; + presentation->rects = tmp_rects; + CopyMemory(presentation->rects, rects, sizeof(RDP_RECT) * num_rects); + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + + if (!stream->decoder) + continue; + + if (stream->decoder->UpdateRenderingArea) + { + ret = stream->decoder->UpdateRenderingArea(stream->decoder, x, y, width, height, + num_rects, rects); + } + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, + const char* name, const char* device) +{ + presentation->audio_name = name; + presentation->audio_device = device; +} + +BOOL tsmf_stream_flush(TSMF_STREAM* stream) +{ + BOOL ret = TRUE; + + //TSMF_SAMPLE* sample; + /* TODO: free lists */ + if (stream->audio) + ret = stream->audio->Flush(stream->audio); + + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = NULL; + stream->delayed_stop = 0; + stream->last_end_time = 0; + stream->next_start_time = 0; + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + stream->presentation->audio_start_time = 0; + stream->presentation->audio_end_time = 0; + } + + return TRUE; +} + +void _tsmf_presentation_free(void* obj) +{ + TSMF_PRESENTATION* presentation = (TSMF_PRESENTATION*)obj; + + if (presentation) + { + tsmf_presentation_stop(presentation); + ArrayList_Clear(presentation->stream_list); + ArrayList_Free(presentation->stream_list); + free(presentation->rects); + ZeroMemory(presentation, sizeof(TSMF_PRESENTATION)); + free(presentation); + } +} + +void tsmf_presentation_free(TSMF_PRESENTATION* presentation) +{ + ArrayList_Remove(presentation_list, presentation); +} + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, + rdpContext* rdpcontext) +{ + TSMF_STREAM* stream; + stream = tsmf_stream_find_by_id(presentation, stream_id); + + if (stream) + { + WLog_ERR(TAG, "duplicated stream id %"PRIu32"!", stream_id); + return NULL; + } + + stream = (TSMF_STREAM*) calloc(1, sizeof(TSMF_STREAM)); + + if (!stream) + { + WLog_ERR(TAG, "Calloc failed"); + return NULL; + } + + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + stream->currentBufferLevel = 1; + stream->seeking = FALSE; + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = NULL; + stream->stream_id = stream_id; + stream->presentation = presentation; + stream->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!stream->stopEvent) + goto error_stopEvent; + + stream->ready = CreateEvent(NULL, TRUE, TRUE, NULL); + + if (!stream->ready) + goto error_ready; + + stream->sample_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_list) + goto error_sample_list; + + stream->sample_list->object.fnObjectFree = tsmf_sample_free; + stream->sample_ack_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_ack_list) + goto error_sample_ack_list; + + stream->sample_ack_list->object.fnObjectFree = tsmf_sample_free; + stream->play_thread = CreateThread(NULL, 0, tsmf_stream_playback_func, + stream, CREATE_SUSPENDED, NULL); + + if (!stream->play_thread) + goto error_play_thread; + + stream->ack_thread = CreateThread(NULL, 0, tsmf_stream_ack_func, stream, + CREATE_SUSPENDED, NULL); + + if (!stream->ack_thread) + goto error_ack_thread; + + if (ArrayList_Add(presentation->stream_list, stream) < 0) + goto error_add; + + stream->rdpcontext = rdpcontext; + return stream; +error_add: + SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + +error_ack_thread: + SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + +error_play_thread: + Queue_Free(stream->sample_ack_list); +error_sample_ack_list: + Queue_Free(stream->sample_list); +error_sample_list: + CloseHandle(stream->ready); +error_ready: + CloseHandle(stream->stopEvent); +error_stopEvent: + free(stream); + return NULL; +} + +void tsmf_stream_start_threads(TSMF_STREAM* stream) +{ + ResumeThread(stream->play_thread); + ResumeThread(stream->ack_thread); +} + +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, + UINT32 stream_id) +{ + UINT32 index; + UINT32 count; + BOOL found = FALSE; + TSMF_STREAM* stream; + ArrayList_Lock(presentation->stream_list); + count = ArrayList_Count(presentation->stream_list); + + for (index = 0; index < count; index++) + { + stream = (TSMF_STREAM*) ArrayList_GetItem(presentation->stream_list, index); + + if (stream->stream_id == stream_id) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + return (found) ? stream : NULL; +} + +static void tsmf_stream_resync(void* arg) +{ + TSMF_STREAM* stream = arg; + ResetEvent(stream->ready); +} + +BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s) +{ + TS_AM_MEDIA_TYPE mediatype; + BOOL ret = TRUE; + + if (stream->decoder) + { + WLog_ERR(TAG, "duplicated call"); + return FALSE; + } + + if (!tsmf_codec_parse_media_type(&mediatype, s)) + { + WLog_ERR(TAG, "unable to parse media type"); + return FALSE; + } + + if (mediatype.MajorType == TSMF_MAJOR_TYPE_VIDEO) + { + DEBUG_TSMF("video width %"PRIu32" height %"PRIu32" bit_rate %"PRIu32" frame_rate %f codec_data %"PRIu32"", + mediatype.Width, mediatype.Height, mediatype.BitRate, + (double) mediatype.SamplesPerSecond.Numerator / (double) + mediatype.SamplesPerSecond.Denominator, + mediatype.ExtraDataSize); + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + } + else if (mediatype.MajorType == TSMF_MAJOR_TYPE_AUDIO) + { + DEBUG_TSMF("audio channel %"PRIu32" sample_rate %"PRIu32" bits_per_sample %"PRIu32" codec_data %"PRIu32"", + mediatype.Channels, mediatype.SamplesPerSecond.Numerator, + mediatype.BitsPerSample, + mediatype.ExtraDataSize); + stream->sample_rate = mediatype.SamplesPerSecond.Numerator; + stream->channels = mediatype.Channels; + stream->bits_per_sample = mediatype.BitsPerSample; + + if (stream->bits_per_sample == 0) + stream->bits_per_sample = 16; + + stream->minBufferLevel = AUDIO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = AUDIO_MAX_BUFFER_LEVEL; + } + + stream->major_type = mediatype.MajorType; + stream->width = mediatype.Width; + stream->height = mediatype.Height; + stream->decoder = tsmf_load_decoder(name, &mediatype); + ret &= tsmf_stream_change_volume(stream, stream->presentation->volume, + stream->presentation->muted); + + if (!stream->decoder) + return FALSE; + + if (stream->decoder->SetAckFunc) + ret &= stream->decoder->SetAckFunc(stream->decoder, tsmf_stream_process_ack, + stream); + + if (stream->decoder->SetSyncFunc) + ret &= stream->decoder->SetSyncFunc(stream->decoder, tsmf_stream_resync, + stream); + + return ret; +} + +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback) +{ + if (!stream) + return; + + stream->eos = 1; + stream->eos_message_id = message_id; + stream->eos_channel_callback = pChannelCallback; +} + +void _tsmf_stream_free(void* obj) +{ + TSMF_STREAM* stream = (TSMF_STREAM*)obj; + + if (!stream) + return; + + tsmf_stream_stop(stream); + SetEvent(stream->stopEvent); + + if (stream->play_thread) + { + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + return; + } + + CloseHandle(stream->play_thread); + stream->play_thread = NULL; + } + + if (stream->ack_thread) + { + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", GetLastError()); + return; + } + + CloseHandle(stream->ack_thread); + stream->ack_thread = NULL; + } + + Queue_Free(stream->sample_list); + Queue_Free(stream->sample_ack_list); + + if (stream->decoder && stream->decoder->Free) + { + stream->decoder->Free(stream->decoder); + stream->decoder = NULL; + } + + CloseHandle(stream->stopEvent); + CloseHandle(stream->ready); + ZeroMemory(stream, sizeof(TSMF_STREAM)); + free(stream); +} + +void tsmf_stream_free(TSMF_STREAM* stream) +{ + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Remove(presentation->stream_list, stream); +} + +BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, + IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, + UINT32 extensions, + UINT32 data_size, BYTE* data) +{ + TSMF_SAMPLE* sample; + SetEvent(stream->ready); + + if (TERMINATING) + return TRUE; + + sample = (TSMF_SAMPLE*) calloc(1, sizeof(TSMF_SAMPLE)); + + if (!sample) + { + WLog_ERR(TAG, "calloc sample failed!"); + return FALSE; + } + + sample->sample_id = sample_id; + sample->start_time = start_time; + sample->end_time = end_time; + sample->duration = duration; + sample->extensions = extensions; + + if ((sample->extensions & 0x00000080) || (sample->extensions & 0x00000040)) + sample->invalidTimestamps = TRUE; + else + sample->invalidTimestamps = FALSE; + + sample->stream = stream; + sample->channel_callback = pChannelCallback; + sample->data_size = data_size; + sample->data = calloc(1, data_size + TSMF_BUFFER_PADDING_SIZE); + + if (!sample->data) + { + WLog_ERR(TAG, "calloc sample->data failed!"); + free(sample); + return FALSE; + } + + CopyMemory(sample->data, data, data_size); + return Queue_Enqueue(stream->sample_list, sample); +} + +#ifndef _WIN32 + +static void tsmf_signal_handler(int s) +{ + TERMINATING = 1; + ArrayList_Free(presentation_list); + + if (s == SIGINT) + { + signal(s, SIG_DFL); + kill(getpid(), s); + } + else if (s == SIGUSR1) + { + signal(s, SIG_DFL); + } +} + +#endif + +BOOL tsmf_media_init(void) +{ +#ifndef _WIN32 + struct sigaction sigtrap; + sigtrap.sa_handler = tsmf_signal_handler; + sigemptyset(&sigtrap.sa_mask); + sigtrap.sa_flags = 0; + sigaction(SIGINT, &sigtrap, 0); + sigaction(SIGUSR1, &sigtrap, 0); +#endif + + if (!presentation_list) + { + presentation_list = ArrayList_New(TRUE); + + if (!presentation_list) + return FALSE; + + ArrayList_Object(presentation_list)->fnObjectFree = _tsmf_presentation_free; + } + + return TRUE; +} diff --git a/channels/tsmf/client/tsmf_media.h b/channels/tsmf/client/tsmf_media.h new file mode 100644 index 0000000..3ab21dd --- /dev/null +++ b/channels/tsmf/client/tsmf_media.h @@ -0,0 +1,73 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The media container maintains a global list of presentations, and a list of + * streams in each presentation. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H + +#include + +typedef struct _TSMF_PRESENTATION TSMF_PRESENTATION; + +typedef struct _TSMF_STREAM TSMF_STREAM; + +typedef struct _TSMF_SAMPLE TSMF_SAMPLE; + +TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid, + IWTSVirtualChannelCallback* pChannelCallback); +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid); +BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation); +UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation); +BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, + UINT32 muted); +BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, + UINT32 x, UINT32 y, UINT32 width, UINT32 height, + int num_rects, RDP_RECT* rects); +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, + const char* name, const char* device); +void tsmf_presentation_free(TSMF_PRESENTATION* presentation); + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, + rdpContext* rdpcontext); +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id); +BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s); +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback); +void tsmf_stream_free(TSMF_STREAM* stream); +BOOL tsmf_stream_flush(TSMF_STREAM* stream); + +BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, UINT32 extensions, + UINT32 data_size, BYTE* data); + +BOOL tsmf_media_init(void); +void tsmf_stream_start_threads(TSMF_STREAM* stream); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H */ + diff --git a/channels/tsmf/client/tsmf_types.h b/channels/tsmf/client/tsmf_types.h new file mode 100644 index 0000000..a6833f0 --- /dev/null +++ b/channels/tsmf/client/tsmf_types.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Types + * + * 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_CHANNEL_TSMF_CLIENT_TYPES_H +#define FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#define TAG CHANNELS_TAG("tsmf.client") + +#ifdef WITH_DEBUG_TSMF +#define DEBUG_TSMF(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_TSMF(...) do { } while (0) +#endif + +typedef struct _TS_AM_MEDIA_TYPE +{ + int MajorType; + int SubType; + int FormatType; + + UINT32 Width; + UINT32 Height; + UINT32 BitRate; + struct + { + UINT32 Numerator; + UINT32 Denominator; + } SamplesPerSecond; + UINT32 Channels; + UINT32 BitsPerSample; + UINT32 BlockAlign; + const BYTE* ExtraData; + UINT32 ExtraDataSize; +} TS_AM_MEDIA_TYPE; + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H */ + diff --git a/channels/urbdrc/CMakeLists.txt b/channels/urbdrc/CMakeLists.txt new file mode 100644 index 0000000..c7e546f --- /dev/null +++ b/channels/urbdrc/CMakeLists.txt @@ -0,0 +1,54 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("urbdrc") + +if(NOT WIN32) + find_package(DevD) + find_package(UDev) + find_package(UUID) + find_package(DbusGlib) + find_package(libusb-1.0) +endif() + +if(DEVD_FOUND OR UDEV_FOUND) + include_directories(${UDEV_INCLUDE_DIR}) + if(UUID_FOUND AND DBUS_GLIB_FOUND AND LIBUSB_1_FOUND) + include_directories(${UUID_INCLUDE_DIRS}) + include_directories(${LIBUSB_1_INCLUDE_DIRS}) + include_directories(${DBUS_GLIB_INCLUDE_DIRS}) + + set(URBDRC_DEPENDENCIES_FOUND TRUE) + message(STATUS "Found all URBDRC dependencies") + else() + if(NOT UUID_FOUND) + message(STATUS "URBDRC dependencie not found: UUID") + endif() + if(NOT DBUS_GLIB_FOUND) + message(STATUS "URBDRC dependencie not found: DBUS_GLIB") + endif() + if(NOT LIBUSB_1_FOUND) + message(STATUS "URBDRC dependencie not found: LIBUSB_1") + endif() + endif() +endif() + +if(WITH_CLIENT_CHANNELS) + if(URBDRC_DEPENDENCIES_FOUND) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) + endif() +endif() diff --git a/channels/urbdrc/ChannelOptions.cmake b/channels/urbdrc/ChannelOptions.cmake new file mode 100644 index 0000000..ff00af9 --- /dev/null +++ b/channels/urbdrc/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "urbdrc" TYPE "dynamic" + DESCRIPTION "USB Devices Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEUSB]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/urbdrc/client/CMakeLists.txt b/channels/urbdrc/client/CMakeLists.txt new file mode 100644 index 0000000..24c7ee9 --- /dev/null +++ b/channels/urbdrc/client/CMakeLists.txt @@ -0,0 +1,56 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Atrust corp. +# Copyright 2012 Alfred Liu +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("urbdrc") + +set(${MODULE_PREFIX}_SRCS + searchman.c + searchman.h + isoch_queue.c + isoch_queue.h + data_transfer.c + data_transfer.h + urbdrc_main.c + urbdrc_main.h + urbdrc_types.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +#set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} libusb-devman) + +set(${MODULE_PREFIX}_LIBS) +if (UDEV_FOUND AND UDEV_LIBRARIES) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${UDEV_LIBRARIES}) +endif() + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +target_link_libraries(${MODULE_NAME} ${PRIVATE_KEYWOARD} ${${MODULE_PREFIX}_LIBS}) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + +# libusb subsystem +add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "libusb" "") diff --git a/channels/urbdrc/client/data_transfer.c b/channels/urbdrc/client/data_transfer.c new file mode 100644 index 0000000..47ab753 --- /dev/null +++ b/channels/urbdrc/client/data_transfer.c @@ -0,0 +1,2451 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include + +#include +#include +#include + +#include "urbdrc_types.h" +#include "data_transfer.h" + +static void usb_process_get_port_status(IUDEVICE* pdev, BYTE* OutputBuffer) +{ + int bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB); + + switch (bcdUSB) + { + case USB_v1_0: + data_write_UINT32(OutputBuffer, 0x303); + break; + + case USB_v1_1: + data_write_UINT32(OutputBuffer, 0x103); + break; + + case USB_v2_0: + data_write_UINT32(OutputBuffer, 0x503); + break; + + default: + data_write_UINT32(OutputBuffer, 0x503); + break; + } +} + +#if ISOCH_FIFO + +static int func_check_isochronous_fds(IUDEVICE* pdev) +{ + int ret = 0; + BYTE* data_temp; + UINT32 size_temp, process_times = 2; + ISOCH_CALLBACK_QUEUE* isoch_queue = NULL; + ISOCH_CALLBACK_DATA* isoch = NULL; + URBDRC_CHANNEL_CALLBACK* callback; + + isoch_queue = (ISOCH_CALLBACK_QUEUE*) pdev->get_isoch_queue(pdev); + + while (process_times) + { + process_times--; + + if (isoch_queue == NULL || !pdev) + return -1; + + pthread_mutex_lock(&isoch_queue->isoch_loading); + + if (isoch_queue->head == NULL) + { + pthread_mutex_unlock(&isoch_queue->isoch_loading); + continue; + } + else + { + isoch = isoch_queue->head; + } + + if (!isoch || !isoch->out_data) + { + pthread_mutex_unlock(&isoch_queue->isoch_loading); + continue; + } + else + { + callback = (URBDRC_CHANNEL_CALLBACK*) isoch->callback; + size_temp = isoch->out_size; + data_temp = isoch->out_data; + + ret = isoch_queue->unregister_data(isoch_queue, isoch); + + if (!ret) + WLog_DBG(TAG, "isoch_queue_unregister_data: Not found isoch data!!"); + + pthread_mutex_unlock(&isoch_queue->isoch_loading); + + if (pdev && !pdev->isSigToEnd(pdev)) + { + callback->channel->Write(callback->channel, size_temp, data_temp, NULL); + zfree(data_temp); + } + } + } + + return 0; +} + +#endif + +static int urbdrc_process_register_request_callback(URBDRC_CHANNEL_CALLBACK* callback, + BYTE* data, UINT32 data_sizem, IUDEVMAN* udevman, UINT32 UsbDevice) +{ + IUDEVICE* pdev; + UINT32 NumRequestCompletion = 0; + UINT32 RequestCompletion = 0; + + WLog_DBG(TAG, "urbdrc_process_register_request_callback"); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + if (data_sizem >= 8) + { + data_read_UINT32(data + 0, NumRequestCompletion); /** must be 1 */ + /** RequestCompletion: + * unique Request Completion interface for the client to use */ + data_read_UINT32(data + 4, RequestCompletion); + pdev->set_ReqCompletion(pdev, RequestCompletion); + } + else /** Unregister the device */ + { + data_read_UINT32(data + 0, RequestCompletion); + + if (1)//(pdev->get_ReqCompletion(pdev) == RequestCompletion) + { + /** The wrong driver may also receive this message, So we + * need some time(default 3s) to check the driver or delete + * it */ + sleep(3); + callback->channel->Write(callback->channel, 0, NULL, NULL); + pdev->SigToEnd(pdev); + } + } + + return 0; +} + +static int urbdrc_process_cancel_request(BYTE* data, UINT32 data_sizem, IUDEVMAN* udevman, UINT32 UsbDevice) +{ + IUDEVICE* pdev; + UINT32 CancelId; + int error = 0; + + data_read_UINT32(data + 0, CancelId); /** RequestId */ + + WLog_DBG(TAG, "urbdrc_process_cancel_request: id 0x%"PRIx32"", CancelId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + error = pdev->cancel_transfer_request(pdev, CancelId); + + return error; +} + +static int urbdrc_process_retract_device_request(BYTE* data, UINT32 data_sizem, IUDEVMAN* udevman, UINT32 UsbDevice) +{ + UINT32 Reason; + WLog_DBG(TAG, "urbdrc_process_retract_device_request"); + + data_read_UINT32(data + 0, Reason); /** Reason */ + + switch (Reason) + { + case UsbRetractReason_BlockedByPolicy: + WLog_DBG(TAG, "UsbRetractReason_BlockedByPolicy: now it is not support"); + return -1; + break; + + default: + WLog_DBG(TAG, "urbdrc_process_retract_device_request: Unknown Reason %"PRIu32"", Reason); + return -1; + break; + } + + return 0; +} + +static int urbdrc_process_io_control(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN * udevman, UINT32 UsbDevice) +{ + IUDEVICE* pdev; + UINT32 out_size; + UINT32 InterfaceId; + UINT32 IoControlCode; + UINT32 InputBufferSize; + UINT32 OutputBufferSize; + UINT32 RequestId; + UINT32 usbd_status = USBD_STATUS_SUCCESS; + BYTE* OutputBuffer; + BYTE* out_data; + int i, offset, success = 0; + + WLog_DBG(TAG, "urbdrc_process__io_control"); + + data_read_UINT32(data + 0, IoControlCode); + data_read_UINT32(data + 4, InputBufferSize); + data_read_UINT32(data + 8 + InputBufferSize, OutputBufferSize); + data_read_UINT32(data + 12 + InputBufferSize, RequestId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + /** process */ + OutputBuffer = (BYTE *)calloc(1, OutputBufferSize); + if (!OutputBuffer) + return ERROR_OUTOFMEMORY; + + switch (IoControlCode) + { + case IOCTL_INTERNAL_USB_SUBMIT_URB: /** 0x00220003 */ + WLog_DBG(TAG, "ioctl: IOCTL_INTERNAL_USB_SUBMIT_URB"); + WLog_ERR(TAG, " Function IOCTL_INTERNAL_USB_SUBMIT_URB: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_RESET_PORT: /** 0x00220007 */ + WLog_DBG(TAG, "ioctl: IOCTL_INTERNAL_USB_RESET_PORT"); + break; + + case IOCTL_INTERNAL_USB_GET_PORT_STATUS: /** 0x00220013 */ + WLog_DBG(TAG, "ioctl: IOCTL_INTERNAL_USB_GET_PORT_STATUS"); + + success = pdev->query_device_port_status(pdev, &usbd_status, &OutputBufferSize, OutputBuffer); + + if (success) + { + if (pdev->isExist(pdev) == 0) + { + data_write_UINT32(OutputBuffer, 0); + } + else + { + usb_process_get_port_status(pdev, OutputBuffer); + OutputBufferSize = 4; + } + + WLog_DBG(TAG, "PORT STATUS(fake!):0x%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"", + OutputBuffer[3], OutputBuffer[2], OutputBuffer[1], OutputBuffer[0]); + } + + break; + + case IOCTL_INTERNAL_USB_CYCLE_PORT: /** 0x0022001F */ + WLog_DBG(TAG, "ioctl: IOCTL_INTERNAL_USB_CYCLE_PORT"); + WLog_ERR(TAG, " Function IOCTL_INTERNAL_USB_CYCLE_PORT: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: /** 0x00220027 */ + WLog_DBG(TAG, "ioctl: IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION"); + WLog_ERR(TAG, " Function IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: Unchecked"); + break; + + default: + WLog_DBG(TAG, "urbdrc_process_io_control: unknown IoControlCode 0x%"PRIX32"", IoControlCode); + zfree(OutputBuffer); + return ERROR_INVALID_OPERATION; + break; + } + + offset = 28; + out_size = offset + OutputBufferSize; + out_data = (BYTE *) calloc(1, out_size); + if (!out_data) + { + zfree(OutputBuffer); + return ERROR_OUTOFMEMORY; + } + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + data_write_UINT32(out_data + 8, IOCONTROL_COMPLETION); /** function id */ + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, USBD_STATUS_SUCCESS); /** HResult */ + data_write_UINT32(out_data + 20, OutputBufferSize); /** Information */ + data_write_UINT32(out_data + 24, OutputBufferSize); /** OutputBufferSize */ + + for (i = 0; i < OutputBufferSize; i++) + { + data_write_BYTE(out_data + offset, OutputBuffer[i]); /** OutputBuffer */ + offset += 1; + } + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + zfree(OutputBuffer); + + return CHANNEL_RC_OK; +} + +static int urbdrc_process_internal_io_control(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice) +{ + IUDEVICE* pdev; + BYTE* out_data; + UINT32 out_size, IoControlCode, InterfaceId, InputBufferSize; + UINT32 OutputBufferSize, RequestId, frames; + + data_read_UINT32(data + 0, IoControlCode); + + WLog_DBG(TAG, "urbdrc_process_internal_io_control:0x%"PRIx32"", IoControlCode); + + data_read_UINT32(data + 4, InputBufferSize); + data_read_UINT32(data + 8, OutputBufferSize); + data_read_UINT32(data + 12, RequestId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + /** Fixme: Currently this is a FALSE bustime... */ + urbdrc_get_mstime(frames); + + out_size = 32; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + data_write_UINT32(out_data + 8, IOCONTROL_COMPLETION); /** function id */ + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0); /** HResult */ + data_write_UINT32(out_data + 20, 4); /** Information */ + data_write_UINT32(out_data + 24, 4); /** OutputBufferSize */ + data_write_UINT32(out_data + 28, frames); /** OutputBuffer */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static int urbdrc_process_query_device_text(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice) +{ + IUDEVICE* pdev; + UINT32 out_size; + UINT32 InterfaceId; + UINT32 TextType; + UINT32 LocaleId; + UINT32 bufferSize = 1024; + BYTE* out_data; + BYTE DeviceDescription[bufferSize]; + int out_offset; + + WLog_DBG(TAG, "urbdrc_process_query_device_text"); + + data_read_UINT32(data + 0, TextType); + data_read_UINT32(data + 4, LocaleId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + pdev->control_query_device_text(pdev, TextType, LocaleId, &bufferSize, DeviceDescription); + + InterfaceId = ((STREAM_ID_STUB << 30) | UsbDevice); + + out_offset = 16; + out_size = out_offset + bufferSize; + + if (bufferSize != 0) + out_size += 2; + + out_data = (BYTE*) malloc(out_size); + memset(out_data, 0, out_size); + + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if (bufferSize != 0) + { + data_write_UINT32(out_data + 8, (bufferSize/2)+1); /** cchDeviceDescription */ + out_offset = 12; + memcpy(out_data + out_offset, DeviceDescription, bufferSize); + out_offset += bufferSize; + data_write_UINT16(out_data + out_offset, 0x0000); + out_offset += 2; + } + else + { + data_write_UINT32(out_data + 8, 0); /** cchDeviceDescription */ + out_offset = 12; + } + + data_write_UINT32(out_data + out_offset, 0); /** HResult */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static void func_select_all_interface_for_msconfig(IUDEVICE* pdev, MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + int inum; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + BYTE InterfaceNumber, AlternateSetting; + UINT32 NumInterfaces = MsConfig->NumInterfaces; + + for (inum = 0; inum < NumInterfaces; inum++) + { + InterfaceNumber = MsInterfaces[inum]->InterfaceNumber; + AlternateSetting = MsInterfaces[inum]->AlternateSetting; + pdev->select_interface(pdev, InterfaceNumber, AlternateSetting); + } +} + +static int urb_select_configuration(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice, int transferDir) +{ + MSUSB_CONFIG_DESCRIPTOR * MsConfig = NULL; + IUDEVICE* pdev = NULL; + UINT32 out_size, InterfaceId, RequestId, NumInterfaces, usbd_status = 0; + BYTE ConfigurationDescriptorIsValid; + BYTE* out_data; + int MsOutSize = 0, offset = 0; + + if (transferDir == 0) + { + WLog_ERR(TAG, "urb_select_configuration: not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + data_read_UINT32(data + 0, RequestId); + data_read_BYTE(data + 4, ConfigurationDescriptorIsValid); + data_read_UINT32(data + 8, NumInterfaces); + offset = 12; + + /** if ConfigurationDescriptorIsValid is zero, then just do nothing.*/ + if (ConfigurationDescriptorIsValid) + { + /* parser data for struct config */ + MsConfig = msusb_msconfig_read(data + offset, data_sizem - offset, NumInterfaces); + /* select config */ + pdev->select_configuration(pdev, MsConfig->bConfigurationValue); + /* select all interface */ + func_select_all_interface_for_msconfig(pdev, MsConfig); + /* complete configuration setup */ + MsConfig = pdev->complete_msconfig_setup(pdev, MsConfig); + } + + if (MsConfig) + MsOutSize = MsConfig->MsOutSize; + + if (MsOutSize > 0) + out_size = 36 + MsOutSize; + else + out_size = 44; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); /** function id */ + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + if (MsOutSize > 0) + { + /** CbTsUrbResult */ + data_write_UINT32(out_data + 16, 8 + MsOutSize); + /** TS_URB_RESULT_HEADER Size*/ + data_write_UINT16(out_data + 20, 8 + MsOutSize); + } + else + { + data_write_UINT32(out_data + 16, 16); + data_write_UINT16(out_data + 20, 16); + } + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_SELECT_CONFIGURATION); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + offset = 28; + /** TS_URB_SELECT_CONFIGURATION_RESULT */ + if (MsOutSize > 0) + { + msusb_msconfig_write(MsConfig, out_data, &offset); + } + else + { + data_write_UINT32(out_data + offset, 0); /** ConfigurationHandle */ + data_write_UINT32(out_data + offset + 4, NumInterfaces); /** NumInterfaces */ + offset += 8; + } + data_write_UINT32(out_data + offset, 0); /** HResult */ + data_write_UINT32(out_data + offset + 4, 0); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + return 0; +} + +static int urb_select_interface(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, UINT32 data_sizem, + UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice, int transferDir) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + IUDEVICE* pdev; + UINT32 out_size, InterfaceId, RequestId, ConfigurationHandle; + UINT32 OutputBufferSize; + BYTE InterfaceNumber; + BYTE* out_data; + int out_offset, interface_size; + + if (transferDir == 0) + { + WLog_ERR(TAG, "urb_select_interface: not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, ConfigurationHandle); + out_offset = 8; + + MsInterface = msusb_msinterface_read(data + out_offset, data_sizem - out_offset, &out_offset); + + data_read_UINT32(data + out_offset, OutputBufferSize); + + pdev->select_interface(pdev, MsInterface->InterfaceNumber, MsInterface->AlternateSetting); + + /* replace device's MsInterface */ + MsConfig = pdev->get_MsConfig(pdev); + InterfaceNumber = MsInterface->InterfaceNumber; + msusb_msinterface_replace(MsConfig, InterfaceNumber, MsInterface); + + /* complete configuration setup */ + MsConfig = pdev->complete_msconfig_setup(pdev, MsConfig); + MsInterface = MsConfig->MsInterfaces[InterfaceNumber]; + interface_size = 16 + (MsInterface->NumberOfPipes * 20); + + out_size = 36 + interface_size ; + out_data = (BYTE*) malloc(out_size); + memset(out_data, 0, out_size); + + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); /** function id */ + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 8 + interface_size); /** CbTsUrbResult */ + /** TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 8 + interface_size); /** Size */ + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_SELECT_INTERFACE); + data_write_UINT32(out_data + 24, USBD_STATUS_SUCCESS); /** UsbdStatus */ + out_offset = 28; + + /** TS_URB_SELECT_INTERFACE_RESULT */ + msusb_msinterface_write(MsInterface, out_data + out_offset, &out_offset); + + data_write_UINT32(out_data + out_offset, 0); /** HResult */ + data_write_UINT32(out_data + out_offset + 4, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static int urb_control_transfer(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice, int transferDir, int External) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, EndpointAddress, PipeHandle; + UINT32 TransferFlags, OutputBufferSize, usbd_status, Timeout; + BYTE bmRequestType, Request; + UINT16 Value, Index, length; + BYTE* buffer; + BYTE* out_data; + int offset, ret; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, PipeHandle); + data_read_UINT32(data + 8, TransferFlags); /** TransferFlags */ + + EndpointAddress = (PipeHandle & 0x000000ff); + offset = 12; + Timeout = 2000; + + switch (External) + { + case URB_CONTROL_TRANSFER_EXTERNAL: + data_read_UINT32(data + offset, Timeout); /** TransferFlags */ + offset += 4; + break; + case URB_CONTROL_TRANSFER_NONEXTERNAL: + break; + } + + /** SetupPacket 8 bytes */ + data_read_BYTE(data + offset, bmRequestType); + data_read_BYTE(data + offset + 1, Request); + data_read_UINT16(data + offset + 2, Value); + data_read_UINT16(data + offset + 4, Index); + data_read_UINT16(data + offset + 6, length); + data_read_UINT32(data + offset + 8, OutputBufferSize); + offset += 12; + + if (length != OutputBufferSize) + { + WLog_ERR(TAG, "urb_control_transfer ERROR: buf != length"); + return -1; + } + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + /** Get Buffer Data */ + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + memcpy(buffer, data + offset, OutputBufferSize); + + /** process URB_FUNCTION_CONTROL_TRANSFER */ + ret = pdev->control_transfer( + pdev, RequestId, EndpointAddress, TransferFlags, + bmRequestType, + Request, + Value, + Index, + &usbd_status, + &OutputBufferSize, + buffer, + Timeout); + + if (ret < 0){ + WLog_DBG(TAG, "control_transfer: error num %d!!", ret); + OutputBufferSize = 0; + } + + /** send data */ + offset = 36; + if (transferDir == USBD_TRANSFER_DIRECTION_IN) + out_size = offset + OutputBufferSize; + else + out_size = offset; + + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if(transferDir == USBD_TRANSFER_DIRECTION_IN && OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_CONTROL_TRANSFER); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static int urb_bulk_or_interrupt_transfer(URBDRC_CHANNEL_CALLBACK* callback, BYTE* data, + UINT32 data_sizem, UINT32 MessageId, IUDEVMAN* udevman, UINT32 UsbDevice, int transferDir) +{ + int offset; + BYTE* Buffer; + IUDEVICE* pdev; + BYTE* out_data; + UINT32 out_size, RequestId, InterfaceId, EndpointAddress, PipeHandle; + UINT32 TransferFlags, OutputBufferSize, usbd_status = 0; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, PipeHandle); + data_read_UINT32(data + 8, TransferFlags); /** TransferFlags */ + data_read_UINT32(data + 12, OutputBufferSize); + offset = 16; + EndpointAddress = (PipeHandle & 0x000000ff); + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + out_size = 36; + else + out_size = 36 + OutputBufferSize; + + Buffer = NULL; + out_data = (BYTE*) malloc(out_size); + memset(out_data, 0, out_size); + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + Buffer = data + offset; + break; + + case USBD_TRANSFER_DIRECTION_IN: + Buffer = out_data + 36; + break; + } + + /** process URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER */ + pdev->bulk_or_interrupt_transfer( + pdev, RequestId, EndpointAddress, + TransferFlags, + &usbd_status, + &OutputBufferSize, + Buffer, + 10000); + + offset = 36; + if (transferDir == USBD_TRANSFER_DIRECTION_IN) + out_size = offset + OutputBufferSize; + else + out_size = offset; + /** send data */ + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + if(transferDir == USBD_TRANSFER_DIRECTION_IN && OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (pdev && !pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + + +static int urb_isoch_transfer(URBDRC_CHANNEL_CALLBACK * callback, BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE * pdev; + UINT32 RequestId, InterfaceId, EndpointAddress; + UINT32 PipeHandle, TransferFlags, StartFrame, NumberOfPackets; + UINT32 ErrorCount, OutputBufferSize, usbd_status = 0; + UINT32 RequestField, noAck = 0; + UINT32 out_size = 0; + BYTE * iso_buffer = NULL; + BYTE * iso_packets = NULL; + BYTE * out_data = NULL; + int offset, nullBuffer = 0, iso_status; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + if (pdev->isSigToEnd(pdev)) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + data_read_UINT32(data + 0, RequestField); + RequestId = RequestField & 0x7fffffff; + noAck = (RequestField & 0x80000000)>>31; + data_read_UINT32(data + 4, PipeHandle); + EndpointAddress = (PipeHandle & 0x000000ff); + data_read_UINT32(data + 8, TransferFlags); /** TransferFlags */ + data_read_UINT32(data + 12, StartFrame); /** StartFrame */ + data_read_UINT32(data + 16, NumberOfPackets); /** NumberOfPackets */ + data_read_UINT32(data + 20, ErrorCount); /** ErrorCount */ + offset = 24 + (NumberOfPackets * 12); + data_read_UINT32(data + offset, OutputBufferSize); + offset += 4; + + /** send data memory alloc */ + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) { + if (!noAck) { + out_size = 48 + (NumberOfPackets * 12); + out_data = (BYTE *) malloc(out_size); + iso_packets = out_data + 40; + } + } + else { + out_size = 48 + OutputBufferSize + (NumberOfPackets * 12); + out_data = (BYTE *) malloc(out_size); + iso_packets = out_data + 40; + } + + if (out_size) + memset(out_data, 0, out_size); + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + /** Get Buffer Data */ + //memcpy(iso_buffer, data + offset, OutputBufferSize); + iso_buffer = data + offset; + break; + case USBD_TRANSFER_DIRECTION_IN: + iso_buffer = out_data + 48 + (NumberOfPackets * 12); + break; + } + + WLog_DBG(TAG, "urb_isoch_transfer: EndpointAddress: 0x%"PRIx32", " + "TransferFlags: 0x%"PRIx32", " + "StartFrame: 0x%"PRIx32", " + "NumberOfPackets: 0x%"PRIx32", " + "OutputBufferSize: 0x%"PRIx32" " + "RequestId: 0x%"PRIx32"", + EndpointAddress, TransferFlags, StartFrame, + NumberOfPackets, OutputBufferSize, RequestId); + +#if ISOCH_FIFO + ISOCH_CALLBACK_QUEUE * isoch_queue = NULL; + ISOCH_CALLBACK_DATA * isoch = NULL; + if(!noAck) + { + isoch_queue = (ISOCH_CALLBACK_QUEUE *)pdev->get_isoch_queue(pdev); + isoch = isoch_queue->register_data(isoch_queue, callback, pdev); + } +#endif + + iso_status = pdev->isoch_transfer( + pdev, RequestId, EndpointAddress, + TransferFlags, + noAck, + &ErrorCount, + &usbd_status, + &StartFrame, + NumberOfPackets, + iso_packets, + &OutputBufferSize, + iso_buffer, + 2000); + + if(noAck) + { + zfree(out_data); + return 0; + } + + if (iso_status < 0) + nullBuffer = 1; + + + out_size = 48; + if (nullBuffer) + OutputBufferSize = 0; + else + out_size += OutputBufferSize + (NumberOfPackets * 12); + /* fill the send data */ + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + if(OutputBufferSize != 0 && !nullBuffer) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 20 + (NumberOfPackets * 12)); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 20 + (NumberOfPackets * 12)); /** Size */ + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_ISOCH_TRANSFER); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, StartFrame); /** StartFrame */ + if (!nullBuffer) + { + /** NumberOfPackets */ + data_write_UINT32(out_data + 32, NumberOfPackets); + data_write_UINT32(out_data + 36, ErrorCount); /** ErrorCount */ + offset = 40 + (NumberOfPackets * 12); + } + else + { + data_write_UINT32(out_data + 32, 0); /** NumberOfPackets */ + data_write_UINT32(out_data + 36, NumberOfPackets); /** ErrorCount */ + offset = 40; + } + + data_write_UINT32(out_data + offset, 0); /** HResult */ + data_write_UINT32(out_data + offset + 4, OutputBufferSize); /** OutputBufferSize */ + +#if ISOCH_FIFO + if(!noAck){ + pthread_mutex_lock(&isoch_queue->isoch_loading); + isoch->out_data = out_data; + isoch->out_size = out_size; + pthread_mutex_unlock(&isoch_queue->isoch_loading); + } +#else + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); +#endif + + if (nullBuffer) + return -1; + + return 0; +} + +static int urb_control_descriptor_request(URBDRC_CHANNEL_CALLBACK* callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + BYTE func_recipient, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, InterfaceId, RequestId, OutputBufferSize, usbd_status; + BYTE bmRequestType, desc_index, desc_type; + UINT16 langId; + BYTE* buffer; + BYTE* out_data; + int ret, offset; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + data_read_UINT32(data + 0, RequestId); + data_read_BYTE(data + 4, desc_index); + data_read_BYTE(data + 5, desc_type); + data_read_UINT16(data + 6, langId); + data_read_UINT32(data + 8, OutputBufferSize); + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + bmRequestType = func_recipient; + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_IN: + bmRequestType |= 0x80; + break; + case USBD_TRANSFER_DIRECTION_OUT: + bmRequestType |= 0x00; + offset = 12; + memcpy(buffer, data + offset, OutputBufferSize); + break; + default: + WLog_DBG(TAG, "get error transferDir"); + OutputBufferSize = 0; + usbd_status = USBD_STATUS_STALL_PID; + break; + } + + /** process get usb device descriptor */ + + ret = pdev->control_transfer( + pdev, RequestId, 0, 0, bmRequestType, + 0x06, /* REQUEST_GET_DESCRIPTOR */ + (desc_type << 8) | desc_index, + langId, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + + if (ret < 0) { + WLog_DBG(TAG, "get_descriptor: error num %d", ret); + OutputBufferSize = 0; + } + + offset = 36; + out_size = offset + OutputBufferSize; + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + return 0; +} + +static int urb_control_get_status_request(URBDRC_CHANNEL_CALLBACK * callback, BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + BYTE func_recipient, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize, usbd_status; + UINT16 Index; + BYTE bmRequestType; + BYTE* buffer; + BYTE* out_data; + int offset, ret; + + if (transferDir == 0){ + WLog_DBG(TAG, "urb_control_get_status_request: not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT16(data + 4, Index); /** Index */ + data_read_UINT32(data + 8, OutputBufferSize); + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + bmRequestType = func_recipient | 0x80; + + ret = pdev->control_transfer( + pdev, RequestId, 0, 0, bmRequestType, + 0x00, /* REQUEST_GET_STATUS */ + 0, + Index, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + if (ret < 0){ + WLog_DBG(TAG, "control_transfer: error num %d!!", ret); + OutputBufferSize = 0; + usbd_status = USBD_STATUS_STALL_PID; + } + else{ + usbd_status = USBD_STATUS_SUCCESS; + } + + /** send data */ + offset = 36; + if (transferDir == USBD_TRANSFER_DIRECTION_IN) + out_size = offset + OutputBufferSize; + else + out_size = offset; + + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if(transferDir == USBD_TRANSFER_DIRECTION_IN && OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + + data_write_UINT32(out_data + 12, RequestId); /** RequestId, include NoAck*/ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_VENDOR_DEVICE); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static int urb_control_vendor_or_class_request(URBDRC_CHANNEL_CALLBACK * callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + BYTE func_type, + BYTE func_recipient, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, TransferFlags, usbd_status; + UINT32 OutputBufferSize; + BYTE ReqTypeReservedBits, Request, bmRequestType; + UINT16 Value, Index, Padding; + BYTE* buffer; + BYTE* out_data; + int offset, ret; + /** control by vendor command */ + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, TransferFlags); /** TransferFlags */ + data_read_BYTE(data + 8, ReqTypeReservedBits); /** ReqTypeReservedBids */ + data_read_BYTE(data + 9, Request); /** Request */ + data_read_UINT16(data + 10, Value); /** value */ + data_read_UINT16(data + 12, Index); /** index */ + data_read_UINT16(data + 14, Padding); /** Padding */ + data_read_UINT32(data + 16, OutputBufferSize); + offset = 20; + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + /** Get Buffer */ + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + memcpy(buffer, data + offset, OutputBufferSize); + + /** vendor or class command */ + bmRequestType = func_type | func_recipient; + + if (TransferFlags & USBD_TRANSFER_DIRECTION) + bmRequestType |= 0x80; + + WLog_DBG(TAG, "urb_control_vendor_or_class_request: " + "RequestId 0x%"PRIx32" TransferFlags: 0x%"PRIx32" ReqTypeReservedBits: 0x%"PRIx8" " + "Request:0x%"PRIx8" Value: 0x%"PRIx16" Index: 0x%"PRIx16" OutputBufferSize: 0x%"PRIx32" bmRequestType: 0x%"PRIx8"!!", + RequestId, TransferFlags, ReqTypeReservedBits, Request, Value, + Index, OutputBufferSize, bmRequestType); + + ret = pdev->control_transfer( + pdev, RequestId, 0, 0, bmRequestType, + Request, + Value, + Index, + &usbd_status, + &OutputBufferSize, + buffer, + 2000); + + if (ret < 0){ + WLog_DBG(TAG, "control_transfer: error num %d!!", ret); + OutputBufferSize = 0; + usbd_status = USBD_STATUS_STALL_PID; + } + else{ + usbd_status = USBD_STATUS_SUCCESS; + } + + offset = 36; + if (transferDir == USBD_TRANSFER_DIRECTION_IN) + out_size = offset + OutputBufferSize; + else + out_size = offset; + /** send data */ + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if(transferDir == USBD_TRANSFER_DIRECTION_IN && OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + + data_write_UINT32(out_data + 12, RequestId); /** RequestId, include NoAck*/ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + data_write_UINT16(out_data + 22, URB_FUNCTION_VENDOR_DEVICE); /** Padding, MUST be ignored upon receipt */ + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + return 0; +} + + + +static int urb_os_feature_descriptor_request(URBDRC_CHANNEL_CALLBACK * callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize, usbd_status; + BYTE Recipient, InterfaceNumber, Ms_PageIndex; + UINT16 Ms_featureDescIndex; + BYTE* out_data; + BYTE* buffer; + int offset, ret; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_BYTE(data + 4, Recipient); /** Recipient */ + Recipient = (Recipient & 0x1f); /* XXX: origin: Recipient && 0x1f !? */ + data_read_BYTE(data + 5, InterfaceNumber); /** InterfaceNumber */ + data_read_BYTE(data + 6, Ms_PageIndex); /** Ms_PageIndex */ + data_read_UINT16(data + 7, Ms_featureDescIndex); /** Ms_featureDescIndex */ + data_read_UINT32(data + 12, OutputBufferSize); + offset = 16; + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + WLog_ERR(TAG, "Function urb_os_feature_descriptor_request: OUT Unchecked"); + memcpy(buffer, data + offset, OutputBufferSize); + break; + case USBD_TRANSFER_DIRECTION_IN: + break; + } + + WLog_DBG(TAG, "Ms descriptor arg: Recipient:0x%"PRIx8", " + "InterfaceNumber:0x%"PRIx8", Ms_PageIndex:0x%"PRIx8", " + "Ms_featureDescIndex:0x%"PRIx16", OutputBufferSize:0x%"PRIx32"", + Recipient, InterfaceNumber, Ms_PageIndex, + Ms_featureDescIndex, OutputBufferSize); + + /** get ms string */ + ret = pdev->os_feature_descriptor_request( + pdev, RequestId, Recipient, + InterfaceNumber, + Ms_PageIndex, + Ms_featureDescIndex, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + if (ret < 0) + WLog_DBG(TAG, "os_feature_descriptor_request: error num %d", ret); + + offset = 36; + out_size = offset + OutputBufferSize; + /** send data */ + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + if(OutputBufferSize!=0) + data_write_UINT32(out_data + 8, URB_COMPLETION); /** function id */ + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + + zfree(out_data); + + return 0; +} + +static int urb_pipe_request(URBDRC_CHANNEL_CALLBACK * callback, BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir, + int action) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, PipeHandle, EndpointAddress; + UINT32 OutputBufferSize, usbd_status = 0; + BYTE* out_data; + int out_offset, ret; + + if (transferDir == 0){ + WLog_DBG(TAG, "urb_pipe_request: not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, PipeHandle); /** PipeHandle */ + data_read_UINT32(data + 8, OutputBufferSize); + EndpointAddress = (PipeHandle & 0x000000ff); + + + switch (action){ + case PIPE_CANCEL: + WLog_DBG(TAG, "urb_pipe_request: PIPE_CANCEL 0x%"PRIx32"", EndpointAddress); + + ret = pdev->control_pipe_request( + pdev, RequestId, EndpointAddress, + &usbd_status, + PIPE_CANCEL); + + if (ret < 0) { + WLog_DBG(TAG, "PIPE SET HALT: error num %d", ret); + } + + + break; + case PIPE_RESET: + WLog_DBG(TAG, "urb_pipe_request: PIPE_RESET ep 0x%"PRIx32"", EndpointAddress); + + ret = pdev->control_pipe_request( + pdev, RequestId, EndpointAddress, + &usbd_status, + PIPE_RESET); + + if (ret < 0) + WLog_DBG(TAG, "PIPE RESET: error num %d!!", ret); + + break; + default: + WLog_DBG(TAG, "urb_pipe_request action: %d is not support!", action); + break; + } + + + /** send data */ + out_offset = 36; + out_size = out_offset + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 0x00000008); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 0x0008); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, 0); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + + return 0; +} + +static int urb_get_current_frame_number(URBDRC_CHANNEL_CALLBACK* callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize; + UINT32 dummy_frames; + BYTE* out_data; + + if (transferDir == 0){ + WLog_DBG(TAG, "urb_get_current_frame_number: not support transfer out"); + //exit(1); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, OutputBufferSize); + + /** Fixme: Need to fill actual frame number!!*/ + urbdrc_get_mstime(dummy_frames); + + out_size = 40; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 12); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 12); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_CURRENT_FRAME_NUMBER); + data_write_UINT32(out_data + 24, USBD_STATUS_SUCCESS); /** UsbdStatus */ + data_write_UINT32(out_data + 28, dummy_frames); /** FrameNumber */ + + data_write_UINT32(out_data + 32, 0); /** HResult */ + data_write_UINT32(out_data + 36, 0); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + return 0; +} + + +/* Unused function for current server */ +static int urb_control_get_configuration_request(URBDRC_CHANNEL_CALLBACK* callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize, usbd_status; + BYTE* buffer; + BYTE* out_data; + int ret, offset; + + if (transferDir == 0) + { + WLog_DBG(TAG, "urb_control_get_configuration_request:" + " not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT32(data + 4, OutputBufferSize); + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + ret = pdev->control_transfer( + pdev, RequestId, 0, 0, 0x80 | 0x00, + 0x08, /* REQUEST_GET_CONFIGURATION */ + 0, + 0, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + if (ret < 0){ + WLog_DBG(TAG, "control_transfer: error num %d", ret); + OutputBufferSize = 0; + } + + + offset = 36; + out_size = offset + OutputBufferSize; + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if (OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 8); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 8); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_CONFIGURATION); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + return 0; +} + +/* Unused function for current server */ +static int urb_control_get_interface_request(URBDRC_CHANNEL_CALLBACK* callback, + BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize, usbd_status; + UINT16 interface; + BYTE* buffer; + BYTE* out_data; + int ret, offset; + + if (transferDir == 0){ + WLog_DBG(TAG, "urb_control_get_interface_request: not support transfer out"); + return -1; + } + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT16(data + 4, interface); + data_read_UINT32(data + 8, OutputBufferSize); + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + ret = pdev->control_transfer(pdev, RequestId, 0, 0, 0x80 | 0x01, + 0x0A, /* REQUEST_GET_INTERFACE */ + 0, + interface, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + if (ret < 0){ + WLog_DBG(TAG, "control_transfer: error num %d", ret); + OutputBufferSize = 0; + } + + offset = 36; + out_size = offset + OutputBufferSize; + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if (OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 8); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 8); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_INTERFACE); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + return 0; +} + +static int urb_control_feature_request(URBDRC_CHANNEL_CALLBACK * callback, BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + BYTE func_recipient, + BYTE command, + int transferDir) +{ + IUDEVICE* pdev; + UINT32 out_size, RequestId, InterfaceId, OutputBufferSize, usbd_status; + UINT16 FeatureSelector, Index; + BYTE bmRequestType, bmRequest; + BYTE* buffer; + BYTE* out_data; + int ret, offset; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + + if (pdev == NULL) + return 0; + + InterfaceId = ((STREAM_ID_PROXY<<30) | pdev->get_ReqCompletion(pdev)); + + data_read_UINT32(data + 0, RequestId); + data_read_UINT16(data + 4, FeatureSelector); + data_read_UINT16(data + 6, Index); + data_read_UINT32(data + 8, OutputBufferSize); + offset = 12; + + out_size = 36 + OutputBufferSize; + out_data = (BYTE *) malloc(out_size); + memset(out_data, 0, out_size); + + buffer = out_data + 36; + + bmRequestType = func_recipient; + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + WLog_ERR(TAG, "Function urb_control_feature_request: OUT Unchecked"); + memcpy(buffer, data + offset, OutputBufferSize); + bmRequestType |= 0x00; + break; + case USBD_TRANSFER_DIRECTION_IN: + bmRequestType |= 0x80; + break; + } + + switch (command) + { + case URB_SET_FEATURE: + bmRequest = 0x03; /* REQUEST_SET_FEATURE */ + break; + case URB_CLEAR_FEATURE: + bmRequest = 0x01; /* REQUEST_CLEAR_FEATURE */ + break; + default: + WLog_ERR(TAG, "urb_control_feature_request: Error Command 0x%02"PRIx8"", command); + zfree(out_data); + return -1; + } + + ret = pdev->control_transfer( + pdev, RequestId, 0, 0, bmRequestType, bmRequest, + FeatureSelector, + Index, + &usbd_status, + &OutputBufferSize, + buffer, + 1000); + + if (ret < 0){ + WLog_DBG(TAG, "feature control transfer: error num %d", ret); + OutputBufferSize = 0; + } + + offset = 36; + out_size = offset + OutputBufferSize; + data_write_UINT32(out_data + 0, InterfaceId); /** interface */ + data_write_UINT32(out_data + 4, MessageId); /** message id */ + + if (OutputBufferSize != 0) + data_write_UINT32(out_data + 8, URB_COMPLETION); + else + data_write_UINT32(out_data + 8, URB_COMPLETION_NO_DATA); + data_write_UINT32(out_data + 12, RequestId); /** RequestId */ + data_write_UINT32(out_data + 16, 8); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + data_write_UINT16(out_data + 20, 8); /** Size */ + + /** Padding, MUST be ignored upon receipt */ + data_write_UINT16(out_data + 22, URB_FUNCTION_GET_INTERFACE); + data_write_UINT32(out_data + 24, usbd_status); /** UsbdStatus */ + + data_write_UINT32(out_data + 28, 0); /** HResult */ + data_write_UINT32(out_data + 32, OutputBufferSize); /** OutputBufferSize */ + + + if (!pdev->isSigToEnd(pdev)) + callback->channel->Write(callback->channel, out_size, out_data, NULL); + zfree(out_data); + return 0; +} + +static int urbdrc_process_transfer_request(URBDRC_CHANNEL_CALLBACK * callback, BYTE * data, + UINT32 data_sizem, + UINT32 MessageId, + IUDEVMAN * udevman, + UINT32 UsbDevice, + int transferDir) +{ + IUDEVICE * pdev; + UINT32 CbTsUrb; + UINT16 Size; + UINT16 URB_Function; + UINT32 OutputBufferSize; + int error = 0; + + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL) + return 0; + data_read_UINT32(data + 0, CbTsUrb); /** CbTsUrb */ + data_read_UINT16(data + 4, Size); /** size */ + data_read_UINT16(data + 6, URB_Function); + data_read_UINT32(data + 4 + CbTsUrb, OutputBufferSize); + + switch (URB_Function) + { + case URB_FUNCTION_SELECT_CONFIGURATION: /** 0x0000 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SELECT_CONFIGURATION"); + error = urb_select_configuration( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_SELECT_INTERFACE: /** 0x0001 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SELECT_INTERFACE"); + error = urb_select_interface( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_ABORT_PIPE: /** 0x0002 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_ABORT_PIPE"); + error = urb_pipe_request( + callback, data + 8, data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + PIPE_CANCEL); + break; + case URB_FUNCTION_TAKE_FRAME_LENGTH_CONTROL: /** 0x0003 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_TAKE_FRAME_LENGTH_CONTROL"); + error = -1; /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + case URB_FUNCTION_RELEASE_FRAME_LENGTH_CONTROL: /** 0x0004 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RELEASE_FRAME_LENGTH_CONTROL"); + error = -1; /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + case URB_FUNCTION_GET_FRAME_LENGTH: /** 0x0005 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_FRAME_LENGTH"); + error = -1; /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + case URB_FUNCTION_SET_FRAME_LENGTH: /** 0x0006 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_FRAME_LENGTH"); + error = -1; /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + case URB_FUNCTION_GET_CURRENT_FRAME_NUMBER: /** 0x0007 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_CURRENT_FRAME_NUMBER"); + error = urb_get_current_frame_number( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_CONTROL_TRANSFER: /** 0x0008 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CONTROL_TRANSFER"); + error = urb_control_transfer( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + URB_CONTROL_TRANSFER_NONEXTERNAL); + break; + case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER: /** 0x0009 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER"); + error = urb_bulk_or_interrupt_transfer( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_ISOCH_TRANSFER: /** 0x000A */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_ISOCH_TRANSFER"); + error = urb_isoch_transfer( + callback, data + 8, data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE: /** 0x000B */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x00, + transferDir); + break; + case URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE: /** 0x000C */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x00, + transferDir); + break; + case URB_FUNCTION_SET_FEATURE_TO_DEVICE: /** 0x000D */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_FEATURE_TO_DEVICE"); + error = urb_control_feature_request(callback, + data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x00, + URB_SET_FEATURE, + transferDir); + break; + case URB_FUNCTION_SET_FEATURE_TO_INTERFACE: /** 0x000E */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_FEATURE_TO_INTERFACE"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x01, + URB_SET_FEATURE, + transferDir); + break; + case URB_FUNCTION_SET_FEATURE_TO_ENDPOINT: /** 0x000F */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_FEATURE_TO_ENDPOINT"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x02, + URB_SET_FEATURE, + transferDir); + break; + case URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE: /** 0x0010 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x00, + URB_CLEAR_FEATURE, + transferDir); + break; + case URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE: /** 0x0011 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x01, + URB_CLEAR_FEATURE, + transferDir); + break; + case URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT: /** 0x0012 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x02, + URB_CLEAR_FEATURE, + transferDir); + break; + case URB_FUNCTION_GET_STATUS_FROM_DEVICE: /** 0x0013 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_STATUS_FROM_DEVICE"); + error = urb_control_get_status_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x00, + transferDir); + break; + case URB_FUNCTION_GET_STATUS_FROM_INTERFACE: /** 0x0014 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_STATUS_FROM_INTERFACE"); + error = urb_control_get_status_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x01, + transferDir); + break; + case URB_FUNCTION_GET_STATUS_FROM_ENDPOINT: /** 0x0015 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_STATUS_FROM_ENDPOINT"); + error = urb_control_get_status_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x02, + transferDir); + break; + case URB_FUNCTION_RESERVED_0X0016: /** 0x0016 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVED_0X0016"); + error = -1; + break; + case URB_FUNCTION_VENDOR_DEVICE: /** 0x0017 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_VENDOR_DEVICE"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x02 << 5), /* vendor type */ + 0x00, + transferDir); + break; + case URB_FUNCTION_VENDOR_INTERFACE: /** 0x0018 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_VENDOR_INTERFACE"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x02 << 5), /* vendor type */ + 0x01, + transferDir); + break; + case URB_FUNCTION_VENDOR_ENDPOINT: /** 0x0019 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_VENDOR_ENDPOINT"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x02 << 5), /* vendor type */ + 0x02, + transferDir); + break; + case URB_FUNCTION_CLASS_DEVICE: /** 0x001A */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLASS_DEVICE"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x01 << 5), /* class type */ + 0x00, + transferDir); + break; + case URB_FUNCTION_CLASS_INTERFACE: /** 0x001B */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLASS_INTERFACE"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x01 << 5), /* class type */ + 0x01, + transferDir); + break; + case URB_FUNCTION_CLASS_ENDPOINT: /** 0x001C */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLASS_ENDPOINT"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x01 << 5), /* class type */ + 0x02, + transferDir); + break; + case URB_FUNCTION_RESERVE_0X001D: /** 0x001D */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X001D"); + error = -1; + break; + case URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL: /** 0x001E */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL"); + error = urb_pipe_request( + callback, data + 8, data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + PIPE_RESET); + break; + case URB_FUNCTION_CLASS_OTHER: /** 0x001F */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLASS_OTHER"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x01 << 5), /* class type */ + 0x03, + transferDir); + break; + case URB_FUNCTION_VENDOR_OTHER: /** 0x0020 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_VENDOR_OTHER"); + error = urb_control_vendor_or_class_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + (0x02 << 5), /* vendor type */ + 0x03, + transferDir); + break; + case URB_FUNCTION_GET_STATUS_FROM_OTHER: /** 0x0021 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_STATUS_FROM_OTHER"); + error = urb_control_get_status_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x03, + transferDir); + break; + case URB_FUNCTION_CLEAR_FEATURE_TO_OTHER: /** 0x0022 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CLEAR_FEATURE_TO_OTHER"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x03, + URB_CLEAR_FEATURE, + transferDir); + break; + case URB_FUNCTION_SET_FEATURE_TO_OTHER: /** 0x0023 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_FEATURE_TO_OTHER"); + error = urb_control_feature_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x03, + URB_SET_FEATURE, + transferDir); + break; + case URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT: /** 0x0024 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x02, + transferDir); + break; + case URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT: /** 0x0025 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x02, + transferDir); + break; + case URB_FUNCTION_GET_CONFIGURATION: /** 0x0026 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_CONFIGURATION"); + error = urb_control_get_configuration_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_GET_INTERFACE: /** 0x0027 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_INTERFACE"); + error = urb_control_get_interface_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE: /** 0x0028 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x01, + transferDir); + break; + case URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE: /** 0x0029 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE"); + error = urb_control_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + 0x01, + transferDir); + break; + case URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR: /** 0x002A */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR"); + error = urb_os_feature_descriptor_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir); + break; + case URB_FUNCTION_RESERVE_0X002B: /** 0x002B */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X002B"); + error = -1; + break; + case URB_FUNCTION_RESERVE_0X002C: /** 0x002C */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X002C"); + error = -1; + break; + case URB_FUNCTION_RESERVE_0X002D: /** 0x002D */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X002D"); + error = -1; + break; + case URB_FUNCTION_RESERVE_0X002E: /** 0x002E */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X002E"); + error = -1; + break; + case URB_FUNCTION_RESERVE_0X002F: /** 0x002F */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_RESERVE_0X002F"); + error = -1; + break; + /** USB 2.0 calls start at 0x0030 */ + case URB_FUNCTION_SYNC_RESET_PIPE: /** 0x0030 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SYNC_RESET_PIPE"); + error = urb_pipe_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + PIPE_RESET); + error = -9; /** function not support */ + break; + case URB_FUNCTION_SYNC_CLEAR_STALL: /** 0x0031 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_SYNC_CLEAR_STALL"); + error = urb_pipe_request( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + PIPE_RESET); + error = -9; + break; + case URB_FUNCTION_CONTROL_TRANSFER_EX: /** 0x0032 */ + WLog_DBG(TAG, "URB_Func: URB_FUNCTION_CONTROL_TRANSFER_EX"); + error = urb_control_transfer( + callback, data + 8, + data_sizem - 8, + MessageId, + udevman, + UsbDevice, + transferDir, + URB_CONTROL_TRANSFER_EXTERNAL); + break; + default: + WLog_DBG(TAG, "URB_Func: %"PRIx16" is not found!", URB_Function); + break; + } + + return error; +} + +void* urbdrc_process_udev_data_transfer(void* arg) +{ + TRANSFER_DATA* transfer_data = (TRANSFER_DATA*) arg; + URBDRC_CHANNEL_CALLBACK * callback = transfer_data->callback; + BYTE * pBuffer = transfer_data->pBuffer; + UINT32 cbSize = transfer_data->cbSize; + UINT32 UsbDevice = transfer_data->UsbDevice; + IUDEVMAN * udevman = transfer_data->udevman; + UINT32 MessageId; + UINT32 FunctionId; + IUDEVICE* pdev; + int error = 0; + pdev = udevman->get_udevice_by_UsbDevice(udevman, UsbDevice); + if (pdev == NULL || pdev->isSigToEnd(pdev)) + { + if (transfer_data->pBuffer) + zfree(transfer_data->pBuffer); + zfree(transfer_data); + return 0; + } + + pdev->push_action(pdev); + + /* USB kernel driver detach!! */ + pdev->detach_kernel_driver(pdev); + + data_read_UINT32(pBuffer + 0, MessageId); + data_read_UINT32(pBuffer + 4, FunctionId); + switch (FunctionId) + { + case CANCEL_REQUEST: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>CANCEL_REQUEST<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_cancel_request( + pBuffer + 8, + cbSize - 8, + udevman, + UsbDevice); + break; + case REGISTER_REQUEST_CALLBACK: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>REGISTER_REQUEST_CALLBACK<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_register_request_callback( + callback, + pBuffer + 8, + cbSize - 8, + udevman, + UsbDevice); + break; + case IO_CONTROL: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>IO_CONTROL<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_io_control( + callback, + pBuffer + 8, + cbSize - 8, + MessageId, + udevman, UsbDevice); + break; + case INTERNAL_IO_CONTROL: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>INTERNAL_IO_CONTROL<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_internal_io_control( + callback, + pBuffer + 8, + cbSize - 8, + MessageId, + udevman, UsbDevice); + break; + case QUERY_DEVICE_TEXT: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>QUERY_DEVICE_TEXT<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_query_device_text( + callback, + pBuffer + 8, + cbSize - 8, + MessageId, + udevman, + UsbDevice); + break; + case TRANSFER_IN_REQUEST: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>TRANSFER_IN_REQUEST<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_transfer_request( + callback, + pBuffer + 8, + cbSize - 8, + MessageId, + udevman, + UsbDevice, + USBD_TRANSFER_DIRECTION_IN); + break; + case TRANSFER_OUT_REQUEST: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>TRANSFER_OUT_REQUEST<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_transfer_request( + callback, + pBuffer + 8, + cbSize - 8, + MessageId, + udevman, + UsbDevice, + USBD_TRANSFER_DIRECTION_OUT); + break; + case RETRACT_DEVICE: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " >>RETRACT_DEVICE<<0x%"PRIX32"", FunctionId); + error = urbdrc_process_retract_device_request( + pBuffer + 8, + cbSize - 8, + udevman, + UsbDevice); + break; + default: + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer:" + " unknown FunctionId 0x%"PRIX32"", FunctionId); + error = -1; + break; + } + + if (transfer_data) + { + if (transfer_data->pBuffer) + zfree(transfer_data->pBuffer); + zfree(transfer_data); + } + + if (pdev) + { +#if ISOCH_FIFO + /* check isochronous fds */ + func_check_isochronous_fds(pdev); +#endif + /* close this channel, if device is not found. */ + pdev->complete_action(pdev); + } + else + { + udevman->push_urb(udevman); + return 0; + } + + udevman->push_urb(udevman); + return 0; +} diff --git a/channels/urbdrc/client/data_transfer.h b/channels/urbdrc/client/data_transfer.h new file mode 100644 index 0000000..bc3d708 --- /dev/null +++ b/channels/urbdrc/client/data_transfer.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H + +#include "urbdrc_main.h" + +#define DEVICE_CTX(dev) ((dev)->ctx) +#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev)) +#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle)) +#define ITRANSFER_CTX(transfer) \ + (TRANSFER_CTX(__USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer))) + +void *urbdrc_process_udev_data_transfer(void* arg); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H */ + diff --git a/channels/urbdrc/client/isoch_queue.c b/channels/urbdrc/client/isoch_queue.c new file mode 100644 index 0000000..4e051f9 --- /dev/null +++ b/channels/urbdrc/client/isoch_queue.c @@ -0,0 +1,183 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include +#include "isoch_queue.h" + +static void isoch_queue_rewind(ISOCH_CALLBACK_QUEUE* queue) +{ + queue->curr = queue->head; +} + +static BOOL isoch_queue_has_next(ISOCH_CALLBACK_QUEUE* queue) +{ + return (queue->curr != NULL); +} + +static ISOCH_CALLBACK_DATA* isoch_queue_get_next(ISOCH_CALLBACK_QUEUE* queue) +{ + ISOCH_CALLBACK_DATA* isoch; + + isoch = queue->curr; + queue->curr = (ISOCH_CALLBACK_DATA*)queue->curr->next; + + return isoch; +} + +static ISOCH_CALLBACK_DATA* isoch_queue_register_data(ISOCH_CALLBACK_QUEUE* queue, void* callback, void* dev) +{ + ISOCH_CALLBACK_DATA* isoch; + + isoch = (ISOCH_CALLBACK_DATA*) calloc(1, sizeof(ISOCH_CALLBACK_DATA)); + if (!isoch) + return NULL; + + isoch->device = dev; + isoch->callback = callback; + + pthread_mutex_lock(&queue->isoch_loading); + + if (queue->head == NULL) + { + /* linked queue is empty */ + queue->head = isoch; + queue->tail = isoch; + } + else + { + /* append data to the end of the linked queue */ + queue->tail->next = (void*)isoch; + isoch->prev = (void*)queue->tail; + queue->tail = isoch; + } + queue->isoch_num += 1; + + pthread_mutex_unlock(&queue->isoch_loading); + + return isoch; +} + +static int isoch_queue_unregister_data(ISOCH_CALLBACK_QUEUE* queue, ISOCH_CALLBACK_DATA* isoch) +{ + ISOCH_CALLBACK_DATA* p; + + queue->rewind(queue); + + while (queue->has_next(queue)) + { + p = queue->get_next(queue); + + if (p != isoch) + continue; + + /* data exists */ + /* set previous data to point to next data */ + + if (isoch->prev != NULL) + { + /* unregistered data is not the head */ + p = (ISOCH_CALLBACK_DATA*)isoch->prev; + p->next = isoch->next; + } + else + { + /* unregistered data is the head, update head */ + queue->head = (ISOCH_CALLBACK_DATA*)isoch->next; + } + + /* set next data to point to previous data */ + + if (isoch->next != NULL) + { + /* unregistered data is not the tail */ + p = (ISOCH_CALLBACK_DATA*)isoch->next; + p->prev = isoch->prev; + } + else + { + /* unregistered data is the tail, update tail */ + queue->tail = (ISOCH_CALLBACK_DATA*)isoch->prev; + } + queue->isoch_num--; + + if (isoch) + { + /* free data info */ + isoch->out_data = NULL; + + zfree(isoch); + } + + return 1; /* unregistration successful */ + } + + /* if we reach this point, the isoch wasn't found */ + return 0; +} + +void isoch_queue_free(ISOCH_CALLBACK_QUEUE* queue) +{ + ISOCH_CALLBACK_DATA* isoch; + + pthread_mutex_lock(&queue->isoch_loading); + + /** unregister all isochronous data*/ + queue->rewind(queue); + + while (queue->has_next(queue)) + { + isoch = queue->get_next(queue); + + if (isoch != NULL) + queue->unregister_data(queue, isoch); + } + + pthread_mutex_unlock(&queue->isoch_loading); + + pthread_mutex_destroy(&queue->isoch_loading); + + /* free queue */ + if (queue) + zfree(queue); +} + +ISOCH_CALLBACK_QUEUE* isoch_queue_new() +{ + ISOCH_CALLBACK_QUEUE* queue; + + queue = (ISOCH_CALLBACK_QUEUE*) calloc(1, sizeof(ISOCH_CALLBACK_QUEUE)); + if (!queue) + return NULL; + + pthread_mutex_init(&queue->isoch_loading, NULL); + + /* load service */ + queue->get_next = isoch_queue_get_next; + queue->has_next = isoch_queue_has_next; + queue->rewind = isoch_queue_rewind; + queue->register_data = isoch_queue_register_data; + queue->unregister_data = isoch_queue_unregister_data; + queue->free = isoch_queue_free; + + return queue; +} diff --git a/channels/urbdrc/client/isoch_queue.h b/channels/urbdrc/client/isoch_queue.h new file mode 100644 index 0000000..90ab1a1 --- /dev/null +++ b/channels/urbdrc/client/isoch_queue.h @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_ISOCH_QUEUE_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_ISOCH_QUEUE_H + +#include "urbdrc_types.h" + + +typedef struct _ISOCH_CALLBACK_DATA ISOCH_CALLBACK_DATA; +typedef struct _ISOCH_CALLBACK_QUEUE ISOCH_CALLBACK_QUEUE; + + +struct _ISOCH_CALLBACK_DATA +{ + void * inode; + void * prev; + void * next; + void * device; + BYTE * out_data; + UINT32 out_size; + void * callback; +}; + + + +struct _ISOCH_CALLBACK_QUEUE +{ + int isoch_num; + ISOCH_CALLBACK_DATA* curr; /* current point */ + ISOCH_CALLBACK_DATA* head; /* head point in linked list */ + ISOCH_CALLBACK_DATA* tail; /* tail point in linked list */ + + pthread_mutex_t isoch_loading; + + /* Isochronous queue service */ + void (*rewind) (ISOCH_CALLBACK_QUEUE * queue); + BOOL (*has_next) (ISOCH_CALLBACK_QUEUE * queue); + int (*unregister_data) (ISOCH_CALLBACK_QUEUE* queue, ISOCH_CALLBACK_DATA* isoch); + ISOCH_CALLBACK_DATA *(*get_next) (ISOCH_CALLBACK_QUEUE * queue); + ISOCH_CALLBACK_DATA *(*register_data) (ISOCH_CALLBACK_QUEUE* queue, + void * callback, void * dev); + void (*free) (ISOCH_CALLBACK_QUEUE * queue); + +}; + + +ISOCH_CALLBACK_QUEUE* isoch_queue_new(void); + + + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_ISOCH_QUEUE_H */ diff --git a/channels/urbdrc/client/libusb/CMakeLists.txt b/channels/urbdrc/client/libusb/CMakeLists.txt new file mode 100644 index 0000000..abb4356 --- /dev/null +++ b/channels/urbdrc/client/libusb/CMakeLists.txt @@ -0,0 +1,48 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Atrust corp. +# Copyright 2012 Alfred Liu +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("urbdrc" "libusb" "") + +set(${MODULE_PREFIX}_SRCS + libusb_udevman.c + libusb_udevice.c + libusb_udevice.h + request_queue.c + request_queue.h) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + + + +set(${MODULE_PREFIX}_LIBS + ${CMAKE_THREAD_LIBS_INIT}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} + ${LIBUSB_1_LIBRARIES}) + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + diff --git a/channels/urbdrc/client/libusb/libusb_udevice.c b/channels/urbdrc/client/libusb/libusb_udevice.c new file mode 100644 index 0000000..6be973d --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevice.c @@ -0,0 +1,2004 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include +#if defined(__linux__) +#include +#endif + +#include +#include + +#include "libusb_udevice.h" + +#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \ + static _type udev_get_##_arg (IUDEVICE* idev) \ + { \ + UDEVICE* pdev = (UDEVICE*) idev; \ + return pdev->_arg; \ + } \ + static void udev_set_##_arg (IUDEVICE* idev, _type _t) \ + { \ + UDEVICE* pdev = (UDEVICE*) idev; \ + pdev->_arg = _t; \ + } + +#define BASIC_POINT_FUNC_DEFINED(_arg, _type) \ + static _type udev_get_p_##_arg (IUDEVICE* idev) \ + { \ + UDEVICE* pdev = (UDEVICE*) idev; \ + return pdev->_arg; \ + } \ + static void udev_set_p_##_arg (IUDEVICE* idev, _type _t) \ + { \ + UDEVICE* pdev = (UDEVICE*) idev; \ + pdev->_arg = _t; \ + } + +#define BASIC_STATE_FUNC_REGISTER(_arg, _dev) \ + _dev->iface.get_##_arg = udev_get_##_arg; \ + _dev->iface.set_##_arg = udev_set_##_arg + + +typedef struct _ISO_USER_DATA ISO_USER_DATA; + +struct _ISO_USER_DATA +{ + BYTE* IsoPacket; + BYTE* output_data; + int iso_status; + int completed; + UINT32 error_count; + int noack; + UINT32 start_frame; +}; + +static int get_next_timeout(libusb_context* ctx, struct timeval* tv, struct timeval* out) +{ + int r; + struct timeval timeout; + r = libusb_get_next_timeout(ctx, &timeout); + + if (r) + { + /* timeout already expired? */ + if (!timerisset(&timeout)) + return 1; + + /* choose the smallest of next URB timeout or user specified timeout */ + if (timercmp(&timeout, tv, <)) + *out = timeout; + else + *out = *tv; + } + else + { + *out = *tv; + } + + return 0; +} + +/* + * a simple wrapper to implement libusb_handle_events_timeout_completed + * function in libusb library git tree (1.0.9 later) */ +static int handle_events_completed(libusb_context* ctx, int* completed) +{ + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; +#ifdef HAVE_NEW_LIBUSB + return libusb_handle_events_timeout_completed(ctx, &tv, completed); +#else + int r; + struct timeval poll_timeout; + r = get_next_timeout(ctx, &tv, &poll_timeout); +retry: + + if (libusb_try_lock_events(ctx) == 0) + { + if (completed == NULL || !*completed) + { + /* we obtained the event lock: do our own event handling */ + WLog_DBG(TAG, "doing our own event handling"); + r = libusb_handle_events_locked(ctx, &tv); + } + + libusb_unlock_events(ctx); + return r; + } + + /* another thread is doing event handling. wait for thread events that + * notify event completion. */ + libusb_lock_event_waiters(ctx); + + if (completed && *completed) + goto already_done; + + if (!libusb_event_handler_active(ctx)) + { + /* we hit a race: whoever was event handling earlier finished in the + * time it took us to reach this point. try the cycle again. */ + libusb_unlock_event_waiters(ctx); + WLog_DBG(TAG, "event handler was active but went away, retrying"); + goto retry; + } + + WLog_DBG(TAG, "another thread is doing event handling"); + r = libusb_wait_for_event(ctx, &poll_timeout); +already_done: + libusb_unlock_event_waiters(ctx); + + if (r < 0) + { + return r; + } + else if (r == 1) + { + return libusb_handle_events_timeout(ctx, &tv); + } + else + { + return 0; + } + +#endif /* HAVE_NEW_LIBUSE */ +} + +static void func_iso_callback(struct libusb_transfer* transfer) +{ + ISO_USER_DATA* iso_user_data = (ISO_USER_DATA*) transfer->user_data; + BYTE* data = iso_user_data->IsoPacket; + int* completed = &iso_user_data->completed; + UINT32 offset = 0; + UINT32 index = 0; + UINT32 i, act_len; + BYTE* b; + *completed = 1; + + /* Fixme: currently fill the dummy frame number, tt needs to be + * filled a real frame number */ + // urbdrc_get_mstime(iso_user_data->start_frame); + if ((transfer->status == LIBUSB_TRANSFER_COMPLETED) && (!iso_user_data->noack)) + { + for (i = 0; i < transfer->num_iso_packets; i++) + { + act_len = transfer->iso_packet_desc[i].actual_length; + data_write_UINT32(data + offset, index); + data_write_UINT32(data + offset + 4, act_len); + data_write_UINT32(data + offset + 8, + transfer->iso_packet_desc[i].status); + offset += 12; + + if (transfer->iso_packet_desc[i].status == USBD_STATUS_SUCCESS) + { + b = libusb_get_iso_packet_buffer_simple(transfer, i); + + if (act_len > 0) + { + if (iso_user_data->output_data + index != b) + memcpy(iso_user_data->output_data + index, b, act_len); + + index += act_len; + } + else + { + //WLog_ERR(TAG, "actual length %"PRIu32"", act_len); + //exit(EXIT_FAILURE); + } + } + else + { + iso_user_data->error_count++; + //print_transfer_status(transfer->iso_packet_desc[i].status); + } + } + + transfer->actual_length = index; + iso_user_data->iso_status = 1; + } + else if ((transfer->status == LIBUSB_TRANSFER_COMPLETED) && (iso_user_data->noack)) + { + /* This situation occurs when we do not need to + * return any packet */ + iso_user_data->iso_status = 1; + } + else + { + //print_status(transfer->status); + iso_user_data->iso_status = -1; + } +} + +static const LIBUSB_ENDPOINT_DESCEIPTOR* func_get_ep_desc(LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig, + MSUSB_CONFIG_DESCRIPTOR* MsConfig, UINT32 EndpointAddress) +{ + BYTE alt; + int inum, pnum; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + const LIBUSB_INTERFACE* interface; + const LIBUSB_ENDPOINT_DESCEIPTOR* endpoint; + MsInterfaces = MsConfig->MsInterfaces; + interface = LibusbConfig->interface; + + for (inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + alt = MsInterfaces[inum]->AlternateSetting; + endpoint = interface[inum].altsetting[alt].endpoint; + + for (pnum = 0; pnum < MsInterfaces[inum]->NumberOfPipes; pnum++) + { + if (endpoint[pnum].bEndpointAddress == EndpointAddress) + { + return &endpoint[pnum]; + } + } + } + + return NULL; +} + +static void func_bulk_transfer_cb(struct libusb_transfer* transfer) +{ + int* completed = transfer->user_data; + *completed = 1; + /* caller interprets results and frees transfer */ +} + +static int func_set_usbd_status(UDEVICE* pdev, UINT32* status, int err_result) +{ + switch (err_result) + { + case LIBUSB_SUCCESS: + *status = USBD_STATUS_SUCCESS; + break; + + case LIBUSB_ERROR_IO: + *status = USBD_STATUS_STALL_PID; + WLog_ERR(TAG, "LIBUSB_ERROR_IO!!"); + break; + + case LIBUSB_ERROR_INVALID_PARAM: + *status = USBD_STATUS_INVALID_PARAMETER; + break; + + case LIBUSB_ERROR_ACCESS: + *status = USBD_STATUS_NOT_ACCESSED; + break; + + case LIBUSB_ERROR_NO_DEVICE: + *status = USBD_STATUS_DEVICE_GONE; + + if (pdev) + { + if (!(pdev->status & URBDRC_DEVICE_NOT_FOUND)) + { + pdev->status |= URBDRC_DEVICE_NOT_FOUND; + WLog_WARN(TAG, "LIBUSB_ERROR_NO_DEVICE!!"); + } + } + + break; + + case LIBUSB_ERROR_NOT_FOUND: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_BUSY: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_TIMEOUT: + *status = USBD_STATUS_TIMEOUT; + break; + + case LIBUSB_ERROR_OVERFLOW: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_PIPE: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_INTERRUPTED: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_NO_MEM: + *status = USBD_STATUS_NO_MEMORY; + break; + + case LIBUSB_ERROR_NOT_SUPPORTED: + *status = USBD_STATUS_NOT_SUPPORTED; + break; + + case LIBUSB_ERROR_OTHER: + *status = USBD_STATUS_STALL_PID; + break; + + default: + *status = USBD_STATUS_SUCCESS; + break; + } + + return 0; +} + +static void func_iso_data_init(ISO_USER_DATA* iso_user_data, UINT32 numPacket, UINT32 buffsize, + UINT32 noAck, BYTE* isoPacket, BYTE* buffer) +{ + /* init struct iso_user_data */ + iso_user_data->IsoPacket = isoPacket; + iso_user_data->output_data = buffer; + iso_user_data->error_count = 0; + iso_user_data->completed = 0; + iso_user_data->noack = noAck; + urbdrc_get_mstime(iso_user_data->start_frame); +} + +static int func_config_release_all_interface(LIBUSB_DEVICE_HANDLE* libusb_handle, + UINT32 NumInterfaces) +{ + int i, ret; + + for (i = 0; i < NumInterfaces; i++) + { + ret = libusb_release_interface(libusb_handle, i); + + if (ret < 0) + { + WLog_ERR(TAG, "config_release_all_interface: error num %d", ret); + return -1; + } + } + + return 0; +} + +static int func_claim_all_interface(LIBUSB_DEVICE_HANDLE* libusb_handle, int NumInterfaces) +{ + int i, ret; + + for (i = 0; i < NumInterfaces; i++) + { + ret = libusb_claim_interface(libusb_handle, i); + + if (ret < 0) + { + WLog_ERR(TAG, "claim_all_interface: error num %d", ret); + return -1; + } + } + + return 0; +} + +/* +static void* print_transfer_status(enum libusb_transfer_status status) +{ + switch (status) + { + case LIBUSB_TRANSFER_COMPLETED: + //WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_COMPLETED"); + break; + case LIBUSB_TRANSFER_ERROR: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_ERROR"); + break; + case LIBUSB_TRANSFER_TIMED_OUT: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_TIMED_OUT"); + break; + case LIBUSB_TRANSFER_CANCELLED: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_CANCELLED"); + break; + case LIBUSB_TRANSFER_STALL: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_STALL"); + break; + case LIBUSB_TRANSFER_NO_DEVICE: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_NO_DEVICE"); + break; + case LIBUSB_TRANSFER_OVERFLOW: + WLog_ERR(TAG, "Transfer Status: LIBUSB_TRANSFER_OVERFLOW"); + break; + default: + WLog_ERR(TAG, "Transfer Status: Get unknow error num %d (0x%x)", + status, status); + } + return 0; +} + +static void print_status(enum libusb_transfer_status status) +{ + switch (status) + { + case LIBUSB_TRANSFER_COMPLETED: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_COMPLETED"); + break; + case LIBUSB_TRANSFER_ERROR: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_ERROR"); + break; + case LIBUSB_TRANSFER_TIMED_OUT: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_TIMED_OUT"); + break; + case LIBUSB_TRANSFER_CANCELLED: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_CANCELLED"); + break; + case LIBUSB_TRANSFER_STALL: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_STALL"); + break; + case LIBUSB_TRANSFER_NO_DEVICE: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_NO_DEVICE"); + break; + case LIBUSB_TRANSFER_OVERFLOW: + WLog_ERR(TAG, "Transfer status: LIBUSB_TRANSFER_OVERFLOW"); + break; + default: + WLog_ERR(TAG, "Transfer status: unknow status %d(0x%x)", status, status); + break; + } +} +*/ + +static LIBUSB_DEVICE* udev_get_libusb_dev(int bus_number, int dev_number) +{ + ssize_t i, total_device; + LIBUSB_DEVICE** libusb_list; + total_device = libusb_get_device_list(NULL, &libusb_list); + + for (i = 0; i < total_device; i++) + { + if ((bus_number == libusb_get_bus_number(libusb_list[i])) && + (dev_number == libusb_get_device_address(libusb_list[i]))) + return libusb_list[i]; + } + + libusb_free_device_list(libusb_list, 1); + return NULL; +} + +static LIBUSB_DEVICE_DESCRIPTOR* udev_new_descript(LIBUSB_DEVICE* libusb_dev) +{ + int ret; + LIBUSB_DEVICE_DESCRIPTOR* descriptor; + descriptor = (LIBUSB_DEVICE_DESCRIPTOR*) malloc(sizeof(LIBUSB_DEVICE_DESCRIPTOR)); + ret = libusb_get_device_descriptor(libusb_dev, descriptor); + + if (ret < 0) + { + WLog_ERR(TAG, "libusb_get_device_descriptor: ERROR!!"); + free(descriptor); + return NULL; + } + + return descriptor; +} + +/* Get HUB handle */ +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +static int udev_get_hub_handle(UDEVICE* pdev, UINT16 bus_number, UINT16 dev_number) +{ + int error; + ssize_t i, total_device, ports_cnt; + uint8_t port_numbers[16]; + LIBUSB_DEVICE** libusb_list; + total_device = libusb_get_device_list(NULL, &libusb_list); + /* Look for device. */ + error = -1; + + for (i = 0; i < total_device; i ++) + { + if ((bus_number != libusb_get_bus_number(libusb_list[i])) || + (dev_number != libusb_get_device_address(libusb_list[i]))) + continue; + + error = libusb_open(libusb_list[i], &pdev->hub_handle); + + if (error < 0) + { + WLog_ERR(TAG, "libusb_open error: %i - %s", error, libusb_strerror(error)); + break; + } + + /* get port number */ + error = libusb_get_port_numbers(libusb_list[i], port_numbers, sizeof(port_numbers)); + libusb_close(pdev->hub_handle); + + if (error < 1) + { + /* Prevent open hub, treat as error. */ + WLog_ERR(TAG, "libusb_get_port_numbers error: %i - %s", error, libusb_strerror(error)); + break; + } + + pdev->port_number = port_numbers[(error - 1)]; + error = 0; + WLog_DBG(TAG, " Port: %d", pdev->port_number); + /* gen device path */ + sprintf_s(pdev->path, ARRAYSIZE(pdev->path), "ugen%"PRIu16".%"PRIu16"", bus_number, dev_number); + WLog_DBG(TAG, " DevPath: %s", pdev->path); + break; + } + + /* Look for device hub. */ + if (error == 0) + { + error = -1; + + for (i = 0; i < total_device; i ++) + { + if ((bus_number != libusb_get_bus_number(libusb_list[i])) || + (1 != libusb_get_device_address(libusb_list[i]))) /* Root hub allways first on bus. */ + continue; + + WLog_DBG(TAG, " Open hub: %"PRIu16"", bus_number); + error = libusb_open(libusb_list[i], &pdev->hub_handle); + + if (error < 0) + WLog_ERR(TAG, "libusb_open error: %i - %s", error, libusb_strerror(error)); + + break; + } + } + + libusb_free_device_list(libusb_list, 1); + + if (error < 0) + return -1; + + WLog_DBG(TAG, "libusb_open success!"); + return 0; +} +#endif +#if defined(__linux__) +static int udev_get_hub_handle(UDEVICE* pdev, UINT16 bus_number, UINT16 dev_number) +{ + struct udev* udev; + struct udev_enumerate* enumerate; + struct udev_list_entry* devices; + struct udev_list_entry* dev_list_entry; + struct udev_device* dev; + LIBUSB_DEVICE* libusb_dev; + int hub_found = 0; + unsigned long hub_bus = 0; + unsigned long hub_dev = 0; + int error = 0; + udev = udev_new(); + + if (!udev) + { + WLog_ERR(TAG, "Can't create udev"); + return -1; + } + + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "usb"); + udev_enumerate_add_match_property(enumerate, "DEVTYPE", "usb_device"); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(dev_list_entry, devices) + { + const char* path; + errno = 0; + path = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, path); + + if (!dev) + continue; + + unsigned long tmp_b, tmp_d; + tmp_b = strtoul(udev_device_get_property_value(dev, "BUSNUM"), NULL, 0); + + if (errno != 0) + continue; + + tmp_d = strtoul(udev_device_get_property_value(dev, "DEVNUM"), NULL, 0); + + if (errno != 0) + continue; + + if (bus_number == tmp_b && dev_number == tmp_d) + { + /* get port number */ + char* p1, *p2; + const char* sysfs_path = + udev_device_get_property_value(dev, "DEVPATH"); + p1 = (char*) sysfs_path; + + do + { + p2 = p1 + 1; + p1 = strchr(p2, '.'); + } + while (p1 != NULL); + + if ((p2 - sysfs_path) < (strlen(sysfs_path) - 2)) + { + p1 = (char*) sysfs_path; + + do + { + p2 = p1 + 1; + p1 = strchr(p2, '-'); + } + while (p1 != NULL); + } + + errno = 0; + { + unsigned long val = strtoul(p2, NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + continue; + + pdev->port_number = val; + } + WLog_DBG(TAG, " Port: %d", pdev->port_number); + /* get device path */ + p1 = (char*) sysfs_path; + + do + { + p2 = p1 + 1; + p1 = strchr(p2, '/'); + } + while (p1 != NULL); + + sprintf_s(pdev->path, ARRAYSIZE(pdev->path), "%s", p2); + WLog_DBG(TAG, " DevPath: %s", pdev->path); + /* query parent hub info */ + dev = udev_device_get_parent(dev); + + if (dev != NULL) + { + hub_found = 1; + hub_bus = strtoul(udev_device_get_property_value(dev, "BUSNUM"), NULL, 0); + hub_dev = strtoul(udev_device_get_property_value(dev, "DEVNUM"), NULL, 0); + WLog_DBG(TAG, " Hub BUS/DEV: %d %d", hub_bus, hub_dev); + } + + udev_device_unref(dev); + break; + } + + udev_device_unref(dev); + } + udev_enumerate_unref(enumerate); + udev_unref(udev); + + if (!hub_found) + { + WLog_WARN(TAG, "hub was not found!"); + return -1; + } + + /* Get libusb hub handle */ + libusb_dev = udev_get_libusb_dev(hub_bus, hub_dev); + + if (libusb_dev == NULL) + { + WLog_DBG(TAG, "get hub libusb_dev fail!"); + return -1; + } + + error = libusb_open(libusb_dev, &pdev->hub_handle); + + if (error < 0) + { + WLog_DBG(TAG, "libusb_open error!"); + return -1; + } + + WLog_DBG(TAG, "libusb_open success!"); + /* Success! */ + return 0; +} +#endif + +static int libusb_udev_select_interface(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting) +{ + int error = 0, diff = 1; + UDEVICE* pdev = (UDEVICE*) idev; + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + MsConfig = pdev->MsConfig; + + if (MsConfig) + { + MsInterfaces = MsConfig->MsInterfaces; + + if ((MsInterfaces) && (MsInterfaces[InterfaceNumber]->AlternateSetting == AlternateSetting)) + { + diff = 0; + } + } + + if (diff) + { + error = libusb_set_interface_alt_setting(pdev->libusb_handle, + InterfaceNumber, AlternateSetting); + + if (error < 0) + { + WLog_ERR(TAG, "Set interface altsetting get error num %d", + error); + } + } + + return error; +} + +static MSUSB_CONFIG_DESCRIPTOR* libusb_udev_complete_msconfig_setup(IUDEVICE* idev, + MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + UDEVICE* pdev = (UDEVICE*) idev; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + MSUSB_PIPE_DESCRIPTOR** MsPipes; + MSUSB_PIPE_DESCRIPTOR* MsPipe; + MSUSB_PIPE_DESCRIPTOR** t_MsPipes; + MSUSB_PIPE_DESCRIPTOR* t_MsPipe; + LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig; + const LIBUSB_INTERFACE* LibusbInterface; + const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting; + const LIBUSB_ENDPOINT_DESCEIPTOR* LibusbEndpoint; + BYTE LibusbNumEndpoint; + int inum = 0, pnum = 0, MsOutSize = 0; + LibusbConfig = pdev->LibusbConfig; + + if (LibusbConfig->bNumInterfaces != MsConfig->NumInterfaces) + { + WLog_ERR(TAG, "Select Configuration: Libusb NumberInterfaces(%"PRIu8") is different " + "with MsConfig NumberInterfaces(%"PRIu32")", + LibusbConfig->bNumInterfaces, MsConfig->NumInterfaces); + } + + /* replace MsPipes for libusb */ + MsInterfaces = MsConfig->MsInterfaces; + + for (inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MsInterface = MsInterfaces[inum]; + /* get libusb's number of endpoints */ + LibusbInterface = &LibusbConfig->interface[MsInterface->InterfaceNumber]; + LibusbAltsetting = &LibusbInterface->altsetting[MsInterface->AlternateSetting]; + LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints; + t_MsPipes = (MSUSB_PIPE_DESCRIPTOR**) calloc(LibusbNumEndpoint, sizeof(MSUSB_PIPE_DESCRIPTOR*)); + + for (pnum = 0; pnum < LibusbNumEndpoint; pnum++) + { + t_MsPipe = (MSUSB_PIPE_DESCRIPTOR*) malloc(sizeof(MSUSB_PIPE_DESCRIPTOR)); + memset(t_MsPipe, 0, sizeof(MSUSB_PIPE_DESCRIPTOR)); + + if (pnum < MsInterface->NumberOfPipes && MsInterface->MsPipes) + { + MsPipe = MsInterface->MsPipes[pnum]; + t_MsPipe->MaximumPacketSize = MsPipe->MaximumPacketSize; + t_MsPipe->MaximumTransferSize = MsPipe->MaximumTransferSize; + t_MsPipe->PipeFlags = MsPipe->PipeFlags; + } + else + { + t_MsPipe->MaximumPacketSize = 0; + t_MsPipe->MaximumTransferSize = 0xffffffff; + t_MsPipe->PipeFlags = 0; + } + + t_MsPipe->PipeHandle = 0; + t_MsPipe->bEndpointAddress = 0; + t_MsPipe->bInterval = 0; + t_MsPipe->PipeType = 0; + t_MsPipe->InitCompleted = 0; + t_MsPipes[pnum] = t_MsPipe; + } + + msusb_mspipes_replace(MsInterface, t_MsPipes, LibusbNumEndpoint); + } + + /* setup configuration */ + MsOutSize = 8; + /* ConfigurationHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>|| + * || bus_number | dev_number | bConfigurationValue || + * --------------------------------------------------------------- + * ***********************/ + MsConfig->ConfigurationHandle = MsConfig->bConfigurationValue | + (pdev->bus_number << 24) | + (pdev->dev_number << 16); + MsInterfaces = MsConfig->MsInterfaces; + + for (inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MsOutSize += 16; + MsInterface = MsInterfaces[inum]; + /* get libusb's interface */ + LibusbInterface = &LibusbConfig->interface[MsInterface->InterfaceNumber]; + LibusbAltsetting = &LibusbInterface->altsetting[MsInterface->AlternateSetting]; + /* InterfaceHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|| + * || bus_number | dev_number | altsetting | interfaceNum || + * --------------------------------------------------------------- + * ***********************/ + MsInterface->InterfaceHandle = LibusbAltsetting->bInterfaceNumber + | (LibusbAltsetting->bAlternateSetting << 8) + | (pdev->dev_number << 16) + | (pdev->bus_number << 24); + MsInterface->Length = 16 + (MsInterface->NumberOfPipes * 20); + MsInterface->bInterfaceClass = LibusbAltsetting->bInterfaceClass; + MsInterface->bInterfaceSubClass = LibusbAltsetting->bInterfaceSubClass; + MsInterface->bInterfaceProtocol = LibusbAltsetting->bInterfaceProtocol; + MsInterface->InitCompleted = 1; + MsPipes = MsInterface->MsPipes; + LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints; + + for (pnum = 0; pnum < LibusbNumEndpoint; pnum++) + { + MsOutSize += 20; + MsPipe = MsPipes[pnum]; + /* get libusb's endpoint */ + LibusbEndpoint = &LibusbAltsetting->endpoint[pnum]; + /* PipeHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>|| + * || bus_number | dev_number | bEndpointAddress || + * --------------------------------------------------------------- + * ***********************/ + MsPipe->PipeHandle = LibusbEndpoint->bEndpointAddress + | (pdev->dev_number << 16) + | (pdev->bus_number << 24); + /* count endpoint max packet size */ + int max = LibusbEndpoint->wMaxPacketSize & 0x07ff; + BYTE attr = LibusbEndpoint->bmAttributes; + + if ((attr & 0x3) == 1 || (attr & 0x3) == 3) + { + max *= (1 + ((LibusbEndpoint->wMaxPacketSize >> 11) & 3)); + } + + MsPipe->MaximumPacketSize = max; + MsPipe->bEndpointAddress = LibusbEndpoint->bEndpointAddress; + MsPipe->bInterval = LibusbEndpoint->bInterval; + MsPipe->PipeType = attr & 0x3; + MsPipe->InitCompleted = 1; + } + } + + MsConfig->MsOutSize = MsOutSize; + MsConfig->InitCompleted = 1; + + /* replace device's MsConfig */ + if (!(MsConfig == pdev->MsConfig)) + { + msusb_msconfig_free(pdev->MsConfig); + pdev->MsConfig = MsConfig; + } + + return MsConfig; +} + +static int libusb_udev_select_configuration(IUDEVICE* idev, UINT32 bConfigurationValue) +{ + UDEVICE* pdev = (UDEVICE*) idev; + MSUSB_CONFIG_DESCRIPTOR* MsConfig = pdev->MsConfig; + LIBUSB_DEVICE_HANDLE* libusb_handle = pdev->libusb_handle; + LIBUSB_DEVICE* libusb_dev = pdev->libusb_dev; + LIBUSB_CONFIG_DESCRIPTOR** LibusbConfig = &pdev->LibusbConfig; + int ret = 0; + + if (MsConfig->InitCompleted) + { + func_config_release_all_interface(libusb_handle, (*LibusbConfig)->bNumInterfaces); + } + + /* The configuration value -1 is mean to put the device in unconfigured state. */ + if (bConfigurationValue == 0) + ret = libusb_set_configuration(libusb_handle, -1); + else + ret = libusb_set_configuration(libusb_handle, bConfigurationValue); + + if (ret < 0) + { + WLog_ERR(TAG, "libusb_set_configuration: ERROR number %d!!", ret); + func_claim_all_interface(libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + else + { + ret = libusb_get_active_config_descriptor(libusb_dev, LibusbConfig); + + if (ret < 0) + { + WLog_ERR(TAG, "libusb_get_config_descriptor_by_value: ERROR number %d!!", ret); + func_claim_all_interface(libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + } + + func_claim_all_interface(libusb_handle, (*LibusbConfig)->bNumInterfaces); + return 0; +} + +static int libusb_udev_control_pipe_request(IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32* UsbdStatus, int command) +{ + int error = 0; + UDEVICE* pdev = (UDEVICE*) idev; + + /* + pdev->request_queue->register_request(pdev->request_queue, RequestId, NULL, 0); + */ + switch (command) + { + case PIPE_CANCEL: + /** cancel bulk or int transfer */ + idev->cancel_all_transfer_request(idev); + //dummy_wait_s_obj(1); + /** set feature to ep (set halt)*/ + error = libusb_control_transfer(pdev->libusb_handle, + LIBUSB_ENDPOINT_OUT | LIBUSB_RECIPIENT_ENDPOINT, + LIBUSB_REQUEST_SET_FEATURE, + ENDPOINT_HALT, + EndpointAddress, + NULL, + 0, + 1000); + break; + + case PIPE_RESET: + idev->cancel_all_transfer_request(idev); + error = libusb_clear_halt(pdev->libusb_handle, EndpointAddress); + //func_set_usbd_status(pdev, UsbdStatus, error); + break; + + default: + error = -0xff; + break; + } + + *UsbdStatus = 0; + /* + if(pdev->request_queue->unregister_request(pdev->request_queue, RequestId)) + WLog_ERR(TAG, "request_queue_unregister_request: not fount request 0x%x", RequestId); + */ + return error; +} + +static int libusb_udev_control_query_device_text(IUDEVICE* idev, UINT32 TextType, + UINT32 LocaleId, + UINT32* BufferSize, + BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*) idev; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = pdev->devDescriptor; + char* strDesc = "Generic Usb String"; + char deviceLocation[25]; + BYTE bus_number; + BYTE device_address; + int ret = 0, i = 0; + + switch (TextType) + { + case DeviceTextDescription: + ret = libusb_get_string_descriptor(pdev->libusb_handle, + devDescriptor->iProduct, + LocaleId, + Buffer, + *BufferSize); + + for (i = 0; i < ret; i++) + { + Buffer[i] = Buffer[i + 2]; + } + + ret -= 2; + + if (ret <= 0 || ret < 4) + { + WLog_DBG(TAG, "libusb_get_string_descriptor: " + "ERROR num %d, iProduct: %"PRIu8"!", ret, devDescriptor->iProduct); + memcpy(Buffer, strDesc, strlen(strDesc)); + Buffer[strlen(strDesc)] = '\0'; + *BufferSize = (strlen((char*)Buffer)) * 2; + + for (i = strlen((char*)Buffer); i > 0; i--) + { + Buffer[i * 2] = Buffer[i]; + Buffer[(i * 2) - 1] = 0; + } + } + else + { + *BufferSize = ret; + } + + break; + + case DeviceTextLocationInformation: + bus_number = libusb_get_bus_number(pdev->libusb_dev); + device_address = libusb_get_device_address(pdev->libusb_dev); + sprintf_s(deviceLocation, ARRAYSIZE(deviceLocation), "Port_#%04"PRIu8".Hub_#%04"PRIu8"", + device_address, bus_number); + + for (i = 0; i < strlen(deviceLocation); i++) + { + Buffer[i * 2] = (BYTE)deviceLocation[i]; + Buffer[(i * 2) + 1] = 0; + } + + *BufferSize = (i * 2); + break; + + default: + WLog_DBG(TAG, "Query Text: unknown TextType %"PRIu32"", TextType); + break; + } + + return 0; +} + + +static int libusb_udev_os_feature_descriptor_request(IUDEVICE* idev, UINT32 RequestId, + BYTE Recipient, + BYTE InterfaceNumber, + BYTE Ms_PageIndex, + UINT16 Ms_featureDescIndex, + UINT32* UsbdStatus, + UINT32* BufferSize, + BYTE* Buffer, + int Timeout) +{ + UDEVICE* pdev = (UDEVICE*) idev; + BYTE ms_string_desc[0x13]; + int error = 0; + /* + pdev->request_queue->register_request(pdev->request_queue, RequestId, NULL, 0); + */ + memset(ms_string_desc, 0, 0x13); + error = libusb_control_transfer(pdev->libusb_handle, + LIBUSB_ENDPOINT_IN | Recipient, + LIBUSB_REQUEST_GET_DESCRIPTOR, + 0x03ee, + 0, + ms_string_desc, + 0x12, + Timeout); + + //WLog_ERR(TAG, "Get ms string: result number %d", error); + if (error > 0) + { + BYTE bMS_Vendorcode; + data_read_BYTE(ms_string_desc + 16, bMS_Vendorcode); + //WLog_ERR(TAG, "bMS_Vendorcode:0x%x", bMS_Vendorcode); + /** get os descriptor */ + error = libusb_control_transfer(pdev->libusb_handle, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | Recipient, + bMS_Vendorcode, + (InterfaceNumber << 8) | Ms_PageIndex, + Ms_featureDescIndex, + Buffer, + *BufferSize, + Timeout); + *BufferSize = error; + } + + if (error < 0) + *UsbdStatus = USBD_STATUS_STALL_PID; + else + *UsbdStatus = USBD_STATUS_SUCCESS; + + /* + if(pdev->request_queue->unregister_request(pdev->request_queue, RequestId)) + WLog_ERR(TAG, "request_queue_unregister_request: not fount request 0x%x", RequestId); + */ + return error; +} + +static int libusb_udev_query_device_descriptor(IUDEVICE* idev, int offset) +{ + UDEVICE* pdev = (UDEVICE*) idev; + + switch (offset) + { + case B_LENGTH: + return pdev->devDescriptor->bLength; + + case B_DESCRIPTOR_TYPE: + return pdev->devDescriptor->bDescriptorType; + + case BCD_USB: + return pdev->devDescriptor->bcdUSB; + + case B_DEVICE_CLASS: + return pdev->devDescriptor->bDeviceClass; + + case B_DEVICE_SUBCLASS: + return pdev->devDescriptor->bDeviceSubClass; + + case B_DEVICE_PROTOCOL: + return pdev->devDescriptor->bDeviceProtocol; + + case B_MAX_PACKET_SIZE0: + return pdev->devDescriptor->bMaxPacketSize0; + + case ID_VENDOR: + return pdev->devDescriptor->idVendor; + + case ID_PRODUCT: + return pdev->devDescriptor->idProduct; + + case BCD_DEVICE: + return pdev->devDescriptor->bcdDevice; + + case I_MANUFACTURER: + return pdev->devDescriptor->iManufacturer; + + case I_PRODUCT: + return pdev->devDescriptor->iProduct; + + case I_SERIAL_NUMBER: + return pdev->devDescriptor->iSerialNumber; + + case B_NUM_CONFIGURATIONS: + return pdev->devDescriptor->bNumConfigurations; + + default: + return 0; + } + + return 0; +} + +static void libusb_udev_detach_kernel_driver(IUDEVICE* idev) +{ + int i, err = 0; + UDEVICE* pdev = (UDEVICE*) idev; + + if ((pdev->status & URBDRC_DEVICE_DETACH_KERNEL) == 0) + { + for (i = 0; i < pdev->LibusbConfig->bNumInterfaces; i++) + { + err = libusb_kernel_driver_active(pdev->libusb_handle, i); + WLog_DBG(TAG, "libusb_kernel_driver_active = %d", err); + + if (err) + { + err = libusb_detach_kernel_driver(pdev->libusb_handle, i); + WLog_DBG(TAG, "libusb_detach_kernel_driver = %d", err); + } + } + + pdev->status |= URBDRC_DEVICE_DETACH_KERNEL; + } +} + +static void libusb_udev_attach_kernel_driver(IUDEVICE* idev) +{ + int i, err = 0; + UDEVICE* pdev = (UDEVICE*) idev; + + for (i = 0; i < pdev->LibusbConfig->bNumInterfaces && err != LIBUSB_ERROR_NO_DEVICE; i++) + { + err = libusb_release_interface(pdev->libusb_handle, i); + + if (err < 0) + { + WLog_DBG(TAG, "libusb_release_interface: error num %d = %d", i, err); + } + + if (err != LIBUSB_ERROR_NO_DEVICE) + { + err = libusb_attach_kernel_driver(pdev->libusb_handle, i); + WLog_DBG(TAG, "libusb_attach_kernel_driver if%d = %d", i, err); + } + } +} + +static int libusb_udev_is_composite_device(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return pdev->isCompositeDevice; +} + +static int libusb_udev_is_signal_end(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return (pdev->status & URBDRC_DEVICE_SIGNAL_END) ? 1 : 0; +} + +static int libusb_udev_is_exist(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return (pdev->status & URBDRC_DEVICE_NOT_FOUND) ? 0 : 1; +} + +static int libusb_udev_is_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return (pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) ? 1 : 0; +} + +static int libusb_udev_is_already_send(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return (pdev->status & URBDRC_DEVICE_ALREADY_SEND) ? 1 : 0; +} + +static void libusb_udev_signal_end(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + pdev->status |= URBDRC_DEVICE_SIGNAL_END; +} + +static void libusb_udev_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED; +} + +static void libusb_udev_set_already_send(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + pdev->status |= URBDRC_DEVICE_ALREADY_SEND; +} + +static char* libusb_udev_get_path(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + return pdev->path; +} + +static int libusb_udev_wait_action_completion(IUDEVICE* idev) +{ + int error, sval; + UDEVICE* pdev = (UDEVICE*) idev; + + while (1) + { + usleep(500000); + error = sem_getvalue(&pdev->sem_id, &sval); + + if (sval == 0) + break; + } + + return error; +} + +static void libusb_udev_push_action(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + sem_post(&pdev->sem_id); +} + +static void libusb_udev_complete_action(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + sem_trywait(&pdev->sem_id); +} + +static int libusb_udev_wait_for_detach(IUDEVICE* idev) +{ + int error = 0; + int times = 0; + UDEVICE* pdev = (UDEVICE*) idev; + + while (times < 25) + { + if (pdev->status & URBDRC_DEVICE_SIGNAL_END) + { + error = -1; + break; + } + + usleep(200000); + times++; + } + + return error; +} + +static void libusb_udev_lock_fifo_isoch(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + pthread_mutex_lock(&pdev->mutex_isoch); +} + +static void libusb_udev_unlock_fifo_isoch(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + pthread_mutex_unlock(&pdev->mutex_isoch); +} + +static int libusb_udev_query_device_port_status(IUDEVICE* idev, UINT32* UsbdStatus, + UINT32* BufferSize, BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*) idev; + int success = 0, ret; + WLog_DBG(TAG, "..."); + + if (pdev->hub_handle != NULL) + { + ret = idev->control_transfer(idev, 0xffff, 0, 0, + LIBUSB_ENDPOINT_IN + | LIBUSB_REQUEST_TYPE_CLASS + | LIBUSB_RECIPIENT_OTHER, + LIBUSB_REQUEST_GET_STATUS, + 0, pdev->port_number, UsbdStatus, BufferSize, Buffer, 1000); + + if (ret < 0) + { + WLog_DBG(TAG, "libusb_control_transfer: error num %d", ret); + *BufferSize = 0; + } + else + { + WLog_DBG(TAG, "PORT STATUS:0x%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"", + Buffer[3], Buffer[2], Buffer[1], Buffer[0]); + success = 1; + } + } + + return success; +} + +static int libusb_udev_request_queue_is_none(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*) idev; + + if (pdev->request_queue->request_num == 0) + return 1; + + return 0; +} + +static int libusb_udev_isoch_transfer(IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress, + UINT32 TransferFlags, int NoAck, UINT32* ErrorCount, + UINT32* UrbdStatus, UINT32* StartFrame, UINT32 NumberOfPackets, + BYTE* IsoPacket, UINT32* BufferSize, BYTE* Buffer, int Timeout) +{ + UINT32 iso_packet_size; + UDEVICE* pdev = (UDEVICE*) idev; + ISO_USER_DATA iso_user_data; + struct libusb_transfer* iso_transfer = NULL; + int status = 0, ret = 0, submit = 0; + iso_packet_size = *BufferSize / NumberOfPackets; + iso_transfer = libusb_alloc_transfer(NumberOfPackets); + + if (iso_transfer == NULL) + { + WLog_ERR(TAG, "Error: libusb_alloc_transfer."); + status = -1; + } + + /** process URB_FUNCTION_IOSCH_TRANSFER */ + func_iso_data_init(&iso_user_data, NumberOfPackets, *BufferSize, NoAck, IsoPacket, Buffer); + /** fill setting */ + libusb_fill_iso_transfer(iso_transfer, + pdev->libusb_handle, EndpointAddress, Buffer, *BufferSize, + NumberOfPackets, func_iso_callback, &iso_user_data, 2000); + libusb_set_iso_packet_lengths(iso_transfer, iso_packet_size); + + if (pdev->status & (URBDRC_DEVICE_SIGNAL_END | URBDRC_DEVICE_NOT_FOUND)) + status = -1; + + iso_user_data.iso_status = 0; + + if (!(status < 0)) + { + submit = libusb_submit_transfer(iso_transfer); + + if (submit < 0) + { + WLog_DBG(TAG, "Error: Failed to submit transfer (ret = %d).", submit); + status = -1; + func_set_usbd_status(pdev, UrbdStatus, ret); + } + } + +#if ISOCH_FIFO + + if (!NoAck) + { + idev->unlock_fifo_isoch(idev); + } + +#endif + + while (pdev && iso_user_data.iso_status == 0 && status >= 0 && submit >= 0) + { + if (pdev->status & URBDRC_DEVICE_NOT_FOUND) + { + status = -1; + break; + } + + ret = handle_events_completed(NULL, &iso_user_data.completed); + + if (ret < 0) + { + WLog_DBG(TAG, "Error: libusb_handle_events (ret = %d).", ret); + status = -1; + break; + } + +#if WAIT_COMPLETE_SLEEP + + if (iso_user_data.iso_status == 0) + { + usleep(WAIT_COMPLETE_SLEEP); + } + +#endif + } + + if (iso_user_data.iso_status < 0) + status = -1; + + *ErrorCount = iso_user_data.error_count; + *StartFrame = iso_user_data.start_frame; + *BufferSize = iso_transfer->actual_length; + libusb_free_transfer(iso_transfer); + return status; +} + +static int libusb_udev_control_transfer(IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress, + UINT32 TransferFlags, BYTE bmRequestType, BYTE Request, UINT16 Value, UINT16 Index, + UINT32* UrbdStatus, UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout) +{ + int status = 0; + UDEVICE* pdev = (UDEVICE*) idev; + status = libusb_control_transfer(pdev->libusb_handle, + bmRequestType, Request, Value, Index, Buffer, *BufferSize, Timeout); + + if (!(status < 0)) + *BufferSize = status; + + func_set_usbd_status(pdev, UrbdStatus, status); + return status; +} + +static int libusb_udev_bulk_or_interrupt_transfer(IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, UINT32* UsbdStatus, UINT32* BufferSize, BYTE* Buffer, + UINT32 Timeout) +{ + UINT32 transfer_type; + UDEVICE* pdev = (UDEVICE*) idev; + const LIBUSB_ENDPOINT_DESCEIPTOR* ep_desc; + struct libusb_transfer* transfer = NULL; + TRANSFER_REQUEST* request = NULL; + int completed = 0, status = 0, submit = 0; + int transferDir = EndpointAddress & 0x80; + /* alloc memory for urb transfer */ + transfer = libusb_alloc_transfer(0); + ep_desc = func_get_ep_desc(pdev->LibusbConfig, pdev->MsConfig, EndpointAddress); + + if (!ep_desc) + { + WLog_ERR(TAG, "func_get_ep_desc: endpoint 0x%"PRIx32" is not found!!", EndpointAddress); + return -1; + } + + transfer_type = (ep_desc->bmAttributes) & 0x3; + WLog_DBG(TAG, "urb_bulk_or_interrupt_transfer: ep:0x%"PRIx32" " + "transfer_type %"PRIu32" flag:%"PRIu32" OutputBufferSize:0x%"PRIx32"", + EndpointAddress, transfer_type, TransferFlags, *BufferSize); + + switch (transfer_type) + { + case BULK_TRANSFER: + /** Bulk Transfer */ + //Timeout = 10000; + break; + + case INTERRUPT_TRANSFER: + + /** Interrupt Transfer */ + /** Sometime, we may have receive a oversized transfer request, + * it make submit urb return error, so we set the length of + * request to wMaxPacketSize */ + if (*BufferSize != (ep_desc->wMaxPacketSize)) + { + WLog_DBG(TAG, "Interrupt Transfer(%s): " + "BufferSize is different than maxPacketsize(0x%x)", + ((transferDir) ? "IN" : "OUT"), ep_desc->wMaxPacketSize); + + if ((*BufferSize) > (ep_desc->wMaxPacketSize) && + transferDir == USBD_TRANSFER_DIRECTION_IN) + (*BufferSize) = ep_desc->wMaxPacketSize; + } + + Timeout = 0; + break; + + default: + WLog_DBG(TAG, "urb_bulk_or_interrupt_transfer:" + " other transfer type 0x%"PRIX32"", transfer_type); + return -1; + break; + } + + libusb_fill_bulk_transfer(transfer, pdev->libusb_handle, EndpointAddress, + Buffer, *BufferSize, func_bulk_transfer_cb, &completed, Timeout); + transfer->type = (unsigned char) transfer_type; + /** Bug fixed in libusb-1.0-8 later: issue of memory crash */ + submit = libusb_submit_transfer(transfer); + + if (submit < 0) + { + WLog_DBG(TAG, "libusb_bulk_transfer: error num %d", status); + func_set_usbd_status(pdev, UsbdStatus, status); + *BufferSize = 0; + } + else + { + request = pdev->request_queue->register_request( + pdev->request_queue, RequestId, transfer, EndpointAddress); + request->submit = 1; + } + + if ((pdev && *UsbdStatus == 0) && (submit >= 0) && + (pdev->iface.isSigToEnd((IUDEVICE*) pdev) == 0)) + { + while (!completed) + { + status = handle_events_completed(NULL, &completed); + + if (status < 0) + { + if (status == LIBUSB_ERROR_INTERRUPTED) + continue; + + libusb_cancel_transfer(transfer); + + while (!completed) + { + if (handle_events_completed(NULL, &completed) < 0) + break; + +#if WAIT_COMPLETE_SLEEP + + if (!completed) + usleep(WAIT_COMPLETE_SLEEP); + +#endif + } + + break; + } + +#if WAIT_COMPLETE_SLEEP + + if (!completed) + usleep(WAIT_COMPLETE_SLEEP); + +#endif + } + + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: + func_set_usbd_status(pdev, UsbdStatus, 0); + break; + + case LIBUSB_TRANSFER_TIMED_OUT: + func_set_usbd_status(pdev, UsbdStatus, LIBUSB_ERROR_TIMEOUT); + break; + + case LIBUSB_TRANSFER_STALL: + func_set_usbd_status(pdev, UsbdStatus, LIBUSB_ERROR_PIPE); + break; + + case LIBUSB_TRANSFER_OVERFLOW: + func_set_usbd_status(pdev, UsbdStatus, LIBUSB_ERROR_OVERFLOW); + break; + + case LIBUSB_TRANSFER_NO_DEVICE: + func_set_usbd_status(pdev, UsbdStatus, LIBUSB_ERROR_NO_DEVICE); + break; + + default: + func_set_usbd_status(pdev, UsbdStatus, LIBUSB_ERROR_OTHER); + break; + } + + *BufferSize = transfer->actual_length; + } + + WLog_DBG(TAG, "bulk or interrupt Transfer data size : 0x%"PRIx32"", *BufferSize); + + if (request) + { + if (pdev->request_queue->unregister_request(pdev->request_queue, RequestId)) + WLog_ERR(TAG, "request_queue_unregister_request: not fount request 0x%"PRIx32"", RequestId); + } + + libusb_free_transfer(transfer); + return 0; +} + +static void libusb_udev_cancel_all_transfer_request(IUDEVICE* idev) +{ + int status; + UDEVICE* pdev = (UDEVICE*) idev; + REQUEST_QUEUE* request_queue = pdev->request_queue; + TRANSFER_REQUEST* request = NULL; + pthread_mutex_lock(&request_queue->request_loading); + request_queue->rewind(request_queue); + + while (request_queue->has_next(request_queue)) + { + request = request_queue->get_next(request_queue); + + if ((!request) || (!request->transfer) || + (request->endpoint != request->transfer->endpoint) || + (request->transfer->endpoint == 0) || (request->submit != 1)) + { + continue; + } + + status = libusb_cancel_transfer(request->transfer); + + if (status < 0) + { + WLog_DBG(TAG, "libusb_cancel_transfer: error num %d!!", status); + } + else + { + request->submit = -1; + } + } + + pthread_mutex_unlock(&request_queue->request_loading); +} + +static int func_cancel_xact_request(TRANSFER_REQUEST* request) +{ + int status; + + if (!request) + return -1; + + if ((!request->transfer) || (request->endpoint != request->transfer->endpoint) || + (request->transfer->endpoint == 0) || (request->submit != 1)) + { + return 0; + } + + status = libusb_cancel_transfer(request->transfer); + + if (status < 0) + { + WLog_DBG(TAG, "libusb_cancel_transfer: error num %d!!", status); + + if (status == LIBUSB_ERROR_NOT_FOUND) + return -1; + } + else + { + WLog_DBG(TAG, "libusb_cancel_transfer: Success num:0x%x!!", request->RequestId); + request->submit = -1; + return 1; + } + + return 0; +} + +static int libusb_udev_cancel_transfer_request(IUDEVICE* idev, UINT32 RequestId) +{ + UDEVICE* pdev = (UDEVICE*) idev; + REQUEST_QUEUE* request_queue = pdev->request_queue; + TRANSFER_REQUEST* request = NULL; + int status = 0, retry_times = 0; +cancel_retry: + pthread_mutex_lock(&request_queue->request_loading); + request_queue->rewind(request_queue); + + while (request_queue->has_next(request_queue)) + { + request = request_queue->get_next(request_queue); + + if (!request) + continue; + + WLog_DBG(TAG, "CancelId:0x%"PRIx32" RequestId:0x%x endpoint 0x%x!!", + RequestId, request->RequestId, request->endpoint); + + if (request->RequestId == (RequestId && retry_times <= 10)) + { + status = func_cancel_xact_request(request); + break; + } + else if ((request->transfer) && (retry_times > 10)) + { + status = -1; + break; + } + } + + pthread_mutex_unlock(&request_queue->request_loading); + + if ((status == 0) && (retry_times < 10)) + { + retry_times++; + usleep(100000); + WLog_DBG(TAG, "urbdrc_process_cancel_request: go retry!!"); + goto cancel_retry; + } + else if ((status < 0) || (retry_times >= 10)) + { + /** END */ + WLog_DBG(TAG, "urbdrc_process_cancel_request: error go exit!!"); + return -1; + } + + WLog_DBG(TAG, "urbdrc_process_cancel_request: success!!"); + return 0; +} + +BASIC_STATE_FUNC_DEFINED(channel_id, UINT32) +BASIC_STATE_FUNC_DEFINED(UsbDevice, UINT32) +BASIC_STATE_FUNC_DEFINED(ReqCompletion, UINT32) +BASIC_STATE_FUNC_DEFINED(bus_number, UINT16) +BASIC_STATE_FUNC_DEFINED(dev_number, UINT16) +BASIC_STATE_FUNC_DEFINED(port_number, int) +BASIC_STATE_FUNC_DEFINED(isoch_queue, void*) +BASIC_STATE_FUNC_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*) + +BASIC_POINT_FUNC_DEFINED(udev, void*) +BASIC_POINT_FUNC_DEFINED(prev, void*) +BASIC_POINT_FUNC_DEFINED(next, void*) + +static void udev_load_interface(UDEVICE* pdev) +{ + /* load interface */ + /* Basic */ + BASIC_STATE_FUNC_REGISTER(channel_id, pdev); + BASIC_STATE_FUNC_REGISTER(UsbDevice, pdev); + BASIC_STATE_FUNC_REGISTER(ReqCompletion, pdev); + BASIC_STATE_FUNC_REGISTER(bus_number, pdev); + BASIC_STATE_FUNC_REGISTER(dev_number, pdev); + BASIC_STATE_FUNC_REGISTER(port_number, pdev); + BASIC_STATE_FUNC_REGISTER(isoch_queue, pdev); + BASIC_STATE_FUNC_REGISTER(MsConfig, pdev); + BASIC_STATE_FUNC_REGISTER(p_udev, pdev); + BASIC_STATE_FUNC_REGISTER(p_prev, pdev); + BASIC_STATE_FUNC_REGISTER(p_next, pdev); + pdev->iface.isCompositeDevice = libusb_udev_is_composite_device; + pdev->iface.isSigToEnd = libusb_udev_is_signal_end; + pdev->iface.isExist = libusb_udev_is_exist; + pdev->iface.isAlreadySend = libusb_udev_is_already_send; + pdev->iface.isChannelClosed = libusb_udev_is_channel_closed; + pdev->iface.SigToEnd = libusb_udev_signal_end; + pdev->iface.setAlreadySend = libusb_udev_set_already_send; + pdev->iface.setChannelClosed = libusb_udev_channel_closed; + pdev->iface.getPath = libusb_udev_get_path; + /* Transfer */ + pdev->iface.isoch_transfer = libusb_udev_isoch_transfer; + pdev->iface.control_transfer = libusb_udev_control_transfer; + pdev->iface.bulk_or_interrupt_transfer = libusb_udev_bulk_or_interrupt_transfer; + pdev->iface.select_interface = libusb_udev_select_interface; + pdev->iface.select_configuration = libusb_udev_select_configuration; + pdev->iface.complete_msconfig_setup = libusb_udev_complete_msconfig_setup; + pdev->iface.control_pipe_request = libusb_udev_control_pipe_request; + pdev->iface.control_query_device_text = libusb_udev_control_query_device_text; + pdev->iface.os_feature_descriptor_request = libusb_udev_os_feature_descriptor_request; + pdev->iface.cancel_all_transfer_request = libusb_udev_cancel_all_transfer_request; + pdev->iface.cancel_transfer_request = libusb_udev_cancel_transfer_request; + pdev->iface.query_device_descriptor = libusb_udev_query_device_descriptor; + pdev->iface.detach_kernel_driver = libusb_udev_detach_kernel_driver; + pdev->iface.attach_kernel_driver = libusb_udev_attach_kernel_driver; + pdev->iface.wait_action_completion = libusb_udev_wait_action_completion; + pdev->iface.push_action = libusb_udev_push_action; + pdev->iface.complete_action = libusb_udev_complete_action; + pdev->iface.lock_fifo_isoch = libusb_udev_lock_fifo_isoch; + pdev->iface.unlock_fifo_isoch = libusb_udev_unlock_fifo_isoch; + pdev->iface.query_device_port_status = libusb_udev_query_device_port_status; + pdev->iface.request_queue_is_none = libusb_udev_request_queue_is_none; + pdev->iface.wait_for_detach = libusb_udev_wait_for_detach; +} + +static IUDEVICE* udev_init(UDEVICE* pdev, UINT16 bus_number, UINT16 dev_number) +{ + int status, num; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor; + LIBUSB_CONFIG_DESCRIPTOR* config_temp; + LIBUSB_INTERFACE_DESCRIPTOR interface_temp; + /* Get HUB handle */ + status = udev_get_hub_handle(pdev, bus_number, dev_number); + + if (status < 0) + { + WLog_ERR(TAG, "USB init: Error to get HUB handle!!"); + pdev->hub_handle = NULL; + } + + pdev->devDescriptor = udev_new_descript(pdev->libusb_dev); + + if (!pdev->devDescriptor) + { + WLog_ERR(TAG, "USB init: Error to get device descriptor!!"); + zfree(pdev); + return NULL; + } + + num = pdev->devDescriptor->bNumConfigurations; + status = libusb_get_active_config_descriptor(pdev->libusb_dev, &pdev->LibusbConfig); + + if (status < 0) + { + WLog_ERR(TAG, "libusb_get_descriptor: ERROR!!ret:%d", status); + zfree(pdev); + return NULL; + } + + config_temp = pdev->LibusbConfig; + /* get the first interface and first altsetting */ + interface_temp = config_temp->interface[0].altsetting[0]; + WLog_DBG(TAG, "Registered Device: Vid: 0x%04"PRIX16" Pid: 0x%04"PRIX16"" + " InterfaceClass = 0x%02"PRIX8"", + pdev->devDescriptor->idVendor, + pdev->devDescriptor->idProduct, + interface_temp.bInterfaceClass); + + /* Denied list */ + switch (interface_temp.bInterfaceClass) + { + case CLASS_RESERVE: + + //case CLASS_COMMUNICATION_IF: + //case CLASS_HID: + //case CLASS_PHYSICAL: + case CLASS_MASS_STORAGE: + case CLASS_HUB: + + //case CLASS_COMMUNICATION_DATA_IF: + case CLASS_SMART_CARD: + case CLASS_CONTENT_SECURITY: + //case CLASS_WIRELESS_CONTROLLER: + //case CLASS_ELSE_DEVICE: + WLog_ERR(TAG, " Device is not supported!!"); + zfree(pdev); + return NULL; + + default: + break; + } + + /* Check composite device */ + devDescriptor = pdev->devDescriptor; + + if ((devDescriptor->bNumConfigurations == 1) && + (config_temp->bNumInterfaces > 1) && + (devDescriptor->bDeviceClass == 0x0)) + { + pdev->isCompositeDevice = 1; + } + else if ((devDescriptor->bDeviceClass == 0xef) && + (devDescriptor->bDeviceSubClass == 0x02) && + (devDescriptor->bDeviceProtocol == 0x01)) + { + pdev->isCompositeDevice = 1; + } + else + { + pdev->isCompositeDevice = 0; + } + + /* set device class to first interface class */ + devDescriptor->bDeviceClass = interface_temp.bInterfaceClass; + devDescriptor->bDeviceSubClass = interface_temp.bInterfaceSubClass; + devDescriptor->bDeviceProtocol = interface_temp.bInterfaceProtocol; + /* initialize pdev */ + pdev->prev = NULL; + pdev->next = NULL; + pdev->bus_number = bus_number; + pdev->dev_number = dev_number; + pdev->status = 0; + pdev->ReqCompletion = 0; + pdev->channel_id = 0xffff; + pdev->request_queue = request_queue_new(); + pdev->isoch_queue = NULL; + sem_init(&pdev->sem_id, 0, 0); + /* set config of windows */ + pdev->MsConfig = msusb_msconfig_new(); + pthread_mutex_init(&pdev->mutex_isoch, NULL); + //deb_config_msg(pdev->libusb_dev, config_temp, devDescriptor->bNumConfigurations); + udev_load_interface(pdev); + return (IUDEVICE*) pdev; +} + +int udev_new_by_id(UINT16 idVendor, UINT16 idProduct, IUDEVICE** * devArray) +{ + LIBUSB_DEVICE_DESCRIPTOR* descriptor; + LIBUSB_DEVICE** libusb_list; + UDEVICE** array; + UINT16 bus_number; + UINT16 dev_number; + ssize_t i, total_device; + int status, num = 0; + WLog_INFO(TAG, "VID: 0x%04"PRIX16", PID: 0x%04"PRIX16"", idVendor, idProduct); + array = (UDEVICE**) malloc(16 * sizeof(UDEVICE*)); + total_device = libusb_get_device_list(NULL, &libusb_list); + + for (i = 0; i < total_device; i++) + { + descriptor = udev_new_descript(libusb_list[i]); + + if ((descriptor->idVendor == idVendor) && (descriptor->idProduct == idProduct)) + { + bus_number = 0; + dev_number = 0; + array[num] = (PUDEVICE) malloc(sizeof(UDEVICE)); + array[num]->libusb_dev = libusb_list[i]; + status = libusb_open(libusb_list[i], &array[num]->libusb_handle); + + if (status < 0) + { + WLog_ERR(TAG, "libusb_open: (by id) error: 0x%08X (%d)", status, status); + zfree(descriptor); + zfree(array[num]); + continue; + } + + bus_number = libusb_get_bus_number(libusb_list[i]); + dev_number = libusb_get_device_address(libusb_list[i]); + array[num] = (PUDEVICE) udev_init(array[num], bus_number, dev_number); + + if (array[num] != NULL) + num++; + } + + zfree(descriptor); + } + + libusb_free_device_list(libusb_list, 1); + *devArray = (IUDEVICE**) array; + return num; +} + +IUDEVICE* udev_new_by_addr(int bus_number, int dev_number) +{ + int status; + UDEVICE* pDev; + WLog_DBG(TAG, "bus:%d dev:%d", bus_number, dev_number); + pDev = (PUDEVICE) malloc(sizeof(UDEVICE)); + pDev->libusb_dev = udev_get_libusb_dev(bus_number, dev_number); + + if (pDev->libusb_dev == NULL) + { + WLog_ERR(TAG, "libusb_device_new: ERROR!!"); + zfree(pDev); + return NULL; + } + + status = libusb_open(pDev->libusb_dev, &pDev->libusb_handle); + + if (status < 0) + { + WLog_ERR(TAG, "libusb_open: (by addr) ERROR!!"); + zfree(pDev); + return NULL; + } + + return udev_init(pDev, bus_number, dev_number); +} diff --git a/channels/urbdrc/client/libusb/libusb_udevice.h b/channels/urbdrc/client/libusb/libusb_udevice.h new file mode 100644 index 0000000..c79dfca --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevice.h @@ -0,0 +1,85 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#include +#else +#include +#endif + +#include "urbdrc_types.h" +#include "request_queue.h" +#include "urbdrc_main.h" + +typedef struct libusb_device LIBUSB_DEVICE; +typedef struct libusb_device_handle LIBUSB_DEVICE_HANDLE; +typedef struct libusb_device_descriptor LIBUSB_DEVICE_DESCRIPTOR; +typedef struct libusb_config_descriptor LIBUSB_CONFIG_DESCRIPTOR; +typedef struct libusb_interface LIBUSB_INTERFACE; +typedef struct libusb_interface_descriptor LIBUSB_INTERFACE_DESCRIPTOR; +typedef struct libusb_endpoint_descriptor LIBUSB_ENDPOINT_DESCEIPTOR; + +typedef struct _UDEVICE UDEVICE; + +struct _UDEVICE +{ + IUDEVICE iface; + + void * udev; + void * prev; + void * next; + + UINT32 UsbDevice; /* An unique interface ID */ + UINT32 ReqCompletion; /* An unique interface ID */ + UINT32 channel_id; + UINT16 status; + UINT16 bus_number; + UINT16 dev_number; + char path[17]; + int port_number; + int isCompositeDevice; + + LIBUSB_DEVICE_HANDLE * libusb_handle; + LIBUSB_DEVICE_HANDLE * hub_handle; + LIBUSB_DEVICE * libusb_dev; + LIBUSB_DEVICE_DESCRIPTOR * devDescriptor; + MSUSB_CONFIG_DESCRIPTOR * MsConfig; + LIBUSB_CONFIG_DESCRIPTOR * LibusbConfig; + + REQUEST_QUEUE * request_queue; + /* Used in isochronous transfer */ + void * isoch_queue; + + pthread_mutex_t mutex_isoch; + sem_t sem_id; +}; +typedef UDEVICE * PUDEVICE; + +int udev_new_by_id(UINT16 idVendor, UINT16 idProduct, IUDEVICE ***devArray); +IUDEVICE* udev_new_by_addr(int bus_number, int dev_number); + +extern int libusb_debug; + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H */ diff --git a/channels/urbdrc/client/libusb/libusb_udevman.c b/channels/urbdrc/client/libusb/libusb_udevman.c new file mode 100644 index 0000000..b0d0fbc --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevman.c @@ -0,0 +1,586 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include + +#include +#include + +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" + +#include "libusb_udevice.h" + +int libusb_debug; + +#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \ + static _type udevman_get_##_arg (IUDEVMAN* idevman) \ + { \ + UDEVMAN * udevman = (UDEVMAN *) idevman; \ + return udevman->_arg; \ + } \ + static void udevman_set_##_arg (IUDEVMAN* idevman, _type _t) \ + { \ + UDEVMAN * udevman = (UDEVMAN *) idevman; \ + udevman->_arg = _t; \ + } + +#define BASIC_STATE_FUNC_REGISTER(_arg, _man) \ + _man->iface.get_##_arg = udevman_get_##_arg; \ + _man->iface.set_##_arg = udevman_set_##_arg + +typedef struct _UDEVMAN UDEVMAN; + +struct _UDEVMAN +{ + IUDEVMAN iface; + + IUDEVICE* idev; /* iterator device */ + IUDEVICE* head; /* head device in linked list */ + IUDEVICE* tail; /* tail device in linked list */ + + UINT32 defUsbDevice; + UINT16 flags; + int device_num; + int sem_timeout; + + pthread_mutex_t devman_loading; + sem_t sem_urb_lock; +}; +typedef UDEVMAN* PUDEVMAN; + +static void udevman_rewind(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + udevman->idev = udevman->head; +} + +static int udevman_has_next(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + + if (udevman->idev == NULL) + return 0; + else + return 1; +} + +static IUDEVICE* udevman_get_next(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + IUDEVICE* pdev; + pdev = udevman->idev; + udevman->idev = (IUDEVICE*)((UDEVICE*) udevman->idev)->next; + return pdev; +} + +static IUDEVICE* udevman_get_udevice_by_addr(IUDEVMAN* idevman, int bus_number, int dev_number) +{ + IUDEVICE* pdev; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = idevman->get_next(idevman); + + if ((pdev->get_bus_number(pdev) == bus_number) && (pdev->get_dev_number(pdev) == dev_number)) + { + idevman->loading_unlock(idevman); + return pdev; + } + } + + idevman->loading_unlock(idevman); + WLog_WARN(TAG, "bus:%d dev:%d not exist in udevman", + bus_number, dev_number); + return NULL; +} + +static int udevman_register_udevice(IUDEVMAN* idevman, int bus_number, int dev_number, + int UsbDevice, UINT16 idVendor, UINT16 idProduct, int flag) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + IUDEVICE* pdev = NULL; + IUDEVICE** devArray; + int i, num, addnum = 0; + pdev = (IUDEVICE*) udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + + if (pdev != NULL) + return 0; + + if (flag == UDEVMAN_FLAG_ADD_BY_ADDR) + { + pdev = udev_new_by_addr(bus_number, dev_number); + + if (pdev == NULL) + return 0; + + pdev->set_UsbDevice(pdev, UsbDevice); + idevman->loading_lock(idevman); + + if (udevman->head == NULL) + { + /* linked list is empty */ + udevman->head = pdev; + udevman->tail = pdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, pdev); + pdev->set_p_prev(pdev, udevman->tail); + udevman->tail = pdev; + } + + udevman->device_num += 1; + idevman->loading_unlock(idevman); + } + else if (flag == UDEVMAN_FLAG_ADD_BY_VID_PID) + { + addnum = 0; + /* register all device that match pid vid */ + num = udev_new_by_id(idVendor, idProduct, &devArray); + + for (i = 0; i < num; i++) + { + pdev = devArray[i]; + + if (udevman_get_udevice_by_addr(idevman, + pdev->get_bus_number(pdev), pdev->get_dev_number(pdev)) != NULL) + { + zfree(pdev); + continue; + } + + pdev->set_UsbDevice(pdev, UsbDevice); + idevman->loading_lock(idevman); + + if (udevman->head == NULL) + { + /* linked list is empty */ + udevman->head = pdev; + udevman->tail = pdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, pdev); + pdev->set_p_prev(pdev, udevman->tail); + udevman->tail = pdev; + } + + udevman->device_num += 1; + idevman->loading_unlock(idevman); + addnum++; + } + + zfree(devArray); + return addnum; + } + else + { + WLog_ERR(TAG, "udevman_register_udevice: function error!!"); + return 0; + } + + return 1; +} + +static int udevman_unregister_udevice(IUDEVMAN* idevman, int bus_number, int dev_number) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + UDEVICE* pdev, * dev; + int ret = 0, err = 0; + dev = (UDEVICE*) udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman) != 0) + { + pdev = (UDEVICE*) idevman->get_next(idevman); + + if (pdev == dev) /* device exists */ + { + /* set previous device to point to next device */ + if (dev->prev != NULL) + { + /* unregistered device is not the head */ + pdev = dev->prev; + pdev->next = dev->next; + } + else + { + /* unregistered device is the head, update head */ + udevman->head = (IUDEVICE*)dev->next; + } + + /* set next device to point to previous device */ + + if (dev->next != NULL) + { + /* unregistered device is not the tail */ + pdev = (UDEVICE*)dev->next; + pdev->prev = dev->prev; + } + else + { + /* unregistered device is the tail, update tail */ + udevman->tail = (IUDEVICE*)dev->prev; + } + + udevman->device_num--; + break; + } + } + + idevman->loading_unlock(idevman); + + if (dev) + { + /* reset device */ + if (err != LIBUSB_ERROR_NO_DEVICE) + { + ret = libusb_reset_device(dev->libusb_handle); + + if (ret < 0) + { + WLog_ERR(TAG, "libusb_reset_device: ERROR!!ret:%d", ret); + } + } + + /* release all interface and attach kernel driver */ + dev->iface.attach_kernel_driver((IUDEVICE*)dev); + + if (dev->request_queue) zfree(dev->request_queue); + + /* free the config descriptor that send from windows */ + msusb_msconfig_free(dev->MsConfig); + libusb_close(dev->libusb_handle); + libusb_close(dev->hub_handle); + sem_destroy(&dev->sem_id); + + /* free device info */ + if (dev->devDescriptor) + zfree(dev->devDescriptor); + + if (dev) + zfree(dev); + + return 1; /* unregistration successful */ + } + + /* if we reach this point, the device wasn't found */ + return 0; +} + +static void udevman_parse_device_addr(char* str, int* id1, int* id2, char sign) +{ + char s1[8]; + char* s2; + ZeroMemory(s1, sizeof(s1)); + s2 = (strchr(str, sign)) + 1; + strncpy(s1, str, strlen(str) - (strlen(s2) + 1)); + *id1 = strtol(s1, NULL, 0); + *id2 = strtol(s2, NULL, 0); +} + +static void udevman_parse_device_pid_vid(char* str, int* id1, int* id2, char sign) +{ + char s1[8]; + char* s2; + ZeroMemory(s1, sizeof(s1)); + s2 = (strchr(str, sign)) + 1; + strncpy(s1, str, strlen(str) - (strlen(s2) + 1)); + *id1 = strtol(s1, NULL, 16); + *id2 = strtol(s2, NULL, 16); +} + +static int udevman_check_device_exist_by_id(IUDEVMAN* idevman, UINT16 idVendor, UINT16 idProduct) +{ + if (libusb_open_device_with_vid_pid(NULL, idVendor, idProduct)) + return 1; + + return 0; +} + +static int udevman_is_auto_add(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + return (udevman->flags & UDEVMAN_FLAG_ADD_BY_AUTO) ? 1 : 0; +} + +static IUDEVICE* udevman_get_udevice_by_UsbDevice_try_again(IUDEVMAN* idevman, UINT32 UsbDevice) +{ + UDEVICE* pdev; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*) idevman->get_next(idevman); + + if (pdev->UsbDevice == UsbDevice) + { + idevman->loading_unlock(idevman); + return (IUDEVICE*) pdev; + } + } + + idevman->loading_unlock(idevman); + return NULL; +} + +static IUDEVICE* udevman_get_udevice_by_UsbDevice(IUDEVMAN* idevman, UINT32 UsbDevice) +{ + UDEVICE* pdev; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*) idevman->get_next(idevman); + + if (pdev->UsbDevice == UsbDevice) + { + idevman->loading_unlock(idevman); + return (IUDEVICE*) pdev; + } + } + + idevman->loading_unlock(idevman); + /* try again */ + pdev = (UDEVICE*) idevman->get_udevice_by_UsbDevice_try_again(idevman, UsbDevice); + + if (pdev) + { + return (IUDEVICE*) pdev; + } + + WLog_ERR(TAG, "0x%"PRIx32" ERROR!!", UsbDevice); + return NULL; +} + +static void udevman_loading_lock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + pthread_mutex_lock(&udevman->devman_loading); +} + +static void udevman_loading_unlock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + pthread_mutex_unlock(&udevman->devman_loading); +} + +static void udevman_wait_urb(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + sem_wait(&udevman->sem_urb_lock); +} + +static void udevman_push_urb(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + sem_post(&udevman->sem_urb_lock); +} + +BASIC_STATE_FUNC_DEFINED(defUsbDevice, UINT32) +BASIC_STATE_FUNC_DEFINED(device_num, int) +BASIC_STATE_FUNC_DEFINED(sem_timeout, int) + +static void udevman_free(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*) idevman; + pthread_mutex_destroy(&udevman->devman_loading); + sem_destroy(&udevman->sem_urb_lock); + libusb_exit(NULL); + + /* free udevman */ + + if (udevman) + zfree(udevman); +} + +static void udevman_load_interface(UDEVMAN* udevman) +{ + /* standard */ + udevman->iface.free = udevman_free; + /* manage devices */ + udevman->iface.rewind = udevman_rewind; + udevman->iface.get_next = udevman_get_next; + udevman->iface.has_next = udevman_has_next; + udevman->iface.register_udevice = udevman_register_udevice; + udevman->iface.unregister_udevice = udevman_unregister_udevice; + udevman->iface.get_udevice_by_UsbDevice = udevman_get_udevice_by_UsbDevice; + udevman->iface.get_udevice_by_UsbDevice_try_again = + udevman_get_udevice_by_UsbDevice_try_again; + /* Extension */ + udevman->iface.check_device_exist_by_id = udevman_check_device_exist_by_id; + udevman->iface.isAutoAdd = udevman_is_auto_add; + /* Basic state */ + BASIC_STATE_FUNC_REGISTER(defUsbDevice, udevman); + BASIC_STATE_FUNC_REGISTER(device_num, udevman); + BASIC_STATE_FUNC_REGISTER(sem_timeout, udevman); + /* control semaphore or mutex lock */ + udevman->iface.loading_lock = udevman_loading_lock; + udevman->iface.loading_unlock = udevman_loading_unlock; + udevman->iface.push_urb = udevman_push_urb; + udevman->iface.wait_urb = udevman_wait_urb; +} + +COMMAND_LINE_ARGUMENT_A urbdrc_udevman_args[] = +{ + { "dbg", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "debug" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "device list" }, + { "id", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_VID_PID" }, + { "addr", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_ADDR" }, + { "auto", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "FLAG_ADD_BY_AUTO" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +static void urbdrc_udevman_register_devices(UDEVMAN* udevman, char* devices) +{ + char* token; + int idVendor; + int idProduct; + int bus_number; + int dev_number; + int success = 0; + char hardware_id[16]; + char* default_devices = "id"; + UINT32 UsbDevice = BASE_USBDEVICE_NUM; + + if (!devices) + devices = default_devices; + + /* register all usb devices */ + token = strtok(devices, "#"); + + while (token) + { + bus_number = 0; + dev_number = 0; + idVendor = 0; + idProduct = 0; + sprintf_s(hardware_id, ARRAYSIZE(hardware_id), "%s", token); + token = strtok(NULL, "#"); + + if (udevman->flags & UDEVMAN_FLAG_ADD_BY_VID_PID) + { + udevman_parse_device_pid_vid(hardware_id, &idVendor, &idProduct, ':'); + success = udevman->iface.register_udevice((IUDEVMAN*) udevman, + 0, 0, UsbDevice, (UINT16) idVendor, (UINT16) idProduct, UDEVMAN_FLAG_ADD_BY_VID_PID); + } + else if (udevman->flags & UDEVMAN_FLAG_ADD_BY_ADDR) + { + udevman_parse_device_addr(hardware_id, &bus_number, &dev_number, ':'); + success = udevman->iface.register_udevice((IUDEVMAN*) udevman, + bus_number, dev_number, UsbDevice, 0, 0, UDEVMAN_FLAG_ADD_BY_ADDR); + } + + if (success) + UsbDevice++; + } + + udevman->defUsbDevice = UsbDevice; +} + +static void urbdrc_udevman_parse_addin_args(UDEVMAN* udevman, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, + urbdrc_udevman_args, flags, udevman, NULL, NULL); + arg = urbdrc_udevman_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dbg") + { + WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE); + } + CommandLineSwitchCase(arg, "dev") + { + urbdrc_udevman_register_devices(udevman, arg->Value); + } + CommandLineSwitchCase(arg, "id") + { + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + } + CommandLineSwitchCase(arg, "addr") + { + udevman->flags = UDEVMAN_FLAG_ADD_BY_ADDR; + } + CommandLineSwitchCase(arg, "auto") + { + udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); +} + +#ifdef BUILTIN_CHANNELS +#define freerdp_urbdrc_client_subsystem_entry libusb_freerdp_urbdrc_client_subsystem_entry +#else +#define freerdp_urbdrc_client_subsystem_entry FREERDP_API freerdp_urbdrc_client_subsystem_entry +#endif + +int freerdp_urbdrc_client_subsystem_entry(PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints) +{ + UDEVMAN* udevman; + ADDIN_ARGV* args = pEntryPoints->args; + libusb_init(NULL); + udevman = (PUDEVMAN) malloc(sizeof(UDEVMAN)); + + if (!udevman) + return -1; + + udevman->device_num = 0; + udevman->idev = NULL; + udevman->head = NULL; + udevman->tail = NULL; + udevman->sem_timeout = 0; + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + pthread_mutex_init(&udevman->devman_loading, NULL); + sem_init(&udevman->sem_urb_lock, 0, MAX_URB_REQUSET_NUM); + /* load usb device service management */ + udevman_load_interface(udevman); + /* set debug flag, to enable Debug message for usb data transfer */ + libusb_debug = 10; + urbdrc_udevman_parse_addin_args(udevman, args); + pEntryPoints->pRegisterUDEVMAN(pEntryPoints->plugin, (IUDEVMAN*) udevman); + WLog_DBG(TAG, "UDEVMAN device registered."); + return 0; +} diff --git a/channels/urbdrc/client/libusb/request_queue.c b/channels/urbdrc/client/libusb/request_queue.c new file mode 100644 index 0000000..86035f1 --- /dev/null +++ b/channels/urbdrc/client/libusb/request_queue.c @@ -0,0 +1,180 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include +#include "request_queue.h" + +TRANSFER_REQUEST* request_queue_get_next(REQUEST_QUEUE* queue) +{ + TRANSFER_REQUEST* request; + + request = queue->ireq; + queue->ireq = (TRANSFER_REQUEST *)queue->ireq->next; + + return request; +} + +int request_queue_has_next(REQUEST_QUEUE* queue) +{ + if (queue->ireq == NULL) + return 0; + else + return 1; +} + +TRANSFER_REQUEST* request_queue_register_request(REQUEST_QUEUE* queue, UINT32 RequestId, + struct libusb_transfer* transfer, BYTE endpoint) +{ + TRANSFER_REQUEST* request; + + request = (TRANSFER_REQUEST*) malloc(sizeof(TRANSFER_REQUEST)); + + request->prev = NULL; + request->next = NULL; + + request->RequestId = RequestId; + request->transfer = transfer; + request->endpoint = endpoint; + request->submit = 0; + + pthread_mutex_lock(&queue->request_loading); + + if (queue->head == NULL) + { + /* linked queue is empty */ + queue->head = request; + queue->tail = request; + } + else + { + /* append data to the end of the linked queue */ + queue->tail->next = (void*) request; + request->prev = (void*) queue->tail; + queue->tail = request; + } + + queue->request_num += 1; + pthread_mutex_unlock(&queue->request_loading); + + return request; +} + +void request_queue_rewind(REQUEST_QUEUE* queue) +{ + queue->ireq = queue->head; +} + +/* Get first*/ +TRANSFER_REQUEST* request_queue_get_request_by_endpoint(REQUEST_QUEUE* queue, BYTE ep) +{ + TRANSFER_REQUEST * request; + pthread_mutex_lock(&queue->request_loading); + queue->rewind (queue); + while (queue->has_next (queue)) + { + request = queue->get_next (queue); + if (request->endpoint == ep) + { + pthread_mutex_unlock(&queue->request_loading); + return request; + } + } + pthread_mutex_unlock(&queue->request_loading); + WLog_ERR(TAG, "request_queue_get_request_by_id: ERROR!!"); + return NULL; +} + +int request_queue_unregister_request(REQUEST_QUEUE* queue, UINT32 RequestId) +{ + TRANSFER_REQUEST *request, *request_temp; + pthread_mutex_lock(&queue->request_loading); + queue->rewind(queue); + + while (queue->has_next(queue) != 0) + { + request = queue->get_next(queue); + + if (request->RequestId == RequestId) + { + + if (request->prev != NULL) + { + request_temp = (TRANSFER_REQUEST*) request->prev; + request_temp->next = (TRANSFER_REQUEST*) request->next; + } + else + { + queue->head = (TRANSFER_REQUEST*) request->next; + } + + if (request->next != NULL) + { + request_temp = (TRANSFER_REQUEST*) request->next; + request_temp->prev = (TRANSFER_REQUEST*) request->prev; + } + else + { + queue->tail = (TRANSFER_REQUEST*) request->prev; + + } + + queue->request_num--; + + if (request) + { + request->transfer = NULL; + zfree(request); + } + + pthread_mutex_unlock(&queue->request_loading); + + return 0; + } + } + pthread_mutex_unlock(&queue->request_loading); + /* it wasn't found */ + return 1; +} + +REQUEST_QUEUE* request_queue_new() +{ + REQUEST_QUEUE* queue; + + queue = (REQUEST_QUEUE*) malloc(sizeof(REQUEST_QUEUE)); + queue->request_num = 0; + queue->ireq = NULL; + queue->head = NULL; + queue->tail = NULL; + + pthread_mutex_init(&queue->request_loading, NULL); + + /* load service */ + queue->get_next = request_queue_get_next; + queue->has_next = request_queue_has_next; + queue->rewind = request_queue_rewind; + queue->register_request = request_queue_register_request; + queue->unregister_request = request_queue_unregister_request; + queue->get_request_by_ep = request_queue_get_request_by_endpoint; + + return queue; +} diff --git a/channels/urbdrc/client/libusb/request_queue.h b/channels/urbdrc/client/libusb/request_queue.h new file mode 100644 index 0000000..35e80c5 --- /dev/null +++ b/channels/urbdrc/client/libusb/request_queue.h @@ -0,0 +1,65 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_REQUEST_QUEUE_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_REQUEST_QUEUE_H + +#include "urbdrc_types.h" + +typedef struct _TRANSFER_REQUEST TRANSFER_REQUEST; +typedef struct _REQUEST_QUEUE REQUEST_QUEUE; + +struct _TRANSFER_REQUEST +{ + void* request; + void* prev; + void* next; + + UINT32 RequestId; + BYTE endpoint; + struct libusb_transfer *transfer; + int submit; +}; + + +struct _REQUEST_QUEUE +{ + int request_num; + TRANSFER_REQUEST* ireq; /* iterator request */ + TRANSFER_REQUEST* head; /* head request in linked queue */ + TRANSFER_REQUEST* tail; /* tail request in linked queue */ + + pthread_mutex_t request_loading; + + /* request queue manager service */ + void (*rewind) (REQUEST_QUEUE *queue); + int (*has_next) (REQUEST_QUEUE* queue); + int (*unregister_request) (REQUEST_QUEUE *queue, UINT32 RequestId); + TRANSFER_REQUEST *(*get_next) (REQUEST_QUEUE* queue); + TRANSFER_REQUEST *(*get_request_by_ep) (REQUEST_QUEUE *queue, BYTE ep); + TRANSFER_REQUEST *(*register_request) (REQUEST_QUEUE* queue, + UINT32 RequestId, struct libusb_transfer * transfer, BYTE endpoint); +}; + + +REQUEST_QUEUE* request_queue_new(void); + + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_REQUEST_QUEUE_H */ diff --git a/channels/urbdrc/client/searchman.c b/channels/urbdrc/client/searchman.c new file mode 100644 index 0000000..e87102c --- /dev/null +++ b/channels/urbdrc/client/searchman.c @@ -0,0 +1,239 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include + +#include +#include + +#include "searchman.h" + +static void searchman_rewind(USB_SEARCHMAN* searchman) +{ + searchman->idev = searchman->head; +} + +static int searchman_has_next(USB_SEARCHMAN* searchman) +{ + if (searchman->idev == NULL) + return 0; + else + return 1; +} + +static USB_SEARCHDEV* searchman_get_next(USB_SEARCHMAN* searchman) +{ + USB_SEARCHDEV* search; + + search = searchman->idev; + searchman->idev = (USB_SEARCHDEV*) searchman->idev->next; + + return search; +} + +static BOOL searchman_list_add(USB_SEARCHMAN* searchman, UINT16 idVendor, UINT16 idProduct) +{ + USB_SEARCHDEV* search; + + search = (USB_SEARCHDEV*) calloc(1, sizeof(USB_SEARCHDEV)); + if (!search) + return FALSE; + + search->idVendor = idVendor; + search->idProduct = idProduct; + + if (searchman->head == NULL) + { + /* linked list is empty */ + searchman->head = search; + searchman->tail = search; + } + else + { + /* append device to the end of the linked list */ + searchman->tail->next = (void*)search; + search->prev = (void*)searchman->tail; + searchman->tail = search; + } + searchman->usb_numbers += 1; + + return TRUE; +} + +static int searchman_list_remove(USB_SEARCHMAN* searchman, UINT16 idVendor, UINT16 idProduct) +{ + USB_SEARCHDEV* search; + USB_SEARCHDEV* point; + + searchman_rewind(searchman); + + while (searchman_has_next(searchman) != 0) + { + point = searchman_get_next(searchman); + + if (point->idVendor == idVendor && + point->idProduct == idProduct) + { + /* set previous device to point to next device */ + + search = point; + if (search->prev != NULL) + { + /* unregistered device is not the head */ + point = (USB_SEARCHDEV*)search->prev; + point->next = search->next; + } + else + { + /* unregistered device is the head, update head */ + searchman->head = (USB_SEARCHDEV*)search->next; + } + + /* set next device to point to previous device */ + + if (search->next != NULL) + { + /* unregistered device is not the tail */ + point = (USB_SEARCHDEV*)search->next; + point->prev = search->prev; + } + else + { + /* unregistered device is the tail, update tail */ + searchman->tail = (USB_SEARCHDEV*)search->prev; + } + searchman->usb_numbers--; + + free(search); + + return 1; /* unregistration successful */ + } + } + + /* if we reach this point, the device wasn't found */ + return 0; +} + +static BOOL searchman_start(USB_SEARCHMAN* self, void* (*func)(void*)) +{ + pthread_t thread; + + /* create search thread */ + if (pthread_create(&thread, 0, func, self) != 0) + return FALSE; + + if (pthread_detach(thread) != 0) + return FALSE; + + self->started = 1; + return TRUE; +} + +/* close thread */ +static void searchman_close(USB_SEARCHMAN* self) +{ + SetEvent(self->term_event); +} + +static void searchman_list_show(USB_SEARCHMAN* self) +{ + int num = 0; + USB_SEARCHDEV* usb; + WLog_DBG(TAG, "=========== Usb Search List ========="); + self->rewind(self); + while (self->has_next(self)) + { + usb = self->get_next(self); + WLog_DBG(TAG, " USB %d: ", num++); + WLog_DBG(TAG, " idVendor: 0x%04"PRIX16"", usb->idVendor); + WLog_DBG(TAG, " idProduct: 0x%04"PRIX16"", usb->idProduct); + } + + WLog_DBG(TAG, "================= END ==============="); +} + +void searchman_free(USB_SEARCHMAN* self) +{ + USB_SEARCHDEV * dev; + + while (self->head != NULL) + { + dev = (USB_SEARCHDEV *)self->head; + self->remove (self, dev->idVendor, dev->idProduct); + } + + /* free searchman */ + sem_destroy(&self->sem_term); + CloseHandle(self->term_event); + free(self); +} + +USB_SEARCHMAN* searchman_new(void * urbdrc, UINT32 UsbDevice) +{ + int ret; + USB_SEARCHMAN* searchman; + + searchman = (USB_SEARCHMAN*) calloc(1, sizeof(USB_SEARCHMAN)); + if (!searchman) + return NULL; + + searchman->urbdrc = urbdrc; + searchman->UsbDevice = UsbDevice; + + ret = pthread_mutex_init(&searchman->mutex, NULL); + if (ret != 0) + { + WLog_ERR(TAG, "searchman mutex initialization: searchman->mutex failed"); + goto out_error_mutex; + } + + /* load service */ + searchman->add = searchman_list_add; + searchman->remove = searchman_list_remove; + searchman->rewind = searchman_rewind; + searchman->get_next = searchman_get_next; + searchman->has_next = searchman_has_next; + searchman->show = searchman_list_show; + searchman->start = searchman_start; + searchman->close = searchman_close; + searchman->free = searchman_free; + + searchman->started = 0; + searchman->term_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!searchman->term_event) + goto out_error_event; + + if (sem_init(&searchman->sem_term, 0, 0) < 0) + goto out_error_sem; + + return searchman; + +out_error_sem: + CloseHandle(searchman->term_event); +out_error_event: + pthread_mutex_destroy(&searchman->mutex); +out_error_mutex: + free(searchman); + return NULL; +} diff --git a/channels/urbdrc/client/searchman.h b/channels/urbdrc/client/searchman.h new file mode 100644 index 0000000..b449a98 --- /dev/null +++ b/channels/urbdrc/client/searchman.h @@ -0,0 +1,78 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_SEARCHMAN_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_SEARCHMAN_H + +#include "urbdrc_types.h" + +typedef struct _USB_SEARCHDEV USB_SEARCHDEV; + +struct _USB_SEARCHDEV +{ + void* inode; + void* prev; + void* next; + UINT16 idVendor; + UINT16 idProduct; +}; + +typedef struct _USB_SEARCHMAN USB_SEARCHMAN; + +struct _USB_SEARCHMAN +{ + int usb_numbers; + UINT32 UsbDevice; + USB_SEARCHDEV* idev; /* iterator device */ + USB_SEARCHDEV* head; /* head device in linked list */ + USB_SEARCHDEV* tail; /* tail device in linked list */ + + pthread_mutex_t mutex; + HANDLE term_event; + sem_t sem_term; + int started; + + /* for urbdrc channel call back */ + void* urbdrc; + + /* load service */ + void (*rewind) (USB_SEARCHMAN* seachman); + /* show all device in the list */ + void (*show) (USB_SEARCHMAN* self); + /* start searchman */ + BOOL (*start) (USB_SEARCHMAN* self, void* (*func)(void*)); + /* close searchman */ + void (*close) (USB_SEARCHMAN* self); + /* add a new usb device for search */ + BOOL (*add) (USB_SEARCHMAN* seachman, UINT16 idVendor, UINT16 idProduct); + /* remove a usb device from list */ + int (*remove) (USB_SEARCHMAN* searchman, UINT16 idVendor, UINT16 idProduct); + /* check list has next device*/ + int (*has_next) (USB_SEARCHMAN* seachman); + /* get the device from list*/ + USB_SEARCHDEV* (*get_next) (USB_SEARCHMAN* seachman); + /* free! */ + void (*free) (USB_SEARCHMAN* searchman); +}; + +USB_SEARCHMAN* searchman_new(void* urbdrc, UINT32 UsbDevice); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_SEARCHMAN_H */ + diff --git a/channels/urbdrc/client/urbdrc_main.c b/channels/urbdrc/client/urbdrc_main.c new file mode 100644 index 0000000..19b78b1 --- /dev/null +++ b/channels/urbdrc/client/urbdrc_main.c @@ -0,0 +1,1616 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include +#include +#include +#include + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#include +#include +#include +#include +#include +#endif +#if defined(__linux__) +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" +#include "data_transfer.h" +#include "searchman.h" + +static int func_hardware_id_format(IUDEVICE* pdev, char(*HardwareIds)[DEVICE_HARDWARE_ID_SIZE]) +{ + char str[DEVICE_HARDWARE_ID_SIZE]; + UINT16 idVendor, idProduct, bcdDevice; + idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR); + idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT); + bcdDevice = (UINT16)pdev->query_device_descriptor(pdev, BCD_DEVICE); + sprintf_s(str, sizeof(str), "USB\\VID_%04"PRIX16"&PID_%04"PRIX16"", idVendor, idProduct); + strncpy(HardwareIds[1], str, DEVICE_HARDWARE_ID_SIZE); + sprintf_s(str, sizeof(str), "%s&REV_%04"PRIX16"", HardwareIds[1], bcdDevice); + strncpy(HardwareIds[0], str, DEVICE_HARDWARE_ID_SIZE); + return 0; +} + +static int func_compat_id_format(IUDEVICE* pdev, + char(*CompatibilityIds)[DEVICE_COMPATIBILITY_ID_SIZE]) +{ + char str[DEVICE_COMPATIBILITY_ID_SIZE]; + UINT8 bDeviceClass, bDeviceSubClass, bDeviceProtocol; + bDeviceClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_CLASS); + bDeviceSubClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_SUBCLASS); + bDeviceProtocol = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_PROTOCOL); + + if (!(pdev->isCompositeDevice(pdev))) + { + sprintf_s(str, sizeof(str), "USB\\Class_%02"PRIX8"", bDeviceClass); + strncpy(CompatibilityIds[2], str, DEVICE_COMPATIBILITY_ID_SIZE); + sprintf_s(str, sizeof(str), "%s&SubClass_%02"PRIX8"", CompatibilityIds[2], bDeviceSubClass); + strncpy(CompatibilityIds[1], str, DEVICE_COMPATIBILITY_ID_SIZE); + sprintf_s(str, sizeof(str), "%s&Prot_%02"PRIX8"", CompatibilityIds[1], bDeviceProtocol); + strncpy(CompatibilityIds[0], str, DEVICE_COMPATIBILITY_ID_SIZE); + } + else + { + sprintf_s(str, sizeof(str), "USB\\DevClass_00"); + strncpy(CompatibilityIds[2], str, DEVICE_COMPATIBILITY_ID_SIZE); + sprintf_s(str, sizeof(str), "%s&SubClass_00", CompatibilityIds[2]); + strncpy(CompatibilityIds[1], str, DEVICE_COMPATIBILITY_ID_SIZE); + sprintf_s(str, sizeof(str), "%s&Prot_00", CompatibilityIds[1]); + strncpy(CompatibilityIds[0], str, DEVICE_COMPATIBILITY_ID_SIZE); + } + + return 0; +} + +static void func_close_udevice(USB_SEARCHMAN* searchman, IUDEVICE* pdev) +{ + int idVendor = 0; + int idProduct = 0; + URBDRC_PLUGIN* urbdrc = searchman->urbdrc; + pdev->SigToEnd(pdev); + idVendor = pdev->query_device_descriptor(pdev, ID_VENDOR); + idProduct = pdev->query_device_descriptor(pdev, ID_PRODUCT); + searchman->add(searchman, (UINT16) idVendor, (UINT16) idProduct); + pdev->cancel_all_transfer_request(pdev); + pdev->wait_action_completion(pdev); +#if ISOCH_FIFO + { + /* free isoch queue */ + ISOCH_CALLBACK_QUEUE* isoch_queue = pdev->get_isoch_queue(pdev); + + if (isoch_queue) + isoch_queue->free(isoch_queue); + } +#endif + urbdrc->udevman->unregister_udevice(urbdrc->udevman, + pdev->get_bus_number(pdev), + pdev->get_dev_number(pdev)); +} + +static int fun_device_string_send_set(char* out_data, int out_offset, char* str) +{ + int i = 0; + int offset = 0; + + while (str[i]) + { + data_write_UINT16(out_data + out_offset + offset, str[i]); /* str */ + i++; + offset += 2; + } + + data_write_UINT16(out_data + out_offset + offset, 0x0000); /* add "\0" */ + offset += 2; + return offset + out_offset; +} + +static int func_container_id_generate(IUDEVICE* pdev, char* strContainerId) +{ + char* p, *path; + UINT8 containerId[17]; + UINT16 idVendor, idProduct; + idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR); + idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT); + path = pdev->getPath(pdev); + + if (strlen(path) > 8) + p = (path + strlen(path)) - 8; + else + p = path; + + ZeroMemory(containerId, sizeof(containerId)); + sprintf_s((char*)containerId, sizeof(containerId), "%04"PRIX16"%04"PRIX16"%s", idVendor, idProduct, + p); + /* format */ + sprintf_s(strContainerId, DEVICE_CONTAINER_STR_SIZE, + "{%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"}", + containerId[0], containerId[1], containerId[2], containerId[3], + containerId[4], containerId[5], containerId[6], containerId[7], + containerId[8], containerId[9], containerId[10], containerId[11], + containerId[12], containerId[13], containerId[14], containerId[15]); + return 0; +} + +static int func_instance_id_generate(IUDEVICE* pdev, char* strInstanceId) +{ + UINT8 instanceId[17]; + ZeroMemory(instanceId, sizeof(instanceId)); + sprintf_s((char*)instanceId, sizeof(instanceId), "\\%s", pdev->getPath(pdev)); + /* format */ + sprintf_s(strInstanceId, DEVICE_INSTANCE_STR_SIZE, + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"-%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"", + instanceId[0], instanceId[1], instanceId[2], instanceId[3], + instanceId[4], instanceId[5], instanceId[6], instanceId[7], + instanceId[8], instanceId[9], instanceId[10], instanceId[11], + instanceId[12], instanceId[13], instanceId[14], instanceId[15]); + return 0; +} + +#if ISOCH_FIFO + +static void func_lock_isoch_mutex(TRANSFER_DATA* transfer_data) +{ + int noAck = 0; + IUDEVICE* pdev; + UINT32 FunctionId; + UINT32 RequestField; + UINT16 URB_Function; + IUDEVMAN* udevman = transfer_data->udevman; + + if (transfer_data->cbSize >= 8) + { + data_read_UINT32(transfer_data->pBuffer + 4, FunctionId); + + if ((FunctionId == TRANSFER_IN_REQUEST || + FunctionId == TRANSFER_OUT_REQUEST) && + transfer_data->cbSize >= 16) + { + data_read_UINT16(transfer_data->pBuffer + 14, URB_Function); + + if (URB_Function == URB_FUNCTION_ISOCH_TRANSFER && + transfer_data->cbSize >= 20) + { + data_read_UINT32(transfer_data->pBuffer + 16, RequestField); + noAck = (RequestField & 0x80000000) >> 31; + + if (!noAck) + { + pdev = udevman->get_udevice_by_UsbDevice(udevman, transfer_data->UsbDevice); + pdev->lock_fifo_isoch(pdev); + } + } + } + } +} + +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_capability_request(URBDRC_CHANNEL_CALLBACK* callback, char* data, + UINT32 data_sizem, UINT32 MessageId) +{ + UINT32 InterfaceId; + UINT32 Version; + UINT32 out_size; + char* out_data; + UINT ret; + WLog_VRB(TAG, ""); + data_read_UINT32(data + 0, Version); + InterfaceId = ((STREAM_ID_NONE << 30) | CAPABILITIES_NEGOTIATOR); + out_size = 16; + out_data = (char*) calloc(1, out_size); + + if (!out_data) + return ERROR_OUTOFMEMORY; + + data_write_UINT32(out_data + 0, InterfaceId); /* interface id */ + data_write_UINT32(out_data + 4, MessageId); /* message id */ + data_write_UINT32(out_data + 8, Version); /* usb protocol version */ + data_write_UINT32(out_data + 12, 0x00000000); /* HRESULT */ + ret = callback->channel->Write(callback->channel, out_size, (BYTE*) out_data, NULL); + zfree(out_data); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_create(URBDRC_CHANNEL_CALLBACK* callback, char* data, + UINT32 data_sizem, UINT32 MessageId) +{ + UINT32 InterfaceId; + UINT32 out_size; + UINT32 MajorVersion; + UINT32 MinorVersion; + UINT32 Capabilities; + char* out_data; + UINT ret; + WLog_VRB(TAG, ""); + data_read_UINT32(data + 0, MajorVersion); + data_read_UINT32(data + 4, MinorVersion); + data_read_UINT32(data + 8, Capabilities); + InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_CHANNEL_NOTIFICATION); + out_size = 24; + out_data = (char*) calloc(1, out_size); + + if (!out_data) + return ERROR_OUTOFMEMORY; + + data_write_UINT32(out_data + 0, InterfaceId); /* interface id */ + data_write_UINT32(out_data + 4, MessageId); /* message id */ + data_write_UINT32(out_data + 8, CHANNEL_CREATED); /* function id */ + data_write_UINT32(out_data + 12, MajorVersion); + data_write_UINT32(out_data + 16, MinorVersion); + data_write_UINT32(out_data + 20, Capabilities); /* capabilities version */ + ret = callback->channel->Write(callback->channel, out_size, (BYTE*)out_data, NULL); + zfree(out_data); + return ret; +} + +static int urdbrc_send_virtual_channel_add(IWTSVirtualChannel* channel, UINT32 MessageId) +{ + UINT32 out_size; + UINT32 InterfaceId; + char* out_data; + WLog_VRB(TAG, ""); + assert(NULL != channel); + assert(NULL != channel->Write); + InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + out_size = 12; + out_data = (char*) malloc(out_size); + memset(out_data, 0, out_size); + data_write_UINT32(out_data + 0, InterfaceId); /* interface */ + data_write_UINT32(out_data + 4, MessageId); /* message id */ + data_write_UINT32(out_data + 8, ADD_VIRTUAL_CHANNEL); /* function id */ + channel->Write(channel, out_size, (BYTE*) out_data, NULL); + zfree(out_data); + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urdbrc_send_usb_device_add(URBDRC_CHANNEL_CALLBACK* callback, IUDEVICE* pdev) +{ + char* out_data; + UINT32 InterfaceId; + char HardwareIds[2][DEVICE_HARDWARE_ID_SIZE]; + char CompatibilityIds[3][DEVICE_COMPATIBILITY_ID_SIZE]; + char strContainerId[DEVICE_CONTAINER_STR_SIZE]; + char strInstanceId[DEVICE_INSTANCE_STR_SIZE]; + char* composite_str = "USB\\COMPOSITE"; + int size, out_offset, cchCompatIds, bcdUSB; + ISOCH_CALLBACK_QUEUE* cb_queue; + UINT ret; + WLog_VRB(TAG, ""); + InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + /* USB kernel driver detach!! */ + pdev->detach_kernel_driver(pdev); +#if ISOCH_FIFO + /* create/initial isoch queue */ + cb_queue = isoch_queue_new(); + + if (!cb_queue) + return ERROR_OUTOFMEMORY; + + pdev->set_isoch_queue(pdev, (void*)cb_queue); +#endif + func_hardware_id_format(pdev, HardwareIds); + func_compat_id_format(pdev, CompatibilityIds); + func_instance_id_generate(pdev, strInstanceId); + func_container_id_generate(pdev, strContainerId); + cchCompatIds = strlen(CompatibilityIds[0]) + 1 + + strlen(CompatibilityIds[1]) + 1 + + strlen(CompatibilityIds[2]) + 2; + + if (pdev->isCompositeDevice(pdev)) + cchCompatIds += strlen(composite_str) + 1; + + out_offset = 24; + size = 24; + size += (strlen(strInstanceId) + 1) * 2 + + (strlen(HardwareIds[0]) + 1) * 2 + 4 + + (strlen(HardwareIds[1]) + 1) * 2 + 2 + + 4 + (cchCompatIds) * 2 + + (strlen(strContainerId) + 1) * 2 + 4 + 28; + out_data = (char*)calloc(1, size); + + if (!out_data) + return ERROR_OUTOFMEMORY; + + data_write_UINT32(out_data + 0, InterfaceId); /* interface */ + /* data_write_UINT32(out_data + 4, 0);*/ /* message id */ + data_write_UINT32(out_data + 8, ADD_DEVICE); /* function id */ + data_write_UINT32(out_data + 12, 0x00000001); /* NumUsbDevice */ + data_write_UINT32(out_data + 16, pdev->get_UsbDevice(pdev)); /* UsbDevice */ + data_write_UINT32(out_data + 20, 0x00000025); /* cchDeviceInstanceId */ + out_offset = fun_device_string_send_set(out_data, out_offset, strInstanceId); + data_write_UINT32(out_data + out_offset, 0x00000036); /* cchHwIds */ + out_offset += 4; + /* HardwareIds 1 */ + out_offset = fun_device_string_send_set(out_data, out_offset, HardwareIds[0]); + /* HardwareIds 2 */ + out_offset = fun_device_string_send_set(out_data, out_offset, HardwareIds[1]); + /*data_write_UINT16(out_data + out_offset, 0x0000);*/ /* add "\0" */ + out_offset += 2; + data_write_UINT32(out_data + out_offset, cchCompatIds); /* cchCompatIds */ + out_offset += 4; + /* CompatibilityIds 1 */ + out_offset = fun_device_string_send_set(out_data, out_offset, CompatibilityIds[0]); + /* CompatibilityIds 2 */ + out_offset = fun_device_string_send_set(out_data, out_offset, CompatibilityIds[1]); + /* CompatibilityIds 3 */ + out_offset = fun_device_string_send_set(out_data, out_offset, CompatibilityIds[2]); + + if (pdev->isCompositeDevice(pdev)) + out_offset = fun_device_string_send_set(out_data, out_offset, composite_str); + + /*data_write_UINT16(out_data + out_offset, 0x0000);*/ /* add "\0" */ + out_offset += 2; + data_write_UINT32(out_data + out_offset, 0x00000027); /* cchContainerId */ + out_offset += 4; + /* ContainerId */ + out_offset = fun_device_string_send_set(out_data, out_offset, strContainerId); + /* USB_DEVICE_CAPABILITIES 28 bytes */ + data_write_UINT32(out_data + out_offset, 0x0000001c); /* CbSize */ + data_write_UINT32(out_data + out_offset + 4, 2); /* UsbBusInterfaceVersion, 0 ,1 or 2 */ + data_write_UINT32(out_data + out_offset + 8, 0x600); /* USBDI_Version, 0x500 or 0x600 */ + /* Supported_USB_Version, 0x110,0x110 or 0x200(usb2.0) */ + bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB); + data_write_UINT32(out_data + out_offset + 12, bcdUSB); + data_write_UINT32(out_data + out_offset + 16, + 0x00000000); /* HcdCapabilities, MUST always be zero */ + + if (bcdUSB < 0x200) + data_write_UINT32(out_data + out_offset + 20, 0x00000000); /* DeviceIsHighSpeed */ + else + data_write_UINT32(out_data + out_offset + 20, 0x00000001); /* DeviceIsHighSpeed */ + + data_write_UINT32(out_data + out_offset + 24, + 0x50); /* NoAckIsochWriteJitterBufferSizeInMs, >=10 or <=512 */ + out_offset += 28; + ret = callback->channel->Write(callback->channel, out_offset, (BYTE*)out_data, NULL); + zfree(out_data); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_exchange_capabilities(URBDRC_CHANNEL_CALLBACK* callback, char* pBuffer, + UINT32 cbSize) +{ + UINT32 MessageId; + UINT32 FunctionId; + UINT error = CHANNEL_RC_OK; + data_read_UINT32(pBuffer + 0, MessageId); + data_read_UINT32(pBuffer + 4, FunctionId); + + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = urbdrc_process_capability_request(callback, pBuffer + 8, cbSize - 8, MessageId); + break; + + default: + WLog_ERR(TAG, "%s: unknown FunctionId 0x%"PRIX32"", __FUNCTION__, FunctionId); + error = ERROR_NOT_FOUND; + break; + } + + return error; +} + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +static char* devd_get_val(char* buf, size_t buf_size, const char* val_name, size_t val_name_size, + size_t* val_size) +{ + char* ret, *buf_end, *ptr; + buf_end = (buf + buf_size); + + for (ret = buf; ret != NULL && ret < buf_end;) + { + ret = memmem(ret, (buf_end - ret), val_name, val_name_size); + + if (ret == NULL) + return NULL; + + /* Found. */ + /* Check: space before or buf+1. */ + if ((buf + 1) < ret && ret[-1] != ' ') + { + ret += val_name_size; + continue; + } + + /* Check: = after name and size for value. */ + ret += val_name_size; + + if ((ret + 1) >= buf_end) + return NULL; + + if (ret[0] != '=') + continue; + + ret ++; + break; + } + + if (ret == NULL || val_size == NULL) + return ret; + + /* Calc value data size. */ + ptr = memchr(ret, ' ', (buf_end - ret)); + + if (ptr == NULL) /* End of string/last value. */ + ptr = buf_end; + + (*val_size) = (ptr - ret); + return ret; +} + +static void* urbdrc_search_usb_device(void* arg) +{ + USB_SEARCHMAN* searchman = (USB_SEARCHMAN*)arg; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)searchman->urbdrc; + IUDEVMAN* udevman = urbdrc->udevman; + IWTSVirtualChannelManager* channel_mgr = urbdrc->listener_callback->channel_mgr; + IWTSVirtualChannel* dvc_channel; + USB_SEARCHDEV* sdev; + IUDEVICE* pdev; + HANDLE listobj[2]; + HANDLE mon_fd; + int devd_skt; + char buf[4096], *val, *ptr, *end_val; + ssize_t data_size; + size_t val_size, tm; + long idVendor, idProduct; + long busnum, devnum; + int action, success, error, found, on_close; + struct sockaddr_un sun; + DWORD status; + UINT32 error; + WLog_DBG(TAG, "urbdrc_search_usb_device - devd: start"); + devd_skt = socket(PF_LOCAL, SOCK_SEQPACKET, 0); + + if (devd_skt == -1) + { + WLog_ERR(TAG, "Can't create devd socket: error = %i", errno); + goto err_out; + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = PF_LOCAL; + sun.sun_len = sizeof(sun); + strlcpy(sun.sun_path, "/var/run/devd.seqpacket.pipe", sizeof(sun.sun_path)); + + if (-1 == connect(devd_skt, (struct sockaddr*)&sun, sizeof(sun))) + { + WLog_ERR(TAG, "Can't connect devd socket: error = %i - %s", errno, strerror(errno)); + goto err_out; + } + + /* Get the file descriptor (fd) for the monitor. + This fd will get passed to select() */ + mon_fd = CreateFileDescriptorEvent(NULL, TRUE, FALSE, devd_skt); + listobj[0] = searchman->term_event; + listobj[1] = mon_fd; + + while (WaitForMultipleObjects(2, listobj, FALSE, INFINITE) != WAIT_OBJECT_0) + { + status = WaitForMultipleObjects(2, listobj, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return 0; + } + + if (status == WAIT_OBJECT_0) + break; + + errno = 0; + WLog_DBG(TAG, "======= SEARCH ======= "); + /* !system=USB subsystem=DEVICE type=ATTACH ugen=ugen3.3 cdev=ugen3.3 vendor=0x046d product=0x082d devclass=0xef devsubclass=0x02 sernum="6E7D726F" release=0x0011 mode=host port=4 parent=ugen3.1 */ + /* !system=USB subsystem=DEVICE type=DETACH ugen=ugen3.3 cdev=ugen3.3 vendor=0x046d product=0x082d devclass=0xef devsubclass=0x02 sernum="6E7D726F" release=0x0011 mode=host port=4 parent=ugen3.1 */ + data_size = read(devd_skt, buf, (sizeof(buf) - 1)); + + if (data_size == -1) + { + WLog_ERR(TAG, "devd socket read: error = %i", errno); + break; + } + + buf[data_size] = 0; + WLog_DBG(TAG, "devd event: %s", buf); + + if (buf[0] != '!') /* Skeep non notify events. */ + continue; + + /* Check: system=USB */ + val = devd_get_val(buf, data_size, "system", 6, &val_size); + + if (val == NULL || val_size != 3 || memcmp(val, "USB", 3) != 0) + continue; + + /* Check: subsystem=DEVICE */ + val = devd_get_val(buf, data_size, "subsystem", 9, &val_size); + + if (val == NULL || val_size != 6 || memcmp(val, "DEVICE", 6) != 0) + continue; + + /* Get event type. */ + val = devd_get_val(buf, data_size, "type", 4, &val_size); + + if (val == NULL || val_size != 6) + continue; + + action = -1; + + if (memcmp(val, "ATTACH", 6) == 0) + action = 0; + + if (memcmp(val, "DETACH", 6) == 0) + action = 1; + + if (action == -1) + continue; /* Skeep other actions. */ + + /* Get bus and dev num. */ + /* ugen=ugen3.3 */ + val = devd_get_val(buf, data_size, "ugen", 4, &val_size); + + if (val == NULL || val_size < 7 || memcmp(val, "ugen", 4) != 0) + continue; + + val += 4; + val_size -= 4; + ptr = memchr(val, '.', val_size); + + if (ptr == NULL) + continue; + + /* Prepare strings. */ + ptr[0] = 0; + ptr ++; + val[val_size] = 0; + /* Extract numbers. */ + busnum = strtol(val, NULL, 0); + + if (errno != 0) + continue; + + devnum = strtol(ptr, NULL, 0); + + if (errno != 0) + continue; + + /* Restore spaces. */ + ptr[-1] = ' '; + val[val_size] = ' '; + /* Handle event. */ + dvc_channel = NULL; + + switch (action) + { + case 0: /* ATTACH */ + sdev = NULL; + success = 0; + found = 0; + /* vendor=0x046d */ + val = devd_get_val(buf, data_size, "vendor", 6, &val_size); + + if (val == NULL || val_size < 1) + continue; + + val[val_size] = 0; + idVendor = strtol(val, NULL, 16); + + if (errno != 0) + continue; + + val[val_size] = ' '; + /* product=0x082d */ + val = devd_get_val(buf, data_size, "product", 7, &val_size); + + if (val == NULL || val_size < 1) + continue; + + val[val_size] = 0; + idProduct = strtol(val, NULL, 16); + + if (errno != 0) + continue; + + val[val_size] = ' '; + WLog_DBG(TAG, "ATTACH: bus: %i, dev: %i, ven: %i, prod: %i", busnum, devnum, idVendor, idProduct); + dvc_channel = channel_mgr->FindChannelById(channel_mgr, urbdrc->first_channel_id); + searchman->rewind(searchman); + + while (dvc_channel && searchman->has_next(searchman)) + { + sdev = searchman->get_next(searchman); + + if (sdev->idVendor == idVendor && + sdev->idProduct == idProduct) + { + WLog_VRB(TAG, "Searchman Found Device: %04"PRIx16":%04"PRIx16"", + sdev->idVendor, sdev->idProduct); + found = 1; + break; + } + } + + if (!found && udevman->isAutoAdd(udevman)) + { + WLog_VRB(TAG, "Auto Find Device: %04x:%04x ", + idVendor, idProduct); + found = 2; + } + + if (found) + { + success = udevman->register_udevice(udevman, busnum, devnum, + searchman->UsbDevice, 0, 0, UDEVMAN_FLAG_ADD_BY_ADDR); + } + + if (success) + { + searchman->UsbDevice ++; + usleep(400000); + error = urdbrc_send_virtual_channel_add(dvc_channel, 0); + + if (found == 1) + searchman->remove(searchman, sdev->idVendor, sdev->idProduct); + } + + break; + + case 1: /* DETACH */ + pdev = NULL; + on_close = 0; + WLog_DBG(TAG, "DETACH: bus: %i, dev: %i", busnum, devnum); + usleep(500000); + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (pdev->get_bus_number(pdev) == busnum && + pdev->get_dev_number(pdev) == devnum) + { + dvc_channel = channel_mgr->FindChannelById(channel_mgr, pdev->get_channel_id(pdev)); + + if (dvc_channel == NULL) + { + WLog_ERR(TAG, "SEARCH: dvc_channel %d is NULL!!", pdev->get_channel_id(pdev)); + func_close_udevice(searchman, pdev); + break; + } + + if (!pdev->isSigToEnd(pdev)) + { + dvc_channel->Write(dvc_channel, 0, NULL, NULL); + pdev->SigToEnd(pdev); + } + + on_close = 1; + break; + } + } + + udevman->loading_unlock(udevman); + usleep(300000); + + if (pdev && on_close && dvc_channel && + pdev->isSigToEnd(pdev) && + !(pdev->isChannelClosed(pdev))) + { + dvc_channel->Close(dvc_channel); + } + + break; + } + } + + CloseHandle(mon_fd); +err_out: + close(devd_skt); + sem_post(&searchman->sem_term); + WLog_DBG(TAG, "urbdrc_search_usb_device - devd: end"); + return 0; +} +#endif +#if defined (__linux__) +static void* urbdrc_search_usb_device(void* arg) +{ + USB_SEARCHMAN* searchman = (USB_SEARCHMAN*) arg; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) searchman->urbdrc; + IUDEVMAN* udevman = urbdrc->udevman; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* dvc_channel; + USB_SEARCHDEV* sdev; + IUDEVICE* pdev = NULL; + HANDLE listobj[2]; + HANDLE mon_fd; + int numobj, timeout; + long busnum, devnum; + int success = 0, on_close = 0, found = 0; + WLog_VRB(TAG, ""); + channel_mgr = urbdrc->listener_callback->channel_mgr; + DWORD status; + DWORD dwError; + /* init usb monitor */ + struct udev* udev; + struct udev_device* dev; + struct udev_monitor* mon; + udev = udev_new(); + + if (!udev) + { + WLog_ERR(TAG, "Can't create udev"); + return 0; + } + + /* Set up a monitor to monitor usb devices */ + mon = udev_monitor_new_from_netlink(udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", "usb_device"); + udev_monitor_enable_receiving(mon); + /* Get the file descriptor (fd) for the monitor. + This fd will get passed to select() */ + mon_fd = CreateFileDescriptorEvent(NULL, TRUE, FALSE, + udev_monitor_get_fd(mon), WINPR_FD_READ); + + if (!mon_fd) + goto fail_create_monfd_event; + + while (1) + { + WLog_VRB(TAG, "======= SEARCH ======= "); + busnum = 0; + devnum = 0; + sdev = NULL; + pdev = NULL; + dvc_channel = NULL; + on_close = 0; + listobj[0] = searchman->term_event; + listobj[1] = mon_fd; + numobj = 2; + status = WaitForMultipleObjects(numobj, listobj, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", dwError); + goto out; + } + + status = WaitForSingleObject(searchman->term_event, 0); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", dwError); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + sem_post(&searchman->sem_term); + goto out; + } + + status = WaitForSingleObject(mon_fd, 0); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", dwError); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + dev = udev_monitor_receive_device(mon); + + if (dev) + { + const char* action = udev_device_get_action(dev); + + if (strcmp(action, "add") == 0) + { + long idVendor, idProduct; + success = 0; + found = 0; + idVendor = strtol(udev_device_get_sysattr_value(dev, "idVendor"), NULL, 16); + + if (errno != 0) + continue; + + idProduct = strtol(udev_device_get_sysattr_value(dev, "idProduct"), NULL, 16); + + if (errno != 0) + continue; + + if (idVendor < 0 || idProduct < 0) + { + udev_device_unref(dev); + continue; + } + + busnum = strtol(udev_device_get_property_value(dev, "BUSNUM"), NULL, 0); + + if (errno != 0) + continue; + + devnum = strtol(udev_device_get_property_value(dev, "DEVNUM"), NULL, 0); + + if (errno != 0) + continue; + + dvc_channel = channel_mgr->FindChannelById(channel_mgr, + urbdrc->first_channel_id); + searchman->rewind(searchman); + + while (dvc_channel && searchman->has_next(searchman)) + { + sdev = searchman->get_next(searchman); + + if (sdev->idVendor == idVendor && + sdev->idProduct == idProduct) + { + WLog_VRB(TAG, "Searchman Find Device: %04"PRIx16":%04"PRIx16"", + sdev->idVendor, sdev->idProduct); + found = 1; + break; + } + } + + if (!found && udevman->isAutoAdd(udevman)) + { + WLog_VRB(TAG, "Auto Find Device: %04x:%04x ", + idVendor, idProduct); + found = 2; + } + + if (found) + { + success = udevman->register_udevice(udevman, busnum, devnum, + searchman->UsbDevice, 0, 0, UDEVMAN_FLAG_ADD_BY_ADDR); + } + + if (success) + { + searchman->UsbDevice++; + /* when we send the usb device add request, + * we will detach the device driver at same + * time. But, if the time of detach the + * driver and attach driver is too close, + * the system will crash. workaround: we + * wait it for some time to avoid system + * crash. */ + listobj[0] = searchman->term_event; + numobj = 1; + timeout = 4000; /* milliseconds */ + status = WaitForMultipleObjects(numobj, listobj, FALSE, timeout); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", dwError); + goto out; + } + + status = WaitForSingleObject(searchman->term_event, 0); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", dwError); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + CloseHandle(mon_fd); + sem_post(&searchman->sem_term); + return 0; + } + + urdbrc_send_virtual_channel_add(dvc_channel, 0); + + if (found == 1) + searchman->remove(searchman, sdev->idVendor, sdev->idProduct); + } + } + else if (strcmp(action, "remove") == 0) + { + busnum = strtol(udev_device_get_property_value(dev, "BUSNUM"), NULL, 0); + + if (errno != 0) + goto out; + + devnum = strtol(udev_device_get_property_value(dev, "DEVNUM"), NULL, 0); + + if (errno != 0) + goto out; + + usleep(500000); + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (pdev->get_bus_number(pdev) == busnum && pdev->get_dev_number(pdev) == devnum) + { + dvc_channel = channel_mgr->FindChannelById(channel_mgr, pdev->get_channel_id(pdev)); + + if (dvc_channel == NULL) + { + WLog_ERR(TAG, "SEARCH: dvc_channel %d is NULL!!", pdev->get_channel_id(pdev)); + func_close_udevice(searchman, pdev); + break; + } + + if (!pdev->isSigToEnd(pdev)) + { + dvc_channel->Write(dvc_channel, 0, NULL, NULL); + pdev->SigToEnd(pdev); + } + + on_close = 1; + break; + } + } + + udevman->loading_unlock(udevman); + listobj[0] = searchman->term_event; + numobj = 1; + timeout = 3000; /* milliseconds */ + status = WaitForMultipleObjects(numobj, listobj, FALSE, timeout); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"!", dwError); + goto out; + } + + status = WaitForSingleObject(searchman->term_event, 0); + + if (status == WAIT_FAILED) + { + dwError = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", dwError); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + CloseHandle(mon_fd); + sem_post(&searchman->sem_term); + return 0; + } + + if (pdev && on_close && dvc_channel && pdev->isSigToEnd(pdev) && !(pdev->isChannelClosed(pdev))) + { + on_close = 0; + dvc_channel->Close(dvc_channel); + } + } + + udev_device_unref(dev); + } + else + { + WLog_ERR(TAG, "No Device from receive_device(). An error occurred."); + } + } + } + +out: + CloseHandle(mon_fd); +fail_create_monfd_event: + sem_post(&searchman->sem_term); + return 0; +} +#endif + +void* urbdrc_new_device_create(void* arg) +{ + TRANSFER_DATA* transfer_data = (TRANSFER_DATA*) arg; + URBDRC_CHANNEL_CALLBACK* callback = transfer_data->callback; + IWTSVirtualChannelManager* channel_mgr; + URBDRC_PLUGIN* urbdrc = transfer_data->urbdrc; + USB_SEARCHMAN* searchman = urbdrc->searchman; + BYTE* pBuffer = transfer_data->pBuffer; + IUDEVMAN* udevman = transfer_data->udevman; + IUDEVICE* pdev = NULL; + UINT32 ChannelId = 0; + UINT32 MessageId; + UINT32 FunctionId; + int i = 0, found = 0; + WLog_DBG(TAG, "..."); + channel_mgr = urbdrc->listener_callback->channel_mgr; + ChannelId = channel_mgr->GetChannelId(callback->channel); + data_read_UINT32(pBuffer + 0, MessageId); + data_read_UINT32(pBuffer + 4, FunctionId); + int error = 0; + + switch (urbdrc->vchannel_status) + { + case INIT_CHANNEL_IN: + urbdrc->first_channel_id = ChannelId; + + if (!searchman->start(searchman, urbdrc_search_usb_device)) + { + WLog_ERR(TAG, "unable to start searchman thread"); + return 0; + } + + for (i = 0; i < udevman->get_device_num(udevman); i++) + error = urdbrc_send_virtual_channel_add(callback->channel, MessageId); + + urbdrc->vchannel_status = INIT_CHANNEL_OUT; + break; + + case INIT_CHANNEL_OUT: + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (!pdev->isAlreadySend(pdev)) + { + found = 1; + pdev->setAlreadySend(pdev); + pdev->set_channel_id(pdev, ChannelId); + break; + } + } + + udevman->loading_unlock(udevman); + + if (found && pdev->isAlreadySend(pdev)) + { + /* when we send the usb device add request, we will detach + * the device driver at same time. But, if the time of detach the + * driver and attach driver is too close, the system will crash. + * workaround: we wait it for some time to avoid system crash. */ + error = pdev->wait_for_detach(pdev); + + if (error >= 0) + urdbrc_send_usb_device_add(callback, pdev); + } + + break; + + default: + WLog_ERR(TAG, "vchannel_status unknown value %"PRIu32"", + urbdrc->vchannel_status); + break; + } + + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_notification(URBDRC_CHANNEL_CALLBACK* callback, char* pBuffer, + UINT32 cbSize) +{ + int i; + UINT32 MessageId; + UINT32 FunctionId; + UINT error = CHANNEL_RC_OK; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) callback->plugin; + WLog_DBG(TAG, "..."); + data_read_UINT32(pBuffer + 0, MessageId); + data_read_UINT32(pBuffer + 4, FunctionId); + + switch (FunctionId) + { + case CHANNEL_CREATED: + error = urbdrc_process_channel_create(callback, pBuffer + 8, cbSize - 8, MessageId); + break; + + case RIMCALL_RELEASE: + WLog_VRB(TAG, "recv RIMCALL_RELEASE"); + pthread_t thread; + TRANSFER_DATA* transfer_data; + transfer_data = (TRANSFER_DATA*)malloc(sizeof(TRANSFER_DATA)); + + if (!transfer_data) + return ERROR_OUTOFMEMORY; + + transfer_data->callback = callback; + transfer_data->urbdrc = urbdrc; + transfer_data->udevman = urbdrc->udevman; + transfer_data->urbdrc = urbdrc; + transfer_data->cbSize = cbSize; + transfer_data->pBuffer = (BYTE*) malloc((cbSize)); + + if (!transfer_data->pBuffer) + { + free(transfer_data); + return ERROR_OUTOFMEMORY; + } + + for (i = 0; i < (cbSize); i++) + { + transfer_data->pBuffer[i] = pBuffer[i]; + } + + if (pthread_create(&thread, 0, urbdrc_new_device_create, transfer_data) != 0) + { + free(transfer_data->pBuffer); + free(transfer_data); + return ERROR_INVALID_OPERATION; + } + + pthread_detach(thread); + break; + + default: + WLog_VRB(TAG, "%s: unknown FunctionId 0x%"PRIX32"", __FUNCTION__, FunctionId); + error = 1; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + URBDRC_CHANNEL_CALLBACK* callback = (URBDRC_CHANNEL_CALLBACK*) pChannelCallback; + URBDRC_PLUGIN* urbdrc; + IUDEVMAN* udevman; + UINT32 InterfaceTemp; + UINT32 InterfaceId; + UINT32 Mask; + UINT error = CHANNEL_RC_OK; + char* pBuffer = (char*)Stream_Pointer(data); + UINT32 cbSize = Stream_GetRemainingLength(data); + + if (callback == NULL) + return 0; + + if (callback->plugin == NULL) + return 0; + + urbdrc = (URBDRC_PLUGIN*) callback->plugin; + + if (urbdrc->udevman == NULL) + return 0; + + udevman = (IUDEVMAN*) urbdrc->udevman; + data_read_UINT32(pBuffer + 0, InterfaceTemp); + InterfaceId = (InterfaceTemp & 0x0fffffff); + Mask = ((InterfaceTemp & 0xf0000000) >> 30); + WLog_VRB(TAG, "Size=%"PRIu32" InterfaceId=0x%"PRIX32" Mask=0x%"PRIX32"", cbSize, InterfaceId, Mask); + + switch (InterfaceId) + { + case CAPABILITIES_NEGOTIATOR: + error = urbdrc_exchange_capabilities(callback, pBuffer + 4, cbSize - 4); + break; + + case SERVER_CHANNEL_NOTIFICATION: + error = urbdrc_process_channel_notification(callback, pBuffer + 4, cbSize - 4); + break; + + default: + WLog_VRB(TAG, "InterfaceId 0x%"PRIX32" Start matching devices list", InterfaceId); + pthread_t thread; + TRANSFER_DATA* transfer_data; + transfer_data = (TRANSFER_DATA*)malloc(sizeof(TRANSFER_DATA)); + + if (!transfer_data) + { + WLog_ERR(TAG, "transfer_data is NULL!!"); + return ERROR_OUTOFMEMORY; + } + + transfer_data->callback = callback; + transfer_data->urbdrc = urbdrc; + transfer_data->udevman = udevman; + transfer_data->cbSize = cbSize - 4; + transfer_data->UsbDevice = InterfaceId; + transfer_data->pBuffer = (BYTE*)malloc((cbSize - 4)); + + if (!transfer_data->pBuffer) + { + free(transfer_data); + return ERROR_OUTOFMEMORY; + } + + memcpy(transfer_data->pBuffer, pBuffer + 4, (cbSize - 4)); + /* To ensure that not too many urb requests at the same time */ + udevman->wait_urb(udevman); +#if ISOCH_FIFO + /* lock isoch mutex */ + func_lock_isoch_mutex(transfer_data); +#endif + error = pthread_create(&thread, 0, urbdrc_process_udev_data_transfer, transfer_data); + + if (error != 0) + { + WLog_ERR(TAG, "Create Data Transfer Thread got error = %"PRIu32"", error); + free(transfer_data->pBuffer); + free(transfer_data); + return ERROR_INVALID_OPERATION; + } + + pthread_detach(thread); + break; + } + + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + URBDRC_CHANNEL_CALLBACK* callback = (URBDRC_CHANNEL_CALLBACK*) pChannelCallback; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) callback->plugin; + IUDEVMAN* udevman = (IUDEVMAN*) urbdrc->udevman; + USB_SEARCHMAN* searchman = (USB_SEARCHMAN*) urbdrc->searchman; + IUDEVICE* pdev = NULL; + UINT32 ChannelId = 0; + int found = 0; + ChannelId = callback->channel_mgr->GetChannelId(callback->channel); + WLog_INFO(TAG, "urbdrc_on_close: channel id %"PRIu32"", ChannelId); + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (pdev->get_channel_id(pdev) == ChannelId) + { + found = 1; + break; + } + } + + udevman->loading_unlock(udevman); + + if (found && pdev && !(pdev->isChannelClosed(pdev))) + { + pdev->setChannelClosed(pdev); + func_close_udevice(searchman, pdev); + } + + zfree(callback); + WLog_DBG(TAG, "success"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* pData, BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback) +{ + URBDRC_LISTENER_CALLBACK* listener_callback = (URBDRC_LISTENER_CALLBACK*) pListenerCallback; + URBDRC_CHANNEL_CALLBACK* callback; + WLog_VRB(TAG, ""); + callback = (URBDRC_CHANNEL_CALLBACK*) calloc(1, sizeof(URBDRC_CHANNEL_CALLBACK)); + + if (!callback) + return ERROR_OUTOFMEMORY; + + callback->iface.OnDataReceived = urbdrc_on_data_received; + callback->iface.OnClose = urbdrc_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*) callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) pPlugin; + IUDEVMAN* udevman = NULL; + USB_SEARCHMAN* searchman = NULL; + WLog_VRB(TAG, ""); + urbdrc->listener_callback = (URBDRC_LISTENER_CALLBACK*) calloc(1, sizeof(URBDRC_LISTENER_CALLBACK)); + + if (!urbdrc->listener_callback) + return CHANNEL_RC_NO_MEMORY; + + urbdrc->listener_callback->iface.OnNewChannelConnection = urbdrc_on_new_channel_connection; + urbdrc->listener_callback->plugin = pPlugin; + urbdrc->listener_callback->channel_mgr = pChannelMgr; + /* Init searchman */ + udevman = urbdrc->udevman; + searchman = searchman_new((void*) urbdrc, udevman->get_defUsbDevice(udevman)); + + if (!searchman) + { + free(urbdrc->listener_callback); + urbdrc->listener_callback = NULL; + return CHANNEL_RC_NO_MEMORY; + } + + urbdrc->searchman = searchman; + return pChannelMgr->CreateListener(pChannelMgr, "URBDRC", 0, + (IWTSListenerCallback*) urbdrc->listener_callback, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_plugin_terminated(IWTSPlugin* pPlugin) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) pPlugin; + IUDEVMAN* udevman = urbdrc->udevman; + USB_SEARCHMAN* searchman = urbdrc->searchman; + WLog_VRB(TAG, ""); + + if (searchman) + { + /* close searchman */ + searchman->close(searchman); + + /* free searchman */ + if (searchman->started) + { + struct timespec ts; + ts.tv_sec = time(NULL) + 10; + ts.tv_nsec = 0; + sem_timedwait(&searchman->sem_term, &ts); + } + + searchman->free(searchman); + searchman = NULL; + } + + if (udevman) + { + udevman->free(udevman); + udevman = NULL; + } + + if (urbdrc->listener_callback) + zfree(urbdrc->listener_callback); + + if (urbdrc) + zfree(urbdrc); + + return CHANNEL_RC_OK; +} + +static void urbdrc_register_udevman_addin(IWTSPlugin* pPlugin, IUDEVMAN* udevman) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*) pPlugin; + + if (urbdrc->udevman) + { + WLog_ERR(TAG, "existing device, abort."); + return; + } + + DEBUG_DVC("device registered."); + urbdrc->udevman = udevman; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_load_udevman_addin(IWTSPlugin* pPlugin, const char* name, ADDIN_ARGV* args) +{ + PFREERDP_URBDRC_DEVICE_ENTRY entry; + FREERDP_URBDRC_SERVICE_ENTRY_POINTS entryPoints; + entry = (PFREERDP_URBDRC_DEVICE_ENTRY) freerdp_load_channel_addin_entry("urbdrc", (LPSTR) name, + NULL, 0); + + if (!entry) + return ERROR_INVALID_OPERATION; + + entryPoints.plugin = pPlugin; + entryPoints.pRegisterUDEVMAN = urbdrc_register_udevman_addin; + entryPoints.args = args; + + if (entry(&entryPoints) != 0) + { + WLog_ERR(TAG, "%s entry returns error.", name); + return ERROR_INVALID_OPERATION; + } + + return CHANNEL_RC_OK; +} + +BOOL urbdrc_set_subsystem(URBDRC_PLUGIN* urbdrc, char* subsystem) +{ + free(urbdrc->subsystem); + urbdrc->subsystem = _strdup(subsystem); + return (urbdrc->subsystem != NULL); +} + +COMMAND_LINE_ARGUMENT_A urbdrc_args[] = +{ + { "dbg", COMMAND_LINE_VALUE_FLAG, "", NULL, BoolValueFalse, -1, NULL, "debug" }, + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "subsystem" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_addin_args(URBDRC_PLUGIN* urbdrc, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, + urbdrc_args, flags, urbdrc, NULL, NULL); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = urbdrc_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "dbg") + { + WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE); + } + CommandLineSwitchCase(arg, "sys") + { + if (!urbdrc_set_subsystem(urbdrc, arg->Value)) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry urbdrc_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = 0; + ADDIN_ARGV* args; + URBDRC_PLUGIN* urbdrc; + urbdrc = (URBDRC_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "urbdrc"); + args = pEntryPoints->GetPluginData(pEntryPoints); + + if (urbdrc == NULL) + { + urbdrc = (URBDRC_PLUGIN*) calloc(1, sizeof(URBDRC_PLUGIN)); + + if (!urbdrc) + return CHANNEL_RC_NO_MEMORY; + + urbdrc->iface.Initialize = urbdrc_plugin_initialize; + urbdrc->iface.Connected = NULL; + urbdrc->iface.Disconnected = NULL; + urbdrc->iface.Terminated = urbdrc_plugin_terminated; + urbdrc->searchman = NULL; + urbdrc->vchannel_status = INIT_CHANNEL_IN; + status = pEntryPoints->RegisterPlugin(pEntryPoints, "urbdrc", (IWTSPlugin*) urbdrc); + + if (status != CHANNEL_RC_OK) + goto error_register; + } + + status = urbdrc_process_addin_args(urbdrc, args); + + if (status != CHANNEL_RC_OK) + { + /* TODO: we should unregister the plugin ? */ + WLog_ERR(TAG, "error processing arguments"); + //return status; + } + + if (!urbdrc->subsystem && !urbdrc_set_subsystem(urbdrc, "libusb")) + { + /* TODO: we should unregister the plugin ? */ + WLog_ERR(TAG, "error setting subsystem"); + return ERROR_OUTOFMEMORY; + } + + return urbdrc_load_udevman_addin((IWTSPlugin*) urbdrc, urbdrc->subsystem, args); +error_register: + free(urbdrc); + return status; +} diff --git a/channels/urbdrc/client/urbdrc_main.h b/channels/urbdrc/client/urbdrc_main.h new file mode 100644 index 0000000..129b844 --- /dev/null +++ b/channels/urbdrc/client/urbdrc_main.h @@ -0,0 +1,227 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H + +#include "searchman.h" +#include "isoch_queue.h" + +#define DEVICE_HARDWARE_ID_SIZE 32 +#define DEVICE_COMPATIBILITY_ID_SIZE 36 +#define DEVICE_INSTANCE_STR_SIZE 37 +#define DEVICE_CONTAINER_STR_SIZE 39 + +typedef struct _IUDEVICE IUDEVICE; +typedef struct _IUDEVMAN IUDEVMAN; + +#define BASIC_DEV_STATE_DEFINED(_arg, _type) \ + _type (*get_##_arg) (IUDEVICE* pdev); \ + void (*set_##_arg) (IUDEVICE* pdev, _type _arg) + +#define BASIC_DEVMAN_STATE_DEFINED(_arg, _type) \ + _type (*get_##_arg) (IUDEVMAN* udevman); \ + void (*set_##_arg) (IUDEVMAN* udevman, _type _arg) + +typedef struct _URBDRC_LISTENER_CALLBACK URBDRC_LISTENER_CALLBACK; + +struct _URBDRC_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +}; + +typedef struct _URBDRC_CHANNEL_CALLBACK URBDRC_CHANNEL_CALLBACK; + +struct _URBDRC_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; + +typedef struct _URBDRC_PLUGIN URBDRC_PLUGIN; + +struct _URBDRC_PLUGIN +{ + IWTSPlugin iface; + + URBDRC_LISTENER_CALLBACK* listener_callback; + + IUDEVMAN* udevman; + USB_SEARCHMAN* searchman; + UINT32 first_channel_id; + UINT32 vchannel_status; + char* subsystem; +}; + +typedef void (*PREGISTERURBDRCSERVICE)(IWTSPlugin* plugin, IUDEVMAN* udevman); + +struct _FREERDP_URBDRC_SERVICE_ENTRY_POINTS +{ + IWTSPlugin* plugin; + PREGISTERURBDRCSERVICE pRegisterUDEVMAN; + ADDIN_ARGV* args; +}; +typedef struct _FREERDP_URBDRC_SERVICE_ENTRY_POINTS FREERDP_URBDRC_SERVICE_ENTRY_POINTS; +typedef FREERDP_URBDRC_SERVICE_ENTRY_POINTS* PFREERDP_URBDRC_SERVICE_ENTRY_POINTS; + +typedef int (*PFREERDP_URBDRC_DEVICE_ENTRY)(PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints); + +typedef struct _TRANSFER_DATA TRANSFER_DATA; + +struct _TRANSFER_DATA +{ + URBDRC_CHANNEL_CALLBACK* callback; + URBDRC_PLUGIN* urbdrc; + IUDEVMAN* udevman; + BYTE* pBuffer; + UINT32 cbSize; + UINT32 UsbDevice; +}; + +struct _IUDEVICE +{ + /* Transfer */ + int (*isoch_transfer) (IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, int NoAck, UINT32* ErrorCount, + UINT32* UrbdStatus, UINT32* StartFrame, UINT32 NumberOfPackets, + BYTE* IsoPacket, UINT32* BufferSize, BYTE* Buffer, int Timeout); + + int (*control_transfer) (IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, BYTE bmRequestType, BYTE Request, UINT16 Value, + UINT16 Index, UINT32* UrbdStatus, UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout); + + int (*bulk_or_interrupt_transfer) (IUDEVICE* idev, UINT32 RequestId, UINT32 EndpointAddress, + UINT32 TransferFlags, UINT32* UsbdStatus, UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout); + + int (*select_configuration) (IUDEVICE* idev, UINT32 bConfigurationValue); + + int (*select_interface) (IUDEVICE* idev, BYTE InterfaceNumber, + BYTE AlternateSetting); + + int (*control_pipe_request) (IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32* UsbdStatus, int command); + + int (*control_query_device_text) (IUDEVICE* idev, UINT32 TextType, + UINT32 LocaleId, UINT32*BufferSize, BYTE* Buffer); + + int (*os_feature_descriptor_request) (IUDEVICE* idev, UINT32 RequestId, BYTE Recipient, + BYTE InterfaceNumber, BYTE Ms_PageIndex, UINT16 Ms_featureDescIndex, UINT32* UsbdStatus, + UINT32* BufferSize, BYTE* Buffer, int Timeout); + + void (*cancel_all_transfer_request) (IUDEVICE* idev); + + int (*cancel_transfer_request) (IUDEVICE* idev, UINT32 RequestId); + + int (*query_device_descriptor) (IUDEVICE* idev, int offset); + + void (*detach_kernel_driver) (IUDEVICE* idev); + + void (*attach_kernel_driver) (IUDEVICE* idev); + + int (*wait_action_completion) (IUDEVICE* idev); + + void (*push_action) (IUDEVICE* idev); + + void (*complete_action) (IUDEVICE* idev); + + /* Wait for 5 sec */ + int (*wait_for_detach) (IUDEVICE* idev); + + /* FIXME: Currently this is a way of stupid, SHOULD to improve it. + * Isochronous transfer must to FIFO */ + void (*lock_fifo_isoch) (IUDEVICE* idev); + void (*unlock_fifo_isoch) (IUDEVICE* idev); + + int (*query_device_port_status) (IUDEVICE* idev, UINT32 *UsbdStatus, + UINT32* BufferSize, + BYTE* Buffer); + + int (*request_queue_is_none) (IUDEVICE* idev); + + MSUSB_CONFIG_DESCRIPTOR* (*complete_msconfig_setup) (IUDEVICE* idev, + MSUSB_CONFIG_DESCRIPTOR* MsConfig); + /* Basic state */ + int (*isCompositeDevice) (IUDEVICE* idev); + int (*isSigToEnd) (IUDEVICE* idev); + int (*isExist) (IUDEVICE* idev); + int (*isAlreadySend) (IUDEVICE* idev); + int (*isChannelClosed) (IUDEVICE* idev); + void (*SigToEnd) (IUDEVICE* idev); + void (*setAlreadySend) (IUDEVICE* idev); + void (*setChannelClosed) (IUDEVICE* idev); + char *(*getPath) (IUDEVICE* idev); + + BASIC_DEV_STATE_DEFINED(channel_id, UINT32); + BASIC_DEV_STATE_DEFINED(UsbDevice, UINT32); + BASIC_DEV_STATE_DEFINED(ReqCompletion, UINT32); + BASIC_DEV_STATE_DEFINED(bus_number, UINT16); + BASIC_DEV_STATE_DEFINED(dev_number, UINT16); + BASIC_DEV_STATE_DEFINED(port_number, int); + BASIC_DEV_STATE_DEFINED(isoch_queue, void*); + BASIC_DEV_STATE_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*); + + BASIC_DEV_STATE_DEFINED(p_udev, void*); + BASIC_DEV_STATE_DEFINED(p_prev, void*); + BASIC_DEV_STATE_DEFINED(p_next, void*); + + /* Control semaphore or mutex lock */ + +}; + +struct _IUDEVMAN +{ + /* Standard */ + void (*free) (IUDEVMAN* idevman); + + /* Manage devices */ + void (*rewind) (IUDEVMAN* idevman); + int (*has_next) (IUDEVMAN* idevman); + int (*unregister_udevice) (IUDEVMAN* idevman, int bus_number, int dev_number); + int (*register_udevice) (IUDEVMAN* idevman, int bus_number, + int dev_number, int UsbDevice, UINT16 idVendor, UINT16 idProduct, int flag); + IUDEVICE *(*get_next) (IUDEVMAN* idevman); + IUDEVICE *(*get_udevice_by_UsbDevice) (IUDEVMAN* idevman, UINT32 UsbDevice); + IUDEVICE *(*get_udevice_by_UsbDevice_try_again) (IUDEVMAN* idevman, UINT32 UsbDevice); + + /* Extension */ + int (*check_device_exist_by_id) (IUDEVMAN* idevman, UINT16 idVendor, UINT16 idProduct); + int (*isAutoAdd) (IUDEVMAN* idevman); + + /* Basic state */ + BASIC_DEVMAN_STATE_DEFINED(defUsbDevice, UINT32); + BASIC_DEVMAN_STATE_DEFINED(device_num, int); + BASIC_DEVMAN_STATE_DEFINED(sem_timeout, int); + + /* control semaphore or mutex lock */ + void (*loading_lock) (IUDEVMAN* idevman); + void (*loading_unlock) (IUDEVMAN* idevman); + void (*push_urb) (IUDEVMAN* idevman); + void (*wait_urb) (IUDEVMAN* idevman); +}; + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H */ diff --git a/channels/urbdrc/client/urbdrc_types.h b/channels/urbdrc/client/urbdrc_types.h new file mode 100644 index 0000000..204cade --- /dev/null +++ b/channels/urbdrc/client/urbdrc_types.h @@ -0,0 +1,333 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TAG CHANNELS_TAG("urbdrc.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) do { } while (0) +#endif + +#define CAPABILITIES_NEGOTIATOR 0x00000000 +#define CLIENT_DEVICE_SINK 0x00000001 +#define SERVER_CHANNEL_NOTIFICATION 0x00000002 +#define CLIENT_CHANNEL_NOTIFICATION 0x00000003 +#define BASE_USBDEVICE_NUM 0x00000005 + +#define RIMCALL_RELEASE 0x00000001 +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +#define CHANNEL_CREATED 0x00000100 +#define ADD_VIRTUAL_CHANNEL 0x00000100 +#define ADD_DEVICE 0x00000101 + +#define INIT_CHANNEL_IN 1 +#define INIT_CHANNEL_OUT 0 + +/* InterfaceClass */ +#define CLASS_RESERVE 0x00 +#define CLASS_AUDIO 0x01 +#define CLASS_COMMUNICATION_IF 0x02 +#define CLASS_HID 0x03 +#define CLASS_PHYSICAL 0x05 +#define CLASS_IMAGE 0x06 +#define CLASS_PRINTER 0x07 +#define CLASS_MASS_STORAGE 0x08 +#define CLASS_HUB 0x09 +#define CLASS_COMMUNICATION_DATA_IF 0x0a +#define CLASS_SMART_CARD 0x0b +#define CLASS_CONTENT_SECURITY 0x0d +#define CLASS_VIDEO 0x0e +#define CLASS_PERSONAL_HEALTHCARE 0x0f +#define CLASS_DIAGNOSTIC 0xdc +#define CLASS_WIRELESS_CONTROLLER 0xe0 +#define CLASS_ELSE_DEVICE 0xef +#define CLASS_DEPENDENCE 0xfe +#define CLASS_VENDOR_DEPENDENCE 0xff + +/* usb version */ +#define USB_v1_0 0x100 +#define USB_v1_1 0x110 +#define USB_v2_0 0x200 +#define USB_v3_0 0x300 + +#define STREAM_ID_NONE 0x0 +#define STREAM_ID_PROXY 0x1 +#define STREAM_ID_STUB 0x2 + +#define CANCEL_REQUEST 0x00000100 +#define REGISTER_REQUEST_CALLBACK 0x00000101 +#define IO_CONTROL 0x00000102 +#define INTERNAL_IO_CONTROL 0x00000103 +#define QUERY_DEVICE_TEXT 0x00000104 +#define TRANSFER_IN_REQUEST 0x00000105 +#define TRANSFER_OUT_REQUEST 0x00000106 +#define RETRACT_DEVICE 0x00000107 + + +#define IOCONTROL_COMPLETION 0x00000100 +#define URB_COMPLETION 0x00000101 +#define URB_COMPLETION_NO_DATA 0x00000102 + +/* The USB device is to be stopped from being redirected because the + * device is blocked by the server's policy. */ +#define UsbRetractReason_BlockedByPolicy 0x00000001 + +enum device_text_type +{ + DeviceTextDescription = 0, + DeviceTextLocationInformation = 1, +}; + +enum device_descriptor_table +{ + B_LENGTH = 0, + B_DESCRIPTOR_TYPE = 1, + BCD_USB = 2, + B_DEVICE_CLASS = 4, + B_DEVICE_SUBCLASS = 5, + B_DEVICE_PROTOCOL = 6, + B_MAX_PACKET_SIZE0 = 7, + ID_VENDOR = 8, + ID_PRODUCT = 10, + BCD_DEVICE = 12, + I_MANUFACTURER = 14, + I_PRODUCT = 15, + I_SERIAL_NUMBER = 16, + B_NUM_CONFIGURATIONS = 17 +}; + +#define PIPE_CANCEL 0 +#define PIPE_RESET 1 + +#define IOCTL_INTERNAL_USB_SUBMIT_URB 0x00220003 +#define IOCTL_INTERNAL_USB_RESET_PORT 0x00220007 +#define IOCTL_INTERNAL_USB_GET_PORT_STATUS 0x00220013 +#define IOCTL_INTERNAL_USB_CYCLE_PORT 0x0022001F +#define IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION 0x00220027 + + + +#define URB_FUNCTION_SELECT_CONFIGURATION 0x0000 +#define URB_FUNCTION_SELECT_INTERFACE 0x0001 +#define URB_FUNCTION_ABORT_PIPE 0x0002 +#define URB_FUNCTION_TAKE_FRAME_LENGTH_CONTROL 0x0003 +#define URB_FUNCTION_RELEASE_FRAME_LENGTH_CONTROL 0x0004 +#define URB_FUNCTION_GET_FRAME_LENGTH 0x0005 +#define URB_FUNCTION_SET_FRAME_LENGTH 0x0006 +#define URB_FUNCTION_GET_CURRENT_FRAME_NUMBER 0x0007 +#define URB_FUNCTION_CONTROL_TRANSFER 0x0008 +#define URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER 0x0009 +#define URB_FUNCTION_ISOCH_TRANSFER 0x000A +#define URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 0x000B +#define URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE 0x000C +#define URB_FUNCTION_SET_FEATURE_TO_DEVICE 0x000D +#define URB_FUNCTION_SET_FEATURE_TO_INTERFACE 0x000E +#define URB_FUNCTION_SET_FEATURE_TO_ENDPOINT 0x000F +#define URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE 0x0010 +#define URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE 0x0011 +#define URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT 0x0012 +#define URB_FUNCTION_GET_STATUS_FROM_DEVICE 0x0013 +#define URB_FUNCTION_GET_STATUS_FROM_INTERFACE 0x0014 +#define URB_FUNCTION_GET_STATUS_FROM_ENDPOINT 0x0015 +#define URB_FUNCTION_RESERVED_0X0016 0x0016 +#define URB_FUNCTION_VENDOR_DEVICE 0x0017 +#define URB_FUNCTION_VENDOR_INTERFACE 0x0018 +#define URB_FUNCTION_VENDOR_ENDPOINT 0x0019 +#define URB_FUNCTION_CLASS_DEVICE 0x001A +#define URB_FUNCTION_CLASS_INTERFACE 0x001B +#define URB_FUNCTION_CLASS_ENDPOINT 0x001C +#define URB_FUNCTION_RESERVE_0X001D 0x001D +#define URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL 0x001E +#define URB_FUNCTION_CLASS_OTHER 0x001F +#define URB_FUNCTION_VENDOR_OTHER 0x0020 +#define URB_FUNCTION_GET_STATUS_FROM_OTHER 0x0021 +#define URB_FUNCTION_CLEAR_FEATURE_TO_OTHER 0x0022 +#define URB_FUNCTION_SET_FEATURE_TO_OTHER 0x0023 +#define URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT 0x0024 +#define URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT 0x0025 +#define URB_FUNCTION_GET_CONFIGURATION 0x0026 +#define URB_FUNCTION_GET_INTERFACE 0x0027 +#define URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE 0x0028 +#define URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE 0x0029 +#define URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR 0x002A +#define URB_FUNCTION_RESERVE_0X002B 0x002B +#define URB_FUNCTION_RESERVE_0X002C 0x002C +#define URB_FUNCTION_RESERVE_0X002D 0x002D +#define URB_FUNCTION_RESERVE_0X002E 0x002E +#define URB_FUNCTION_RESERVE_0X002F 0x002F +// USB 2.0 calls start at 0x0030 +#define URB_FUNCTION_SYNC_RESET_PIPE 0x0030 +#define URB_FUNCTION_SYNC_CLEAR_STALL 0x0031 +#define URB_FUNCTION_CONTROL_TRANSFER_EX 0x0032 + +#define USBD_STATUS_SUCCESS 0x0 +#define USBD_STATUS_PENDING 0x40000000 +#define USBD_STATUS_CANCELED 0xC0010000 + +#define USBD_STATUS_CRC 0xC0000001 +#define USBD_STATUS_BTSTUFF 0xC0000002 +#define USBD_STATUS_DATA_TOGGLE_MISMATCH 0xC0000003 +#define USBD_STATUS_STALL_PID 0xC0000004 +#define USBD_STATUS_DEV_NOT_RESPONDING 0xC0000005 +#define USBD_STATUS_PID_CHECK_FAILURE 0xC0000006 +#define USBD_STATUS_UNEXPECTED_PID 0xC0000007 +#define USBD_STATUS_DATA_OVERRUN 0xC0000008 +#define USBD_STATUS_DATA_UNDERRUN 0xC0000009 +#define USBD_STATUS_RESERVED1 0xC000000A +#define USBD_STATUS_RESERVED2 0xC000000B +#define USBD_STATUS_BUFFER_OVERRUN 0xC000000C +#define USBD_STATUS_BUFFER_UNDERRUN 0xC000000D + +/* unknown */ +#define USBD_STATUS_NO_DATA 0xC000000E + +#define USBD_STATUS_NOT_ACCESSED 0xC000000F +#define USBD_STATUS_FIFO 0xC0000010 +#define USBD_STATUS_XACT_ERROR 0xC0000011 +#define USBD_STATUS_BABBLE_DETECTED 0xC0000012 +#define USBD_STATUS_DATA_BUFFER_ERROR 0xC0000013 + +#define USBD_STATUS_NOT_SUPPORTED 0xC0000E00 +#define USBD_STATUS_BUFFER_TOO_SMALL 0xC0003000 +#define USBD_STATUS_TIMEOUT 0xC0006000 +#define USBD_STATUS_DEVICE_GONE 0xC0007000 + +#define USBD_STATUS_NO_MEMORY 0x80000100 +#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200 +#define USBD_STATUS_INVALID_PARAMETER 0x80000300 +#define USBD_STATUS_REQUEST_FAILED 0x80000500 +#define USBD_STATUS_INVALID_PIPE_HANDLE 0x80000600 +#define USBD_STATUS_ERROR_SHORT_TRANSFER 0x80000900 + +// Values for URB TransferFlags Field +// + +/* + Set if data moves device->host +*/ +#define USBD_TRANSFER_DIRECTION 0x00000001 +/* + This bit if not set indicates that a short packet, and hence, + a short transfer is an error condition +*/ +#define USBD_SHORT_TRANSFER_OK 0x00000002 +/* + Subit the iso transfer on the next frame +*/ +#define USBD_START_ISO_TRANSFER_ASAP 0x00000004 +#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008 + + +#define USBD_TRANSFER_DIRECTION_FLAG(flags) ((flags) & USBD_TRANSFER_DIRECTION) + +#define USBD_TRANSFER_DIRECTION_OUT 0 +#define USBD_TRANSFER_DIRECTION_IN 1 + +#define VALID_TRANSFER_FLAGS_MASK USBD_SHORT_TRANSFER_OK | \ + USBD_TRANSFER_DIRECTION | \ + USBD_START_ISO_TRANSFER_ASAP | \ + USBD_DEFAULT_PIPE_TRANSFER) + +#define ENDPOINT_HALT 0x00 +#define DEVICE_REMOTE_WAKEUP 0x01 + +/* transfer type */ +#define CONTROL_TRANSFER 0x00 +#define ISOCHRONOUS_TRANSFER 0x01 +#define BULK_TRANSFER 0x02 +#define INTERRUPT_TRANSFER 0x03 + +#define ClearHubFeature (0x2000 | LIBUSB_REQUEST_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | LIBUSB_REQUEST_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | LIBUSB_REQUEST_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | LIBUSB_REQUEST_GET_STATUS) +#define GetPortStatus (0xa300 | LIBUSB_REQUEST_GET_STATUS) +#define SetHubFeature (0x2000 | LIBUSB_REQUEST_SET_FEATURE) +#define SetPortFeature (0x2300 | LIBUSB_REQUEST_SET_FEATURE) + +#define USBD_PF_CHANGE_MAX_PACKET 0x00000001 +#define USBD_PF_SHORT_PACKET_OPT 0x00000002 +#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004 +#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008 + +/* feature request */ +#define URB_SET_FEATURE 0x00 +#define URB_CLEAR_FEATURE 0x01 + +#define USBD_PF_CHANGE_MAX_PACKET 0x00000001 +#define USBD_PF_SHORT_PACKET_OPT 0x00000002 +#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004 +#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008 + +#define URB_CONTROL_TRANSFER_EXTERNAL 0x1 +#define URB_CONTROL_TRANSFER_NONEXTERNAL 0x0 + +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_ISO_ASAP 0x02 +#define USBFS_URB_BULK_CONTINUATION 0x04 +#define USBFS_URB_QUEUE_BULK 0x10 + +#define URBDRC_DEVICE_INITIALIZED 0x01 +#define URBDRC_DEVICE_NOT_FOUND 0x02 +#define URBDRC_DEVICE_SIGNAL_END 0x04 +#define URBDRC_DEVICE_CHANNEL_CLOSED 0x08 +#define URBDRC_DEVICE_ALREADY_SEND 0x10 +#define URBDRC_DEVICE_DETACH_KERNEL 0x20 + +#define UDEVMAN_FLAG_ADD_BY_VID_PID 0x01 +#define UDEVMAN_FLAG_ADD_BY_ADDR 0x02 +#define UDEVMAN_FLAG_ADD_BY_AUTO 0x04 +#define UDEVMAN_FLAG_DEBUG 0x08 + +#define MAX_URB_REQUSET_NUM 0x80 + +#define LOG_LEVEL 1 + +#define dummy_wait_obj(void) do{ sleep(5); } while(0) +#define dummy_wait_s_obj(_s) do{ sleep(_s); } while(0) + +#define ISOCH_FIFO 1 +#define WAIT_COMPLETE_SLEEP 10000 /* for cpu high loading */ + +#define urbdrc_get_mstime(_t) do { \ + struct timeval _tp; \ + gettimeofday(&_tp, 0); \ + _t = (_tp.tv_sec * 1000) + (_tp.tv_usec / 1000); \ + } while (0) + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H */ diff --git a/channels/video/CMakeLists.txt b/channels/video/CMakeLists.txt new file mode 100644 index 0000000..f03c851 --- /dev/null +++ b/channels/video/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("video") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/video/ChannelOptions.cmake b/channels/video/ChannelOptions.cmake new file mode 100644 index 0000000..e7f9ce8 --- /dev/null +++ b/channels/video/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "video" TYPE "dynamic" + DESCRIPTION "Video optimized remoting Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEVOR]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) + diff --git a/channels/video/client/CMakeLists.txt b/channels/video/client/CMakeLists.txt new file mode 100644 index 0000000..cbbe483 --- /dev/null +++ b/channels/video/client/CMakeLists.txt @@ -0,0 +1,39 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2018 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("video") + +set(${MODULE_PREFIX}_SRCS + video_main.c + video_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + + + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/video/client/video_main.c b/channels/video/client/video_main.c new file mode 100755 index 0000000..2258750 --- /dev/null +++ b/channels/video/client/video_main.c @@ -0,0 +1,1169 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#define TAG CHANNELS_TAG("video") + +#include "video_main.h" + +struct _VIDEO_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _VIDEO_CHANNEL_CALLBACK VIDEO_CHANNEL_CALLBACK; + +struct _VIDEO_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + VIDEO_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _VIDEO_LISTENER_CALLBACK VIDEO_LISTENER_CALLBACK; + +struct _VIDEO_PLUGIN +{ + IWTSPlugin wtsPlugin; + + IWTSListener* controlListener; + IWTSListener* dataListener; + VIDEO_LISTENER_CALLBACK* control_callback; + VIDEO_LISTENER_CALLBACK* data_callback; + + VideoClientContext *context; +}; +typedef struct _VIDEO_PLUGIN VIDEO_PLUGIN; + + +#define XF_VIDEO_UNLIMITED_RATE 31 + +static const BYTE MFVideoFormat_H264[] = {'H', '2', '6', '4', + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}; + +typedef struct _PresentationContext PresentationContext; +typedef struct _VideoFrame VideoFrame; + + +/** @brief private data for the channel */ +struct _VideoClientContextPriv +{ + VideoClientContext *video; + GeometryClientContext *geometry; + wQueue *frames; + CRITICAL_SECTION framesLock; + wBufferPool *surfacePool; + UINT32 publishedFrames; + UINT32 droppedFrames; + UINT32 lastSentRate; + UINT64 nextFeedbackTime; + PresentationContext *currentPresentation; +}; + +/** @brief */ +struct _VideoFrame +{ + UINT64 publishTime; + UINT64 hnsDuration; + MAPPED_GEOMETRY *geometry; + UINT32 w, h; + BYTE *surfaceData; + PresentationContext *presentation; +}; + +/** @brief */ +struct _PresentationContext +{ + VideoClientContext *video; + BYTE PresentationId; + UINT32 SourceWidth, SourceHeight; + UINT32 ScaledWidth, ScaledHeight; + MAPPED_GEOMETRY *geometry; + + UINT64 startTimeStamp; + UINT64 publishOffset; + H264_CONTEXT *h264; + YUV_CONTEXT *yuv; + wStream *currentSample; + UINT64 lastPublishTime, nextPublishTime; + volatile LONG refCounter; + BYTE *surfaceData; + VideoSurface *surface; +}; + + +static const char *video_command_name(BYTE cmd) +{ + switch(cmd) + { + case TSMM_START_PRESENTATION: + return "start"; + case TSMM_STOP_PRESENTATION: + return "stop"; + default: + return ""; + } +} + +static BOOL yuv_to_rgb(PresentationContext *presentation, BYTE *dest) +{ + const BYTE* pYUVPoint[3]; + H264_CONTEXT *h264 = presentation->h264; + + BYTE** ppYUVData; + ppYUVData = h264->pYUVData; + + pYUVPoint[0] = ppYUVData[0]; + pYUVPoint[1] = ppYUVData[1]; + pYUVPoint[2] = ppYUVData[2]; + + if (!yuv_context_decode(presentation->yuv, pYUVPoint, h264->iStride, PIXEL_FORMAT_BGRX32, dest, h264->width * 4)) + { + WLog_ERR(TAG, "error in yuv_to_rgb conversion"); + return FALSE; + } + + return TRUE; +} + + +static void video_client_context_set_geometry(VideoClientContext *video, GeometryClientContext *geometry) +{ + video->priv->geometry = geometry; +} + +VideoClientContextPriv *VideoClientContextPriv_new(VideoClientContext *video) +{ + VideoClientContextPriv *ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->frames = Queue_New(TRUE, 10, 2); + if (!ret->frames) + { + WLog_ERR(TAG, "unable to allocate frames queue"); + goto error_frames; + } + + ret->surfacePool = BufferPool_New(FALSE, 0, 16); + if (!ret->surfacePool) + { + WLog_ERR(TAG, "unable to create surface pool"); + goto error_surfacePool; + } + + if (!InitializeCriticalSectionAndSpinCount(&ret->framesLock, 4 * 1000)) + { + WLog_ERR(TAG, "unable to initialize frames lock"); + goto error_spinlock; + } + + ret->video = video; + + /* don't set to unlimited so that we have the chance to send a feedback in + * the first second (for servers that want feedback directly) + */ + ret->lastSentRate = 30; + return ret; + +error_spinlock: + BufferPool_Free(ret->surfacePool); +error_surfacePool: + Queue_Free(ret->frames); +error_frames: + free(ret); + return NULL; +} + + +static PresentationContext *PresentationContext_new(VideoClientContext *video, BYTE PresentationId, + UINT32 x, UINT32 y, UINT32 width, UINT32 height) +{ + VideoClientContextPriv *priv = video->priv; + PresentationContext *ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->video = video; + ret->PresentationId = PresentationId; + + ret->h264 = h264_context_new(FALSE); + if (!ret->h264) + { + WLog_ERR(TAG, "unable to create a h264 context"); + goto error_h264; + } + h264_context_reset(ret->h264, width, height); + + ret->currentSample = Stream_New(NULL, 4096); + if (!ret->currentSample) + { + WLog_ERR(TAG, "unable to create current packet stream"); + goto error_currentSample; + } + + ret->surfaceData = BufferPool_Take(priv->surfacePool, width * height * 4); + if (!ret->surfaceData) + { + WLog_ERR(TAG, "unable to allocate surfaceData"); + goto error_surfaceData; + } + + ret->surface = video->createSurface(video, ret->surfaceData, x, y, width, height); + if (!ret->surface) + { + WLog_ERR(TAG, "unable to create surface"); + goto error_surface; + } + + ret->yuv = yuv_context_new(FALSE); + if (!ret->yuv) + { + WLog_ERR(TAG, "unable to create YUV decoder"); + goto error_yuv; + } + + yuv_context_reset(ret->yuv, width, height); + ret->refCounter = 1; + return ret; + +error_yuv: + video->deleteSurface(video, ret->surface); +error_surface: + BufferPool_Return(priv->surfacePool, ret->surfaceData); +error_surfaceData: + Stream_Free(ret->currentSample, TRUE); +error_currentSample: + h264_context_free(ret->h264); +error_h264: + free(ret); + return NULL; +} + + +static void PresentationContext_unref(PresentationContext *presentation) +{ + VideoClientContextPriv *priv; + MAPPED_GEOMETRY *geometry; + + if (!presentation) + return; + + if (InterlockedDecrement(&presentation->refCounter) != 0) + return; + + geometry = presentation->geometry; + if (geometry) + { + geometry->MappedGeometryUpdate = NULL; + geometry->MappedGeometryClear = NULL; + geometry->custom = NULL; + mappedGeometryUnref(geometry); + } + + priv = presentation->video->priv; + + h264_context_free(presentation->h264); + Stream_Free(presentation->currentSample, TRUE); + presentation->video->deleteSurface(presentation->video, presentation->surface); + BufferPool_Return(priv->surfacePool, presentation->surfaceData); + yuv_context_free(presentation->yuv); + free(presentation); +} + + +static void VideoFrame_free(VideoFrame **pframe) +{ + VideoFrame *frame = *pframe; + + mappedGeometryUnref(frame->geometry); + BufferPool_Return(frame->presentation->video->priv->surfacePool, frame->surfaceData); + PresentationContext_unref(frame->presentation); + free(frame); + *pframe = NULL; +} + + +static void VideoClientContextPriv_free(VideoClientContextPriv *priv) +{ + EnterCriticalSection(&priv->framesLock); + while (Queue_Count(priv->frames)) + { + VideoFrame *frame = Queue_Dequeue(priv->frames); + if (frame) + VideoFrame_free(&frame); + } + + Queue_Free(priv->frames); + LeaveCriticalSection(&priv->framesLock); + + DeleteCriticalSection(&priv->framesLock); + + if (priv->currentPresentation) + PresentationContext_unref(priv->currentPresentation); + + BufferPool_Free(priv->surfacePool); + free(priv); +} + + +static UINT video_control_send_presentation_response(VideoClientContext *context, TSMM_PRESENTATION_RESPONSE *resp) +{ + BYTE buf[12]; + wStream *s; + VIDEO_PLUGIN* video = (VIDEO_PLUGIN *)context->handle; + IWTSVirtualChannel* channel; + UINT ret; + + s = Stream_New(buf, 12); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 12); /* cbSize */ + Stream_Write_UINT32(s, TSMM_PACKET_TYPE_PRESENTATION_RESPONSE); /* PacketType */ + Stream_Write_UINT8(s, resp->PresentationId); + Stream_Zero(s, 3); + Stream_SealLength(s); + + channel = video->control_callback->channel_callback->channel; + ret = channel->Write(channel, 12, buf, NULL); + Stream_Free(s, FALSE); + + return ret; +} + +static BOOL video_onMappedGeometryUpdate(MAPPED_GEOMETRY *geometry) +{ + PresentationContext *presentation = (PresentationContext *)geometry->custom; + RDP_RECT *r = &geometry->geometry.boundingRect; + WLog_DBG(TAG, "geometry updated topGeom=(%d,%d-%dx%d) geom=(%d,%d-%dx%d) rects=(%d,%d-%dx%d)", + geometry->topLevelLeft, geometry->topLevelTop, + geometry->topLevelRight - geometry->topLevelLeft, geometry->topLevelBottom - geometry->topLevelTop, + + geometry->left, geometry->top, + geometry->right - geometry->left, geometry->bottom - geometry->top, + + r->x, r->y, r->width, r->height + ); + + presentation->surface->x = geometry->topLevelLeft + geometry->left; + presentation->surface->y = geometry->topLevelTop + geometry->top; + + return TRUE; +} + +static BOOL video_onMappedGeometryClear(MAPPED_GEOMETRY *geometry) +{ + PresentationContext *presentation = (PresentationContext *)geometry->custom; + + mappedGeometryUnref(presentation->geometry); + presentation->geometry = NULL; + return TRUE; +} + +static UINT video_PresentationRequest(VideoClientContext* video, TSMM_PRESENTATION_REQUEST *req) +{ + VideoClientContextPriv *priv = video->priv; + PresentationContext *presentation; + UINT ret = CHANNEL_RC_OK; + + presentation = priv->currentPresentation; + + if (req->Command == TSMM_START_PRESENTATION) + { + MAPPED_GEOMETRY *geom; + TSMM_PRESENTATION_RESPONSE resp; + + if (memcmp(req->VideoSubtypeId, MFVideoFormat_H264, 16) != 0) + { + WLog_ERR(TAG, "not a H264 video, ignoring request"); + return CHANNEL_RC_OK; + } + + if (presentation) + { + if (presentation->PresentationId == req->PresentationId) + { + WLog_ERR(TAG, "ignoring start request for existing presentation %d", req->PresentationId); + return CHANNEL_RC_OK; + } + + WLog_ERR(TAG, "releasing current presentation %d", req->PresentationId); + PresentationContext_unref(presentation); + presentation = priv->currentPresentation = NULL; + } + + if (!priv->geometry) + { + WLog_ERR(TAG, "geometry channel not ready, ignoring request"); + return CHANNEL_RC_OK; + } + + geom = HashTable_GetItemValue(priv->geometry->geometries, &(req->GeometryMappingId)); + if (!geom) + { + WLog_ERR(TAG, "geometry mapping 0x%"PRIx64" not registered", req->GeometryMappingId); + return CHANNEL_RC_OK; + } + + WLog_DBG(TAG, "creating presentation 0x%x", req->PresentationId); + presentation = PresentationContext_new(video, req->PresentationId, + geom->topLevelLeft + geom->left, + geom->topLevelTop + geom->top, + req->SourceWidth, req->SourceHeight); + if (!presentation) + { + WLog_ERR(TAG, "unable to create presentation video"); + return CHANNEL_RC_NO_MEMORY; + } + + mappedGeometryRef(geom); + presentation->geometry = geom; + + priv->currentPresentation = presentation; + presentation->video = video; + presentation->SourceWidth = req->SourceWidth; + presentation->SourceHeight = req->SourceHeight; + presentation->ScaledWidth = req->ScaledWidth; + presentation->ScaledHeight = req->ScaledHeight; + + geom->custom = presentation; + geom->MappedGeometryUpdate = video_onMappedGeometryUpdate; + geom->MappedGeometryClear = video_onMappedGeometryClear; + + /* send back response */ + resp.PresentationId = req->PresentationId; + ret = video_control_send_presentation_response(video, &resp); + } + else if (req->Command == TSMM_STOP_PRESENTATION) + { + WLog_DBG(TAG, "stopping presentation 0x%x", req->PresentationId); + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation to stop %d", req->PresentationId); + return CHANNEL_RC_OK; + } + + priv->currentPresentation = NULL; + priv->droppedFrames = 0; + priv->publishedFrames = 0; + PresentationContext_unref(presentation); + } + + return CHANNEL_RC_OK; +} + + +static UINT video_read_tsmm_presentation_req(VideoClientContext *context, wStream *s) +{ + TSMM_PRESENTATION_REQUEST req; + + if (Stream_GetRemainingLength(s) < 60) + { + WLog_ERR(TAG, "not enough bytes for a TSMM_PRESENTATION_REQUEST"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, req.PresentationId); + Stream_Read_UINT8(s, req.Version); + Stream_Read_UINT8(s, req.Command); + Stream_Read_UINT8(s, req.FrameRate); /* FrameRate - reserved and ignored */ + + Stream_Seek_UINT16(s); /* AverageBitrateKbps reserved and ignored */ + Stream_Seek_UINT16(s); /* reserved */ + + Stream_Read_UINT32(s, req.SourceWidth); + Stream_Read_UINT32(s, req.SourceHeight); + Stream_Read_UINT32(s, req.ScaledWidth); + Stream_Read_UINT32(s, req.ScaledHeight); + Stream_Read_UINT64(s, req.hnsTimestampOffset); + Stream_Read_UINT64(s, req.GeometryMappingId); + Stream_Read(s, req.VideoSubtypeId, 16); + + Stream_Read_UINT32(s, req.cbExtra); + + if (Stream_GetRemainingLength(s) < req.cbExtra) + { + WLog_ERR(TAG, "not enough bytes for cbExtra of TSMM_PRESENTATION_REQUEST"); + return ERROR_INVALID_DATA; + } + + req.pExtraData = Stream_Pointer(s); + + WLog_DBG(TAG, "presentationReq: id:%"PRIu8" version:%"PRIu8" command:%s srcWidth/srcHeight=%"PRIu32"x%"PRIu32 + " scaled Width/Height=%"PRIu32"x%"PRIu32" timestamp=%"PRIu64" mappingId=%"PRIx64"", + req.PresentationId, req.Version, video_command_name(req.Command), + req.SourceWidth, req.SourceHeight, req.ScaledWidth, req.ScaledHeight, + req.hnsTimestampOffset, req.GeometryMappingId); + + return video_PresentationRequest(context, &req); +} + + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_control_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *s) +{ + VIDEO_CHANNEL_CALLBACK* callback = (VIDEO_CHANNEL_CALLBACK*) pChannelCallback; + VIDEO_PLUGIN* video; + VideoClientContext *context; + UINT ret = CHANNEL_RC_OK; + UINT32 cbSize, packetType; + + video = (VIDEO_PLUGIN*) callback->plugin; + context = (VideoClientContext *)video->wtsPlugin.pInterface; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cbSize); + if (cbSize < 8 || Stream_GetRemainingLength(s) < (cbSize-4)) + { + WLog_ERR(TAG, "invalid cbSize"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, packetType); + switch (packetType) + { + case TSMM_PACKET_TYPE_PRESENTATION_REQUEST: + ret = video_read_tsmm_presentation_req(context, s); + break; + default: + WLog_ERR(TAG, "not expecting packet type %"PRIu32"", packetType); + ret = ERROR_UNSUPPORTED_TYPE; + break; + } + + return ret; +} + +static UINT video_control_send_client_notification(VideoClientContext *context, TSMM_CLIENT_NOTIFICATION *notif) +{ + BYTE buf[100]; + wStream *s; + VIDEO_PLUGIN* video = (VIDEO_PLUGIN *)context->handle; + IWTSVirtualChannel* channel; + UINT ret; + UINT32 cbSize; + + s = Stream_New(buf, 30); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + cbSize = 16; + Stream_Seek_UINT32(s); /* cbSize */ + Stream_Write_UINT32(s, TSMM_PACKET_TYPE_CLIENT_NOTIFICATION); /* PacketType */ + Stream_Write_UINT8(s, notif->PresentationId); + Stream_Write_UINT8(s, notif->NotificationType); + Stream_Zero(s, 2); + if (notif->NotificationType == TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE) + { + Stream_Write_UINT32(s, 16); /* cbData */ + + /* TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE */ + Stream_Write_UINT32(s, notif->FramerateOverride.Flags); + Stream_Write_UINT32(s, notif->FramerateOverride.DesiredFrameRate); + Stream_Zero(s, 4 * 2); + + cbSize += 4 * 4; + } + else + { + Stream_Write_UINT32(s, 0); /* cbData */ + } + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + Stream_Write_UINT32(s, cbSize); + Stream_Free(s, FALSE); + + channel = video->control_callback->channel_callback->channel; + ret = channel->Write(channel, cbSize, buf, NULL); + + return ret; +} + +static void video_timer(VideoClientContext *video, UINT64 now) +{ + PresentationContext *presentation; + VideoClientContextPriv *priv = video->priv; + VideoFrame *peekFrame, *frame = NULL; + + EnterCriticalSection(&priv->framesLock); + do + { + peekFrame = (VideoFrame *)Queue_Peek(priv->frames); + if (!peekFrame) + break; + + if (peekFrame->publishTime > now) + break; + + if (frame) + { + WLog_DBG(TAG, "dropping frame @%"PRIu64, frame->publishTime); + priv->droppedFrames++; + VideoFrame_free(&frame); + } + frame = peekFrame; + Queue_Dequeue(priv->frames); + } + while (1); + LeaveCriticalSection(&priv->framesLock); + + if (!frame) + goto treat_feedback; + + presentation = frame->presentation; + + priv->publishedFrames++; + memcpy(presentation->surfaceData, frame->surfaceData, frame->w * frame->h * 4); + + video->showSurface(video, presentation->surface); + + VideoFrame_free(&frame); + +treat_feedback: + if (priv->nextFeedbackTime < now) + { + /* we can compute some feedback only if we have some published frames and + * a current presentation + */ + if (priv->publishedFrames && priv->currentPresentation) + { + UINT32 computedRate; + + InterlockedIncrement(&priv->currentPresentation->refCounter); + + if (priv->droppedFrames) + { + /** + * some dropped frames, looks like we're asking too many frames per seconds, + * try lowering rate. We go directly from unlimited rate to 24 frames/seconds + * otherwise we lower rate by 2 frames by seconds + */ + if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE) + computedRate = 24; + else + { + computedRate = priv->lastSentRate - 2; + if (!computedRate) + computedRate = 2; + } + } + else + { + /** + * we treat all frames ok, so either ask the server to send more, + * or stay unlimited + */ + if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE) + computedRate = XF_VIDEO_UNLIMITED_RATE; /* stay unlimited */ + else + { + computedRate = priv->lastSentRate + 2; + if (computedRate > XF_VIDEO_UNLIMITED_RATE) + computedRate = XF_VIDEO_UNLIMITED_RATE; + } + } + + if (computedRate != priv->lastSentRate) + { + TSMM_CLIENT_NOTIFICATION notif; + notif.PresentationId = priv->currentPresentation->PresentationId; + notif.NotificationType = TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE; + if (computedRate == XF_VIDEO_UNLIMITED_RATE) + { + notif.FramerateOverride.Flags = 0x01; + notif.FramerateOverride.DesiredFrameRate = 0x00; + } + else + { + notif.FramerateOverride.Flags = 0x02; + notif.FramerateOverride.DesiredFrameRate = computedRate; + } + + video_control_send_client_notification(video, ¬if); + priv->lastSentRate = computedRate; + + WLog_DBG(TAG, "server notified with rate %d published=%d dropped=%d", priv->lastSentRate, + priv->publishedFrames, priv->droppedFrames); + } + + PresentationContext_unref(priv->currentPresentation); + } + + WLog_DBG(TAG, "currentRate=%d published=%d dropped=%d", priv->lastSentRate, + priv->publishedFrames, priv->droppedFrames); + + priv->droppedFrames = 0; + priv->publishedFrames = 0; + priv->nextFeedbackTime = now + 1000; + } +} + + +static UINT video_VideoData(VideoClientContext* context, TSMM_VIDEO_DATA *data) +{ + VideoClientContextPriv *priv = context->priv; + PresentationContext *presentation; + int status; + + presentation = priv->currentPresentation; + if (!presentation) + { + WLog_ERR(TAG, "no current presentation"); + return CHANNEL_RC_OK; + } + + if (presentation->PresentationId != data->PresentationId) + { + WLog_ERR(TAG, "current presentation id=%d doesn't match data id=%d", presentation->PresentationId, + data->PresentationId); + return CHANNEL_RC_OK; + } + + if (!Stream_EnsureRemainingCapacity(presentation->currentSample, data->cbSample)) + { + WLog_ERR(TAG, "unable to expand the current packet"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(presentation->currentSample, data->pSample, data->cbSample); + + if (data->CurrentPacketIndex == data->PacketsInSample) + { + H264_CONTEXT *h264 = presentation->h264; + UINT64 startTime = GetTickCount64(), timeAfterH264; + MAPPED_GEOMETRY *geom = presentation->geometry; + + Stream_SealLength(presentation->currentSample); + Stream_SetPosition(presentation->currentSample, 0); + + status = h264->subsystem->Decompress(h264, Stream_Pointer(presentation->currentSample), + Stream_Length(presentation->currentSample)); + if (status == 0) + return CHANNEL_RC_OK; + + if (status < 0) + return CHANNEL_RC_OK; + + timeAfterH264 = GetTickCount64(); + if (data->SampleNumber == 1) + { + presentation->lastPublishTime = startTime; + } + + presentation->lastPublishTime += (data->hnsDuration / 10000); + if (presentation->lastPublishTime <= timeAfterH264 + 10) + { + int dropped = 0; + + /* if the frame is to be published in less than 10 ms, let's consider it's now */ + yuv_to_rgb(presentation, presentation->surfaceData); + + context->showSurface(context, presentation->surface); + + priv->publishedFrames++; + + /* cleanup previously scheduled frames */ + EnterCriticalSection(&priv->framesLock); + while (Queue_Count(priv->frames) > 0) + { + VideoFrame *frame = Queue_Dequeue(priv->frames); + if (frame) + { + priv->droppedFrames++; + VideoFrame_free(&frame); + dropped++; + } + } + LeaveCriticalSection(&priv->framesLock); + + if (dropped) + WLog_DBG(TAG, "showing frame (%d dropped)", dropped); + } + else + { + BOOL enqueueResult; + VideoFrame *frame = calloc(1, sizeof(*frame)); + if (!frame) + { + WLog_ERR(TAG, "unable to create frame"); + return CHANNEL_RC_NO_MEMORY; + } + mappedGeometryRef(geom); + + frame->presentation = presentation; + frame->publishTime = presentation->lastPublishTime; + frame->geometry = geom; + frame->w = presentation->SourceWidth; + frame->h = presentation->SourceHeight; + + frame->surfaceData = BufferPool_Take(priv->surfacePool, frame->w * frame->h * 4); + if (!frame->surfaceData) + { + WLog_ERR(TAG, "unable to allocate frame data"); + mappedGeometryUnref(geom); + free(frame); + return CHANNEL_RC_NO_MEMORY; + } + + if (!yuv_to_rgb(presentation, frame->surfaceData)) + { + WLog_ERR(TAG, "error during YUV->RGB conversion"); + BufferPool_Return(priv->surfacePool, frame->surfaceData); + mappedGeometryUnref(geom); + free(frame); + return CHANNEL_RC_NO_MEMORY; + } + + InterlockedIncrement(&presentation->refCounter); + + EnterCriticalSection(&priv->framesLock); + enqueueResult = Queue_Enqueue(priv->frames, frame); + LeaveCriticalSection(&priv->framesLock); + + if (!enqueueResult) + { + WLog_ERR(TAG, "unable to enqueue frame"); + VideoFrame_free(&frame); + return CHANNEL_RC_NO_MEMORY; + } + + WLog_DBG(TAG, "scheduling frame in %"PRIu32" ms", (frame->publishTime-startTime)); + } + } + + return CHANNEL_RC_OK; +} + + + +static UINT video_data_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *s) +{ + VIDEO_CHANNEL_CALLBACK* callback = (VIDEO_CHANNEL_CALLBACK*) pChannelCallback; + VIDEO_PLUGIN* video; + VideoClientContext *context; + UINT32 cbSize, packetType; + TSMM_VIDEO_DATA data; + + video = (VIDEO_PLUGIN*) callback->plugin; + context = (VideoClientContext *)video->wtsPlugin.pInterface; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cbSize); + if (cbSize < 8 || Stream_GetRemainingLength(s) < (cbSize-4)) + { + WLog_ERR(TAG, "invalid cbSize"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, packetType); + if (packetType != TSMM_PACKET_TYPE_VIDEO_DATA) + { + WLog_ERR(TAG, "only expecting VIDEO_DATA on the data channel"); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < 32) + { + WLog_ERR(TAG, "not enough bytes for a TSMM_VIDEO_DATA"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, data.PresentationId); + Stream_Read_UINT8(s, data.Version); + Stream_Read_UINT8(s, data.Flags); + Stream_Seek_UINT8(s); /* reserved */ + Stream_Read_UINT64(s, data.hnsTimestamp); + Stream_Read_UINT64(s, data.hnsDuration); + Stream_Read_UINT16(s, data.CurrentPacketIndex); + Stream_Read_UINT16(s, data.PacketsInSample); + Stream_Read_UINT32(s, data.SampleNumber); + Stream_Read_UINT32(s, data.cbSample); + data.pSample = Stream_Pointer(s); + +/* + WLog_DBG(TAG, "videoData: id:%"PRIu8" version:%"PRIu8" flags:0x%"PRIx8" timestamp=%"PRIu64" duration=%"PRIu64 + " curPacketIndex:%"PRIu16" packetInSample:%"PRIu16" sampleNumber:%"PRIu32" cbSample:%"PRIu32"", + data.PresentationId, data.Version, data.Flags, data.hnsTimestamp, data.hnsDuration, + data.CurrentPacketIndex, data.PacketsInSample, data.SampleNumber, data.cbSample); +*/ + + return video_VideoData(context, &data); +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_control_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +static UINT video_data_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_control_on_new_channel_connection(IWTSListenerCallback* listenerCallback, + IWTSVirtualChannel* channel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + VIDEO_CHANNEL_CALLBACK* callback; + VIDEO_LISTENER_CALLBACK* listener_callback = (VIDEO_LISTENER_CALLBACK*) listenerCallback; + + callback = (VIDEO_CHANNEL_CALLBACK*) calloc(1, sizeof(VIDEO_CHANNEL_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = video_control_on_data_received; + callback->iface.OnClose = video_control_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = channel; + listener_callback->channel_callback = callback; + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return CHANNEL_RC_OK; +} + +static UINT video_data_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + VIDEO_CHANNEL_CALLBACK* callback; + VIDEO_LISTENER_CALLBACK* listener_callback = (VIDEO_LISTENER_CALLBACK*) pListenerCallback; + + callback = (VIDEO_CHANNEL_CALLBACK*) calloc(1, sizeof(VIDEO_CHANNEL_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = video_data_on_data_received; + callback->iface.OnClose = video_data_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return CHANNEL_RC_OK; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_plugin_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManager* channelMgr) +{ + UINT status; + VIDEO_PLUGIN* video = (VIDEO_PLUGIN *)plugin; + VIDEO_LISTENER_CALLBACK *callback; + + video->control_callback = callback = (VIDEO_LISTENER_CALLBACK*) calloc(1, sizeof(VIDEO_LISTENER_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc for control callback failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnNewChannelConnection = video_control_on_new_channel_connection; + callback->plugin = plugin; + callback->channel_mgr = channelMgr; + + status = channelMgr->CreateListener(channelMgr, VIDEO_CONTROL_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*)callback, &(video->controlListener)); + + if (status != CHANNEL_RC_OK) + return status; + video->controlListener->pInterface = video->wtsPlugin.pInterface; + + + video->data_callback = callback = (VIDEO_LISTENER_CALLBACK*) calloc(1, sizeof(VIDEO_LISTENER_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc for data callback failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnNewChannelConnection = video_data_on_new_channel_connection; + callback->plugin = plugin; + callback->channel_mgr = channelMgr; + + status = channelMgr->CreateListener(channelMgr, VIDEO_DATA_DVC_CHANNEL_NAME, 0, + (IWTSListenerCallback*)callback, &(video->dataListener)); + + if (status == CHANNEL_RC_OK) + video->dataListener->pInterface = video->wtsPlugin.pInterface; + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_plugin_terminated(IWTSPlugin* pPlugin) +{ + VIDEO_PLUGIN* video = (VIDEO_PLUGIN*) pPlugin; + + if (video->context) + VideoClientContextPriv_free(video->context->priv); + + free(video->control_callback); + free(video->data_callback); + free(video->wtsPlugin.pInterface); + free(pPlugin); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry video_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + VIDEO_PLUGIN* videoPlugin; + VideoClientContext* videoContext; + VideoClientContextPriv *priv; + + videoPlugin = (VIDEO_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "video"); + if (!videoPlugin) + { + videoPlugin = (VIDEO_PLUGIN*) calloc(1, sizeof(VIDEO_PLUGIN)); + if (!videoPlugin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + videoPlugin->wtsPlugin.Initialize = video_plugin_initialize; + videoPlugin->wtsPlugin.Connected = NULL; + videoPlugin->wtsPlugin.Disconnected = NULL; + videoPlugin->wtsPlugin.Terminated = video_plugin_terminated; + + videoContext = (VideoClientContext*) calloc(1, sizeof(VideoClientContext)); + if (!videoContext) + { + WLog_ERR(TAG, "calloc failed!"); + free(videoPlugin); + return CHANNEL_RC_NO_MEMORY; + } + + priv = VideoClientContextPriv_new(videoContext); + if (!priv) + { + WLog_ERR(TAG, "VideoClientContextPriv_new failed!"); + free(videoContext); + free(videoPlugin); + return CHANNEL_RC_NO_MEMORY; + } + + videoContext->handle = (void*) videoPlugin; + videoContext->priv = priv; + videoContext->timer = video_timer; + videoContext->setGeometry = video_client_context_set_geometry; + + videoPlugin->wtsPlugin.pInterface = (void*) videoContext; + videoPlugin->context = videoContext; + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", (IWTSPlugin*) videoPlugin); + } + else + { + WLog_ERR(TAG, "could not get video Plugin."); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/channels/video/client/video_main.h b/channels/video/client/video_main.h new file mode 100644 index 0000000..f5e5ac9 --- /dev/null +++ b/channels/video/client/video_main.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H +#define FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + + +#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */ + diff --git a/ci/cmake-preloads/config-android.txt b/ci/cmake-preloads/config-android.txt new file mode 100644 index 0000000..38833e7 --- /dev/null +++ b/ci/cmake-preloads/config-android.txt @@ -0,0 +1,7 @@ +message("PRELOADING android cache") +set(CMAKE_TOOLCHAIN_FILE "cmake/AndroidToolchain.cmake" CACHE PATH "ToolChain file") +set(WITH_SANITIZE_ADDRESS ON) +set(FREERDP_EXTERNAL_SSL_PATH $ENV{ANDROID_SSL_PATH} CACHE PATH "android ssl") +# ANDROID_NDK and ANDROID_SDK must be set as environment variable +#set(ANDROID_NDK $ENV{ANDROID_SDK} CACHE PATH "Android NDK") +#set(ANDROID_SDK "${ANDROID_NDK}" CACHE PATH "android SDK") diff --git a/ci/cmake-preloads/config-debian-squeeze.txt b/ci/cmake-preloads/config-debian-squeeze.txt new file mode 100644 index 0000000..c7319cf --- /dev/null +++ b/ci/cmake-preloads/config-debian-squeeze.txt @@ -0,0 +1,11 @@ +message("PRELOADING cache") +set (WITH_MANPAGES OFF CACHE BOOL "man pages") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (WITH_CUPS OFF CACHE BOOL "CUPS printing") +set (WITH_GSSAPI ON CACHE BOOL "Kerberos support") +set (WITH_ALSA OFF CACHE BOOL "alsa audio") +set (WITH_FFMPEG OFF CACHE BOOL "ffmepg support") +set (WITH_XV OFF CACHE BOOL "xvideo support") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_XSHM OFF CACHE BOOL "build with xshm support") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/ci/cmake-preloads/config-ios.txt b/ci/cmake-preloads/config-ios.txt new file mode 100644 index 0000000..d37eda6 --- /dev/null +++ b/ci/cmake-preloads/config-ios.txt @@ -0,0 +1,6 @@ +message("PRELOADING android cache") +set (CMAKE_TOOLCHAIN_FILE "cmake/iOSToolchain.cmake" CACHE PATH "ToolChain file") +set (FREERDP_IOS_EXTERNAL_SSL_PATH $ENV{FREERDP_IOS_EXTERNAL_SSL_PATH} CACHE PATH "android ssl") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (IOS_PLATFORM "SIMULATOR" CACHE STRING "iso platfrorm to build") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/ci/cmake-preloads/config-linux-all.txt b/ci/cmake-preloads/config-linux-all.txt new file mode 100644 index 0000000..16d3f9f --- /dev/null +++ b/ci/cmake-preloads/config-linux-all.txt @@ -0,0 +1,50 @@ +message("PRELOADING cache") +set (BUILD_TESTING ON CACHE BOOL "testing") +set (WITH_MANPAGES OFF CACHE BOOL "man pages") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_PULSE ON CACHE BOOL "pulse") +set (WITH_CHANNELS ON CACHE BOOL "channels") +set (BUILTIN_CHANNELS ON CACHE BOOL "static channels") +set (WITH_CUPS ON CACHE BOOL "cups") +set (WITH_GSSAPI ON CACHE BOOL "Kerberos support") +set (WITH_PCSC ON CACHE BOOL "PCSC") +set (WITH_JPEG ON CACHE BOOL "jepg") +set (WITH_GSTREAMER_0_10 ON CACHE BOOL "gstreamer") +set (WITH_GSM ON CACHE BOOL "gsm") +set (CHANNEL_URBDRC ON CACHE BOOL "urbdrc") +set (CHANNEL_URBDRC_CLIENT ON CACHE BOOL "urbdrc client") +set (WITH_SERVER ON CACHE BOOL "server side") +set (WITH_DEBUG_ALL OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_CAPABILITIES OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_CERTIFICATE OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_CHANNELS OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_CLIPRDR OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_DVC OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_KBD OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_LICENSE OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_NEGO OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_NLA OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_NTLM OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_RAIL OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_RDP OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_RDPEI OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_REDIR OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_RDPDR OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_RFX OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_SCARD OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_SND OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_SVC OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_THREADS OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_TIMEZONE OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_TRANSPORT OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_TSG OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_TSMF OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_WND OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_X11 OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_X11_CLIPRDR OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_X11_LOCAL_MOVESIZE OFF CACHE BOOL "enable debug") +set (WITH_DEBUG_XV OFF CACHE BOOL "enable debug") +set (WITH_SAMPLE ON CACHE BOOL "samples") +set (WITH_NO_UNDEFINED ON CACHE BOOL "don't link with undefined symbols") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/ci/cmake-preloads/config-macosx.txt b/ci/cmake-preloads/config-macosx.txt new file mode 100644 index 0000000..8c68aed --- /dev/null +++ b/ci/cmake-preloads/config-macosx.txt @@ -0,0 +1,7 @@ +message("PRELOADING mac cache") +set (WITH_MANPAGES OFF CACHE BOOL "man pages") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (WITH_CUPS ON CACHE BOOL "CUPS printing") +set (WITH_X11 ON CACHE BOOL "Enable X11") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/ci/cmake-preloads/config-ubuntu-1204.txt b/ci/cmake-preloads/config-ubuntu-1204.txt new file mode 100644 index 0000000..c7319cf --- /dev/null +++ b/ci/cmake-preloads/config-ubuntu-1204.txt @@ -0,0 +1,11 @@ +message("PRELOADING cache") +set (WITH_MANPAGES OFF CACHE BOOL "man pages") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (WITH_CUPS OFF CACHE BOOL "CUPS printing") +set (WITH_GSSAPI ON CACHE BOOL "Kerberos support") +set (WITH_ALSA OFF CACHE BOOL "alsa audio") +set (WITH_FFMPEG OFF CACHE BOOL "ffmepg support") +set (WITH_XV OFF CACHE BOOL "xvideo support") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_XSHM OFF CACHE BOOL "build with xshm support") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/ci/cmake-preloads/config-windows.txt b/ci/cmake-preloads/config-windows.txt new file mode 100644 index 0000000..33dc8b4 --- /dev/null +++ b/ci/cmake-preloads/config-windows.txt @@ -0,0 +1,5 @@ +message("PRELOADING windows cache") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (WITH_SERVER "ON" CACHE BOOL "Build server binaries") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/client/Android/CMakeLists.txt b/client/Android/CMakeLists.txt new file mode 100644 index 0000000..7fcdb63 --- /dev/null +++ b/client/Android/CMakeLists.txt @@ -0,0 +1,54 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# Android Client +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Bernhard Miklautz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "freerdp-android") +set(MODULE_PREFIX "FREERDP_CLIENT_ANDROID") + +include_directories(.) + +if(CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-pointer-sign") +endif() + +set(${MODULE_PREFIX}_SRCS + android_event.c + android_event.h + android_freerdp.c + android_freerdp.h + android_jni_utils.c + android_jni_utils.h + android_jni_callback.c + android_jni_callback.h) + +if(WITH_CLIENT_CHANNELS) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + android_cliprdr.c + android_cliprdr.h) +endif() + +add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp freerdp-client) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} dl) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} jnigraphics) + +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries EXPORT AndroidTargets) diff --git a/client/Android/Studio/.gitignore b/client/Android/Studio/.gitignore new file mode 100644 index 0000000..a2dc231 --- /dev/null +++ b/client/Android/Studio/.gitignore @@ -0,0 +1,37 @@ +#built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Windows thumbnail db +Thumbs.db + +# OSX files +.DS_Store + +# Eclipse project files +.classpath +.project + +# Android Studio +*.iml +.idea +#.idea/workspace.xml - remove # and delete .idea if it better suit your needs. +.gradle +build/ + +#NDK +obj/ +jniLibs/ diff --git a/client/Android/Studio/aFreeRDP/build.gradle b/client/Android/Studio/aFreeRDP/build.gradle new file mode 100644 index 0000000..be32859 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion = 27 + buildToolsVersion = "27.0.3" + + defaultConfig { + applicationId "com.freerdp.afreerdp" + minSdkVersion 14 + targetSdkVersion 27 + vectorDrawables.useSupportLibrary = true + versionCode = 14 + versionName = rootProject.ext.versionName + } + + signingConfigs { + release { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + storeType "jks" + } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + debug { + jniDebuggable true + renderscriptDebuggable true + } + } +} + +dependencies { + compile project(':freeRDPCore') +} diff --git a/client/Android/Studio/aFreeRDP/lint.xml b/client/Android/Studio/aFreeRDP/lint.xml new file mode 100644 index 0000000..c70207f --- /dev/null +++ b/client/Android/Studio/aFreeRDP/lint.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml b/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7d7558d --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png b/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png new file mode 100644 index 0000000..1e27262 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/about.css b/client/Android/Studio/aFreeRDP/src/main/assets/about.css new file mode 100644 index 0000000..604e505 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/about.css @@ -0,0 +1,147 @@ +p { + border: none; + padding: 0in; + font-variant: normal; + font-family: "Helvetica"; + font-style: normal; + font-weight: normal; + line-height: 100%; + text-align: center; +} + +td p { + border: none; + padding: 0in; + font-variant: normal; + font-family: "Helvetica"; + font-style: normal; + font-weight: normal; + line-height: 100%; + text-align: center; +} + +h2 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; +} + +h2.western { + font-style: normal; + } + +h2.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h2.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +h3 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; + page-break-before: auto; + page-break-after: auto; +} + +h3.western { + font-style: normal; +} + +h3.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h3.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +h4 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; + page-break-before: auto; + page-break-after: auto; +} + +h4.western { + font-style: normal; +} + +h4.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h4.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +pre { + direction: inherit; + font-variant: normal; + line-height: 100%; + text-align: center; + page-break-before: auto; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.western { + font-size: 8pt; + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.cjk { + font-family: "AR PL SungtiL GB", monospace; + font-size: 8pt; + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.ctl { + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +a:link { + color: #0000ff +} \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html b/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html new file mode 100644 index 0000000..fa73e42 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html @@ -0,0 +1,397 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client

+
+

+ +

+
+

aFreeRDP is an open source client + capable of natively using
+ Remote Desktop Protocol (RDP) in + order to remotely access your Windows desktop.
+

+
+
+

+
+
+ +

+
+

+ Version Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+

+ Credits

+
+

aFreeRDP + is a part of FreeRDP +

+
+

+ + Data protection

+
+

Details + about data collection and usage by aFreeRDP are available at

+

http://www.freerdp.com/privacy +

+
+

+ + Licenses

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+

+
+
+ +

+
+

+ + FreeRDP

+
+
Licensed under the Apache License, Version 2.0 (the "License");
+
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0 
+Unless required by applicable law or agreed to in writing, software
+
+distributed under the License is distributed on an "AS IS" BASIS,
+
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+See the License for the specific language governing permissions and
+
+limitations under the License. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publically available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html new file mode 100644 index 0000000..fa73e42 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html @@ -0,0 +1,397 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client

+
+

+ +

+
+

aFreeRDP is an open source client + capable of natively using
+ Remote Desktop Protocol (RDP) in + order to remotely access your Windows desktop.
+

+
+
+

+
+
+ +

+
+

+ Version Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+

+ Credits

+
+

aFreeRDP + is a part of FreeRDP +

+
+

+ + Data protection

+
+

Details + about data collection and usage by aFreeRDP are available at

+

http://www.freerdp.com/privacy +

+
+

+ + Licenses

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+

+
+
+ +

+
+

+ + FreeRDP

+
+
Licensed under the Apache License, Version 2.0 (the "License");
+
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0 
+Unless required by applicable law or agreed to in writing, software
+
+distributed under the License is distributed on an "AS IS" BASIS,
+
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+See the License for the specific language governing permissions and
+
+limitations under the License. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publically available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg b/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg new file mode 100644 index 0000000..fd4e1d3 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html new file mode 100644 index 0000000..90760c1 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html @@ -0,0 +1,410 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client
+

+
+
+

+ + +

+
+
+

+ aFreeRDP ist ein Open Source Programm + mit nativer Unterstützung des Remote Desktop Protocol (RDP)
+ um + einen entfernten Zugriff auf Windows Desktops zu ermöglichen.
+

+
+

+ Versions Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+
+

+ +
+
+ +

+
+

+ + Credits

+
+

aFreeRDP + ist ein Teil von FreeRDP +

+
+
+

+ +
+
+ +

+
+

+ + Datenschutz

+
+

Details + zu den Daten die aFreeRDP sammelt und verarbeitet sind unter

+

http://www.freerdp.com/privacy + zu finden.

+
+

+ + Lizenzen

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + FreeRDP

+
+
Licensed under the Apache License, Version 2.0 (the "License");
+
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0 
+Unless required by applicable law or agreed to in writing, software
+
+distributed under the License is distributed on an "AS IS" BASIS,
+
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+See the License for the specific language governing permissions and
+
+limitations under the License. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publically available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html new file mode 100644 index 0000000..a5d9664 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client
+

+
+
+

+ + +

+
+
+

+ aFreeRDP ist ein Open Source Programm + mit nativer Unterstützung des Remote Desktop Protocol (RDP)
+ um + einen entfernten Zugriff auf Windows Desktops zu ermöglichen.
+

+
+

+ Versions Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+
+

+ +
+
+ +

+
+

+ + Credits

+
+

aFreeRDP + ist ein Teil von FreeRDP +

+
+
+

+ +
+
+ +

+
+

+ + Datenschutz

+
+

Details + zu den Daten die aFreeRDP sammelt und verarbeitet sind unter

+

http://www.freerdp.com/privacy + zu finden.

+
+

+ + Lizenzen

+
+
+

aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope
+that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be
+obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + FreeRDP

+
+
Licensed under the Apache License, Version 2.0 (the "License");
+
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0 
+Unless required by applicable law or agreed to in writing, software
+
+distributed under the License is distributed on an "AS IS" BASIS,
+
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+See the License for the specific language governing permissions and
+
+limitations under the License. 
+A copy of the product's source code can be obtained
+ from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publically available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html new file mode 100644 index 0000000..879f2ee --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html @@ -0,0 +1,33 @@ + + + + + + + Help + + + + + +
+
+ + +
+

Gesten

+

+ aFreeRDP ist für Touch Geräte entwickelt worden. + Diese Gesten lassen sie die häufigsten Operationen mit ihren Fingern + durchführen.

+

+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png new file mode 100644 index 0000000..78b3e7b Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html new file mode 100644 index 0000000..b72dabf --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html @@ -0,0 +1,38 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gesten

+

+ aFreeRDP ist für Touch Geräte entwickelt worden. + Diese Gesten lassen sie die häufigsten Operationen mit ihren Fingern + durchführen.

+

+ + +
+
+
+ + + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png new file mode 100644 index 0000000..4eea33e Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png new file mode 100644 index 0000000..50bfaa2 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png new file mode 100644 index 0000000..f66b24d Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png new file mode 100644 index 0000000..930fc9c Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html new file mode 100644 index 0000000..c40694a --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Tastatur

+ Zeige/verstecke die standard und die erweiterte Tastatur mit Funktionstasten +
+
+

Touch Zeiger

+ Zeige/verstecke den gesten gesteuerten Zeiger +
+
+

Beenden

+ Beende die aktuelle Sitzung. Seihen sie sich bewusst, dass das Beenden kein Logout + ist. +
+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png new file mode 100644 index 0000000..42f055b Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html new file mode 100644 index 0000000..65d0f94 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Tastatur

+ Zeige/verstecke die standard und die erweiterte Tastatur mit Funktionstasten +
+
+

Touch Zeiger

+ Zeige/verstecke den gesten gesteuerten Zeiger +
+
+

Beenden

+ Beende die aktuelle Sitzung. Seihen sie sich bewusst, dass das Beenden kein Logout + ist. +
+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png new file mode 100644 index 0000000..278cd3a Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html new file mode 100644 index 0000000..3da3ef5 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html @@ -0,0 +1,29 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png new file mode 100644 index 0000000..af3ebca Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html new file mode 100644 index 0000000..58e68df --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html @@ -0,0 +1,30 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png new file mode 100644 index 0000000..ab7c598 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help.css b/client/Android/Studio/aFreeRDP/src/main/assets/help.css new file mode 100644 index 0000000..e845acd --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help.css @@ -0,0 +1,100 @@ + \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html new file mode 100644 index 0000000..a9ae66d --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html @@ -0,0 +1,32 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gestures

+

+ aFreeRDP is designed for touch sensitive devices. + These gestures let you do the most usual operations with your fingers.

+

+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png new file mode 100644 index 0000000..78b3e7b Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html new file mode 100644 index 0000000..8c81048 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html @@ -0,0 +1,33 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gestures

+

+ aFreeRDP is designed for touch sensitive devices. + These gestures let you do the most usual operations with your fingers.

+

+
+
+
+ + + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png new file mode 100644 index 0000000..4eea33e Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png new file mode 100644 index 0000000..50bfaa2 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png new file mode 100644 index 0000000..f66b24d Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png new file mode 100644 index 0000000..930fc9c Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html new file mode 100644 index 0000000..639fba9 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Keyboards

+ Display/hide the default keyboard as well as an extended keyboard with function keys +
+
+

Touch Pointer

+ Display/hide the gesture controlled cursor +
+
+

Disconnect

+ Disconnect your current session. Please be aware that a disconnect is not the same + as a log out. +
+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png new file mode 100644 index 0000000..42f055b Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html new file mode 100644 index 0000000..78f7357 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html @@ -0,0 +1,50 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Keyboards

+ Display/hide the default keyboard as well as an extended keyboard with function keys +
+
+

Touch Pointer

+ Display/hide the gesture controlled cursor +
+ +
+

Disconnect

+ Disconnect your current session. Please be aware that a disconnect is not the same + as a log out. +
+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png new file mode 100644 index 0000000..278cd3a Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html new file mode 100644 index 0000000..3da3ef5 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html @@ -0,0 +1,29 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png new file mode 100644 index 0000000..af3ebca Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html new file mode 100644 index 0000000..58e68df --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html @@ -0,0 +1,30 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+
+ + diff --git a/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png new file mode 100644 index 0000000..ab7c598 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java b/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java new file mode 100644 index 0000000..c0b36f0 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java @@ -0,0 +1,6 @@ +package com.freerdp.afreerdp.application; + + +public class GlobalApp extends com.freerdp.freerdpcore.application.GlobalApp { + +} diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png b/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..ff31f25 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png b/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..49726f4 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png b/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..6b18c0a Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml b/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..a6aeb24 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png b/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png new file mode 100644 index 0000000..53c5b36 Binary files /dev/null and b/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml b/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml new file mode 100644 index 0000000..4cd72ac --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..16fc2ba --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml @@ -0,0 +1,4 @@ + + + Entfernte Rechner + \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..401d0f2 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml @@ -0,0 +1,4 @@ + + + Remote Computers + diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..054f6c2 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml @@ -0,0 +1,4 @@ + + + L\'ordinateur distant + diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..401d0f2 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml @@ -0,0 +1,4 @@ + + + Remote Computers + diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..86d2230 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml @@ -0,0 +1,4 @@ + + + Remote Computer + \ No newline at end of file diff --git a/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml b/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml new file mode 100644 index 0000000..1c00d49 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + aFreeRDP + + aFreeRDP + Remote Computers + diff --git a/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml b/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml new file mode 100644 index 0000000..d8b5f0b --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml @@ -0,0 +1,22 @@ + + diff --git a/client/Android/Studio/build.gradle b/client/Android/Studio/build.gradle new file mode 100644 index 0000000..f6174c8 --- /dev/null +++ b/client/Android/Studio/build.gradle @@ -0,0 +1,55 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +Properties properties = new Properties() +File file = new File('release.properties') +if (file.canRead()) { + properties.load(new FileInputStream(file)) +} + +if (!hasProperty('RELEASE_STORE_FILE')) { + ext.RELEASE_STORE_FILE='' +} +if (!hasProperty('RELEASE_KEY_ALIAS')) { + ext.RELEASE_KEY_ALIAS='' +} +if (!hasProperty('RELEASE_KEY_ALIAS')) { + ext.RELEASE_KEY_ALIAS='' +} +if (!hasProperty('RELEASE_KEY_PASSWORD')) { + ext.RELEASE_KEY_PASSWORD='' +} + +def getVersionName = { -> + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags' + standardOutput = stdout + } + return stdout.toString().trim() +} + +ext { + versionName = properties.get('VERSION_NAME', getVersionName()) + + println '----------------- Project configuration -------------------' + println 'VERSION_NAME: ' + versionName + println 'RELEASE_STORE_FILE: '+ RELEASE_STORE_FILE + println 'RELEASE_KEY_ALIAS: '+ RELEASE_KEY_ALIAS + println '-----------------------------------------------------------' +} + +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} diff --git a/client/Android/Studio/freeRDPCore/build.gradle b/client/Android/Studio/freeRDPCore/build.gradle new file mode 100644 index 0000000..28c2e6b --- /dev/null +++ b/client/Android/Studio/freeRDPCore/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion = 27 + buildToolsVersion = "27.0.3" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 27 + vectorDrawables.useSupportLibrary = true + versionCode = 14 + versionName = rootProject.ext.versionName + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + debug { + jniDebuggable true + renderscriptDebuggable true + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:27.0.2' + compile 'com.android.support:support-v4:27.0.2' + compile 'com.android.support:support-vector-drawable:27.0.2' +} diff --git a/client/Android/Studio/freeRDPCore/lint.xml b/client/Android/Studio/freeRDPCore/lint.xml new file mode 100644 index 0000000..c70207f --- /dev/null +++ b/client/Android/Studio/freeRDPCore/lint.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml b/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml new file mode 100644 index 0000000..570cdc5 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java new file mode 100644 index 0000000..ce6c4d8 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java @@ -0,0 +1,190 @@ +/* + Android Main Application + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; +import com.freerdp.freerdpcore.services.BookmarkDB; +import com.freerdp.freerdpcore.services.HistoryDB; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.services.ManualBookmarkGateway; +import com.freerdp.freerdpcore.services.QuickConnectHistoryGateway; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +public class GlobalApp extends Application implements LibFreeRDP.EventListener { + // event notification defines + public static final String EVENT_TYPE = "EVENT_TYPE"; + public static final String EVENT_PARAM = "EVENT_PARAM"; + public static final String EVENT_STATUS = "EVENT_STATUS"; + public static final String EVENT_ERROR = "EVENT_ERROR"; + public static final String ACTION_EVENT_FREERDP = "com.freerdp.freerdp.event.freerdp"; + public static final int FREERDP_EVENT_CONNECTION_SUCCESS = 1; + public static final int FREERDP_EVENT_CONNECTION_FAILURE = 2; + public static final int FREERDP_EVENT_DISCONNECTED = 3; + private static final String TAG = "GlobalApp"; + public static boolean ConnectedTo3G = false; + private static Map sessionMap; + private static BookmarkDB bookmarkDB; + private static ManualBookmarkGateway manualBookmarkGateway; + + private static HistoryDB historyDB; + private static QuickConnectHistoryGateway quickConnectHistoryGateway; + + // timer for disconnecting sessions after the screen was turned off + private static Timer disconnectTimer = null; + + public static ManualBookmarkGateway getManualBookmarkGateway() { + return manualBookmarkGateway; + } + + public static QuickConnectHistoryGateway getQuickConnectHistoryGateway() { + return quickConnectHistoryGateway; + } + + // Disconnect handling for Screen on/off events + public void startDisconnectTimer() { + final int timeoutMinutes = ApplicationSettingsActivity.getDisconnectTimeout(this); + if (timeoutMinutes > 0) { + // start disconnect timeout... + disconnectTimer = new Timer(); + disconnectTimer.schedule(new DisconnectTask(), timeoutMinutes * 60 * 1000); + } + } + + static public void cancelDisconnectTimer() { + // cancel any pending timer events + if (disconnectTimer != null) { + disconnectTimer.cancel(); + disconnectTimer.purge(); + disconnectTimer = null; + } + } + + // RDP session handling + static public SessionState createSession(BookmarkBase bookmark, Context context) { + SessionState session = new SessionState(LibFreeRDP.newInstance(context), bookmark); + sessionMap.put(Long.valueOf(session.getInstance()), session); + return session; + } + + static public SessionState createSession(Uri openUri, Context context) { + SessionState session = new SessionState(LibFreeRDP.newInstance(context), openUri); + sessionMap.put(Long.valueOf(session.getInstance()), session); + return session; + } + + static public SessionState getSession(long instance) { + return sessionMap.get(instance); + } + + static public Collection getSessions() { + // return a copy of the session items + return new ArrayList(sessionMap.values()); + } + + static public void freeSession(long instance) { + if (GlobalApp.sessionMap.containsKey(instance)) { + GlobalApp.sessionMap.remove(instance); + LibFreeRDP.freeInstance(instance); + } + } + + @Override + public void onCreate() { + super.onCreate(); + + /* Initialize preferences. */ + ApplicationSettingsActivity.get(this); + + sessionMap = Collections.synchronizedMap(new HashMap()); + + LibFreeRDP.setEventListener(this); + + bookmarkDB = new BookmarkDB(this); + + manualBookmarkGateway = new ManualBookmarkGateway(bookmarkDB); + + historyDB = new HistoryDB(this); + quickConnectHistoryGateway = new QuickConnectHistoryGateway(historyDB); + + ConnectedTo3G = NetworkStateReceiver.isConnectedTo3G(this); + + // init screen receiver here (this can't be declared in AndroidManifest - refer to: + // http://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/ + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(new ScreenReceiver(), filter); + } + + // helper to send FreeRDP notifications + private void sendRDPNotification(int type, long param) { + // send broadcast + Intent intent = new Intent(ACTION_EVENT_FREERDP); + intent.putExtra(EVENT_TYPE, type); + intent.putExtra(EVENT_PARAM, param); + sendBroadcast(intent); + } + + @Override + public void OnPreConnect(long instance) { + Log.v(TAG, "OnPreConnect"); + } + + // ////////////////////////////////////////////////////////////////////// + // Implementation of LibFreeRDP.EventListener + public void OnConnectionSuccess(long instance) { + Log.v(TAG, "OnConnectionSuccess"); + sendRDPNotification(FREERDP_EVENT_CONNECTION_SUCCESS, instance); + } + + public void OnConnectionFailure(long instance) { + Log.v(TAG, "OnConnectionFailure"); + + // send notification to session activity + sendRDPNotification(FREERDP_EVENT_CONNECTION_FAILURE, instance); + } + + public void OnDisconnecting(long instance) { + Log.v(TAG, "OnDisconnecting"); + } + + public void OnDisconnected(long instance) { + Log.v(TAG, "OnDisconnected"); + sendRDPNotification(FREERDP_EVENT_DISCONNECTED, instance); + } + + // TimerTask for disconnecting sessions after screen was turned off + private static class DisconnectTask extends TimerTask { + @Override + public void run() { + Log.v("DisconnectTask", "Doing action"); + + // disconnect any running rdp session + Collection sessions = GlobalApp.getSessions(); + for (SessionState session : sessions) { + LibFreeRDP.disconnect(session.getInstance()); + } + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java new file mode 100644 index 0000000..38a98a6 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java @@ -0,0 +1,49 @@ +/* + Network State Receiver + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.util.Log; + +public class NetworkStateReceiver extends BroadcastReceiver { + + public static boolean isConnectedTo3G(Context context) { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = connectivity.getActiveNetworkInfo(); + + // no connection or background data disabled + if (info == null || !info.isConnected()) + return false; + + return (info.getType() != ConnectivityManager.TYPE_WIFI && info.getType() != ConnectivityManager.TYPE_WIMAX); + } + + @Override + public void onReceive(Context context, Intent intent) { + + // check if we are connected via 3g or wlan + if (intent.getExtras() != null) { + NetworkInfo info = (NetworkInfo) intent.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO); + + // are we connected at all? + if (info != null && info.isConnected()) { + // see if we are connected through 3G or WiFi + Log.d("app", "Connected via type " + info.getTypeName()); + GlobalApp.ConnectedTo3G = (info.getType() != ConnectivityManager.TYPE_WIFI && info.getType() != ConnectivityManager.TYPE_WIMAX); + } + + Log.v("NetworkState", info.toString()); + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java new file mode 100644 index 0000000..f4b36fb --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java @@ -0,0 +1,29 @@ +/* + Helper class to receive notifications when the screen is turned on/off + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class ScreenReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + GlobalApp app = (GlobalApp) context.getApplicationContext(); + Log.v("ScreenReceiver", "Received action: " + intent.getAction()); + if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) + app.startDisconnectTimer(); + else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) + app.cancelDisconnectTimer(); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java new file mode 100644 index 0000000..75c9015 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java @@ -0,0 +1,111 @@ +/* + Session State class + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.services.LibFreeRDP; + +public class SessionState implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SessionState createFromParcel(Parcel in) { + return new SessionState(in); + } + + @Override + public SessionState[] newArray(int size) { + return new SessionState[size]; + } + }; + private long instance; + private BookmarkBase bookmark; + private Uri openUri; + private BitmapDrawable surface; + private LibFreeRDP.UIEventListener uiEventListener; + + public SessionState(Parcel parcel) { + instance = parcel.readLong(); + bookmark = parcel.readParcelable(null); + openUri = parcel.readParcelable(null); + + Bitmap bitmap = parcel.readParcelable(null); + surface = new BitmapDrawable(bitmap); + } + + public SessionState(long instance, BookmarkBase bookmark) { + this.instance = instance; + this.bookmark = bookmark; + this.openUri = null; + this.uiEventListener = null; + } + + public SessionState(long instance, Uri openUri) { + this.instance = instance; + this.bookmark = null; + this.openUri = openUri; + this.uiEventListener = null; + } + + public void connect(Context context) { + if (bookmark != null) { + LibFreeRDP.setConnectionInfo(context, instance, bookmark); + } else { + LibFreeRDP.setConnectionInfo(context, instance, openUri); + } + LibFreeRDP.connect(instance); + } + + public long getInstance() { + return instance; + } + + public BookmarkBase getBookmark() { + return bookmark; + } + + public Uri getOpenUri() { + return openUri; + } + + public LibFreeRDP.UIEventListener getUIEventListener() { + return uiEventListener; + } + + public void setUIEventListener(LibFreeRDP.UIEventListener uiEventListener) { + this.uiEventListener = uiEventListener; + } + + public BitmapDrawable getSurface() { + return surface; + } + + public void setSurface(BitmapDrawable surface) { + this.surface = surface; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(instance); + out.writeParcelable(bookmark, flags); + out.writeParcelable(openUri, flags); + out.writeParcelable(surface.getBitmap(), flags); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java new file mode 100644 index 0000000..f45cc50 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java @@ -0,0 +1,969 @@ +/* + Defines base attributes of a bookmark object + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +import com.freerdp.freerdpcore.application.GlobalApp; + +import java.util.Locale; + +public class BookmarkBase implements Parcelable, Cloneable { + public static final int TYPE_INVALID = -1; + public static final int TYPE_MANUAL = 1; + public static final int TYPE_QUICKCONNECT = 2; + public static final int TYPE_PLACEHOLDER = 3; + public static final int TYPE_CUSTOM_BASE = 1000; + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public BookmarkBase createFromParcel(Parcel in) { + return new BookmarkBase(in); + } + + @Override + public BookmarkBase[] newArray(int size) { + return new BookmarkBase[size]; + } + }; + protected int type; + private long id; + private String label; + private String username; + private String password; + private String domain; + private ScreenSettings screenSettings; + private PerformanceFlags performanceFlags; + private AdvancedSettings advancedSettings; + private DebugSettings debugSettings; + + public BookmarkBase(Parcel parcel) { + type = parcel.readInt(); + id = parcel.readLong(); + label = parcel.readString(); + username = parcel.readString(); + password = parcel.readString(); + domain = parcel.readString(); + + screenSettings = parcel.readParcelable(ScreenSettings.class + .getClassLoader()); + performanceFlags = parcel.readParcelable(PerformanceFlags.class + .getClassLoader()); + advancedSettings = parcel.readParcelable(AdvancedSettings.class + .getClassLoader()); + debugSettings = parcel.readParcelable(DebugSettings.class + .getClassLoader()); + } + + public BookmarkBase() { + init(); + } + + private void init() { + type = TYPE_INVALID; + id = -1; + label = ""; + username = ""; + password = ""; + domain = ""; + + screenSettings = new ScreenSettings(); + performanceFlags = new PerformanceFlags(); + advancedSettings = new AdvancedSettings(); + debugSettings = new DebugSettings(); + } + + @SuppressWarnings("unchecked") + public T get() { + return (T) this; + } + + public int getType() { + return type; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public ScreenSettings getScreenSettings() { + return screenSettings; + } + + public void setScreenSettings(ScreenSettings screenSettings) { + this.screenSettings = screenSettings; + } + + public PerformanceFlags getPerformanceFlags() { + return performanceFlags; + } + + public void setPerformanceFlags(PerformanceFlags performanceFlags) { + this.performanceFlags = performanceFlags; + } + + public AdvancedSettings getAdvancedSettings() { + return advancedSettings; + } + + public void setAdvancedSettings(AdvancedSettings advancedSettings) { + this.advancedSettings = advancedSettings; + } + + public DebugSettings getDebugSettings() { + return debugSettings; + } + + public void setDebugSettings(DebugSettings debugSettings) { + this.debugSettings = debugSettings; + } + + public ScreenSettings getActiveScreenSettings() { + return (GlobalApp.ConnectedTo3G && advancedSettings + .getEnable3GSettings()) ? advancedSettings.getScreen3G() + : screenSettings; + } + + public PerformanceFlags getActivePerformanceFlags() { + return (GlobalApp.ConnectedTo3G && advancedSettings + .getEnable3GSettings()) ? advancedSettings.getPerformance3G() + : performanceFlags; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + out.writeLong(id); + out.writeString(label); + out.writeString(username); + out.writeString(password); + out.writeString(domain); + + out.writeParcelable(screenSettings, flags); + out.writeParcelable(performanceFlags, flags); + out.writeParcelable(advancedSettings, flags); + out.writeParcelable(debugSettings, flags); + } + + // write to shared preferences + public void writeToSharedPreferences(SharedPreferences sharedPrefs) { + + Locale locale = Locale.ENGLISH; + + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.clear(); + editor.putString("bookmark.label", label); + editor.putString("bookmark.username", username); + editor.putString("bookmark.password", password); + editor.putString("bookmark.domain", domain); + + editor.putInt("bookmark.colors", screenSettings.getColors()); + editor.putString("bookmark.resolution", screenSettings + .getResolutionString().toLowerCase(locale)); + editor.putInt("bookmark.width", screenSettings.getWidth()); + editor.putInt("bookmark.height", screenSettings.getHeight()); + + editor.putBoolean("bookmark.perf_remotefx", + performanceFlags.getRemoteFX()); + editor.putBoolean("bookmark.perf_gfx", + performanceFlags.getGfx()); + editor.putBoolean("bookmark.perf_gfx_h264", + performanceFlags.getH264()); + editor.putBoolean("bookmark.perf_wallpaper", + performanceFlags.getWallpaper()); + editor.putBoolean("bookmark.perf_font_smoothing", + performanceFlags.getFontSmoothing()); + editor.putBoolean("bookmark.perf_desktop_composition", + performanceFlags.getDesktopComposition()); + editor.putBoolean("bookmark.perf_window_dragging", + performanceFlags.getFullWindowDrag()); + editor.putBoolean("bookmark.perf_menu_animation", + performanceFlags.getMenuAnimations()); + editor.putBoolean("bookmark.perf_themes", performanceFlags.getTheming()); + + editor.putBoolean("bookmark.enable_3g_settings", + advancedSettings.getEnable3GSettings()); + + editor.putInt("bookmark.colors_3g", advancedSettings.getScreen3G() + .getColors()); + editor.putString("bookmark.resolution_3g", advancedSettings + .getScreen3G().getResolutionString().toLowerCase(locale)); + editor.putInt("bookmark.width_3g", advancedSettings.getScreen3G() + .getWidth()); + editor.putInt("bookmark.height_3g", advancedSettings.getScreen3G() + .getHeight()); + + editor.putBoolean("bookmark.perf_remotefx_3g", advancedSettings + .getPerformance3G().getRemoteFX()); + editor.putBoolean("bookmark.perf_gfx_3g", advancedSettings + .getPerformance3G().getGfx()); + editor.putBoolean("bookmark.perf_gfx_h264_3g", advancedSettings + .getPerformance3G().getH264()); + editor.putBoolean("bookmark.perf_wallpaper_3g", advancedSettings + .getPerformance3G().getWallpaper()); + editor.putBoolean("bookmark.perf_font_smoothing_3g", advancedSettings + .getPerformance3G().getFontSmoothing()); + editor.putBoolean("bookmark.perf_desktop_composition_3g", + advancedSettings.getPerformance3G().getDesktopComposition()); + editor.putBoolean("bookmark.perf_window_dragging_3g", advancedSettings + .getPerformance3G().getFullWindowDrag()); + editor.putBoolean("bookmark.perf_menu_animation_3g", advancedSettings + .getPerformance3G().getMenuAnimations()); + editor.putBoolean("bookmark.perf_themes_3g", advancedSettings + .getPerformance3G().getTheming()); + + editor.putBoolean("bookmark.redirect_sdcard", + advancedSettings.getRedirectSDCard()); + editor.putInt("bookmark.redirect_sound", + advancedSettings.getRedirectSound()); + editor.putBoolean("bookmark.redirect_microphone", + advancedSettings.getRedirectMicrophone()); + editor.putInt("bookmark.security", advancedSettings.getSecurity()); + editor.putString("bookmark.remote_program", + advancedSettings.getRemoteProgram()); + editor.putString("bookmark.work_dir", advancedSettings.getWorkDir()); + editor.putBoolean("bookmark.console_mode", + advancedSettings.getConsoleMode()); + + editor.putBoolean("bookmark.async_channel", debugSettings.getAsyncChannel()); + editor.putBoolean("bookmark.async_input", debugSettings.getAsyncInput()); + editor.putBoolean("bookmark.async_update", debugSettings.getAsyncUpdate()); + editor.putString("bookmark.debug_level", + debugSettings.getDebugLevel()); + + editor.apply(); + } + + // read from shared preferences + public void readFromSharedPreferences(SharedPreferences sharedPrefs) { + label = sharedPrefs.getString("bookmark.label", ""); + username = sharedPrefs.getString("bookmark.username", ""); + password = sharedPrefs.getString("bookmark.password", ""); + domain = sharedPrefs.getString("bookmark.domain", ""); + + screenSettings.setColors(sharedPrefs.getInt("bookmark.colors", 16)); + screenSettings.setResolution( + sharedPrefs.getString("bookmark.resolution", "automatic"), + sharedPrefs.getInt("bookmark.width", 800), + sharedPrefs.getInt("bookmark.height", 600)); + + performanceFlags.setRemoteFX(sharedPrefs.getBoolean( + "bookmark.perf_remotefx", false)); + performanceFlags.setGfx(sharedPrefs.getBoolean( + "bookmark.perf_gfx", false)); + performanceFlags.setH264(sharedPrefs.getBoolean( + "bookmark.perf_gfx_h264", false)); + performanceFlags.setWallpaper(sharedPrefs.getBoolean( + "bookmark.perf_wallpaper", false)); + performanceFlags.setFontSmoothing(sharedPrefs.getBoolean( + "bookmark.perf_font_smoothing", false)); + performanceFlags.setDesktopComposition(sharedPrefs.getBoolean( + "bookmark.perf_desktop_composition", false)); + performanceFlags.setFullWindowDrag(sharedPrefs.getBoolean( + "bookmark.perf_window_dragging", false)); + performanceFlags.setMenuAnimations(sharedPrefs.getBoolean( + "bookmark.perf_menu_animation", false)); + performanceFlags.setTheming(sharedPrefs.getBoolean( + "bookmark.perf_themes", false)); + + advancedSettings.setEnable3GSettings(sharedPrefs.getBoolean( + "bookmark.enable_3g_settings", false)); + + advancedSettings.getScreen3G().setColors( + sharedPrefs.getInt("bookmark.colors_3g", 16)); + advancedSettings.getScreen3G().setResolution( + sharedPrefs.getString("bookmark.resolution_3g", "automatic"), + sharedPrefs.getInt("bookmark.width_3g", 800), + sharedPrefs.getInt("bookmark.height_3g", 600)); + + advancedSettings.getPerformance3G().setRemoteFX( + sharedPrefs.getBoolean("bookmark.perf_remotefx_3g", false)); + advancedSettings.getPerformance3G().setGfx(sharedPrefs.getBoolean( + "bookmark.perf_gfx_3g", false)); + advancedSettings.getPerformance3G().setH264(sharedPrefs.getBoolean( + "bookmark.perf_gfx_h264_3g", false)); + advancedSettings.getPerformance3G().setWallpaper( + sharedPrefs.getBoolean("bookmark.perf_wallpaper_3g", false)); + advancedSettings.getPerformance3G().setFontSmoothing( + sharedPrefs + .getBoolean("bookmark.perf_font_smoothing_3g", false)); + advancedSettings.getPerformance3G().setDesktopComposition( + sharedPrefs.getBoolean("bookmark.perf_desktop_composition_3g", + false)); + advancedSettings.getPerformance3G().setFullWindowDrag( + sharedPrefs.getBoolean("bookmark.perf_window_dragging_3g", + false)); + advancedSettings.getPerformance3G().setMenuAnimations( + sharedPrefs + .getBoolean("bookmark.perf_menu_animation_3g", false)); + advancedSettings.getPerformance3G().setTheming( + sharedPrefs.getBoolean("bookmark.perf_themes_3g", false)); + + advancedSettings.setRedirectSDCard(sharedPrefs.getBoolean("bookmark.redirect_sdcard", false)); + advancedSettings.setRedirectSound(sharedPrefs.getInt("bookmark.redirect_sound", 0)); + advancedSettings.setRedirectMicrophone(sharedPrefs.getBoolean("bookmark.redirect_microphone", false)); + advancedSettings.setSecurity(sharedPrefs.getInt("bookmark.security", 0)); + advancedSettings.setRemoteProgram(sharedPrefs.getString("bookmark.remote_program", "")); + advancedSettings.setWorkDir(sharedPrefs.getString("bookmark.work_dir", "")); + advancedSettings.setConsoleMode(sharedPrefs.getBoolean("bookmark.console_mode", false)); + + debugSettings.setAsyncChannel(sharedPrefs.getBoolean("bookmark.async_channel", true)); + debugSettings.setAsyncInput(sharedPrefs.getBoolean("bookmark.async_input", true)); + debugSettings.setAsyncUpdate(sharedPrefs.getBoolean("bookmark.async_update", true)); + debugSettings.setDebugLevel(sharedPrefs.getString("bookmark.debug_level", "INFO")); + } + + // Cloneable + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + // performance flags + public static class PerformanceFlags implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public PerformanceFlags createFromParcel(Parcel in) { + return new PerformanceFlags(in); + } + + @Override + public PerformanceFlags[] newArray(int size) { + return new PerformanceFlags[size]; + } + }; + private boolean remotefx; + private boolean gfx; + private boolean h264; + private boolean wallpaper; + private boolean theming; + private boolean fullWindowDrag; + private boolean menuAnimations; + private boolean fontSmoothing; + private boolean desktopComposition; + + public PerformanceFlags() { + remotefx = false; + gfx = false; + h264 = false; + wallpaper = false; + theming = false; + fullWindowDrag = false; + menuAnimations = false; + fontSmoothing = false; + desktopComposition = false; + } + + public PerformanceFlags(Parcel parcel) { + remotefx = parcel.readInt() == 1; + gfx = parcel.readInt() == 1; + h264 = parcel.readInt() == 1; + wallpaper = parcel.readInt() == 1; + theming = parcel.readInt() == 1; + fullWindowDrag = (parcel.readInt() == 1); + menuAnimations = parcel.readInt() == 1; + fontSmoothing = parcel.readInt() == 1; + desktopComposition = parcel.readInt() == 1; + } + + public boolean getRemoteFX() { + return remotefx; + } + + public void setRemoteFX(boolean remotefx) { + this.remotefx = remotefx; + } + + public boolean getGfx() { + return gfx; + } + + public void setGfx(boolean gfx) { + this.gfx = gfx; + } + + public boolean getH264() { + return h264; + } + + public void setH264(boolean h264) { + this.h264 = h264; + } + + public boolean getWallpaper() { + return wallpaper; + } + + public void setWallpaper(boolean wallpaper) { + this.wallpaper = wallpaper; + } + + public boolean getTheming() { + return theming; + } + + public void setTheming(boolean theming) { + this.theming = theming; + } + + public boolean getFullWindowDrag() { + return fullWindowDrag; + } + + public void setFullWindowDrag(boolean fullWindowDrag) { + this.fullWindowDrag = fullWindowDrag; + } + + public boolean getMenuAnimations() { + return menuAnimations; + } + + public void setMenuAnimations(boolean menuAnimations) { + this.menuAnimations = menuAnimations; + } + + public boolean getFontSmoothing() { + return fontSmoothing; + } + + public void setFontSmoothing(boolean fontSmoothing) { + this.fontSmoothing = fontSmoothing; + } + + public boolean getDesktopComposition() { + return desktopComposition; + } + + public void setDesktopComposition(boolean desktopComposition) { + this.desktopComposition = desktopComposition; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(remotefx ? 1 : 0); + out.writeInt(gfx ? 1 : 0); + out.writeInt(h264 ? 1 : 0); + out.writeInt(wallpaper ? 1 : 0); + out.writeInt(theming ? 1 : 0); + out.writeInt(fullWindowDrag ? 1 : 0); + out.writeInt(menuAnimations ? 1 : 0); + out.writeInt(fontSmoothing ? 1 : 0); + out.writeInt(desktopComposition ? 1 : 0); + } + } + + // Screen Settings class + public static class ScreenSettings implements Parcelable { + public static final int FITSCREEN = -2; + public static final int AUTOMATIC = -1; + public static final int CUSTOM = 0; + public static final int PREDEFINED = 1; + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ScreenSettings createFromParcel(Parcel in) { + return new ScreenSettings(in); + } + + @Override + public ScreenSettings[] newArray(int size) { + return new ScreenSettings[size]; + } + }; + private int resolution; + private int colors; + private int width; + private int height; + + public ScreenSettings() { + init(); + } + + public ScreenSettings(Parcel parcel) { + resolution = parcel.readInt(); + colors = parcel.readInt(); + width = parcel.readInt(); + height = parcel.readInt(); + } + + private void validate() { + switch (colors) { + case 32: + case 24: + case 16: + case 15: + case 8: + break; + default: + colors = 32; + break; + } + + if ((width <= 0) || (width > 65536)) { + width = 1024; + } + + if ((height <= 0) || (height > 65536)) { + height = 768; + } + + switch(resolution) { + case FITSCREEN: + case AUTOMATIC: + case CUSTOM: + case PREDEFINED: + break; + default: + resolution = AUTOMATIC; + break; + } + } + + private void init() { + resolution = AUTOMATIC; + colors = 16; + width = 0; + height = 0; + } + + public void setResolution(String resolution, int width, int height) { + if (resolution.contains("x")) { + String[] dimensions = resolution.split("x"); + this.width = Integer.valueOf(dimensions[0]); + this.height = Integer.valueOf(dimensions[1]); + this.resolution = PREDEFINED; + } else if (resolution.equalsIgnoreCase("custom")) { + this.width = width; + this.height = height; + this.resolution = CUSTOM; + } else if (resolution.equalsIgnoreCase("fitscreen")) { + this.width = this.height = 0; + this.resolution = FITSCREEN; + } else { + this.width = this.height = 0; + this.resolution = AUTOMATIC; + } + } + + public int getResolution() { + return resolution; + } + + public void setResolution(int resolution) { + this.resolution = resolution; + + if (resolution == AUTOMATIC || resolution == FITSCREEN) { + width = 0; + height = 0; + } + } + + public String getResolutionString() { + if (isPredefined()) + return (width + "x" + height); + + return (isFitScreen() ? "fitscreen" : isAutomatic() ? "automatic" + : "custom"); + } + + public boolean isPredefined() { + validate(); + return (resolution == PREDEFINED); + } + + public boolean isAutomatic() { + validate(); + return (resolution == AUTOMATIC); + } + + public boolean isFitScreen() { + validate(); + return (resolution == FITSCREEN); + } + + public boolean isCustom() { + validate(); + return (resolution == CUSTOM); + } + + public int getWidth() { + validate(); + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + validate(); + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getColors() { + validate(); + return colors; + } + + public void setColors(int colors) { + this.colors = colors; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(resolution); + out.writeInt(colors); + out.writeInt(width); + out.writeInt(height); + } + } + + public static class DebugSettings implements Parcelable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public DebugSettings createFromParcel(Parcel in) { + return new DebugSettings(in); + } + + @Override + public DebugSettings[] newArray(int size) { + return new DebugSettings[size]; + } + }; + private String debug; + private boolean asyncChannel; + private boolean asyncTransport; + private boolean asyncInput; + private boolean asyncUpdate; + + public DebugSettings() { + init(); + } + + // Session Settings + public DebugSettings(Parcel parcel) { + asyncChannel = parcel.readInt() == 1; + asyncTransport = parcel.readInt() == 1; + asyncInput = parcel.readInt() == 1; + asyncUpdate = parcel.readInt() == 1; + debug = parcel.readString(); + } + + private void init() { + debug = "INFO"; + asyncChannel = true; + asyncTransport = false; + asyncInput = true; + asyncUpdate = true; + } + + private void validate() { + final String[] levels = { + "OFF", + "FATAL", + "ERROR", + "WARN", + "INFO", + "DEBUG", + "TRACE" + }; + + for (String level : levels) { + if (level.equalsIgnoreCase(this.debug)) { + return; + } + } + + this.debug = "INFO"; + } + + public String getDebugLevel() { + validate(); + return debug; + } + + public void setDebugLevel(String debug) { + this.debug = debug; + } + + public boolean getAsyncUpdate() { + return asyncUpdate; + } + + public void setAsyncUpdate(boolean enabled) { + asyncUpdate = enabled; + } + + public boolean getAsyncInput() { + return asyncInput; + } + + public void setAsyncInput(boolean enabled) { + asyncInput = enabled; + } + + public boolean getAsyncChannel() { + return asyncChannel; + } + + public void setAsyncChannel(boolean enabled) { + asyncChannel = enabled; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(asyncChannel ? 1 : 0); + out.writeInt(asyncTransport ? 1 : 0); + out.writeInt(asyncInput ? 1 : 0); + out.writeInt(asyncUpdate ? 1 : 0); + out.writeString(debug); + } + } + + // Session Settings + public static class AdvancedSettings implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public AdvancedSettings createFromParcel(Parcel in) { + return new AdvancedSettings(in); + } + + @Override + public AdvancedSettings[] newArray(int size) { + return new AdvancedSettings[size]; + } + }; + private boolean enable3GSettings; + private ScreenSettings screen3G; + private PerformanceFlags performance3G; + private boolean redirectSDCard; + private int redirectSound; + private boolean redirectMicrophone; + private int security; + private boolean consoleMode; + private String remoteProgram; + private String workDir; + + public AdvancedSettings() { + init(); + } + + public AdvancedSettings(Parcel parcel) { + enable3GSettings = parcel.readInt() == 1; + screen3G = parcel.readParcelable(ScreenSettings.class + .getClassLoader()); + performance3G = parcel.readParcelable(PerformanceFlags.class + .getClassLoader()); + redirectSDCard = parcel.readInt() == 1; + redirectSound = parcel.readInt(); + redirectMicrophone = parcel.readInt() == 1; + security = parcel.readInt(); + consoleMode = parcel.readInt() == 1; + remoteProgram = parcel.readString(); + workDir = parcel.readString(); + } + + private void init() { + enable3GSettings = false; + screen3G = new ScreenSettings(); + performance3G = new PerformanceFlags(); + redirectSDCard = false; + redirectSound = 0; + redirectMicrophone = false; + security = 0; + consoleMode = false; + remoteProgram = ""; + workDir = ""; + } + + private void validate() { + switch (redirectSound) { + case 0: + case 1: + case 2: + break; + default: + redirectSound = 0; + break; + } + + switch (security) { + case 0: + case 1: + case 2: + case 3: + break; + default: + security = 0; + break; + } + } + + public boolean getEnable3GSettings() { + return enable3GSettings; + } + + public void setEnable3GSettings(boolean enable3GSettings) { + this.enable3GSettings = enable3GSettings; + } + + public ScreenSettings getScreen3G() { + return screen3G; + } + + public void setScreen3G(ScreenSettings screen3G) { + this.screen3G = screen3G; + } + + public PerformanceFlags getPerformance3G() { + return performance3G; + } + + public void setPerformance3G(PerformanceFlags performance3G) { + this.performance3G = performance3G; + } + + public boolean getRedirectSDCard() { + return redirectSDCard; + } + + public void setRedirectSDCard(boolean redirectSDCard) { + this.redirectSDCard = redirectSDCard; + } + + public int getRedirectSound() { + validate(); + return redirectSound; + } + + public void setRedirectSound(int redirect) { + this.redirectSound = redirect; + } + + public boolean getRedirectMicrophone() { + return redirectMicrophone; + } + + public void setRedirectMicrophone(boolean redirect) { + this.redirectMicrophone = redirect; + } + + public int getSecurity() { + validate(); + return security; + } + + public void setSecurity(int security) { + this.security = security; + } + + public boolean getConsoleMode() { + return consoleMode; + } + + public void setConsoleMode(boolean consoleMode) { + this.consoleMode = consoleMode; + } + + public String getRemoteProgram() { + return remoteProgram; + } + + public void setRemoteProgram(String remoteProgram) { + this.remoteProgram = remoteProgram; + } + + public String getWorkDir() { + return workDir; + } + + public void setWorkDir(String workDir) { + this.workDir = workDir; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(enable3GSettings ? 1 : 0); + out.writeParcelable(screen3G, flags); + out.writeParcelable(performance3G, flags); + out.writeInt(redirectSDCard ? 1 : 0); + out.writeInt(redirectSound); + out.writeInt(redirectMicrophone ? 1 : 0); + out.writeInt(security); + out.writeInt(consoleMode ? 1 : 0); + out.writeString(remoteProgram); + out.writeString(workDir); + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java new file mode 100644 index 0000000..ac8c2fb --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java @@ -0,0 +1,69 @@ +/* + A RDP connection reference. References can use bookmark ids or hostnames to connect to a RDP server. + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +public class ConnectionReference { + public static final String PATH_MANUAL_BOOKMARK_ID = "MBMID/"; + public static final String PATH_HOSTNAME = "HOST/"; + public static final String PATH_PLACEHOLDER = "PLCHLD/"; + public static final String PATH_FILE = "FILE/"; + + public static String getManualBookmarkReference(long bookmarkId) { + return (PATH_MANUAL_BOOKMARK_ID + bookmarkId); + } + + public static String getHostnameReference(String hostname) { + return (PATH_HOSTNAME + hostname); + } + + public static String getPlaceholderReference(String name) { + return (PATH_PLACEHOLDER + name); + } + + public static String getFileReference(String uri) { + return (PATH_FILE + uri); + } + + public static boolean isBookmarkReference(String refStr) { + return refStr.startsWith(PATH_MANUAL_BOOKMARK_ID); + } + + public static boolean isManualBookmarkReference(String refStr) { + return refStr.startsWith(PATH_MANUAL_BOOKMARK_ID); + } + + public static boolean isHostnameReference(String refStr) { + return refStr.startsWith(PATH_HOSTNAME); + } + + public static boolean isPlaceholderReference(String refStr) { + return refStr.startsWith(PATH_PLACEHOLDER); + } + + public static boolean isFileReference(String refStr) { + return refStr.startsWith(PATH_FILE); + } + + public static long getManualBookmarkId(String refStr) { + return Integer.parseInt(refStr.substring(PATH_MANUAL_BOOKMARK_ID.length())); + } + + public static String getHostname(String refStr) { + return refStr.substring(PATH_HOSTNAME.length()); + } + + public static String getPlaceholder(String refStr) { + return refStr.substring(PATH_PLACEHOLDER.length()); + } + + public static String getFile(String refStr) { + return refStr.substring(PATH_FILE.length()); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java new file mode 100644 index 0000000..0e5b677 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java @@ -0,0 +1,224 @@ +/* + Manual Bookmark implementation + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class ManualBookmark extends BookmarkBase { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ManualBookmark createFromParcel(Parcel in) { + return new ManualBookmark(in); + } + + @Override + public ManualBookmark[] newArray(int size) { + return new ManualBookmark[size]; + } + }; + private String hostname; + private int port; + private boolean enableGatewaySettings; + private GatewaySettings gatewaySettings; + + public ManualBookmark(Parcel parcel) { + super(parcel); + type = TYPE_MANUAL; + hostname = parcel.readString(); + port = parcel.readInt(); + + enableGatewaySettings = (parcel.readInt() == 1 ? true : false); + gatewaySettings = parcel.readParcelable(GatewaySettings.class.getClassLoader()); + } + + public ManualBookmark() { + super(); + init(); + } + + private void init() { + type = TYPE_MANUAL; + hostname = ""; + port = 3389; + enableGatewaySettings = false; + gatewaySettings = new GatewaySettings(); + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public boolean getEnableGatewaySettings() { + return enableGatewaySettings; + } + + public void setEnableGatewaySettings(boolean enableGatewaySettings) { + this.enableGatewaySettings = enableGatewaySettings; + } + + public GatewaySettings getGatewaySettings() { + return gatewaySettings; + } + + public void setGatewaySettings(GatewaySettings gatewaySettings) { + this.gatewaySettings = gatewaySettings; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(hostname); + out.writeInt(port); + out.writeInt(enableGatewaySettings ? 1 : 0); + out.writeParcelable(gatewaySettings, flags); + } + + @Override + public void writeToSharedPreferences(SharedPreferences sharedPrefs) { + super.writeToSharedPreferences(sharedPrefs); + + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.putString("bookmark.hostname", hostname); + editor.putInt("bookmark.port", port); + editor.putBoolean("bookmark.enable_gateway_settings", enableGatewaySettings); + editor.putString("bookmark.gateway_hostname", gatewaySettings.getHostname()); + editor.putInt("bookmark.gateway_port", gatewaySettings.getPort()); + editor.putString("bookmark.gateway_username", gatewaySettings.getUsername()); + editor.putString("bookmark.gateway_password", gatewaySettings.getPassword()); + editor.putString("bookmark.gateway_domain", gatewaySettings.getDomain()); + editor.commit(); + } + + @Override + public void readFromSharedPreferences(SharedPreferences sharedPrefs) { + super.readFromSharedPreferences(sharedPrefs); + + hostname = sharedPrefs.getString("bookmark.hostname", ""); + port = sharedPrefs.getInt("bookmark.port", 3389); + enableGatewaySettings = sharedPrefs.getBoolean("bookmark.enable_gateway_settings", false); + gatewaySettings.setHostname(sharedPrefs.getString("bookmark.gateway_hostname", "")); + gatewaySettings.setPort(sharedPrefs.getInt("bookmark.gateway_port", 443)); + gatewaySettings.setUsername(sharedPrefs.getString("bookmark.gateway_username", "")); + gatewaySettings.setPassword(sharedPrefs.getString("bookmark.gateway_password", "")); + gatewaySettings.setDomain(sharedPrefs.getString("bookmark.gateway_domain", "")); + } + + // Cloneable + public Object clone() { + return super.clone(); + } + + // Gateway Settings class + public static class GatewaySettings implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public GatewaySettings createFromParcel(Parcel in) { + return new GatewaySettings(in); + } + + @Override + public GatewaySettings[] newArray(int size) { + return new GatewaySettings[size]; + } + }; + private String hostname; + private int port; + private String username; + private String password; + private String domain; + + public GatewaySettings() { + hostname = ""; + port = 443; + username = ""; + password = ""; + domain = ""; + } + + public GatewaySettings(Parcel parcel) { + hostname = parcel.readString(); + port = parcel.readInt(); + username = parcel.readString(); + password = parcel.readString(); + domain = parcel.readString(); + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(hostname); + out.writeInt(port); + out.writeString(username); + out.writeString(password); + out.writeString(domain); + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java new file mode 100644 index 0000000..b181af3 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java @@ -0,0 +1,76 @@ +/* + Placeholder for bookmark items with a special purpose (i.e. just displaying some text) + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class PlaceholderBookmark extends BookmarkBase { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public PlaceholderBookmark createFromParcel(Parcel in) { + return new PlaceholderBookmark(in); + } + + @Override + public PlaceholderBookmark[] newArray(int size) { + return new PlaceholderBookmark[size]; + } + }; + private String name; + + public PlaceholderBookmark(Parcel parcel) { + super(parcel); + type = TYPE_PLACEHOLDER; + name = parcel.readString(); + } + + public PlaceholderBookmark() { + super(); + type = TYPE_PLACEHOLDER; + name = ""; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(name); + } + + @Override + public void writeToSharedPreferences(SharedPreferences sharedPrefs) { + super.writeToSharedPreferences(sharedPrefs); + } + + @Override + public void readFromSharedPreferences(SharedPreferences sharedPrefs) { + super.readFromSharedPreferences(sharedPrefs); + } + + // Cloneable + public Object clone() { + return super.clone(); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java new file mode 100644 index 0000000..7df9db8 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java @@ -0,0 +1,64 @@ +/* + Quick Connect bookmark (used for quick connects using just a hostname) + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class QuickConnectBookmark extends ManualBookmark { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public QuickConnectBookmark createFromParcel(Parcel in) { + return new QuickConnectBookmark(in); + } + + @Override + public QuickConnectBookmark[] newArray(int size) { + return new QuickConnectBookmark[size]; + } + }; + + public QuickConnectBookmark(Parcel parcel) { + super(parcel); + type = TYPE_QUICKCONNECT; + } + + public QuickConnectBookmark() { + super(); + type = TYPE_QUICKCONNECT; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + } + + @Override + public void writeToSharedPreferences(SharedPreferences sharedPrefs) { + super.writeToSharedPreferences(sharedPrefs); + } + + @Override + public void readFromSharedPreferences(SharedPreferences sharedPrefs) { + super.readFromSharedPreferences(sharedPrefs); + } + + // Cloneable + public Object clone() { + return super.clone(); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java new file mode 100644 index 0000000..c9320d4 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java @@ -0,0 +1,106 @@ +package com.freerdp.freerdpcore.presentation; + +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.nfc.FormatException; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.text.TextUtilsCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.services.LibFreeRDP; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Formatter; +import java.util.IllegalFormatException; +import java.util.Locale; + +public class AboutActivity extends AppCompatActivity { + private static final String TAG = AboutActivity.class.toString(); + private WebView mWebView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + mWebView = (WebView) findViewById(R.id.activity_about_webview); + } + + @Override + protected void onResume() { + populate(); + super.onResume(); + } + + private void populate() { + StringBuilder total = new StringBuilder(); + + String filename = "about_phone.html"; + if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) { + filename = "about.html"; + } + Locale def = Locale.getDefault(); + String prefix = def.getLanguage().toLowerCase(def); + + String dir = prefix + "_about_page/"; + String file = dir + filename; + InputStream is; + try { + is = getAssets().open(file); + is.close(); + } catch (IOException e) { + Log.e(TAG, "Missing localized asset " + file, e); + dir = "about_page/"; + file = dir + filename; + } + + try { + BufferedReader r = new BufferedReader(new InputStreamReader(getAssets().open(file))); + try { + String line; + while ((line = r.readLine()) != null) { + total.append(line); + total.append("\n"); + } + } finally { + r.close(); + } + } catch (IOException e) { + Log.e(TAG, "Could not read about page " + file, e); + } + + // append FreeRDP core version to app version + // get app version + String version; + try { + version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + version = "unknown"; + } + version = version + " (" + LibFreeRDP.getVersion() + ")"; + + WebSettings settings = mWebView.getSettings(); + settings.setDomStorageEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setSupportZoom(true); + + final String base = "file:///android_asset/" + dir; + + final String rawHtml = total.toString(); + final String html = rawHtml.replaceAll("%AFREERDP_VERSION%", version) + .replaceAll("%SYSTEM_VERSION%", Build.VERSION.RELEASE) + .replaceAll("%DEVICE_MODEL%", Build.MODEL); + + mWebView.loadDataWithBaseURL(base, html, "text/html", null, + "about:blank"); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java new file mode 100644 index 0000000..05fb792 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java @@ -0,0 +1,247 @@ +/* + Application Settings Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.support.v7.app.AlertDialog; +import android.widget.Toast; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.utils.AppCompatPreferenceActivity; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +public class ApplicationSettingsActivity extends AppCompatPreferenceActivity { + private static boolean isXLargeTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + private void setupActionBar() { + android.app.ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onIsMultiPane() { + return isXLargeTablet(this); + } + + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.settings_app_headers, target); + } + + protected boolean isValidFragment(String fragmentName) { + return PreferenceFragment.class.getName().equals(fragmentName) + || ClientPreferenceFragment.class.getName().equals(fragmentName) + || UiPreferenceFragment.class.getName().equals(fragmentName) + || PowerPreferenceFragment.class.getName().equals(fragmentName) + || SecurityPreferenceFragment.class.getName().equals(fragmentName); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class ClientPreferenceFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_client); + SharedPreferences preferences = get(getActivity()); + preferences.registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (isAdded()) { + final String clientNameKey = getString(R.string.preference_key_client_name); + + get(getActivity()); + if (key.equals(clientNameKey)) { + final String clientNameValue = sharedPreferences.getString(clientNameKey, ""); + EditTextPreference pref = (EditTextPreference) findPreference(clientNameKey); + pref.setText(clientNameValue); + } + } + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class UiPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_ui); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class PowerPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_power); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class SecurityPreferenceFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_security); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + final String clear = getString(R.string.preference_key_security_clear_certificate_cache); + if (preference.getKey().equals(clear)) { + showDialog(); + return true; + } else { + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + } + + private void showDialog() { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.dlg_title_clear_cert_cache) + .setMessage(R.string.dlg_msg_clear_cert_cache) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + clearCertificateCache(); + dialog.dismiss(); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setIcon(android.R.drawable.ic_delete) + .show(); + } + + private boolean deleteDirectory(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (String file : children) { + if (!deleteDirectory(new File(dir, file))) + return false; + } + } + return dir.delete(); + } + + private void clearCertificateCache() { + Context context = getActivity(); + if ((new File(context.getFilesDir() + "/.freerdp")).exists()) { + if (deleteDirectory(new File(context.getFilesDir() + "/.freerdp"))) + Toast.makeText(context, R.string.info_reset_success, Toast.LENGTH_LONG).show(); + else + Toast.makeText(context, R.string.info_reset_failed, Toast.LENGTH_LONG).show(); + } else + Toast.makeText(context, R.string.info_reset_success, Toast.LENGTH_LONG).show(); + } + } + + public static SharedPreferences get(Context context) { + Context appContext = context.getApplicationContext(); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_client, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_power, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_security, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_ui, false); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext); + + final String key = context.getString(R.string.preference_key_client_name); + final String value = preferences.getString(key, ""); + if (value.isEmpty()) { + final String android_id = UUID.randomUUID().toString(); + final String defaultValue = context.getString(R.string.preference_default_client_name); + final String name = defaultValue + "-" + android_id; + preferences.edit().putString(key, name.substring(0, 31)).apply(); + } + + return preferences; + } + + public static int getDisconnectTimeout(Context context) { + SharedPreferences preferences = get(context); + return preferences.getInt(context.getString(R.string.preference_key_power_disconnect_timeout), 0); + } + + public static boolean getHideStatusBar(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_hide_status_bar), false); + } + + public static boolean getHideActionBar(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_hide_action_bar), false); + } + + public static boolean getAcceptAllCertificates(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_accept_certificates), false); + } + + public static boolean getHideZoomControls(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_hide_zoom_controls), false); + } + + public static boolean getSwapMouseButtons(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_swap_mouse_buttons), false); + } + + public static boolean getInvertScrolling(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_invert_scrolling), false); + } + + public static boolean getAskOnExit(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_ask_on_exit), false); + } + + public static boolean getAutoScrollTouchPointer(Context context) { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_auto_scroll_touchpointer), false); + } + + public static String getClientName(Context context) { + SharedPreferences preferences = get(context); + return preferences.getString(context.getString(R.string.preference_key_client_name), ""); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java new file mode 100644 index 0000000..232039d --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java @@ -0,0 +1,694 @@ +/* + Bookmark editing activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.View; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.services.BookmarkBaseGateway; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.RDPFileParser; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +public class BookmarkActivity extends PreferenceActivity implements + OnSharedPreferenceChangeListener { + public static final String PARAM_CONNECTION_REFERENCE = "conRef"; + + private static final String TAG = "BookmarkActivity"; + private static final int PREFERENCES_BOOKMARK = 1; + private static final int PREFERENCES_CREDENTIALS = 2; + private static final int PREFERENCES_SCREEN = 3; + private static final int PREFERENCES_PERFORMANCE = 4; + private static final int PREFERENCES_ADVANCED = 5; + private static final int PREFERENCES_SCREEN3G = 6; + private static final int PREFERENCES_PERFORMANCE3G = 7; + private static final int PREFERENCES_GATEWAY = 8; + private static final int PREFERENCES_DEBUG = 9; + // bookmark needs to be static because the activity is started for each + // subview + // (we have to do this because Android has a bug where the style for + // Preferences + // is only applied to the first PreferenceScreen but not to subsequent ones) + private static BookmarkBase bookmark = null; + private static boolean settings_changed = false; + private static boolean new_bookmark = false; + private int current_preferences; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceManager mgr = getPreferenceManager(); + // init shared preferences for activity + mgr.setSharedPreferencesName("TEMP"); + mgr.setSharedPreferencesMode(MODE_PRIVATE); + + if (bookmark == null) { + // if we have a bookmark id set in the extras we are in edit mode + Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + // See if we got a connection reference to a bookmark + if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) { + String refStr = bundle + .getString(PARAM_CONNECTION_REFERENCE); + if (ConnectionReference.isManualBookmarkReference(refStr)) { + bookmark = GlobalApp.getManualBookmarkGateway() + .findById( + ConnectionReference + .getManualBookmarkId(refStr)); + new_bookmark = false; + } else if (ConnectionReference.isHostnameReference(refStr)) { + bookmark = new ManualBookmark(); + bookmark.get().setLabel( + ConnectionReference.getHostname(refStr)); + bookmark.get().setHostname( + ConnectionReference.getHostname(refStr)); + new_bookmark = true; + } else if (ConnectionReference.isFileReference(refStr)) { + String file = ConnectionReference.getFile(refStr); + + bookmark = new ManualBookmark(); + bookmark.setLabel(file); + + try { + RDPFileParser rdpFile = new RDPFileParser(file); + updateBookmarkFromFile((ManualBookmark) bookmark, + rdpFile); + + bookmark.setLabel(new File(file).getName()); + new_bookmark = true; + } catch (IOException e) { + Log.e(TAG, "Failed reading RDP file", e); + } + } + } + } + + // last chance - ensure we really have a valid bookmark + if (bookmark == null) + bookmark = new ManualBookmark(); + + // hide gateway settings if we edit a non-manual bookmark + if (current_preferences == PREFERENCES_ADVANCED + && bookmark.getType() != ManualBookmark.TYPE_MANUAL) { + PreferenceScreen screen = getPreferenceScreen(); + screen.removePreference(findPreference("bookmark.enable_gateway")); + screen.removePreference(findPreference("bookmark.gateway")); + } + + updateH264Preferences(); + + // update preferences from bookmark + bookmark.writeToSharedPreferences(mgr.getSharedPreferences()); + + // no settings changed yet + settings_changed = false; + } + + // load the requested settings resource + if (getIntent() == null || getIntent().getData() == null) { + addPreferencesFromResource(R.xml.bookmark_settings); + current_preferences = PREFERENCES_BOOKMARK; + } else if (getIntent().getData().toString() + .equals("preferences://screen_settings")) { + addPreferencesFromResource(R.xml.screen_settings); + current_preferences = PREFERENCES_SCREEN; + } else if (getIntent().getData().toString() + .equals("preferences://performance_flags")) { + addPreferencesFromResource(R.xml.performance_flags); + current_preferences = PREFERENCES_PERFORMANCE; + } else if (getIntent().getData().toString() + .equals("preferences://screen_settings_3g")) { + addPreferencesFromResource(R.xml.screen_settings_3g); + current_preferences = PREFERENCES_SCREEN3G; + } else if (getIntent().getData().toString() + .equals("preferences://performance_flags_3g")) { + addPreferencesFromResource(R.xml.performance_flags_3g); + current_preferences = PREFERENCES_PERFORMANCE3G; + } else if (getIntent().getData().toString() + .equals("preferences://advanced_settings")) { + addPreferencesFromResource(R.xml.advanced_settings); + current_preferences = PREFERENCES_ADVANCED; + } else if (getIntent().getData().toString() + .equals("preferences://credentials_settings")) { + addPreferencesFromResource(R.xml.credentials_settings); + current_preferences = PREFERENCES_CREDENTIALS; + } else if (getIntent().getData().toString() + .equals("preferences://gateway_settings")) { + addPreferencesFromResource(R.xml.gateway_settings); + current_preferences = PREFERENCES_GATEWAY; + } else if (getIntent().getData().toString() + .equals("preferences://debug_settings")) { + addPreferencesFromResource(R.xml.debug_settings); + current_preferences = PREFERENCES_DEBUG; + } else { + addPreferencesFromResource(R.xml.bookmark_settings); + current_preferences = PREFERENCES_BOOKMARK; + } + + // update UI with bookmark data + SharedPreferences spref = mgr.getSharedPreferences(); + initSettings(spref); + + // register for preferences changed notification + mgr.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + + // set the correct component names in our preferencescreen settings + setIntentComponentNames(); + + updateH264Preferences(); + } + + private void updateH264Preferences() { + if (!LibFreeRDP.hasH264Support()) { + final int preferenceIdList[] = { + R.string.preference_key_h264, + R.string.preference_key_h264_3g + }; + + PreferenceManager mgr = getPreferenceManager(); + for (int id : preferenceIdList) { + final String key = getString(id); + Preference preference = mgr.findPreference(key); + if (preference != null) { + preference.setEnabled(false); + } + } + } + } + + private void updateBookmarkFromFile(ManualBookmark bookmark, + RDPFileParser rdpFile) { + String s; + Integer i; + + s = rdpFile.getString("full address"); + if (s != null) { + // this gets complicated as it can include port + if (s.lastIndexOf(":") > s.lastIndexOf("]")) { + try { + String port = s.substring(s.lastIndexOf(":") + 1); + bookmark.setPort(Integer.parseInt(port)); + } catch (NumberFormatException e) { + Log.e(TAG, "Malformed address"); + } + + s = s.substring(0, s.lastIndexOf(":")); + } + + // or even be an ipv6 address + if (s.startsWith("[") && s.endsWith("]")) + s = s.substring(1, s.length() - 1); + + bookmark.setHostname(s); + } + + i = rdpFile.getInteger("server port"); + if (i != null) + bookmark.setPort(i); + + s = rdpFile.getString("username"); + if (s != null) + bookmark.setUsername(s); + + s = rdpFile.getString("domain"); + if (s != null) + bookmark.setDomain(s); + + i = rdpFile.getInteger("connect to console"); + if (i != null) + bookmark.getAdvancedSettings().setConsoleMode(i == 1); + } + + private void setIntentComponentNames() { + // we set the component name for our sub-activity calls here because we + // don't know the package + // name of the main app in our library project. + ComponentName compName = new ComponentName(getPackageName(), + BookmarkActivity.class.getName()); + ArrayList prefKeys = new ArrayList(); + + prefKeys.add("bookmark.credentials"); + prefKeys.add("bookmark.screen"); + prefKeys.add("bookmark.performance"); + prefKeys.add("bookmark.advanced"); + prefKeys.add("bookmark.screen_3g"); + prefKeys.add("bookmark.performance_3g"); + prefKeys.add("bookmark.gateway_settings"); + prefKeys.add("bookmark.debug"); + + for (String p : prefKeys) { + Preference pref = findPreference(p); + if (pref != null) + pref.getIntent().setComponent(compName); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + settings_changed = true; + switch (current_preferences) { + case PREFERENCES_DEBUG: + debugSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_BOOKMARK: + bookmarkSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_ADVANCED: + advancedSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_CREDENTIALS: + credentialsSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_SCREEN: + case PREFERENCES_SCREEN3G: + screenSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_GATEWAY: + gatewaySettingsChanged(sharedPreferences, key); + break; + + default: + break; + } + + } + + private void initSettings(SharedPreferences sharedPreferences) { + switch (current_preferences) { + case PREFERENCES_BOOKMARK: + initBookmarkSettings(sharedPreferences); + break; + + case PREFERENCES_ADVANCED: + initAdvancedSettings(sharedPreferences); + break; + + case PREFERENCES_CREDENTIALS: + initCredentialsSettings(sharedPreferences); + break; + + case PREFERENCES_SCREEN: + initScreenSettings(sharedPreferences); + break; + + case PREFERENCES_SCREEN3G: + initScreenSettings3G(sharedPreferences); + break; + + case PREFERENCES_GATEWAY: + initGatewaySettings(sharedPreferences); + break; + + case PREFERENCES_DEBUG: + initDebugSettings(sharedPreferences); + break; + + default: + break; + } + } + + private void initBookmarkSettings(SharedPreferences sharedPreferences) { + bookmarkSettingsChanged(sharedPreferences, "bookmark.label"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.hostname"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.port"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.username"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.resolution"); + } + + private void bookmarkSettingsChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals("bookmark.label") && findPreference(key) != null) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.hostname") && findPreference(key) != null) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.port") && findPreference(key) != null) + findPreference(key).setSummary( + String.valueOf(sharedPreferences.getInt(key, -1))); + else if (key.equals("bookmark.username")) { + String username = sharedPreferences.getString(key, ""); + if (username.length() == 0) + username = ""; + findPreference("bookmark.credentials").setSummary(username); + } else if (key.equals("bookmark.resolution") + || key.equals("bookmark.colors") + || key.equals("bookmark.width") + || key.equals("bookmark.height")) { + String resolution = sharedPreferences.getString( + "bookmark.resolution", "800x600"); + // compare english string from resolutions_values_array array, + // decode to localized + // text for display + if (resolution.equals("automatic")) { + resolution = getResources().getString( + R.string.resolution_automatic); + } + if (resolution.equals("custom")) { + resolution = getResources().getString( + R.string.resolution_custom); + } + if (resolution.equals("fitscreen")) { + resolution = getResources().getString(R.string.resolution_fit); + } + resolution += "@" + sharedPreferences.getInt("bookmark.colors", 16); + findPreference("bookmark.screen").setSummary(resolution); + } + } + + private void initAdvancedSettings(SharedPreferences sharedPreferences) { + advancedSettingsChanged(sharedPreferences, + "bookmark.enable_gateway_settings"); + advancedSettingsChanged(sharedPreferences, + "bookmark.enable_3g_settings"); + advancedSettingsChanged(sharedPreferences, "bookmark.security"); + advancedSettingsChanged(sharedPreferences, "bookmark.resolution_3g"); + advancedSettingsChanged(sharedPreferences, "bookmark.remote_program"); + advancedSettingsChanged(sharedPreferences, "bookmark.work_dir"); + } + + private void advancedSettingsChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals("bookmark.enable_gateway_settings")) { + boolean enabled = sharedPreferences.getBoolean(key, false); + findPreference("bookmark.gateway_settings").setEnabled(enabled); + } else if (key.equals("bookmark.enable_3g_settings")) { + boolean enabled = sharedPreferences.getBoolean(key, false); + findPreference("bookmark.screen_3g").setEnabled(enabled); + findPreference("bookmark.performance_3g").setEnabled(enabled); + } else if (key.equals("bookmark.security")) { + ListPreference listPreference = (ListPreference) findPreference(key); + CharSequence security = listPreference.getEntries()[sharedPreferences + .getInt(key, 0)]; + listPreference.setSummary(security); + } else if (key.equals("bookmark.resolution_3g") + || key.equals("bookmark.colors_3g") + || key.equals("bookmark.width_3g") + || key.equals("bookmark.height_3g")) { + String resolution = sharedPreferences.getString( + "bookmark.resolution_3g", "800x600"); + if (resolution.equals("automatic")) + resolution = getResources().getString( + R.string.resolution_automatic); + else if (resolution.equals("custom")) + resolution = getResources().getString( + R.string.resolution_custom); + resolution += "@" + + sharedPreferences.getInt("bookmark.colors_3g", 16); + findPreference("bookmark.screen_3g").setSummary(resolution); + } else if (key.equals("bookmark.remote_program")) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.work_dir")) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + } + + private void initCredentialsSettings(SharedPreferences sharedPreferences) { + credentialsSettingsChanged(sharedPreferences, "bookmark.username"); + credentialsSettingsChanged(sharedPreferences, "bookmark.password"); + credentialsSettingsChanged(sharedPreferences, "bookmark.domain"); + } + + private void credentialsSettingsChanged( + SharedPreferences sharedPreferences, String key) { + if (key.equals("bookmark.username")) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.password")) { + if (sharedPreferences.getString(key, "").length() == 0) + findPreference(key).setSummary( + getResources().getString( + R.string.settings_password_empty)); + else + findPreference(key).setSummary( + getResources().getString( + R.string.settings_password_present)); + } else if (key.equals("bookmark.domain")) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + } + + private void initScreenSettings(SharedPreferences sharedPreferences) { + screenSettingsChanged(sharedPreferences, "bookmark.colors"); + screenSettingsChanged(sharedPreferences, "bookmark.resolution"); + screenSettingsChanged(sharedPreferences, "bookmark.width"); + screenSettingsChanged(sharedPreferences, "bookmark.height"); + } + + private void initScreenSettings3G(SharedPreferences sharedPreferences) { + screenSettingsChanged(sharedPreferences, "bookmark.colors_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.resolution_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.width_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.height_3g"); + } + + private void screenSettingsChanged(SharedPreferences sharedPreferences, + String key) { + // could happen during initialization because 3g and non-3g settings + // share this routine - just skip + if (findPreference(key) == null) + return; + + if (key.equals("bookmark.colors") || key.equals("bookmark.colors_3g")) { + ListPreference listPreference = (ListPreference) findPreference(key); + listPreference.setSummary(listPreference.getEntry()); + } else if (key.equals("bookmark.resolution") + || key.equals("bookmark.resolution_3g")) { + ListPreference listPreference = (ListPreference) findPreference(key); + listPreference.setSummary(listPreference.getEntry()); + + String value = listPreference.getValue(); + boolean enabled = value.equalsIgnoreCase("custom"); + if (key.equals("bookmark.resolution")) { + findPreference("bookmark.width").setEnabled(enabled); + findPreference("bookmark.height").setEnabled(enabled); + } else { + findPreference("bookmark.width_3g").setEnabled(enabled); + findPreference("bookmark.height_3g").setEnabled(enabled); + } + } else if (key.equals("bookmark.width") + || key.equals("bookmark.width_3g")) + findPreference(key).setSummary( + String.valueOf(sharedPreferences.getInt(key, 800))); + else if (key.equals("bookmark.height") + || key.equals("bookmark.height_3g")) + findPreference(key).setSummary( + String.valueOf(sharedPreferences.getInt(key, 600))); + } + + private void initDebugSettings(SharedPreferences sharedPreferences) { + debugSettingsChanged(sharedPreferences, "bookmark.debug_level"); + debugSettingsChanged(sharedPreferences, "bookmark.async_channel"); + debugSettingsChanged(sharedPreferences, "bookmark.async_update"); + debugSettingsChanged(sharedPreferences, "bookmark.async_input"); + } + + private void initGatewaySettings(SharedPreferences sharedPreferences) { + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_hostname"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_port"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_username"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_password"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_domain"); + } + + private void debugSettingsChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals("bookmark.debug_level")) { + String level = sharedPreferences.getString(key, "INFO"); + Preference pref = findPreference("bookmark.debug_level"); + pref.setDefaultValue(level); + } else if (key.equals("bookmark.async_channel")) { + boolean enabled = sharedPreferences.getBoolean(key, false); + Preference pref = findPreference("bookmark.async_channel"); + pref.setDefaultValue(enabled); + } else if (key.equals("bookmark.async_update")) { + boolean enabled = sharedPreferences.getBoolean(key, false); + Preference pref = findPreference("bookmark.async_update"); + pref.setDefaultValue(enabled); + } else if (key.equals("bookmark.async_input")) { + boolean enabled = sharedPreferences.getBoolean(key, false); + Preference pref = findPreference("bookmark.async_input"); + pref.setDefaultValue(enabled); + } + } + + private void gatewaySettingsChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals("bookmark.gateway_hostname")) { + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + } else if (key.equals("bookmark.gateway_port")) { + findPreference(key).setSummary( + String.valueOf(sharedPreferences.getInt(key, 443))); + } else if (key.equals("bookmark.gateway_username")) { + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + } else if (key.equals("bookmark.gateway_password")) { + if (sharedPreferences.getString(key, "").length() == 0) + findPreference(key).setSummary( + getResources().getString( + R.string.settings_password_empty)); + else + findPreference(key).setSummary( + getResources().getString( + R.string.settings_password_present)); + } else if (key.equals("bookmark.gateway_domain")) + findPreference(key) + .setSummary(sharedPreferences.getString(key, "")); + } + + private boolean verifySettings(SharedPreferences sharedPreferences) { + + boolean verifyFailed = false; + // perform sanity checks on settings + // Label set + if (sharedPreferences.getString("bookmark.label", "").length() == 0) + verifyFailed = true; + + // Server and port specified + if (!verifyFailed + && sharedPreferences.getString("bookmark.hostname", "") + .length() == 0) + verifyFailed = true; + + // Server and port specified + if (!verifyFailed && sharedPreferences.getInt("bookmark.port", -1) <= 0) + verifyFailed = true; + + // if an error occurred - display toast and return false + return (!verifyFailed); + } + + private void finishAndResetBookmark() { + bookmark = null; + getPreferenceManager().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + finish(); + } + + @Override + public void onBackPressed() { + // only proceed if we are in the main preferences screen + if (current_preferences != PREFERENCES_BOOKMARK) { + super.onBackPressed(); + getPreferenceManager().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + return; + } + + SharedPreferences sharedPreferences = getPreferenceManager() + .getSharedPreferences(); + if (!verifySettings(sharedPreferences)) { + // ask the user if he wants to cancel or continue editing + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.error_bookmark_incomplete_title) + .setMessage(R.string.error_bookmark_incomplete) + .setPositiveButton(R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + finishAndResetBookmark(); + } + }) + .setNegativeButton(R.string.cont, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + dialog.cancel(); + } + }).show(); + + return; + } else { + // ask the user if he wants to save or cancel editing if a setting + // has changed + if (new_bookmark || settings_changed) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_save_bookmark) + .setMessage(R.string.dlg_save_bookmark) + .setPositiveButton(R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + // read shared prefs back to bookmark + bookmark.readFromSharedPreferences(getPreferenceManager() + .getSharedPreferences()); + + BookmarkBaseGateway bookmarkGateway; + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL) { + bookmarkGateway = GlobalApp + .getManualBookmarkGateway(); + // remove any history entry for this + // bookmark + GlobalApp + .getQuickConnectHistoryGateway() + .removeHistoryItem( + bookmark.get() + .getHostname()); + } else { + assert false; + return; + } + + // insert or update bookmark and leave + // activity + if (bookmark.getId() > 0) + bookmarkGateway.update(bookmark); + else + bookmarkGateway.insert(bookmark); + + finishAndResetBookmark(); + } + }) + .setNegativeButton(R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + finishAndResetBookmark(); + } + }).show(); + } else { + finishAndResetBookmark(); + } + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java new file mode 100644 index 0000000..247635e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java @@ -0,0 +1,71 @@ +/* + Activity that displays the help pages + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Locale; + +public class HelpActivity extends AppCompatActivity { + + private static final String TAG = HelpActivity.class.toString(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + WebView webview = new WebView(this); + setContentView(webview); + + String filename; + if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) + filename = "gestures.html"; + else + filename = "gestures_phone.html"; + + WebSettings settings = webview.getSettings(); + settings.setDomStorageEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setSupportZoom(true); + settings.setJavaScriptEnabled(true); + + settings.setAllowContentAccess(true); + settings.setAllowFileAccess(true); + + final Locale def = Locale.getDefault(); + final String prefix = def.getLanguage().toLowerCase(def); + + final String base = "file:///android_asset/"; + final String baseName = "help_page"; + String dir = prefix + "_" + baseName + "/"; + String file = dir + filename; + InputStream is; + try { + is = getAssets().open(file); + is.close(); + } catch (IOException e) { + Log.e(TAG, "Missing localized asset " + file, e); + dir = baseName + "/"; + file = dir + filename; + } + + webview.loadUrl(base + file); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java new file mode 100644 index 0000000..7407373 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java @@ -0,0 +1,347 @@ +/* + Main/Home Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ListView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.PlaceholderBookmark; +import com.freerdp.freerdpcore.domain.QuickConnectBookmark; +import com.freerdp.freerdpcore.utils.BookmarkArrayAdapter; +import com.freerdp.freerdpcore.utils.SeparatedListAdapter; + +import java.util.ArrayList; + +public class HomeActivity extends AppCompatActivity { + private final static String ADD_BOOKMARK_PLACEHOLDER = "add_bookmark"; + private static final String TAG = "HomeActivity"; + private static final String PARAM_SUPERBAR_TEXT = "superbar_text"; + private ListView listViewBookmarks; + private Button clearTextButton; + private EditText superBarEditText; + private BookmarkArrayAdapter manualBookmarkAdapter; + private SeparatedListAdapter separatedListAdapter; + private PlaceholderBookmark addBookmarkPlaceholder; + private String sectionLabelBookmarks; + + View mDecor; + + @Override + public void onCreate(Bundle savedInstanceState) { + setTitle(R.string.title_home); + super.onCreate(savedInstanceState); + setContentView(R.layout.home); + + mDecor = getWindow().getDecorView(); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + long heapSize = Runtime.getRuntime().maxMemory(); + Log.i(TAG, "Max HeapSize: " + heapSize); + Log.i(TAG, "App data folder: " + getFilesDir().toString()); + + // load strings + sectionLabelBookmarks = getResources().getString(R.string.section_bookmarks); + + // create add bookmark/quick connect bookmark placeholder + addBookmarkPlaceholder = new PlaceholderBookmark(); + addBookmarkPlaceholder.setName(ADD_BOOKMARK_PLACEHOLDER); + addBookmarkPlaceholder.setLabel(getResources().getString(R.string.list_placeholder_add_bookmark)); + + // check for passed .rdp file and open it in a new bookmark + Intent caller = getIntent(); + Uri callParameter = caller.getData(); + + if (Intent.ACTION_VIEW.equals(caller.getAction()) && callParameter != null) { + String refStr = ConnectionReference.getFileReference(callParameter.getPath()); + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivity(bookmarkIntent); + } + + // load views + clearTextButton = (Button) findViewById(R.id.clear_search_btn); + superBarEditText = (EditText) findViewById(R.id.superBarEditText); + + listViewBookmarks = (ListView) findViewById(R.id.listViewBookmarks); + + // set listeners for the list view + listViewBookmarks.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + String curSection = separatedListAdapter.getSectionForPosition(position); + Log.v(TAG, "Clicked on item id " + separatedListAdapter.getItemId(position) + " in section " + curSection); + if (curSection == sectionLabelBookmarks) { + String refStr = view.getTag().toString(); + if (ConnectionReference.isManualBookmarkReference(refStr) || + ConnectionReference.isHostnameReference(refStr)) { + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent sessionIntent = new Intent(view.getContext(), SessionActivity.class); + sessionIntent.putExtras(bundle); + startActivity(sessionIntent); + + // clear any search text + superBarEditText.setText(""); + superBarEditText.clearFocus(); + } else if (ConnectionReference.isPlaceholderReference(refStr)) { + // is this the add bookmark placeholder? + if (ConnectionReference.getPlaceholder(refStr).equals(ADD_BOOKMARK_PLACEHOLDER)) { + Intent bookmarkIntent = new Intent(view.getContext(), BookmarkActivity.class); + startActivity(bookmarkIntent); + } + } + } + } + }); + + listViewBookmarks.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + // if the selected item is not a session item (tag == null) and not a quick connect entry + // (not a hostname connection reference) inflate the context menu + View itemView = ((AdapterContextMenuInfo) menuInfo).targetView; + String refStr = itemView.getTag() != null ? itemView.getTag().toString() : null; + if (refStr != null && !ConnectionReference.isHostnameReference(refStr) && !ConnectionReference.isPlaceholderReference(refStr)) { + getMenuInflater().inflate(R.menu.bookmark_context_menu, menu); + menu.setHeaderTitle(getResources().getString(R.string.menu_title_bookmark)); + } + } + }); + + superBarEditText.addTextChangedListener(new SuperBarTextWatcher()); + + clearTextButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + superBarEditText.setText(""); + } + }); + } + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // ignore orientation/keyboard change + super.onConfigurationChanged(newConfig); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + @Override + public boolean onSearchRequested() { + superBarEditText.requestFocus(); + return true; + } + + @Override + public boolean onContextItemSelected(MenuItem aItem) { + + // get connection reference + AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) aItem.getMenuInfo(); + String refStr = menuInfo.targetView.getTag().toString(); + + // refer to http://tools.android.com/tips/non-constant-fields why we can't use switch/case here .. + int itemId = aItem.getItemId(); + if (itemId == R.id.bookmark_connect) { + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent sessionIntent = new Intent(this, SessionActivity.class); + sessionIntent.putExtras(bundle); + + startActivity(sessionIntent); + return true; + } else if (itemId == R.id.bookmark_edit) { + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivity(bookmarkIntent); + return true; + } else if (itemId == R.id.bookmark_delete) { + if (ConnectionReference.isManualBookmarkReference(refStr)) { + long id = ConnectionReference.getManualBookmarkId(refStr); + GlobalApp.getManualBookmarkGateway().delete(id); + manualBookmarkAdapter.remove(id); + separatedListAdapter.notifyDataSetChanged(); + } else { + assert false; + } + + // clear super bar text + superBarEditText.setText(""); + return true; + } + + return false; + } + + @Override + protected void onResume() { + super.onResume(); + Log.v(TAG, "HomeActivity.onResume"); + + // create bookmark cursor adapter + manualBookmarkAdapter = new BookmarkArrayAdapter(this, R.layout.bookmark_list_item, GlobalApp.getManualBookmarkGateway().findAll()); + + // add add bookmark item to manual adapter + manualBookmarkAdapter.insert(addBookmarkPlaceholder, 0); + + // attach all adapters to the separatedListView adapter and assign it to the list view + separatedListAdapter = new SeparatedListAdapter(this); + separatedListAdapter.addSection(sectionLabelBookmarks, manualBookmarkAdapter); + listViewBookmarks.setAdapter(separatedListAdapter); + + // if we have a filter text entered cause an update to be caused here + String filter = superBarEditText.getText().toString(); + if (filter.length() > 0) + superBarEditText.setText(filter); + } + + @Override + protected void onPause() { + super.onPause(); + Log.v(TAG, "HomeActivity.onPause"); + + // reset adapters + listViewBookmarks.setAdapter(null); + separatedListAdapter = null; + manualBookmarkAdapter = null; + } + + @Override + public void onBackPressed() { + // if back was pressed - ask the user if he really wants to exit + if (ApplicationSettingsActivity.getAskOnExit(this)) { + final CheckBox cb = new CheckBox(this); + cb.setChecked(!ApplicationSettingsActivity.getAskOnExit(this)); + cb.setText(R.string.dlg_dont_show_again); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_exit) + .setMessage(R.string.dlg_msg_exit) + .setView(cb) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .create() + .show(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(PARAM_SUPERBAR_TEXT, superBarEditText.getText().toString()); + } + + @Override + protected void onRestoreInstanceState(Bundle inState) { + super.onRestoreInstanceState(inState); + superBarEditText.setText(inState.getString(PARAM_SUPERBAR_TEXT)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.home_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + // refer to http://tools.android.com/tips/non-constant-fields why we can't use switch/case here .. + int itemId = item.getItemId(); + if (itemId == R.id.newBookmark) { + Intent bookmarkIntent = new Intent(this, BookmarkActivity.class); + startActivity(bookmarkIntent); + } else if (itemId == R.id.appSettings) { + Intent settingsIntent = new Intent(this, ApplicationSettingsActivity.class); + startActivity(settingsIntent); + } else if (itemId == R.id.help) { + Intent helpIntent = new Intent(this, HelpActivity.class); + startActivity(helpIntent); + } else if (itemId == R.id.about) { + Intent aboutIntent = new Intent(this, AboutActivity.class); + startActivity(aboutIntent); + } + + return true; + } + + private class SuperBarTextWatcher implements TextWatcher { + @Override + public void afterTextChanged(Editable s) { + if (separatedListAdapter != null) { + String text = s.toString(); + if (text.length() > 0) { + ArrayList computers_list = GlobalApp.getQuickConnectHistoryGateway().findHistory(text); + computers_list.addAll(GlobalApp.getManualBookmarkGateway().findByLabelOrHostnameLike(text)); + manualBookmarkAdapter.replaceItems(computers_list); + QuickConnectBookmark qcBm = new QuickConnectBookmark(); + qcBm.setLabel(text); + qcBm.setHostname(text); + manualBookmarkAdapter.insert(qcBm, 0); + } else { + manualBookmarkAdapter.replaceItems(GlobalApp.getManualBookmarkGateway().findAll()); + manualBookmarkAdapter.insert(addBookmarkPlaceholder, 0); + } + + separatedListAdapter.notifyDataSetChanged(); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java new file mode 100644 index 0000000..a2fee7c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java @@ -0,0 +1,1150 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Revised 5/19/2010 by GORGES + * Now supports two-dimensional view scrolling + * http://GORGES.us + */ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Scroller; +import android.widget.TextView; + +import java.util.List; + +/** + * Layout container for a view hierarchy that can be scrolled by the user, + * allowing it to be larger than the physical display. A TwoDScrollView + * is a {@link FrameLayout}, meaning you should place one child in it + * containing the entire contents to scroll; this child may itself be a layout + * manager with a complex hierarchy of objects. A child that is often used + * is a {@link LinearLayout} in a vertical orientation, presenting a vertical + * array of top-level items that the user can scroll through. + *

+ *

The {@link TextView} class also + * takes care of its own scrolling, so does not require a TwoDScrollView, but + * using the two together is possible to achieve the effect of a text view + * within a larger container. + */ +public class ScrollView2D extends FrameLayout { + + static final int ANIMATED_SCROLL_GAP = 250; + static final float MAX_SCROLL_FACTOR = 0.5f; + private final Rect mTempRect = new Rect(); + private ScrollView2DListener scrollView2DListener = null; + private long mLastScroll; + private Scroller mScroller; + private boolean scrollEnabled = true; + /** + * Flag to indicate that we are moving focus ourselves. This is so the + * code that watches for focus changes initiated outside this TwoDScrollView + * knows that it does not have to do anything. + */ + private boolean mTwoDScrollViewMovedFocus; + /** + * Position of the last motion event. + */ + private float mLastMotionY; + private float mLastMotionX; + /** + * True when the layout has changed but the traversal has not come through yet. + * Ideally the view hierarchy would keep track of this for us. + */ + private boolean mIsLayoutDirty = true; + /** + * The child to give focus to in the event that a child has requested focus while the + * layout is dirty. This prevents the scroll from being wrong if the child has not been + * laid out before requesting focus. + */ + private View mChildToScrollTo = null; + /** + * True if the user is currently dragging this TwoDScrollView around. This is + * not the same as 'is being flinged', which can be checked by + * mScroller.isFinished() (flinging begins when the user lifts his finger). + */ + private boolean mIsBeingDragged = false; + /** + * Determines speed during touch scrolling + */ + private VelocityTracker mVelocityTracker; + /** + * Whether arrow scrolling is animated. + */ + private int mTouchSlop; + private int mMinimumVelocity; + private int mMaximumVelocity; + public ScrollView2D(Context context) { + super(context); + initTwoDScrollView(); + } + + public ScrollView2D(Context context, AttributeSet attrs) { + super(context, attrs); + initTwoDScrollView(); + } + + public ScrollView2D(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initTwoDScrollView(); + } + + @Override + protected float getTopFadingEdgeStrength() { + if (getChildCount() == 0) { + return 0.0f; + } + final int length = getVerticalFadingEdgeLength(); + if (getScrollY() < length) { + return getScrollY() / (float) length; + } + return 1.0f; + } + + @Override + protected float getBottomFadingEdgeStrength() { + if (getChildCount() == 0) { + return 0.0f; + } + final int length = getVerticalFadingEdgeLength(); + final int bottomEdge = getHeight() - getPaddingBottom(); + final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge; + if (span < length) { + return span / (float) length; + } + return 1.0f; + } + + @Override + protected float getLeftFadingEdgeStrength() { + if (getChildCount() == 0) { + return 0.0f; + } + final int length = getHorizontalFadingEdgeLength(); + if (getScrollX() < length) { + return getScrollX() / (float) length; + } + return 1.0f; + } + + @Override + protected float getRightFadingEdgeStrength() { + if (getChildCount() == 0) { + return 0.0f; + } + final int length = getHorizontalFadingEdgeLength(); + final int rightEdge = getWidth() - getPaddingRight(); + final int span = getChildAt(0).getRight() - getScrollX() - rightEdge; + if (span < length) { + return span / (float) length; + } + return 1.0f; + } + + /** + * Disable/Enable scrolling + */ + public void setScrollEnabled(boolean enable) { + scrollEnabled = enable; + } + + /** + * @return The maximum amount this scroll view will scroll in response to + * an arrow event. + */ + public int getMaxScrollAmountVertical() { + return (int) (MAX_SCROLL_FACTOR * getHeight()); + } + + public int getMaxScrollAmountHorizontal() { + return (int) (MAX_SCROLL_FACTOR * getWidth()); + } + + private void initTwoDScrollView() { + mScroller = new Scroller(getContext()); + setFocusable(true); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + setWillNotDraw(false); + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + } + + @Override + public void addView(View child) { + if (getChildCount() > 0) { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child); + } + + @Override + public void addView(View child, int index) { + if (getChildCount() > 0) { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, index); + } + + @Override + public void addView(View child, ViewGroup.LayoutParams params) { + if (getChildCount() > 0) { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, params); + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (getChildCount() > 0) { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, index, params); + } + + /** + * @return Returns true this TwoDScrollView can be scrolled + */ + private boolean canScroll() { + if (!scrollEnabled) + return false; + View child = getChildAt(0); + if (child != null) { + int childHeight = child.getHeight(); + int childWidth = child.getWidth(); + return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) || + (getWidth() < childWidth + getPaddingLeft() + getPaddingRight()); + } + return false; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Let the focused view and/or our descendants get the key first + boolean handled = super.dispatchKeyEvent(event); + if (handled) { + return true; + } + return executeKeyEvent(event); + } + + /** + * You can call this function yourself to have the scroll view perform + * scrolling from a key event, just as if the event had been dispatched to + * it by the view hierarchy. + * + * @param event The key event to execute. + * @return Return true if the event was handled, else false. + */ + public boolean executeKeyEvent(KeyEvent event) { + mTempRect.setEmpty(); + if (!canScroll()) { + if (isFocused()) { + View currentFocused = findFocus(); + if (currentFocused == this) currentFocused = null; + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, View.FOCUS_DOWN); + return nextFocused != null && nextFocused != this && nextFocused.requestFocus(View.FOCUS_DOWN); + } + return false; + } + boolean handled = false; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + if (!event.isAltPressed()) { + handled = arrowScroll(View.FOCUS_UP, false); + } else { + handled = fullScroll(View.FOCUS_UP, false); + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!event.isAltPressed()) { + handled = arrowScroll(View.FOCUS_DOWN, false); + } else { + handled = fullScroll(View.FOCUS_DOWN, false); + } + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + if (!event.isAltPressed()) { + handled = arrowScroll(View.FOCUS_LEFT, true); + } else { + handled = fullScroll(View.FOCUS_LEFT, true); + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (!event.isAltPressed()) { + handled = arrowScroll(View.FOCUS_RIGHT, true); + } else { + handled = fullScroll(View.FOCUS_RIGHT, true); + } + break; + } + } + return handled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onMotionEvent will be called and we do the actual + * scrolling there. + * + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { + return true; + } + if (!canScroll()) { + mIsBeingDragged = false; + return false; + } + final float y = ev.getY(); + final float x = ev.getX(); + switch (action) { + case MotionEvent.ACTION_MOVE: + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + /* + * Locally do absolute value. mLastMotionY is set to the y value + * of the down event. + */ + final int yDiff = (int) Math.abs(y - mLastMotionY); + final int xDiff = (int) Math.abs(x - mLastMotionX); + if (yDiff > mTouchSlop || xDiff > mTouchSlop) { + mIsBeingDragged = true; + } + break; + + case MotionEvent.ACTION_DOWN: + /* Remember location of down touch */ + mLastMotionY = y; + mLastMotionX = x; + + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + mIsBeingDragged = !mScroller.isFinished(); + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + /* Release the drag */ + mIsBeingDragged = false; + break; + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mIsBeingDragged; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + + if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { + // Don't handle edge touches immediately -- they may actually belong to one of our + // descendants. + return false; + } + + if (!canScroll()) { + return false; + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + final int action = ev.getAction(); + final float y = ev.getY(); + final float x = ev.getX(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + // Remember where the motion event started + mLastMotionY = y; + mLastMotionX = x; + break; + case MotionEvent.ACTION_MOVE: + // Scroll to follow the motion event + int deltaX = (int) (mLastMotionX - x); + int deltaY = (int) (mLastMotionY - y); + mLastMotionX = x; + mLastMotionY = y; + + if (deltaX < 0) { + if (getScrollX() < 0) { + deltaX = 0; + } + } else if (deltaX > 0) { + final int rightEdge = getWidth() - getPaddingRight(); + final int availableToScroll = getChildAt(0).getRight() - getScrollX() - rightEdge; + if (availableToScroll > 0) { + deltaX = Math.min(availableToScroll, deltaX); + } else { + deltaX = 0; + } + } + if (deltaY < 0) { + if (getScrollY() < 0) { + deltaY = 0; + } + } else if (deltaY > 0) { + final int bottomEdge = getHeight() - getPaddingBottom(); + final int availableToScroll = getChildAt(0).getBottom() - getScrollY() - bottomEdge; + if (availableToScroll > 0) { + deltaY = Math.min(availableToScroll, deltaY); + } else { + deltaY = 0; + } + } + if (deltaY != 0 || deltaX != 0) + scrollBy(deltaX, deltaY); + break; + case MotionEvent.ACTION_UP: + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialXVelocity = (int) velocityTracker.getXVelocity(); + int initialYVelocity = (int) velocityTracker.getYVelocity(); + if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) && getChildCount() > 0) { + fling(-initialXVelocity, -initialYVelocity); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + return true; + } + + /** + * Finds the next focusable component that fits in this View's bounds + * (excluding fading edges) pretending that this View's top is located at + * the parameter top. + * + * @param topFocus look for a candidate is the one at the top of the bounds + * if topFocus is true, or at the bottom of the bounds if topFocus is + * false + * @param top the top offset of the bounds in which a focusable must be + * found (the fading edge is assumed to start at this position) + * @param preferredFocusable the View that has highest priority and will be + * returned if it is within my bounds (null is valid) + * @return the next focusable component in the bounds or null if none can be + * found + */ + private View findFocusableViewInMyBounds(final boolean topFocus, final int top, final boolean leftFocus, final int left, View preferredFocusable) { + /* + * The fading edge's transparent side should be considered for focus + * since it's mostly visible, so we divide the actual fading edge length + * by 2. + */ + final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2; + final int topWithoutFadingEdge = top + verticalFadingEdgeLength; + final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength; + final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2; + final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength; + final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength; + + if ((preferredFocusable != null) + && (preferredFocusable.getTop() < bottomWithoutFadingEdge) + && (preferredFocusable.getBottom() > topWithoutFadingEdge) + && (preferredFocusable.getLeft() < rightWithoutFadingEdge) + && (preferredFocusable.getRight() > leftWithoutFadingEdge)) { + return preferredFocusable; + } + return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge, leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge); + } + + /** + * Finds the next focusable component that fits in the specified bounds. + *

+ * + * @param topFocus look for a candidate is the one at the top of the bounds + * if topFocus is true, or at the bottom of the bounds if topFocus is + * false + * @param top the top offset of the bounds in which a focusable must be + * found + * @param bottom the bottom offset of the bounds in which a focusable must + * be found + * @return the next focusable component in the bounds or null if none can + * be found + */ + private View findFocusableViewInBounds(boolean topFocus, int top, int bottom, boolean leftFocus, int left, int right) { + List focusables = getFocusables(View.FOCUS_FORWARD); + View focusCandidate = null; + + /* + * A fully contained focusable is one where its top is below the bound's + * top, and its bottom is above the bound's bottom. A partially + * contained focusable is one where some part of it is within the + * bounds, but it also has some part that is not within bounds. A fully contained + * focusable is preferred to a partially contained focusable. + */ + boolean foundFullyContainedFocusable = false; + + int count = focusables.size(); + for (int i = 0; i < count; i++) { + View view = focusables.get(i); + int viewTop = view.getTop(); + int viewBottom = view.getBottom(); + int viewLeft = view.getLeft(); + int viewRight = view.getRight(); + + if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right) { + /* + * the focusable is in the target area, it is a candidate for + * focusing + */ + final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) && (left < viewLeft) && (viewRight < right); + if (focusCandidate == null) { + /* No candidate, take this one */ + focusCandidate = view; + foundFullyContainedFocusable = viewIsFullyContained; + } else { + final boolean viewIsCloserToVerticalBoundary = + (topFocus && viewTop < focusCandidate.getTop()) || + (!topFocus && viewBottom > focusCandidate.getBottom()); + final boolean viewIsCloserToHorizontalBoundary = + (leftFocus && viewLeft < focusCandidate.getLeft()) || + (!leftFocus && viewRight > focusCandidate.getRight()); + if (foundFullyContainedFocusable) { + if (viewIsFullyContained && viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) { + /* + * We're dealing with only fully contained views, so + * it has to be closer to the boundary to beat our + * candidate + */ + focusCandidate = view; + } + } else { + if (viewIsFullyContained) { + /* Any fully contained view beats a partially contained view */ + focusCandidate = view; + foundFullyContainedFocusable = true; + } else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) { + /* + * Partially contained view beats another partially + * contained view if it's closer + */ + focusCandidate = view; + } + } + } + } + } + return focusCandidate; + } + + /** + *

Handles scrolling in response to a "home/end" shortcut press. This + * method will scroll the view to the top or bottom and give the focus + * to the topmost/bottommost component in the new visible area. If no + * component is a good candidate for focus, this scrollview reclaims the + * focus.

+ * + * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} + * to go the top of the view or + * {@link android.view.View#FOCUS_DOWN} to go the bottom + * @return true if the key event is consumed by this method, false otherwise + */ + public boolean fullScroll(int direction, boolean horizontal) { + if (!horizontal) { + boolean down = direction == View.FOCUS_DOWN; + int height = getHeight(); + mTempRect.top = 0; + mTempRect.bottom = height; + if (down) { + int count = getChildCount(); + if (count > 0) { + View view = getChildAt(count - 1); + mTempRect.bottom = view.getBottom(); + mTempRect.top = mTempRect.bottom - height; + } + } + return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0); + } else { + boolean right = direction == View.FOCUS_DOWN; + int width = getWidth(); + mTempRect.left = 0; + mTempRect.right = width; + if (right) { + int count = getChildCount(); + if (count > 0) { + View view = getChildAt(count - 1); + mTempRect.right = view.getBottom(); + mTempRect.left = mTempRect.right - width; + } + } + return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom); + } + } + + /** + *

Scrolls the view to make the area defined by top and + * bottom visible. This method attempts to give the focus + * to a component visible in this area. If no component can be focused in + * the new visible area, the focus is reclaimed by this scrollview.

+ * + * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} + * to go upward + * {@link android.view.View#FOCUS_DOWN} to downward + * @param top the top offset of the new area to be made visible + * @param bottom the bottom offset of the new area to be made visible + * @return true if the key event is consumed by this method, false otherwise + */ + private boolean scrollAndFocus(int directionY, int top, int bottom, int directionX, int left, int right) { + boolean handled = true; + int height = getHeight(); + int containerTop = getScrollY(); + int containerBottom = containerTop + height; + boolean up = directionY == View.FOCUS_UP; + int width = getWidth(); + int containerLeft = getScrollX(); + int containerRight = containerLeft + width; + boolean leftwards = directionX == View.FOCUS_UP; + View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right); + if (newFocused == null) { + newFocused = this; + } + if ((top >= containerTop && bottom <= containerBottom) || (left >= containerLeft && right <= containerRight)) { + handled = false; + } else { + int deltaY = up ? (top - containerTop) : (bottom - containerBottom); + int deltaX = leftwards ? (left - containerLeft) : (right - containerRight); + doScroll(deltaX, deltaY); + } + if (newFocused != findFocus() && newFocused.requestFocus(directionY)) { + mTwoDScrollViewMovedFocus = true; + mTwoDScrollViewMovedFocus = false; + } + return handled; + } + + /** + * Handle scrolling in response to an up or down arrow click. + * + * @param direction The direction corresponding to the arrow key that was + * pressed + * @return True if we consumed the event, false otherwise + */ + public boolean arrowScroll(int direction, boolean horizontal) { + View currentFocused = findFocus(); + if (currentFocused == this) currentFocused = null; + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); + final int maxJump = horizontal ? getMaxScrollAmountHorizontal() : getMaxScrollAmountVertical(); + + if (!horizontal) { + if (nextFocused != null) { + nextFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(nextFocused, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(0, scrollDelta); + nextFocused.requestFocus(direction); + } else { + // no new focus + int scrollDelta = maxJump; + if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) { + scrollDelta = getScrollY(); + } else if (direction == View.FOCUS_DOWN) { + if (getChildCount() > 0) { + int daBottom = getChildAt(0).getBottom(); + int screenBottom = getScrollY() + getHeight(); + if (daBottom - screenBottom < maxJump) { + scrollDelta = daBottom - screenBottom; + } + } + } + if (scrollDelta == 0) { + return false; + } + doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta); + } + } else { + if (nextFocused != null) { + nextFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(nextFocused, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(scrollDelta, 0); + nextFocused.requestFocus(direction); + } else { + // no new focus + int scrollDelta = maxJump; + if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) { + scrollDelta = getScrollY(); + } else if (direction == View.FOCUS_DOWN) { + if (getChildCount() > 0) { + int daBottom = getChildAt(0).getBottom(); + int screenBottom = getScrollY() + getHeight(); + if (daBottom - screenBottom < maxJump) { + scrollDelta = daBottom - screenBottom; + } + } + } + if (scrollDelta == 0) { + return false; + } + doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0); + } + } + return true; + } + + /** + * Smooth scroll by a Y delta + * + * @param delta the number of pixels to scroll by on the Y axis + */ + private void doScroll(int deltaX, int deltaY) { + if (deltaX != 0 || deltaY != 0) { + smoothScrollBy(deltaX, deltaY); + } + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param dx the number of pixels to scroll by on the X axis + * @param dy the number of pixels to scroll by on the Y axis + */ + public final void smoothScrollBy(int dx, int dy) { + long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; + if (duration > ANIMATED_SCROLL_GAP) { + mScroller.startScroll(getScrollX(), getScrollY(), dx, dy); + awakenScrollBars(mScroller.getDuration()); + invalidate(); + } else { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + scrollBy(dx, dy); + } + mLastScroll = AnimationUtils.currentAnimationTimeMillis(); + } + + /** + * Like {@link #scrollTo}, but scroll smoothly instead of immediately. + * + * @param x the position where to scroll on the X axis + * @param y the position where to scroll on the Y axis + */ + public final void smoothScrollTo(int x, int y) { + smoothScrollBy(x - getScrollX(), y - getScrollY()); + } + + /** + *

The scroll range of a scroll view is the overall height of all of its + * children.

+ */ + @Override + protected int computeVerticalScrollRange() { + int count = getChildCount(); + return count == 0 ? getHeight() : (getChildAt(0)).getBottom(); + } + + @Override + protected int computeHorizontalScrollRange() { + int count = getChildCount(); + return count == 0 ? getWidth() : (getChildAt(0)).getRight(); + } + + @Override + protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { + ViewGroup.LayoutParams lp = child.getLayoutParams(); + int childWidthMeasureSpec; + int childHeightMeasureSpec; + + childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width); + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED); + final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + // This is called at drawing time by ViewGroup. We don't want to + // re-show the scrollbars at this point, which scrollTo will do, + // so we replicate most of scrollTo here. + // + // It's a little odd to call onScrollChanged from inside the drawing. + // + // It is, except when you remember that computeScroll() is used to + // animate scrolling. So unless we want to defer the onScrollChanged() + // until the end of the animated scrolling, we don't really have a + // choice here. + // + // I agree. The alternative, which I think would be worse, is to post + // something and tell the subclasses later. This is bad because there + // will be a window where mScrollX/Y is different from what the app + // thinks it is. + // + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollTo(clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()), + clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight())); + } else { + scrollTo(x, y); + } + if (oldX != getScrollX() || oldY != getScrollY()) { + onScrollChanged(getScrollX(), getScrollY(), oldX, oldY); + } + + // Keep on drawing until the animation has finished. + postInvalidate(); + } + } + + /** + * Scrolls the view to the given child. + * + * @param child the View to scroll to + */ + private void scrollToChild(View child) { + child.getDrawingRect(mTempRect); + /* Offset from child's local coordinates to TwoDScrollView coordinates */ + offsetDescendantRectToMyCoords(child, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + if (scrollDelta != 0) { + scrollBy(0, scrollDelta); + } + } + + /** + * If rect is off screen, scroll just enough to get it (or at least the + * first screen size chunk of it) on screen. + * + * @param rect The rectangle. + * @param immediate True to scroll immediately without animation + * @return true if scrolling was performed + */ + private boolean scrollToChildRect(Rect rect, boolean immediate) { + final int delta = computeScrollDeltaToGetChildRectOnScreen(rect); + final boolean scroll = delta != 0; + if (scroll) { + if (immediate) { + scrollBy(0, delta); + } else { + smoothScrollBy(0, delta); + } + } + return scroll; + } + + /** + * Compute the amount to scroll in the Y direction in order to get + * a rectangle completely on the screen (or, if taller than the screen, + * at least the first screen size chunk of it). + * + * @param rect The rect. + * @return The scroll delta. + */ + protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + if (getChildCount() == 0) return 0; + int height = getHeight(); + int screenTop = getScrollY(); + int screenBottom = screenTop + height; + int fadingEdge = getVerticalFadingEdgeLength(); + // leave room for top fading edge as long as rect isn't at very top + if (rect.top > 0) { + screenTop += fadingEdge; + } + + // leave room for bottom fading edge as long as rect isn't at very bottom + if (rect.bottom < getChildAt(0).getHeight()) { + screenBottom -= fadingEdge; + } + int scrollYDelta = 0; + if (rect.bottom > screenBottom && rect.top > screenTop) { + // need to move down to get it in view: move down just enough so + // that the entire rectangle is in view (or at least the first + // screen size chunk). + if (rect.height() > height) { + // just enough to get screen size chunk on + scrollYDelta += (rect.top - screenTop); + } else { + // get entire rect at bottom of screen + scrollYDelta += (rect.bottom - screenBottom); + } + + // make sure we aren't scrolling beyond the end of our content + int bottom = getChildAt(0).getBottom(); + int distanceToBottom = bottom - screenBottom; + scrollYDelta = Math.min(scrollYDelta, distanceToBottom); + + } else if (rect.top < screenTop && rect.bottom < screenBottom) { + // need to move up to get it in view: move up just enough so that + // entire rectangle is in view (or at least the first screen + // size chunk of it). + + if (rect.height() > height) { + // screen size chunk + scrollYDelta -= (screenBottom - rect.bottom); + } else { + // entire rect at top + scrollYDelta -= (screenTop - rect.top); + } + + // make sure we aren't scrolling any further than the top our content + scrollYDelta = Math.max(scrollYDelta, -getScrollY()); + } + return scrollYDelta; + } + + @Override + public void requestChildFocus(View child, View focused) { + if (!mTwoDScrollViewMovedFocus) { + if (!mIsLayoutDirty) { + scrollToChild(focused); + } else { + // The child may not be laid out yet, we can't compute the scroll yet + mChildToScrollTo = focused; + } + } + super.requestChildFocus(child, focused); + } + + /** + * When looking for focus in children of a scroll view, need to be a little + * more careful not to give focus to something that is scrolled off screen. + *

+ * This is more expensive than the default {@link android.view.ViewGroup} + * implementation, otherwise this behavior might have been made the default. + */ + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // convert from forward / backward notation to up / down / left / right + // (ugh). + if (direction == View.FOCUS_FORWARD) { + direction = View.FOCUS_DOWN; + } else if (direction == View.FOCUS_BACKWARD) { + direction = View.FOCUS_UP; + } + + final View nextFocus = previouslyFocusedRect == null ? + FocusFinder.getInstance().findNextFocus(this, null, direction) : + FocusFinder.getInstance().findNextFocusFromRect(this, + previouslyFocusedRect, direction); + + if (nextFocus == null) { + return false; + } + + return nextFocus.requestFocus(direction, previouslyFocusedRect); + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + // offset into coordinate space of this scroll view + rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY()); + return scrollToChildRect(rectangle, immediate); + } + + @Override + public void requestLayout() { + mIsLayoutDirty = true; + super.requestLayout(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mIsLayoutDirty = false; + // Give a child focus if it needs it + if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) { + scrollToChild(mChildToScrollTo); + } + mChildToScrollTo = null; + + // Calling this with the present values causes it to re-clam them + scrollTo(getScrollX(), getScrollY()); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + View currentFocused = findFocus(); + if (null == currentFocused || this == currentFocused) + return; + + // If the currently-focused view was visible on the screen when the + // screen was at the old height, then scroll the screen to make that + // view visible with the new screen height. + currentFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(currentFocused, mTempRect); + int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(scrollDeltaX, scrollDeltaY); + } + + /** + * Return true if child is an descendant of parent, (or equal to the parent). + */ + private boolean isViewDescendantOf(View child, View parent) { + if (child == parent) { + return true; + } + + final ViewParent theParent = child.getParent(); + return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); + } + + /** + * Fling the scroll view + * + * @param velocityY The initial velocity in the Y direction. Positive + * numbers mean that the finger/curor is moving down the screen, + * which means we want to scroll towards the top. + */ + public void fling(int velocityX, int velocityY) { + if (getChildCount() > 0) { + int height = getHeight() - getPaddingBottom() - getPaddingTop(); + int bottom = getChildAt(0).getHeight(); + int width = getWidth() - getPaddingRight() - getPaddingLeft(); + int right = getChildAt(0).getWidth(); + + mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, bottom - height); + + final boolean movingDown = velocityY > 0; + final boolean movingRight = velocityX > 0; + + View newFocused = findFocusableViewInMyBounds(movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus()); + if (newFocused == null) { + newFocused = this; + } + + if (newFocused != findFocus() && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) { + mTwoDScrollViewMovedFocus = true; + mTwoDScrollViewMovedFocus = false; + } + + awakenScrollBars(mScroller.getDuration()); + invalidate(); + } + } + + /** + * {@inheritDoc} + *

+ *

This version also clamps the scrolling to the bounds of our child. + */ + public void scrollTo(int x, int y) { + // we rely on the fact the View.scrollBy calls scrollTo. + if (getChildCount() > 0) { + View child = getChildAt(0); + x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()); + y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()); + if (x != getScrollX() || y != getScrollY()) { + super.scrollTo(x, y); + } + } + } + + private int clamp(int n, int my, int child) { + if (my >= child || n < 0) { + /* my >= child is this case: + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * + * n < 0 is this case: + * |------ me ------| + * |-------- child --------| + * |-- mScrollX --| + */ + return 0; + } + if ((my + n) > child) { + /* this case: + * |------ me ------| + * |------ child ------| + * |-- mScrollX --| + */ + return child - my; + } + return n; + } + + public void setScrollViewListener(ScrollView2DListener scrollViewListener) { + this.scrollView2DListener = scrollViewListener; + } + + @Override + protected void onScrollChanged(int x, int y, int oldx, int oldy) { + super.onScrollChanged(x, y, oldx, oldy); + if (scrollView2DListener != null) { + scrollView2DListener.onScrollChanged(this, x, y, oldx, oldy); + } + } + + // interface to receive notifications when the view is scrolled + public interface ScrollView2DListener { + abstract void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java new file mode 100644 index 0000000..81cb993 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java @@ -0,0 +1,1366 @@ +/* + Android Session Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.app.UiModeManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.Toast; +import android.widget.ZoomControls; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.ClipboardManagerProxy; +import com.freerdp.freerdpcore.utils.KeyboardMapper; +import com.freerdp.freerdpcore.utils.Mouse; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class SessionActivity extends AppCompatActivity implements + LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener, + ScrollView2D.ScrollView2DListener, + KeyboardMapper.KeyProcessingListener, SessionView.SessionViewListener, + TouchPointerView.TouchPointerListener, + ClipboardManagerProxy.OnClipboardChangedListener { + public static final String PARAM_CONNECTION_REFERENCE = "conRef"; + public static final String PARAM_INSTANCE = "instance"; + private static final float ZOOMING_STEP = 0.5f; + private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000; + // timeout between subsequent scrolling requests when the touch-pointer is + // at the edge of the session view + private static final int SCROLLING_TIMEOUT = 50; + private static final int SCROLLING_DISTANCE = 20; + private static final String TAG = "FreeRDP.SessionActivity"; + // variables for delayed move event sending + private static final int MAX_DISCARDED_MOVE_EVENTS = 3; + private static final int SEND_MOVE_EVENT_TIMEOUT = 150; + private Bitmap bitmap; + private SessionState session; + private SessionView sessionView; + private TouchPointerView touchPointerView; + private ProgressDialog progressDialog; + private KeyboardView keyboardView; + private KeyboardView modifiersKeyboardView; + private ZoomControls zoomControls; + private KeyboardMapper keyboardMapper; + + private Keyboard specialkeysKeyboard; + private Keyboard numpadKeyboard; + private Keyboard cursorKeyboard; + private Keyboard modifiersKeyboard; + + private AlertDialog dlgVerifyCertificate; + private AlertDialog dlgUserCredentials; + private View userCredView; + + private UIHandler uiHandler; + + private int screen_width; + private int screen_height; + + private boolean connectCancelledByUser = false; + private boolean sessionRunning = false; + private boolean toggleMouseButtons = false; + + private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver; + private ScrollView2D scrollView; + // keyboard visibility flags + private boolean sysKeyboardVisible = false; + private boolean extKeyboardVisible = false; + private int discardedMoveEvents = 0; + private ClipboardManagerProxy mClipboardManager; + private boolean callbackDialogResult; + View mDecor; + + private void createDialogs() { + // build verify certificate dialog + dlgVerifyCertificate = new AlertDialog.Builder(this) + .setTitle(R.string.dlg_title_verify_certificate) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + callbackDialogResult = true; + synchronized (dialog) { + dialog.notify(); + } + } + }) + .setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + callbackDialogResult = false; + connectCancelledByUser = true; + synchronized (dialog) { + dialog.notify(); + } + } + }).setCancelable(false).create(); + + // build the dialog + userCredView = getLayoutInflater().inflate(R.layout.credentials, null, + true); + dlgUserCredentials = new AlertDialog.Builder(this) + .setView(userCredView) + .setTitle(R.string.dlg_title_credentials) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + callbackDialogResult = true; + synchronized (dialog) { + dialog.notify(); + } + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + callbackDialogResult = false; + connectCancelledByUser = true; + synchronized (dialog) { + dialog.notify(); + } + } + }).setCancelable(false).create(); + } + + private boolean hasHardwareMenuButton() { + if (Build.VERSION.SDK_INT <= 10) + return true; + + if (Build.VERSION.SDK_INT >= 14) { + boolean rc = false; + final ViewConfiguration cfg = ViewConfiguration.get(this); + + return cfg.hasPermanentMenuKey(); + } + + return false; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // show status bar or make fullscreen? + if (ApplicationSettingsActivity.getHideStatusBar(this)) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + this.setContentView(R.layout.session); + if (hasHardwareMenuButton() || ApplicationSettingsActivity.getHideActionBar(this)) { + this.getSupportActionBar().hide(); + } else + this.getSupportActionBar().show(); + + Log.v(TAG, "Session.onCreate"); + + // ATTENTION: We use the onGlobalLayout notification to start our + // session. + // This is because only then we can know the exact size of our session + // when using fit screen + // accounting for any status bars etc. that Android might throws on us. + // A bit weird looking + // but this is the only way ... + final View activityRootView = findViewById(R.id.session_root_view); + activityRootView.getViewTreeObserver().addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + screen_width = activityRootView.getWidth(); + screen_height = activityRootView.getHeight(); + + // start session + if (!sessionRunning && getIntent() != null) { + processIntent(getIntent()); + sessionRunning = true; + } + } + }); + + sessionView = (SessionView) findViewById(R.id.sessionView); + sessionView.setScaleGestureDetector(new ScaleGestureDetector(this, + new PinchZoomListener())); + sessionView.setSessionViewListener(this); + sessionView.requestFocus(); + + touchPointerView = (TouchPointerView) findViewById(R.id.touchPointerView); + touchPointerView.setTouchPointerListener(this); + + keyboardMapper = new KeyboardMapper(); + keyboardMapper.init(this); + keyboardMapper.reset(this); + + modifiersKeyboard = new Keyboard(getApplicationContext(), + R.xml.modifiers_keyboard); + specialkeysKeyboard = new Keyboard(getApplicationContext(), + R.xml.specialkeys_keyboard); + numpadKeyboard = new Keyboard(getApplicationContext(), + R.xml.numpad_keyboard); + cursorKeyboard = new Keyboard(getApplicationContext(), + R.xml.cursor_keyboard); + + // hide keyboard below the sessionView + keyboardView = (KeyboardView) findViewById(R.id.extended_keyboard); + keyboardView.setKeyboard(specialkeysKeyboard); + keyboardView.setOnKeyboardActionListener(this); + + modifiersKeyboardView = (KeyboardView) findViewById(R.id.extended_keyboard_header); + modifiersKeyboardView.setKeyboard(modifiersKeyboard); + modifiersKeyboardView.setOnKeyboardActionListener(this); + + scrollView = (ScrollView2D) findViewById(R.id.sessionScrollView); + scrollView.setScrollViewListener(this); + uiHandler = new UIHandler(); + libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver(); + + zoomControls = (ZoomControls) findViewById(R.id.zoomControls); + zoomControls.hide(); + zoomControls.setOnZoomInClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resetZoomControlsAutoHideTimeout(); + zoomControls.setIsZoomInEnabled(sessionView + .zoomIn(ZOOMING_STEP)); + zoomControls.setIsZoomOutEnabled(true); + } + }); + zoomControls.setOnZoomOutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resetZoomControlsAutoHideTimeout(); + zoomControls.setIsZoomOutEnabled(sessionView + .zoomOut(ZOOMING_STEP)); + zoomControls.setIsZoomInEnabled(true); + } + }); + + toggleMouseButtons = false; + + createDialogs(); + + // register freerdp events broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(GlobalApp.ACTION_EVENT_FREERDP); + registerReceiver(libFreeRDPBroadcastReceiver, filter); + + mClipboardManager = ClipboardManagerProxy.getClipboardManager(this); + mClipboardManager.addClipboardChangedListener(this); + + mDecor = getWindow().getDecorView(); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + @Override + protected void onStart() { + super.onStart(); + Log.v(TAG, "Session.onStart"); + } + + @Override + protected void onRestart() { + super.onRestart(); + Log.v(TAG, "Session.onRestart"); + } + + @Override + protected void onResume() { + super.onResume(); + Log.v(TAG, "Session.onResume"); + } + + @Override + protected void onPause() { + super.onPause(); + Log.v(TAG, "Session.onPause"); + + // hide any visible keyboards + showKeyboard(false, false); + } + + @Override + protected void onStop() { + super.onStop(); + Log.v(TAG, "Session.onStop"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.v(TAG, "Session.onDestroy"); + + // Cancel running disconnect timers. + GlobalApp.cancelDisconnectTimer(); + + // Disconnect all remaining sessions. + Collection sessions = GlobalApp.getSessions(); + for (SessionState session : sessions) + LibFreeRDP.disconnect(session.getInstance()); + + // unregister freerdp events broadcast receiver + unregisterReceiver(libFreeRDPBroadcastReceiver); + + // remove clipboard listener + mClipboardManager.removeClipboardboardChangedListener(this); + + // free session + GlobalApp.freeSession(session.getInstance()); + + session = null; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // reload keyboard resources (changed from landscape) + modifiersKeyboard = new Keyboard(getApplicationContext(), + R.xml.modifiers_keyboard); + specialkeysKeyboard = new Keyboard(getApplicationContext(), + R.xml.specialkeys_keyboard); + numpadKeyboard = new Keyboard(getApplicationContext(), + R.xml.numpad_keyboard); + cursorKeyboard = new Keyboard(getApplicationContext(), + R.xml.cursor_keyboard); + + // apply loaded keyboards + keyboardView.setKeyboard(specialkeysKeyboard); + modifiersKeyboardView.setKeyboard(modifiersKeyboard); + + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + private void processIntent(Intent intent) { + // get either session instance or create one from a bookmark/uri + Bundle bundle = intent.getExtras(); + Uri openUri = intent.getData(); + if (openUri != null) { + // Launched from URI, e.g: + // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=- + connect(openUri); + } else if (bundle.containsKey(PARAM_INSTANCE)) { + int inst = bundle.getInt(PARAM_INSTANCE); + session = GlobalApp.getSession(inst); + bitmap = session.getSurface().getBitmap(); + bindSession(); + } else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) { + BookmarkBase bookmark = null; + String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE); + if (ConnectionReference.isHostnameReference(refStr)) { + bookmark = new ManualBookmark(); + bookmark.get().setHostname( + ConnectionReference.getHostname(refStr)); + } else if (ConnectionReference.isBookmarkReference(refStr)) { + if (ConnectionReference.isManualBookmarkReference(refStr)) + bookmark = GlobalApp.getManualBookmarkGateway().findById( + ConnectionReference.getManualBookmarkId(refStr)); + else + assert false; + } + + if (bookmark != null) + connect(bookmark); + else + closeSessionActivity(RESULT_CANCELED); + } else { + // no session found - exit + closeSessionActivity(RESULT_CANCELED); + } + } + + private void connect(BookmarkBase bookmark) { + session = GlobalApp.createSession(bookmark, getApplicationContext()); + + BookmarkBase.ScreenSettings screenSettings = session.getBookmark() + .getActiveScreenSettings(); + Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString()); + if (screenSettings.isAutomatic()) { + if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) { + // large screen device i.e. tablet: simply use screen info + screenSettings.setHeight(screen_height); + screenSettings.setWidth(screen_width); + } else { + // small screen device i.e. phone: + // Automatic uses the largest side length of the screen and + // makes a 16:10 resolution setting out of it + int screenMax = (screen_width > screen_height) ? screen_width + : screen_height; + screenSettings.setHeight(screenMax); + screenSettings.setWidth((int) ((float) screenMax * 1.6f)); + } + } + if (screenSettings.isFitScreen()) { + screenSettings.setHeight(screen_height); + screenSettings.setWidth(screen_width); + } + + connectWithTitle(bookmark.getLabel()); + } + + private void connect(Uri openUri) { + session = GlobalApp.createSession(openUri, getApplicationContext()); + + connectWithTitle(openUri.getAuthority()); + } + + private void connectWithTitle(String title) { + session.setUIEventListener(this); + + progressDialog = new ProgressDialog(this); + progressDialog.setTitle(title); + progressDialog.setMessage(getResources().getText( + R.string.dlg_msg_connecting)); + progressDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "Cancel", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + connectCancelledByUser = true; + LibFreeRDP.cancelConnection(session.getInstance()); + } + }); + progressDialog.setCancelable(false); + progressDialog.show(); + + Thread thread = new Thread(new Runnable() { + public void run() { + session.connect(getApplicationContext()); + } + }); + thread.start(); + } + + // binds the current session to the activity by wiring it up with the + // sessionView and updating all internal objects accordingly + private void bindSession() { + Log.v(TAG, "bindSession called"); + session.setUIEventListener(this); + sessionView.onSurfaceChange(session); + scrollView.requestLayout(); + keyboardMapper.reset(this); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + } + + private void hideSoftInput() { + InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + if (mgr.isActive()) { + mgr.toggleSoftInput(InputMethodManager.HIDE_NOT_ALWAYS, 0); + } else { + mgr.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + // displays either the system or the extended keyboard or non of them + private void showKeyboard(final boolean showSystemKeyboard, + final boolean showExtendedKeyboard) { + // no matter what we are doing ... hide the zoom controls + // TODO: this is not working correctly as hiding the keyboard issues a + // onScrollChange notification showing the control again ... + uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS); + if (zoomControls.getVisibility() == View.VISIBLE) + zoomControls.hide(); + + InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + if (showSystemKeyboard) { + // hide extended keyboard + keyboardView.setVisibility(View.GONE); + // show system keyboard + mgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + + // show modifiers keyboard + modifiersKeyboardView.setVisibility(View.VISIBLE); + } else if (showExtendedKeyboard) { + // hide system keyboard + hideSoftInput(); + + // show extended keyboard + keyboardView.setKeyboard(specialkeysKeyboard); + keyboardView.setVisibility(View.VISIBLE); + modifiersKeyboardView.setVisibility(View.VISIBLE); + } else { + // hide both + hideSoftInput(); + keyboardView.setVisibility(View.GONE); + modifiersKeyboardView.setVisibility(View.GONE); + + // clear any active key modifiers) + keyboardMapper.clearlAllModifiers(); + } + + sysKeyboardVisible = showSystemKeyboard; + extKeyboardVisible = showExtendedKeyboard; + } + + private void closeSessionActivity(int resultCode) { + // Go back to home activity (and send intent data back to home) + setResult(resultCode, getIntent()); + finish(); + } + + // update the state of our modifier keys + private void updateModifierKeyStates() { + // check if any key is in the keycodes list + + List keys = modifiersKeyboard.getKeys(); + for (Iterator it = keys.iterator(); it.hasNext(); ) { + // if the key is a sticky key - just set it to off + Keyboard.Key curKey = it.next(); + if (curKey.sticky) { + switch (keyboardMapper.getModifierState(curKey.codes[0])) { + case KeyboardMapper.KEYSTATE_ON: + curKey.on = true; + curKey.pressed = false; + break; + + case KeyboardMapper.KEYSTATE_OFF: + curKey.on = false; + curKey.pressed = false; + break; + + case KeyboardMapper.KEYSTATE_LOCKED: + curKey.on = true; + curKey.pressed = true; + break; + } + } + } + + // refresh image + modifiersKeyboardView.invalidateAllKeys(); + } + + private void sendDelayedMoveEvent(int x, int y) { + if (uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT)) { + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + discardedMoveEvents++; + } else + discardedMoveEvents = 0; + + if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS) + LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, + Mouse.getMoveEvent()); + else + uiHandler.sendMessageDelayed( + Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y), + SEND_MOVE_EVENT_TIMEOUT); + } + + private void cancelDelayedMoveEvent() { + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.session_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // refer to http://tools.android.com/tips/non-constant-fields why we + // can't use switch/case here .. + int itemId = item.getItemId(); + + if (itemId == R.id.session_touch_pointer) { + // toggle touch pointer + if (touchPointerView.getVisibility() == View.VISIBLE) { + touchPointerView.setVisibility(View.INVISIBLE); + sessionView.setTouchPointerPadding(0, 0); + } else { + touchPointerView.setVisibility(View.VISIBLE); + sessionView.setTouchPointerPadding( + touchPointerView.getPointerWidth(), + touchPointerView.getPointerHeight()); + } + } else if (itemId == R.id.session_sys_keyboard) { + showKeyboard(!sysKeyboardVisible, false); + } else if (itemId == R.id.session_ext_keyboard) { + showKeyboard(false, !extKeyboardVisible); + } else if (itemId == R.id.session_disconnect) { + showKeyboard(false, false); + LibFreeRDP.disconnect(session.getInstance()); + } + + return true; + } + + @Override + public void onBackPressed() { + // hide keyboards (if any visible) or send alt+f4 to the session + if (sysKeyboardVisible || extKeyboardVisible) + showKeyboard(false, false); + else + keyboardMapper.sendAltF4(); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + LibFreeRDP.disconnect(session.getInstance()); + return true; + } + return super.onKeyLongPress(keyCode, event); + } + + // android keyboard input handling + // We always use the unicode value to process input from the android + // keyboard except if key modifiers + // (like Win, Alt, Ctrl) are activated. In this case we will send the + // virtual key code to allow key + // combinations (like Win + E to open the explorer). + @Override + public boolean onKeyDown(int keycode, KeyEvent event) { + return keyboardMapper.processAndroidKeyEvent(event); + } + + @Override + public boolean onKeyUp(int keycode, KeyEvent event) { + return keyboardMapper.processAndroidKeyEvent(event); + } + + // onKeyMultiple is called for input of some special characters like umlauts + // and some symbol characters + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return keyboardMapper.processAndroidKeyEvent(event); + } + + // **************************************************************************** + // KeyboardView.KeyboardActionEventListener + @Override + public void onKey(int primaryCode, int[] keyCodes) { + keyboardMapper.processCustomKeyEvent(primaryCode); + } + + @Override + public void onText(CharSequence text) { + } + + @Override + public void swipeRight() { + } + + @Override + public void swipeLeft() { + } + + @Override + public void swipeDown() { + } + + @Override + public void swipeUp() { + } + + @Override + public void onPress(int primaryCode) { + } + + @Override + public void onRelease(int primaryCode) { + } + + // **************************************************************************** + // KeyboardMapper.KeyProcessingListener implementation + @Override + public void processVirtualKey(int virtualKeyCode, boolean down) { + LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down); + } + + @Override + public void processUnicodeKey(int unicodeKey) { + LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey); + } + + @Override + public void switchKeyboard(int keyboardType) { + switch (keyboardType) { + case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS: + keyboardView.setKeyboard(specialkeysKeyboard); + break; + + case KeyboardMapper.KEYBOARD_TYPE_NUMPAD: + keyboardView.setKeyboard(numpadKeyboard); + break; + + case KeyboardMapper.KEYBOARD_TYPE_CURSOR: + keyboardView.setKeyboard(cursorKeyboard); + break; + + default: + break; + } + } + + @Override + public void modifiersChanged() { + updateModifierKeyStates(); + } + + // **************************************************************************** + // LibFreeRDP UI event listener implementation + @Override + public void OnSettingsChanged(int width, int height, int bpp) { + + if (bpp > 16) + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap(width, height, Config.RGB_565); + + session.setSurface(new BitmapDrawable(bitmap)); + + if (session.getBookmark() == null) { + // Return immediately if we launch from URI + return; + } + + // check this settings and initial settings - if they are not equal the + // server doesn't support our settings + // FIXME: the additional check (settings.getWidth() != width + 1) is for + // the RDVH bug fix to avoid accidental notifications + // (refer to android_freerdp.c for more info on this problem) + BookmarkBase.ScreenSettings settings = session.getBookmark() + .getActiveScreenSettings(); + if ((settings.getWidth() != width && settings.getWidth() != width + 1) + || settings.getHeight() != height + || settings.getColors() != bpp) + uiHandler + .sendMessage(Message.obtain( + null, + UIHandler.DISPLAY_TOAST, + getResources().getText( + R.string.info_capabilities_changed))); + } + + @Override + public void OnGraphicsUpdate(int x, int y, int width, int height) { + LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, + height); + + sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height)); + + /* + * since sessionView can only be modified from the UI thread any + * modifications to it need to be scheduled + */ + + uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW); + } + + @Override + public void OnGraphicsResize(int width, int height, int bpp) { + // replace bitmap + if (bpp > 16) + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap(width, height, Config.RGB_565); + session.setSurface(new BitmapDrawable(bitmap)); + + /* + * since sessionView can only be modified from the UI thread any + * modifications to it need to be scheduled + */ + uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED); + } + + @Override + public boolean OnAuthenticate(StringBuilder username, StringBuilder domain, + StringBuilder password) { + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set text fields + ((EditText) userCredView.findViewById(R.id.editTextUsername)) + .setText(username); + ((EditText) userCredView.findViewById(R.id.editTextDomain)) + .setText(domain); + ((EditText) userCredView.findViewById(R.id.editTextPassword)) + .setText(password); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, + dlgUserCredentials)); + + // wait for result + try { + synchronized (dlgUserCredentials) { + dlgUserCredentials.wait(); + } + } catch (InterruptedException e) { + } + + // clear buffers + username.setLength(0); + domain.setLength(0); + password.setLength(0); + + // read back user credentials + username.append(((EditText) userCredView + .findViewById(R.id.editTextUsername)).getText().toString()); + domain.append(((EditText) userCredView + .findViewById(R.id.editTextDomain)).getText().toString()); + password.append(((EditText) userCredView + .findViewById(R.id.editTextPassword)).getText().toString()); + + return callbackDialogResult; + } + + @Override + public boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain, StringBuilder password) { + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set text fields + ((EditText) userCredView.findViewById(R.id.editTextUsername)) + .setText(username); + ((EditText) userCredView.findViewById(R.id.editTextDomain)) + .setText(domain); + ((EditText) userCredView.findViewById(R.id.editTextPassword)) + .setText(password); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, + dlgUserCredentials)); + + // wait for result + try { + synchronized (dlgUserCredentials) { + dlgUserCredentials.wait(); + } + } catch (InterruptedException e) { + } + + // clear buffers + username.setLength(0); + domain.setLength(0); + password.setLength(0); + + // read back user credentials + username.append(((EditText) userCredView + .findViewById(R.id.editTextUsername)).getText().toString()); + domain.append(((EditText) userCredView + .findViewById(R.id.editTextDomain)).getText().toString()); + password.append(((EditText) userCredView + .findViewById(R.id.editTextPassword)).getText().toString()); + + return callbackDialogResult; + } + + @Override + public int OnVerifiyCertificate(String commonName, String subject, String issuer, String fingerprint, boolean mismatch) { + // see if global settings says accept all + if (ApplicationSettingsActivity.getAcceptAllCertificates(this)) + return 0; + + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set message + String msg = getResources().getString( + R.string.dlg_msg_verify_certificate); + msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer + + "\nFingerprint: " + fingerprint; + dlgVerifyCertificate.setMessage(msg); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, + dlgVerifyCertificate)); + + // wait for result + try { + synchronized (dlgVerifyCertificate) { + dlgVerifyCertificate.wait(); + } + } catch (InterruptedException e) { + } + + return callbackDialogResult ? 1 : 0; + } + + @Override + public int OnVerifyChangedCertificate(String commonName, String subject, String issuer, String fingerprint, String oldSubject, String oldIssuer, String oldFingerprint) { + // see if global settings says accept all + if (ApplicationSettingsActivity.getAcceptAllCertificates(this)) + return 0; + + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set message + String msg = getResources().getString( + R.string.dlg_msg_verify_certificate); + msg = msg + "\n\nSubject: " + subject + "\nIssuer: " + issuer + + "\nFingerprint: " + fingerprint; + dlgVerifyCertificate.setMessage(msg); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, + dlgVerifyCertificate)); + + // wait for result + try { + synchronized (dlgVerifyCertificate) { + dlgVerifyCertificate.wait(); + } + } catch (InterruptedException e) { + } + + return callbackDialogResult ? 1 : 0; + } + + @Override + public void OnRemoteClipboardChanged(String data) { + Log.v(TAG, "OnRemoteClipboardChanged: " + data); + mClipboardManager.setClipboardData(data); + } + + // **************************************************************************** + // ScrollView2DListener implementation + private void resetZoomControlsAutoHideTimeout() { + uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS); + uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS, + ZOOMCONTROLS_AUTOHIDE_TIMEOUT); + } + + @Override + public void onScrollChanged(ScrollView2D scrollView, int x, int y, + int oldx, int oldy) { + zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom()); + zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom()); + if (!ApplicationSettingsActivity.getHideZoomControls(this) + && zoomControls.getVisibility() != View.VISIBLE) + zoomControls.show(); + resetZoomControlsAutoHideTimeout(); + } + + // **************************************************************************** + // SessionView.SessionViewListener + @Override + public void onSessionViewBeginTouch() { + scrollView.setScrollEnabled(false); + } + + @Override + public void onSessionViewEndTouch() { + scrollView.setScrollEnabled(true); + } + + @Override + public void onSessionViewLeftTouch(int x, int y, boolean down) { + if (!down) + cancelDelayedMoveEvent(); + + LibFreeRDP.sendCursorEvent( + session.getInstance(), + x, + y, + toggleMouseButtons ? Mouse.getRightButtonEvent(this, down) : Mouse + .getLeftButtonEvent(this, down)); + + if (!down) + toggleMouseButtons = false; + } + + public void onSessionViewRightTouch(int x, int y, boolean down) { + if (!down) + toggleMouseButtons = !toggleMouseButtons; + } + + @Override + public void onSessionViewMove(int x, int y) { + sendDelayedMoveEvent(x, y); + } + + @Override + public void onSessionViewScroll(boolean down) { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, + Mouse.getScrollEvent(this, down)); + } + + // **************************************************************************** + // TouchPointerView.TouchPointerListener + @Override + public void onTouchPointerClose() { + touchPointerView.setVisibility(View.INVISIBLE); + sessionView.setTouchPointerPadding(0, 0); + } + + private Point mapScreenCoordToSessionCoord(int x, int y) { + int mappedX = (int) ((float) (x + scrollView.getScrollX()) / sessionView + .getZoom()); + int mappedY = (int) ((float) (y + scrollView.getScrollY()) / sessionView + .getZoom()); + if (mappedX > bitmap.getWidth()) + mappedX = bitmap.getWidth(); + if (mappedY > bitmap.getHeight()) + mappedY = bitmap.getHeight(); + return new Point(mappedX, mappedY); + } + + @Override + public void onTouchPointerLeftClick(int x, int y, boolean down) { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, + Mouse.getLeftButtonEvent(this, down)); + } + + @Override + public void onTouchPointerRightClick(int x, int y, boolean down) { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, + Mouse.getRightButtonEvent(this, down)); + } + + @Override + public void onTouchPointerMove(int x, int y) { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, + Mouse.getMoveEvent()); + + if (ApplicationSettingsActivity.getAutoScrollTouchPointer(this) + && !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED)) { + Log.v(TAG, "Starting auto-scroll"); + uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED, + SCROLLING_TIMEOUT); + } + } + + @Override + public void onTouchPointerScroll(boolean down) { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, + Mouse.getScrollEvent(this, down)); + } + + @Override + public void onTouchPointerToggleKeyboard() { + showKeyboard(!sysKeyboardVisible, false); + } + + @Override + public void onTouchPointerToggleExtKeyboard() { + showKeyboard(false, !extKeyboardVisible); + } + + @Override + public void onTouchPointerResetScrollZoom() { + sessionView.setZoom(1.0f); + scrollView.scrollTo(0, 0); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent e) { + super.onGenericMotionEvent(e); + switch (e.getAction()) { + case MotionEvent.ACTION_SCROLL: + final float vScroll = e.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (vScroll < 0) { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, false)); + } + if (vScroll > 0) { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, true)); + } + break; + } + return true; + } + + // **************************************************************************** + // ClipboardManagerProxy.OnClipboardChangedListener + @Override + public void onClipboardChanged(String data) { + Log.v(TAG, "onClipboardChanged: " + data); + LibFreeRDP.sendClipboardData(session.getInstance(), data); + } + + private class UIHandler extends Handler { + + public static final int REFRESH_SESSIONVIEW = 1; + public static final int DISPLAY_TOAST = 2; + public static final int HIDE_ZOOMCONTROLS = 3; + public static final int SEND_MOVE_EVENT = 4; + public static final int SHOW_DIALOG = 5; + public static final int GRAPHICS_CHANGED = 6; + public static final int SCROLLING_REQUESTED = 7; + + UIHandler() { + super(); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case GRAPHICS_CHANGED: { + sessionView.onSurfaceChange(session); + scrollView.requestLayout(); + break; + } + case REFRESH_SESSIONVIEW: { + sessionView.invalidateRegion(); + break; + } + case DISPLAY_TOAST: { + Toast errorToast = Toast.makeText(getApplicationContext(), + msg.obj.toString(), Toast.LENGTH_LONG); + errorToast.show(); + break; + } + case HIDE_ZOOMCONTROLS: { + zoomControls.hide(); + break; + } + case SEND_MOVE_EVENT: { + LibFreeRDP.sendCursorEvent(session.getInstance(), msg.arg1, + msg.arg2, Mouse.getMoveEvent()); + break; + } + case SHOW_DIALOG: { + // create and show the dialog + ((Dialog) msg.obj).show(); + break; + } + case SCROLLING_REQUESTED: { + int scrollX = 0; + int scrollY = 0; + float[] pointerPos = touchPointerView.getPointerPosition(); + + if (pointerPos[0] > (screen_width - touchPointerView + .getPointerWidth())) + scrollX = SCROLLING_DISTANCE; + else if (pointerPos[0] < 0) + scrollX = -SCROLLING_DISTANCE; + + if (pointerPos[1] > (screen_height - touchPointerView + .getPointerHeight())) + scrollY = SCROLLING_DISTANCE; + else if (pointerPos[1] < 0) + scrollY = -SCROLLING_DISTANCE; + + scrollView.scrollBy(scrollX, scrollY); + + // see if we reached the min/max scroll positions + if (scrollView.getScrollX() == 0 + || scrollView.getScrollX() == (sessionView.getWidth() - scrollView + .getWidth())) + scrollX = 0; + if (scrollView.getScrollY() == 0 + || scrollView.getScrollY() == (sessionView.getHeight() - scrollView + .getHeight())) + scrollY = 0; + + if (scrollX != 0 || scrollY != 0) + uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED, + SCROLLING_TIMEOUT); + else + Log.v(TAG, "Stopping auto-scroll"); + break; + } + } + } + } + + private class PinchZoomListener extends + ScaleGestureDetector.SimpleOnScaleGestureListener { + private float scaleFactor = 1.0f; + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + scrollView.setScrollEnabled(false); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + + // calc scale factor + scaleFactor *= detector.getScaleFactor(); + scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR, + Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR)); + sessionView.setZoom(scaleFactor); + + if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom()) { + // transform scroll origin to the new zoom space + float transOriginX = scrollView.getScrollX() + * detector.getScaleFactor(); + float transOriginY = scrollView.getScrollY() + * detector.getScaleFactor(); + + // transform center point to the zoomed space + float transCenterX = (scrollView.getScrollX() + detector + .getFocusX()) * detector.getScaleFactor(); + float transCenterY = (scrollView.getScrollY() + detector + .getFocusY()) * detector.getScaleFactor(); + + // scroll by the difference between the distance of the + // transformed center/origin point and their old distance + // (focusX/Y) + scrollView.scrollBy( + (int) ((transCenterX - transOriginX) - detector + .getFocusX()), + (int) ((transCenterY - transOriginY) - detector + .getFocusY())); + } + + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector de) { + scrollView.setScrollEnabled(true); + } + } + + private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + // still got a valid session? + if (session == null) + return; + + // is this event for the current session? + if (session.getInstance() != intent.getExtras().getLong( + GlobalApp.EVENT_PARAM, -1)) + return; + + switch (intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1)) { + case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS: + OnConnectionSuccess(context); + break; + + case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE: + OnConnectionFailure(context); + break; + case GlobalApp.FREERDP_EVENT_DISCONNECTED: + OnDisconnected(context); + break; + } + } + + private void OnConnectionSuccess(Context context) { + Log.v(TAG, "OnConnectionSuccess"); + + // bind session + bindSession(); + + if (progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + + if (session.getBookmark() == null) { + // Return immediately if we launch from URI + return; + } + + // add hostname to history if quick connect was used + Bundle bundle = getIntent().getExtras(); + if (bundle != null + && bundle.containsKey(PARAM_CONNECTION_REFERENCE)) { + if (ConnectionReference.isHostnameReference(bundle + .getString(PARAM_CONNECTION_REFERENCE))) { + assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL; + String item = session.getBookmark().get() + .getHostname(); + if (!GlobalApp.getQuickConnectHistoryGateway() + .historyItemExists(item)) + GlobalApp.getQuickConnectHistoryGateway() + .addHistoryItem(item); + } + } + } + + private void OnConnectionFailure(Context context) { + Log.v(TAG, "OnConnectionFailure"); + + // remove pending move events + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + + if (progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + + // post error message on UI thread + if (!connectCancelledByUser) + uiHandler.sendMessage(Message.obtain( + null, + UIHandler.DISPLAY_TOAST, + getResources().getText( + R.string.error_connection_failure))); + + closeSessionActivity(RESULT_CANCELED); + } + + private void OnDisconnected(Context context) { + Log.v(TAG, "OnDisconnected"); + + // remove pending move events + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + + if (progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + + session.setUIEventListener(null); + closeSessionActivity(RESULT_OK); + } + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java new file mode 100644 index 0000000..5ecbcb7 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java @@ -0,0 +1,350 @@ +/* + Android Session view + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.DoubleGestureDetector; +import com.freerdp.freerdpcore.utils.GestureDetector; +import com.freerdp.freerdpcore.utils.Mouse; + +import java.util.Stack; + + +public class SessionView extends View { + public static final float MAX_SCALE_FACTOR = 3.0f; + public static final float MIN_SCALE_FACTOR = 1.0f; + private static final String TAG = "SessionView"; + private static final float SCALE_FACTOR_DELTA = 0.0001f; + private static final float TOUCH_SCROLL_DELTA = 10.0f; + private int width; + private int height; + private BitmapDrawable surface; + private Stack invalidRegions; + private int touchPointerPaddingWidth = 0; + private int touchPointerPaddingHeight = 0; + private SessionViewListener sessionViewListener = null; + // helpers for scaling gesture handling + private float scaleFactor = 1.0f; + private Matrix scaleMatrix; + private Matrix invScaleMatrix; + private RectF invalidRegionF; + private GestureDetector gestureDetector; + private SessionState currentSession; + + //private static final String TAG = "FreeRDP.SessionView"; + private DoubleGestureDetector doubleGestureDetector; + public SessionView(Context context) { + super(context); + initSessionView(context); + } + + public SessionView(Context context, AttributeSet attrs) { + super(context, attrs); + initSessionView(context); + } + + public SessionView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initSessionView(context); + } + + private void initSessionView(Context context) { + invalidRegions = new Stack(); + gestureDetector = new GestureDetector(context, new SessionGestureListener(), null, true); + doubleGestureDetector = new DoubleGestureDetector(context, null, new SessionDoubleGestureListener()); + + scaleFactor = 1.0f; + scaleMatrix = new Matrix(); + invScaleMatrix = new Matrix(); + invalidRegionF = new RectF(); + + setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) { + doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector); + } + + public void setSessionViewListener(SessionViewListener sessionViewListener) { + this.sessionViewListener = sessionViewListener; + } + + public void addInvalidRegion(Rect invalidRegion) { + // correctly transform invalid region depending on current scaling + invalidRegionF.set(invalidRegion); + scaleMatrix.mapRect(invalidRegionF); + invalidRegionF.roundOut(invalidRegion); + + invalidRegions.add(invalidRegion); + } + + public void invalidateRegion() { + invalidate(invalidRegions.pop()); + } + + public void onSurfaceChange(SessionState session) { + surface = session.getSurface(); + Bitmap bitmap = surface.getBitmap(); + width = bitmap.getWidth(); + height = bitmap.getHeight(); + surface.setBounds(0, 0, width, height); + + setMinimumWidth(width); + setMinimumHeight(height); + + requestLayout(); + currentSession = session; + } + + public float getZoom() { + return scaleFactor; + } + + public void setZoom(float factor) { + // calc scale matrix and inverse scale matrix (to correctly transform the view and moues coordinates) + scaleFactor = factor; + scaleMatrix.setScale(scaleFactor, scaleFactor); + invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor); + + // update layout + requestLayout(); + } + + public boolean isAtMaxZoom() { + return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA)); + } + + public boolean isAtMinZoom() { + return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA)); + } + + public boolean zoomIn(float factor) { + boolean res = true; + scaleFactor += factor; + if (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA)) { + scaleFactor = MAX_SCALE_FACTOR; + res = false; + } + setZoom(scaleFactor); + return res; + } + + public boolean zoomOut(float factor) { + boolean res = true; + scaleFactor -= factor; + if (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA)) { + scaleFactor = MIN_SCALE_FACTOR; + res = false; + } + setZoom(scaleFactor); + return res; + } + + public void setTouchPointerPadding(int widht, int height) { + touchPointerPaddingWidth = widht; + touchPointerPaddingHeight = height; + requestLayout(); + } + + public int getTouchPointerPaddingWidth() { + return touchPointerPaddingWidth; + } + + public int getTouchPointerPaddingHeight() { + return touchPointerPaddingHeight; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Log.v(TAG, width + "x" + height); + this.setMeasuredDimension((int) (width * scaleFactor) + touchPointerPaddingWidth, (int) (height * scaleFactor) + touchPointerPaddingHeight); + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.save(); + canvas.concat(scaleMatrix); + surface.draw(canvas); + canvas.restore(); + } + + // dirty hack: we call back to our activity and call onBackPressed as this doesn't reach us when the soft keyboard is shown ... + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) + ((SessionActivity) this.getContext()).onBackPressed(); + return super.dispatchKeyEventPreIme(event); + } + + // perform mapping on the touch event's coordinates according to the current scaling + private MotionEvent mapTouchEvent(MotionEvent event) { + MotionEvent mappedEvent = MotionEvent.obtain(event); + float[] coordinates = {mappedEvent.getX(), mappedEvent.getY()}; + invScaleMatrix.mapPoints(coordinates); + mappedEvent.setLocation(coordinates[0], coordinates[1]); + return mappedEvent; + } + + // perform mapping on the double touch event's coordinates according to the current scaling + private MotionEvent mapDoubleTouchEvent(MotionEvent event) { + MotionEvent mappedEvent = MotionEvent.obtain(event); + float[] coordinates = {(mappedEvent.getX(0) + mappedEvent.getX(1)) / 2, (mappedEvent.getY(0) + mappedEvent.getY(1)) / 2}; + invScaleMatrix.mapPoints(coordinates); + mappedEvent.setLocation(coordinates[0], coordinates[1]); + return mappedEvent; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean res = gestureDetector.onTouchEvent(event); + res |= doubleGestureDetector.onTouchEvent(event); + return res; + } + + public interface SessionViewListener { + abstract void onSessionViewBeginTouch(); + + abstract void onSessionViewEndTouch(); + + abstract void onSessionViewLeftTouch(int x, int y, boolean down); + + abstract void onSessionViewRightTouch(int x, int y, boolean down); + + abstract void onSessionViewMove(int x, int y); + + abstract void onSessionViewScroll(boolean down); + } + + private class SessionGestureListener extends GestureDetector.SimpleOnGestureListener { + boolean longPressInProgress = false; + + public boolean onDown(MotionEvent e) { + return true; + } + + public boolean onUp(MotionEvent e) { + sessionViewListener.onSessionViewEndTouch(); + return true; + } + + public void onLongPress(MotionEvent e) { + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewBeginTouch(); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + longPressInProgress = true; + } + + public void onLongPressUp(MotionEvent e) { + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + longPressInProgress = false; + sessionViewListener.onSessionViewEndTouch(); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (longPressInProgress) { + MotionEvent mappedEvent = mapTouchEvent(e2); + sessionViewListener.onSessionViewMove((int) mappedEvent.getX(), (int) mappedEvent.getY()); + return true; + } + + return false; + } + + public boolean onDoubleTap(MotionEvent e) { + // send 2nd click for double click + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + return true; + } + + public boolean onSingleTapUp(MotionEvent e) { + // send single click + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewBeginTouch(); + switch (e.getButtonState()) { + case MotionEvent.BUTTON_PRIMARY: + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + break; + case MotionEvent.BUTTON_SECONDARY: + sessionViewListener.onSessionViewRightTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + sessionViewListener.onSessionViewRightTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + break; + } + sessionViewListener.onSessionViewEndTouch(); + return true; + } + } + + private class SessionDoubleGestureListener implements DoubleGestureDetector.OnDoubleGestureListener { + private MotionEvent prevEvent = null; + + public boolean onDoubleTouchDown(MotionEvent e) { + sessionViewListener.onSessionViewBeginTouch(); + prevEvent = MotionEvent.obtain(e); + return true; + } + + public boolean onDoubleTouchUp(MotionEvent e) { + if (prevEvent != null) { + prevEvent.recycle(); + prevEvent = null; + } + sessionViewListener.onSessionViewEndTouch(); + return true; + } + + public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2) { + // calc if user scrolled up or down (or if any scrolling happened at all) + float deltaY = e2.getY() - prevEvent.getY(); + if (deltaY > TOUCH_SCROLL_DELTA) { + sessionViewListener.onSessionViewScroll(true); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } else if (deltaY < -TOUCH_SCROLL_DELTA) { + sessionViewListener.onSessionViewScroll(false); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + return true; + } + + public boolean onDoubleTouchSingleTap(MotionEvent e) { + // send single click + MotionEvent mappedEvent = mapDoubleTouchEvent(e); + sessionViewListener.onSessionViewRightTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), true); + sessionViewListener.onSessionViewRightTouch((int) mappedEvent.getX(), (int) mappedEvent.getY(), false); + return true; + } + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java new file mode 100644 index 0000000..7101fb4 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java @@ -0,0 +1,147 @@ +/* + Android Shortcut activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.TextView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.services.SessionRequestHandlerActivity; +import com.freerdp.freerdpcore.utils.BookmarkArrayAdapter; + +import java.util.ArrayList; + +public class ShortcutsActivity extends ListActivity { + + public static final String TAG = "ShortcutsActivity"; + + + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) { + // set listeners for the list view + getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + String refStr = view.getTag().toString(); + String defLabel = ((TextView) (view.findViewById(R.id.bookmark_text1))).getText().toString(); + setupShortcut(refStr, defLabel); + } + }); + } else { + // just exit + finish(); + } + } + + @Override + public void onResume() { + super.onResume(); + // create bookmark cursor adapter + ArrayList bookmarks = GlobalApp.getManualBookmarkGateway().findAll(); + BookmarkArrayAdapter bookmarkAdapter = new BookmarkArrayAdapter(this, android.R.layout.simple_list_item_2, bookmarks); + getListView().setAdapter(bookmarkAdapter); + } + + public void onPause() { + super.onPause(); + getListView().setAdapter(null); + } + + /** + * This function creates a shortcut and returns it to the caller. There are actually two + * intents that you will send back. + *

+ * The first intent serves as a container for the shortcut and is returned to the launcher by + * setResult(). This intent must contain three fields: + *

+ *

    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ *

+ * If you use a simple drawable resource, note that you must wrapper it using + * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so + * that the launcher can access resources that are stored in your application's .apk file. If + * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras + * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. + *

+ * The shortcut intent can be any intent that you wish the launcher to send, when the user + * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} + * with an appropriate Uri for your content, but any Intent will work here as long as it + * triggers the desired action within your Activity. + */ + + private void setupShortcut(String strRef, String defaultLabel) { + final String paramStrRef = strRef; + final String paramDefaultLabel = defaultLabel; + final Context paramContext = this; + + // display edit dialog to the user so he can specify the shortcut name + final EditText input = new EditText(this); + input.setText(defaultLabel); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_create_shortcut) + .setMessage(R.string.dlg_msg_create_shortcut) + .setView(input) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String label = input.getText().toString(); + if (label.length() == 0) + label = paramDefaultLabel; + + Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); + shortcutIntent.setClassName(paramContext, SessionRequestHandlerActivity.class.getName()); + shortcutIntent.setData(Uri.parse(paramStrRef)); + + // Then, set up the container intent (the response to the caller) + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + Parcelable iconResource = Intent.ShortcutIconResource.fromContext(paramContext, R.drawable.icon_launcher_freerdp); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + + // Now, return the result to the launcher + setResult(RESULT_OK, intent); + finish(); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .create().show(); + + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java new file mode 100644 index 0000000..d58159d --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java @@ -0,0 +1,332 @@ +/* + Android Touch Pointer view + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ImageView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.utils.GestureDetector; + +public class TouchPointerView extends ImageView { + + private static final int POINTER_ACTION_CURSOR = 0; + private static final int POINTER_ACTION_CLOSE = 3; + + + // the touch pointer consists of 9 quadrants with the following functionality: + // + // ------------- + // | 0 | 1 | 2 | + // ------------- + // | 3 | 4 | 5 | + // ------------- + // | 6 | 7 | 8 | + // ------------- + // + // 0 ... contains the actual pointer (the tip must be centered in the quadrant) + // 1 ... is left empty + // 2, 3, 5, 6, 7, 8 ... function quadrants that issue a callback + // 4 ... pointer center used for left clicks and to drag the pointer + private static final int POINTER_ACTION_RCLICK = 2; + private static final int POINTER_ACTION_LCLICK = 4; + private static final int POINTER_ACTION_MOVE = 4; + private static final int POINTER_ACTION_SCROLL = 5; + private static final int POINTER_ACTION_RESET = 6; + private static final int POINTER_ACTION_KEYBOARD = 7; + private static final int POINTER_ACTION_EXTKEYBOARD = 8; + private static final float SCROLL_DELTA = 10.0f; + private static final int DEFAULT_TOUCH_POINTER_RESTORE_DELAY = 150; + private RectF pointerRect; + private RectF pointerAreaRects[] = new RectF[9]; + private Matrix translationMatrix; + private boolean pointerMoving = false; + private boolean pointerScrolling = false; + private TouchPointerListener listener = null; + private UIHandler uiHandler = new UIHandler(); + // gesture detection + private GestureDetector gestureDetector; + public TouchPointerView(Context context) { + super(context); + initTouchPointer(context); + } + + public TouchPointerView(Context context, AttributeSet attrs) { + super(context, attrs); + initTouchPointer(context); + } + + public TouchPointerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initTouchPointer(context); + } + + private void initTouchPointer(Context context) { + gestureDetector = new GestureDetector(context, new TouchPointerGestureListener(), null, true); + gestureDetector.setLongPressTimeout(500); + translationMatrix = new Matrix(); + setScaleType(ScaleType.MATRIX); + setImageMatrix(translationMatrix); + + // init rects + final float rectSizeWidth = (float) getDrawable().getIntrinsicWidth() / 3.0f; + final float rectSizeHeight = (float) getDrawable().getIntrinsicWidth() / 3.0f; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + int left = (int) (j * rectSizeWidth); + int top = (int) (i * rectSizeHeight); + int right = left + (int) rectSizeWidth; + int bottom = top + (int) rectSizeHeight; + pointerAreaRects[i * 3 + j] = new RectF(left, top, right, bottom); + } + } + pointerRect = new RectF(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight()); + } + + public void setTouchPointerListener(TouchPointerListener listener) { + this.listener = listener; + } + + public int getPointerWidth() { + return getDrawable().getIntrinsicWidth(); + } + + public int getPointerHeight() { + return getDrawable().getIntrinsicHeight(); + } + + public float[] getPointerPosition() { + float[] curPos = new float[2]; + translationMatrix.mapPoints(curPos); + return curPos; + } + + private void movePointer(float deltaX, float deltaY) { + translationMatrix.postTranslate(deltaX, deltaY); + setImageMatrix(translationMatrix); + } + + private void ensureVisibility(int screen_width, int screen_height) { + float[] curPos = new float[2]; + translationMatrix.mapPoints(curPos); + + if (curPos[0] > (screen_width - pointerRect.width())) + curPos[0] = screen_width - pointerRect.width(); + if (curPos[0] < 0) + curPos[0] = 0; + if (curPos[1] > (screen_height - pointerRect.height())) + curPos[1] = screen_height - pointerRect.height(); + if (curPos[1] < 0) + curPos[1] = 0; + + translationMatrix.setTranslate(curPos[0], curPos[1]); + setImageMatrix(translationMatrix); + } + + private void displayPointerImageAction(int resId) { + setPointerImage(resId); + uiHandler.sendEmptyMessageDelayed(0, DEFAULT_TOUCH_POINTER_RESTORE_DELAY); + } + + private void setPointerImage(int resId) { + setImageResource(resId); + } + + // returns the pointer area with the current translation matrix applied + private RectF getCurrentPointerArea(int area) { + RectF transRect = new RectF(pointerAreaRects[area]); + translationMatrix.mapRect(transRect); + return transRect; + } + + private boolean pointerAreaTouched(MotionEvent event, int area) { + RectF transRect = new RectF(pointerAreaRects[area]); + translationMatrix.mapRect(transRect); + if (transRect.contains(event.getX(), event.getY())) + return true; + return false; + } + + private boolean pointerTouched(MotionEvent event) { + RectF transRect = new RectF(pointerRect); + translationMatrix.mapRect(transRect); + if (transRect.contains(event.getX(), event.getY())) + return true; + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // check if pointer is being moved or if we are in scroll mode or if the pointer is touched + if (!pointerMoving && !pointerScrolling && !pointerTouched(event)) + return false; + return gestureDetector.onTouchEvent(event); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + // ensure touch pointer is visible + if (changed) + ensureVisibility(right - left, bottom - top); + } + + // touch pointer listener - is triggered if an action field is + public interface TouchPointerListener { + abstract void onTouchPointerClose(); + + abstract void onTouchPointerLeftClick(int x, int y, boolean down); + + abstract void onTouchPointerRightClick(int x, int y, boolean down); + + abstract void onTouchPointerMove(int x, int y); + + abstract void onTouchPointerScroll(boolean down); + + abstract void onTouchPointerToggleKeyboard(); + + abstract void onTouchPointerToggleExtKeyboard(); + + abstract void onTouchPointerResetScrollZoom(); + } + + private class UIHandler extends Handler { + + UIHandler() { + super(); + } + + @Override + public void handleMessage(Message msg) { + setPointerImage(R.drawable.touch_pointer_default); + } + } + + private class TouchPointerGestureListener extends GestureDetector.SimpleOnGestureListener { + + private MotionEvent prevEvent = null; + + public boolean onDown(MotionEvent e) { + if (pointerAreaTouched(e, POINTER_ACTION_MOVE)) { + prevEvent = MotionEvent.obtain(e); + pointerMoving = true; + } else if (pointerAreaTouched(e, POINTER_ACTION_SCROLL)) { + prevEvent = MotionEvent.obtain(e); + pointerScrolling = true; + setPointerImage(R.drawable.touch_pointer_scroll); + } + + return true; + } + + public boolean onUp(MotionEvent e) { + if (prevEvent != null) { + prevEvent.recycle(); + prevEvent = null; + } + + if (pointerScrolling) + setPointerImage(R.drawable.touch_pointer_default); + + pointerMoving = false; + pointerScrolling = false; + return true; + } + + public void onLongPress(MotionEvent e) { + if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) { + setPointerImage(R.drawable.touch_pointer_active); + pointerMoving = true; + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), true); + } + } + + public void onLongPressUp(MotionEvent e) { + if (pointerMoving) { + setPointerImage(R.drawable.touch_pointer_default); + pointerMoving = false; + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), false); + } + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (pointerMoving) { + // move pointer graphics + movePointer((int) (e2.getX() - prevEvent.getX()), (int) (e2.getY() - prevEvent.getY())); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + + // send move notification + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerMove((int) rect.centerX(), (int) rect.centerY()); + return true; + } else if (pointerScrolling) { + // calc if user scrolled up or down (or if any scrolling happened at all) + float deltaY = e2.getY() - prevEvent.getY(); + if (deltaY > SCROLL_DELTA) { + listener.onTouchPointerScroll(true); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } else if (deltaY < -SCROLL_DELTA) { + listener.onTouchPointerScroll(false); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + return true; + } + return false; + } + + public boolean onSingleTapUp(MotionEvent e) { + // look what area got touched and fire actions accordingly + if (pointerAreaTouched(e, POINTER_ACTION_CLOSE)) + listener.onTouchPointerClose(); + else if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) { + displayPointerImageAction(R.drawable.touch_pointer_lclick); + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), true); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), false); + } else if (pointerAreaTouched(e, POINTER_ACTION_RCLICK)) { + displayPointerImageAction(R.drawable.touch_pointer_rclick); + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerRightClick((int) rect.centerX(), (int) rect.centerY(), true); + listener.onTouchPointerRightClick((int) rect.centerX(), (int) rect.centerY(), false); + } else if (pointerAreaTouched(e, POINTER_ACTION_KEYBOARD)) { + displayPointerImageAction(R.drawable.touch_pointer_keyboard); + listener.onTouchPointerToggleKeyboard(); + } else if (pointerAreaTouched(e, POINTER_ACTION_EXTKEYBOARD)) { + displayPointerImageAction(R.drawable.touch_pointer_extkeyboard); + listener.onTouchPointerToggleExtKeyboard(); + } else if (pointerAreaTouched(e, POINTER_ACTION_RESET)) { + displayPointerImageAction(R.drawable.touch_pointer_reset); + listener.onTouchPointerResetScrollZoom(); + } + + return true; + } + + public boolean onDoubleTap(MotionEvent e) { + // issue a double click notification if performed in center quadrant + if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) { + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), true); + listener.onTouchPointerLeftClick((int) rect.centerX(), (int) rect.centerY(), false); + } + return true; + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java new file mode 100644 index 0000000..5335ff5 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java @@ -0,0 +1,474 @@ +/* + Helper class to access bookmark database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; + +import java.util.ArrayList; + +public abstract class BookmarkBaseGateway { + private final static String TAG = "BookmarkBaseGateway"; + private SQLiteOpenHelper bookmarkDB; + + private static final String JOIN_PREFIX = "join_"; + private static final String KEY_BOOKMARK_ID = "bookmarkId"; + private static final String KEY_SCREEN_COLORS = "screenColors"; + private static final String KEY_SCREEN_COLORS_3G = "screenColors3G"; + private static final String KEY_SCREEN_RESOLUTION = "screenResolution"; + private static final String KEY_SCREEN_RESOLUTION_3G = "screenResolution3G"; + private static final String KEY_SCREEN_WIDTH = "screenWidth"; + private static final String KEY_SCREEN_WIDTH_3G = "screenWidth3G"; + private static final String KEY_SCREEN_HEIGHT = "screenHeight"; + private static final String KEY_SCREEN_HEIGHT_3G = "screenHeight3G"; + + private static final String KEY_PERFORMANCE_RFX = "performanceRemoteFX"; + private static final String KEY_PERFORMANCE_RFX_3G = "performanceRemoteFX3G"; + private static final String KEY_PERFORMANCE_GFX = "performanceGfx"; + private static final String KEY_PERFORMANCE_GFX_3G = "performanceGfx3G"; + private static final String KEY_PERFORMANCE_H264 = "performanceGfxH264"; + private static final String KEY_PERFORMANCE_H264_3G = "performanceGfxH2643G"; + private static final String KEY_PERFORMANCE_WALLPAPER = "performanceWallpaper"; + private static final String KEY_PERFORMANCE_WALLPAPER_3G = "performanceWallpaper3G"; + private static final String KEY_PERFORMANCE_THEME = "performanceTheming"; + private static final String KEY_PERFORMANCE_THEME_3G = "performanceTheming3G"; + + private static final String KEY_PERFORMANCE_DRAG = "performanceFullWindowDrag"; + private static final String KEY_PERFORMANCE_DRAG_3G = "performanceFullWindowDrag3G"; + private static final String KEY_PERFORMANCE_MENU_ANIMATIONS = "performanceMenuAnimations"; + private static final String KEY_PERFORMANCE_MENU_ANIMATIONS_3G = "performanceMenuAnimations3G"; + private static final String KEY_PERFORMANCE_FONTS = "performanceFontSmoothing"; + private static final String KEY_PERFORMANCE_FONTS_3G = "performanceFontSmoothing3G"; + private static final String KEY_PERFORMANCE_COMPOSITION = "performanceDesktopComposition"; + private static final String KEY_PERFORMANCE_COMPOSITION_3G = "performanceDesktopComposition3G"; + + public BookmarkBaseGateway(SQLiteOpenHelper bookmarkDB) { + this.bookmarkDB = bookmarkDB; + } + + protected abstract BookmarkBase createBookmark(); + + protected abstract String getBookmarkTableName(); + + protected abstract void addBookmarkSpecificColumns(ArrayList columns); + + protected abstract void addBookmarkSpecificColumns(BookmarkBase bookmark, ContentValues columns); + + protected abstract void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor); + + public void insert(BookmarkBase bookmark) { + // begin transaction + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + long rowid; + ContentValues values = new ContentValues(); + values.put(BookmarkDB.DB_KEY_BOOKMARK_LABEL, bookmark.getLabel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_USERNAME, bookmark.getUsername()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD, bookmark.getPassword()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN, bookmark.getDomain()); + // insert screen and performance settings + rowid = insertScreenSettings(db, bookmark.getScreenSettings()); + values.put(BookmarkDB.DB_KEY_SCREEN_SETTINGS, rowid); + rowid = insertPerformanceFlags(db, bookmark.getPerformanceFlags()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FLAGS, rowid); + + // advanced settings + values.put(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE, bookmark.getAdvancedSettings().getEnable3GSettings()); + // insert 3G screen and 3G performance settings + rowid = insertScreenSettings(db, bookmark.getAdvancedSettings().getScreen3G()); + values.put(BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G, rowid); + rowid = insertPerformanceFlags(db, bookmark.getAdvancedSettings().getPerformance3G()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G, rowid); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD, bookmark.getAdvancedSettings().getRedirectSDCard()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND, bookmark.getAdvancedSettings().getRedirectSound()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, bookmark.getAdvancedSettings().getRedirectMicrophone()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_SECURITY, bookmark.getAdvancedSettings().getSecurity()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE, bookmark.getAdvancedSettings().getConsoleMode()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM, bookmark.getAdvancedSettings().getRemoteProgram()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR, bookmark.getAdvancedSettings().getWorkDir()); + + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL, bookmark.getDebugSettings().getAsyncChannel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_INPUT, bookmark.getDebugSettings().getAsyncInput()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE, bookmark.getDebugSettings().getAsyncUpdate()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL, bookmark.getDebugSettings().getDebugLevel()); + + // add any special columns + addBookmarkSpecificColumns(bookmark, values); + + // insert bookmark and end transaction + db.insertOrThrow(getBookmarkTableName(), null, values); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public boolean update(BookmarkBase bookmark) { + // start a transaction + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + // bookmark settings + ContentValues values = new ContentValues(); + values.put(BookmarkDB.DB_KEY_BOOKMARK_LABEL, bookmark.getLabel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_USERNAME, bookmark.getUsername()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD, bookmark.getPassword()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN, bookmark.getDomain()); + // update screen and performance settings settings + updateScreenSettings(db, bookmark); + updatePerformanceFlags(db, bookmark); + + // advanced settings + values.put(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE, bookmark.getAdvancedSettings().getEnable3GSettings()); + // update 3G screen and 3G performance settings settings + updateScreenSettings3G(db, bookmark); + updatePerformanceFlags3G(db, bookmark); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD, bookmark.getAdvancedSettings().getRedirectSDCard()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND, bookmark.getAdvancedSettings().getRedirectSound()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, bookmark.getAdvancedSettings().getRedirectMicrophone()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_SECURITY, bookmark.getAdvancedSettings().getSecurity()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE, bookmark.getAdvancedSettings().getConsoleMode()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM, bookmark.getAdvancedSettings().getRemoteProgram()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR, bookmark.getAdvancedSettings().getWorkDir()); + + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL, bookmark.getDebugSettings().getAsyncChannel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_INPUT, bookmark.getDebugSettings().getAsyncInput()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE, bookmark.getDebugSettings().getAsyncUpdate()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL, bookmark.getDebugSettings().getDebugLevel()); + + addBookmarkSpecificColumns(bookmark, values); + + // update bookmark + boolean res = (db.update(getBookmarkTableName(), values, BookmarkDB.ID + " = " + bookmark.getId(), null) == 1); + + // commit + db.setTransactionSuccessful(); + db.endTransaction(); + + return res; + } + + public void delete(long id) { + SQLiteDatabase db = getWritableDatabase(); + db.delete(getBookmarkTableName(), BookmarkDB.ID + " = " + id, null); + } + + public BookmarkBase findById(long id) { + Cursor cursor = queryBookmarks(getBookmarkTableName() + "." + BookmarkDB.ID + " = " + id, null); + if (cursor.getCount() == 0) { + cursor.close(); + return null; + } + + cursor.moveToFirst(); + BookmarkBase bookmark = getBookmarkFromCursor(cursor); + cursor.close(); + return bookmark; + } + + public BookmarkBase findByLabel(String label) { + Cursor cursor = queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " = '" + label + "'", BookmarkDB.DB_KEY_BOOKMARK_LABEL); + if (cursor.getCount() > 1) + Log.e(TAG, "More than one bookmark with the same label found!"); + + BookmarkBase bookmark = null; + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + bookmark = getBookmarkFromCursor(cursor); + + cursor.close(); + return bookmark; + } + + public ArrayList findByLabelLike(String pattern) { + Cursor cursor = queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " LIKE '%" + pattern + "%'", BookmarkDB.DB_KEY_BOOKMARK_LABEL); + ArrayList bookmarks = new ArrayList(cursor.getCount()); + + if (cursor.moveToFirst() && (cursor.getCount() > 0)) { + do { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + public ArrayList findAll() { + Cursor cursor = queryBookmarks(null, BookmarkDB.DB_KEY_BOOKMARK_LABEL); + final int count = cursor.getCount(); + ArrayList bookmarks = new ArrayList<>(count); + + if (cursor.moveToFirst() && (count > 0)) { + do { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + protected Cursor queryBookmarks(String whereClause, String orderBy) { + // create tables string + final String ID = BookmarkDB.ID; + final String tables = BookmarkDB.DB_TABLE_BOOKMARK + " INNER JOIN " + + BookmarkDB.DB_TABLE_SCREEN + " AS " + JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + " ON " + JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + + ID + " = " + BookmarkDB.DB_TABLE_BOOKMARK + "." + BookmarkDB.DB_KEY_SCREEN_SETTINGS + + " INNER JOIN " + BookmarkDB.DB_TABLE_PERFORMANCE + + " AS " + JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + " ON " + JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + ID + " = " + + BookmarkDB.DB_TABLE_BOOKMARK + "." + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + + " INNER JOIN " + BookmarkDB.DB_TABLE_SCREEN + + " AS " + JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + " ON " + JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + ID + " = " + + BookmarkDB.DB_TABLE_BOOKMARK + "." + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + + " INNER JOIN " + BookmarkDB.DB_TABLE_PERFORMANCE + + " AS " + JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + " ON " + JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + ID + " = " + + BookmarkDB.DB_TABLE_BOOKMARK + "." + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G; + + // create columns list + ArrayList columns = new ArrayList<>(); + addBookmarkColumns(columns); + addScreenSettingsColumns(columns); + addPerformanceFlagsColumns(columns); + addScreenSettings3GColumns(columns); + addPerformanceFlags3GColumns(columns); + + String[] cols = new String[columns.size()]; + columns.toArray(cols); + + SQLiteDatabase db = getReadableDatabase(); + final String query = SQLiteQueryBuilder.buildQueryString(false, tables, cols, whereClause, null, null, orderBy, null); + return db.rawQuery(query, null); + } + + private void addBookmarkColumns(ArrayList columns) { + columns.add(getBookmarkTableName() + "." + BookmarkDB.ID + " " + KEY_BOOKMARK_ID); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_LABEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_USERNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN); + + // advanced settings + columns.add(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_SECURITY); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR); + + // debug settings + columns.add(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_INPUT); + + addBookmarkSpecificColumns(columns); + } + + private void addScreenSettingsColumns(ArrayList columns) { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + BookmarkDB.DB_KEY_SCREEN_COLORS + " as " + KEY_SCREEN_COLORS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + BookmarkDB.DB_KEY_SCREEN_RESOLUTION + " as " + KEY_SCREEN_RESOLUTION); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + BookmarkDB.DB_KEY_SCREEN_WIDTH + " as " + KEY_SCREEN_WIDTH); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + BookmarkDB.DB_KEY_SCREEN_HEIGHT + " as " + KEY_SCREEN_HEIGHT); + } + + private void addPerformanceFlagsColumns(ArrayList columns) { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_RFX + " as " + KEY_PERFORMANCE_RFX); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_GFX + " as " + KEY_PERFORMANCE_GFX); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_H264 + " as " + KEY_PERFORMANCE_H264); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER + " as " + KEY_PERFORMANCE_WALLPAPER); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_THEME + " as " + KEY_PERFORMANCE_THEME); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_DRAG + " as " + KEY_PERFORMANCE_DRAG); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS + " as " + KEY_PERFORMANCE_MENU_ANIMATIONS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_FONTS + " as " + KEY_PERFORMANCE_FONTS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION + " " + KEY_PERFORMANCE_COMPOSITION); + } + + private void addScreenSettings3GColumns(ArrayList columns) { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + BookmarkDB.DB_KEY_SCREEN_COLORS + " as " + KEY_SCREEN_COLORS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + BookmarkDB.DB_KEY_SCREEN_RESOLUTION + " as " + KEY_SCREEN_RESOLUTION_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + BookmarkDB.DB_KEY_SCREEN_WIDTH + " as " + KEY_SCREEN_WIDTH_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + BookmarkDB.DB_KEY_SCREEN_HEIGHT + " as " + KEY_SCREEN_HEIGHT_3G); + } + + private void addPerformanceFlags3GColumns(ArrayList columns) { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_RFX + " as " + KEY_PERFORMANCE_RFX_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_GFX + " as " + KEY_PERFORMANCE_GFX_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_H264 + " as " + KEY_PERFORMANCE_H264_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER + " as " + KEY_PERFORMANCE_WALLPAPER_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_THEME + " as " + KEY_PERFORMANCE_THEME_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_DRAG + " as " + KEY_PERFORMANCE_DRAG_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS + " as " + KEY_PERFORMANCE_MENU_ANIMATIONS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_FONTS + " as " + KEY_PERFORMANCE_FONTS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION + " " + KEY_PERFORMANCE_COMPOSITION_3G); + } + + protected BookmarkBase getBookmarkFromCursor(Cursor cursor) { + BookmarkBase bookmark = createBookmark(); + bookmark.setId(cursor.getLong(cursor.getColumnIndex(KEY_BOOKMARK_ID))); + bookmark.setLabel(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_LABEL))); + bookmark.setUsername(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_USERNAME))); + bookmark.setPassword(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD))); + bookmark.setDomain(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN))); + readScreenSettings(bookmark, cursor); + readPerformanceFlags(bookmark, cursor); + + // advanced settings + bookmark.getAdvancedSettings().setEnable3GSettings(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE)) != 0); + readScreenSettings3G(bookmark, cursor); + readPerformanceFlags3G(bookmark, cursor); + bookmark.getAdvancedSettings().setRedirectSDCard(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD)) != 0); + bookmark.getAdvancedSettings().setRedirectSound(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND))); + bookmark.getAdvancedSettings().setRedirectMicrophone(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE)) != 0); + bookmark.getAdvancedSettings().setSecurity(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_SECURITY))); + bookmark.getAdvancedSettings().setConsoleMode(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE)) != 0); + bookmark.getAdvancedSettings().setRemoteProgram(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM))); + bookmark.getAdvancedSettings().setWorkDir(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR))); + + bookmark.getDebugSettings().setAsyncChannel( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL)) == 1); + bookmark.getDebugSettings().setAsyncInput( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_INPUT)) == 1); + bookmark.getDebugSettings().setAsyncUpdate( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE)) == 1); + bookmark.getDebugSettings().setDebugLevel(cursor.getString(cursor.getColumnIndex + (BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL))); + + readBookmarkSpecificColumns(bookmark, cursor); + + return bookmark; + } + + private void readScreenSettings(BookmarkBase bookmark, Cursor cursor) { + BookmarkBase.ScreenSettings screenSettings = bookmark.getScreenSettings(); + screenSettings.setColors(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_COLORS))); + screenSettings.setResolution(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_RESOLUTION))); + screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_WIDTH))); + screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_HEIGHT))); + } + + private void readPerformanceFlags(BookmarkBase bookmark, Cursor cursor) { + BookmarkBase.PerformanceFlags perfFlags = bookmark.getPerformanceFlags(); + perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_RFX)) != 0); + perfFlags.setGfx(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_GFX)) != 0); + perfFlags.setH264(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_H264)) != 0); + perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_WALLPAPER)) != 0); + perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_THEME)) != 0); + perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_DRAG)) != 0); + perfFlags.setMenuAnimations(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_MENU_ANIMATIONS)) != 0); + perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_FONTS)) != 0); + perfFlags.setDesktopComposition(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_COMPOSITION)) != 0); + } + + private void readScreenSettings3G(BookmarkBase bookmark, Cursor cursor) { + BookmarkBase.ScreenSettings screenSettings = bookmark.getAdvancedSettings().getScreen3G(); + screenSettings.setColors(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_COLORS_3G))); + screenSettings.setResolution(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_RESOLUTION_3G))); + screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_WIDTH_3G))); + screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_HEIGHT_3G))); + } + + private void readPerformanceFlags3G(BookmarkBase bookmark, Cursor cursor) { + BookmarkBase.PerformanceFlags perfFlags = bookmark.getAdvancedSettings().getPerformance3G(); + perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_RFX_3G)) != 0); + perfFlags.setGfx(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_GFX_3G)) != 0); + perfFlags.setH264(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_H264_3G)) != 0); + perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_WALLPAPER_3G)) != 0); + perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_THEME_3G)) != 0); + perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_DRAG_3G)) != 0); + perfFlags.setMenuAnimations(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_MENU_ANIMATIONS_3G)) != 0); + perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_FONTS_3G)) != 0); + perfFlags.setDesktopComposition(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_COMPOSITION_3G)) != 0); + } + + private void fillScreenSettingsContentValues(BookmarkBase.ScreenSettings settings, ContentValues values) { + values.put(BookmarkDB.DB_KEY_SCREEN_COLORS, settings.getColors()); + values.put(BookmarkDB.DB_KEY_SCREEN_RESOLUTION, settings.getResolution()); + values.put(BookmarkDB.DB_KEY_SCREEN_WIDTH, settings.getWidth()); + values.put(BookmarkDB.DB_KEY_SCREEN_HEIGHT, settings.getHeight()); + } + + private void fillPerformanceFlagsContentValues(BookmarkBase.PerformanceFlags perfFlags, ContentValues values) { + values.put(BookmarkDB.DB_KEY_PERFORMANCE_RFX, perfFlags.getRemoteFX()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_GFX, perfFlags.getGfx()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_H264, perfFlags.getH264()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER, perfFlags.getWallpaper()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_THEME, perfFlags.getTheming()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_DRAG, perfFlags.getFullWindowDrag()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS, perfFlags.getMenuAnimations()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FONTS, perfFlags.getFontSmoothing()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION, perfFlags.getDesktopComposition()); + } + + private long insertScreenSettings(SQLiteDatabase db, BookmarkBase.ScreenSettings settings) { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(settings, values); + return db.insertOrThrow(BookmarkDB.DB_TABLE_SCREEN, null, values); + } + + private boolean updateScreenSettings(SQLiteDatabase db, BookmarkBase bookmark) { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(bookmark.getScreenSettings(), values); + String whereClause = BookmarkDB.ID + " IN " + "(SELECT " + BookmarkDB.DB_KEY_SCREEN_SETTINGS + " FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_SCREEN, values, whereClause, null) == 1); + } + + private boolean updateScreenSettings3G(SQLiteDatabase db, BookmarkBase bookmark) { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(bookmark.getAdvancedSettings().getScreen3G(), values); + String whereClause = BookmarkDB.ID + " IN " + "(SELECT " + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + " FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_SCREEN, values, whereClause, null) == 1); + } + + private long insertPerformanceFlags(SQLiteDatabase db, BookmarkBase.PerformanceFlags perfFlags) { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(perfFlags, values); + return db.insertOrThrow(BookmarkDB.DB_TABLE_PERFORMANCE, null, values); + } + + private boolean updatePerformanceFlags(SQLiteDatabase db, BookmarkBase bookmark) { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(bookmark.getPerformanceFlags(), values); + String whereClause = BookmarkDB.ID + " IN " + "(SELECT " + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + " FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_PERFORMANCE, values, whereClause, null) == 1); + } + + private boolean updatePerformanceFlags3G(SQLiteDatabase db, BookmarkBase bookmark) { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(bookmark.getAdvancedSettings().getPerformance3G(), values); + String whereClause = BookmarkDB.ID + " IN " + "(SELECT " + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + " FROM " + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_PERFORMANCE, values, whereClause, null) == 1); + } + + // safety wrappers + // in case of getReadableDatabase it could happen that upgradeDB gets called which is + // a problem if the DB is only readable + private SQLiteDatabase getWritableDatabase() { + return bookmarkDB.getWritableDatabase(); + } + + private SQLiteDatabase getReadableDatabase() { + SQLiteDatabase db; + try { + db = bookmarkDB.getReadableDatabase(); + } catch (SQLiteException e) { + db = bookmarkDB.getWritableDatabase(); + } + return db; + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java new file mode 100644 index 0000000..e2537d5 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java @@ -0,0 +1,391 @@ +/* + Android Bookmark Database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.BaseColumns; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BookmarkDB extends SQLiteOpenHelper { + public static final String ID = BaseColumns._ID; + private static final int DB_VERSION = 9; + private static final String DB_BACKUP_PREFIX = "temp_"; + private static final String DB_NAME = "bookmarks.db"; + static final String DB_TABLE_BOOKMARK = "tbl_manual_bookmarks"; + static final String DB_TABLE_SCREEN = "tbl_screen_settings"; + static final String DB_TABLE_PERFORMANCE = "tbl_performance_flags"; + private static final String[] DB_TABLES = { + DB_TABLE_BOOKMARK, + DB_TABLE_SCREEN, + DB_TABLE_PERFORMANCE + }; + + static final String DB_KEY_SCREEN_COLORS = "colors"; + static final String DB_KEY_SCREEN_RESOLUTION = "resolution"; + static final String DB_KEY_SCREEN_WIDTH = "width"; + static final String DB_KEY_SCREEN_HEIGHT = "height"; + + static final String DB_KEY_SCREEN_SETTINGS = "screen_settings"; + static final String DB_KEY_SCREEN_SETTINGS_3G = "screen_3g"; + static final String DB_KEY_PERFORMANCE_FLAGS = "performance_flags"; + static final String DB_KEY_PERFORMANCE_FLAGS_3G = "performance_3g"; + + static final String DB_KEY_PERFORMANCE_RFX = "perf_remotefx"; + static final String DB_KEY_PERFORMANCE_GFX = "perf_gfx"; + static final String DB_KEY_PERFORMANCE_H264 = "perf_gfx_h264"; + static final String DB_KEY_PERFORMANCE_WALLPAPER = "perf_wallpaper"; + static final String DB_KEY_PERFORMANCE_THEME = "perf_theming"; + static final String DB_KEY_PERFORMANCE_DRAG = "perf_full_window_drag"; + static final String DB_KEY_PERFORMANCE_MENU_ANIMATIONS = "perf_menu_animations"; + static final String DB_KEY_PERFORMANCE_FONTS = "perf_font_smoothing"; + static final String DB_KEY_PERFORMANCE_COMPOSITION = "perf_desktop_composition"; + + static final String DB_KEY_BOOKMARK_LABEL = "label"; + static final String DB_KEY_BOOKMARK_HOSTNAME = "hostname"; + static final String DB_KEY_BOOKMARK_USERNAME = "username"; + static final String DB_KEY_BOOKMARK_PASSWORD = "password"; + static final String DB_KEY_BOOKMARK_DOMAIN = "domain"; + static final String DB_KEY_BOOKMARK_PORT = "port"; + + static final String DB_KEY_BOOKMARK_REDIRECT_SDCARD = "redirect_sdcard"; + static final String DB_KEY_BOOKMARK_REDIRECT_SOUND = "redirect_sound"; + static final String DB_KEY_BOOKMARK_REDIRECT_MICROPHONE = "redirect_microphone"; + static final String DB_KEY_BOOKMARK_SECURITY = "security"; + static final String DB_KEY_BOOKMARK_REMOTE_PROGRAM = "remote_program"; + static final String DB_KEY_BOOKMARK_WORK_DIR = "work_dir"; + static final String DB_KEY_BOOKMARK_ASYNC_CHANNEL = "async_channel"; + static final String DB_KEY_BOOKMARK_ASYNC_INPUT = "async_input"; + static final String DB_KEY_BOOKMARK_ASYNC_UPDATE = "async_update"; + static final String DB_KEY_BOOKMARK_CONSOLE_MODE = "console_mode"; + static final String DB_KEY_BOOKMARK_DEBUG_LEVEL = "debug_level"; + + static final String DB_KEY_BOOKMARK_GW_ENABLE = "enable_gateway_settings"; + static final String DB_KEY_BOOKMARK_GW_HOSTNAME = "gateway_hostname"; + static final String DB_KEY_BOOKMARK_GW_PORT = "gateway_port"; + static final String DB_KEY_BOOKMARK_GW_USERNAME = "gateway_username"; + static final String DB_KEY_BOOKMARK_GW_PASSWORD = "gateway_password"; + static final String DB_KEY_BOOKMARK_GW_DOMAIN = "gateway_domain"; + static final String DB_KEY_BOOKMARK_3G_ENABLE = "enable_3g_settings"; + + public BookmarkDB(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + private static List GetColumns(SQLiteDatabase db, String tableName) { + List ar = null; + Cursor c = null; + try { + c = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 1", null); + if (c != null) { + ar = new ArrayList<>(Arrays.asList(c.getColumnNames())); + } + } catch (Exception e) { + Log.v(tableName, e.getMessage(), e); + e.printStackTrace(); + } finally { + if (c != null) + c.close(); + } + return ar; + } + + private static String joinStrings(List list, String delim) { + StringBuilder buf = new StringBuilder(); + int num = list.size(); + for (int i = 0; i < num; i++) { + if (i != 0) + buf.append(delim); + buf.append((String) list.get(i)); + } + return buf.toString(); + } + + private void backupTables(SQLiteDatabase db) { + for (String table : DB_TABLES) { + final String tmpTable = DB_BACKUP_PREFIX + table; + final String query = "ALTER TABLE '" + table + "' RENAME TO '" + tmpTable + "'"; + try { + db.execSQL(query); + } catch (Exception e) { + /* Ignore errors if table does not exist. */ + } + } + } + + private void dropOldTables(SQLiteDatabase db) { + for (String table : DB_TABLES) { + final String tmpTable = DB_BACKUP_PREFIX + table; + final String query = "DROP TABLE IF EXISTS '" + tmpTable + "'"; + db.execSQL(query); + } + } + + private void createDB(SQLiteDatabase db) { + final String sqlScreenSettings = + "CREATE TABLE IF NOT EXISTS " + DB_TABLE_SCREEN + " (" + + ID + " INTEGER PRIMARY KEY, " + + DB_KEY_SCREEN_COLORS + " INTEGER DEFAULT 16, " + + DB_KEY_SCREEN_RESOLUTION + " INTEGER DEFAULT 0, " + + DB_KEY_SCREEN_WIDTH + ", " + + DB_KEY_SCREEN_HEIGHT + ");"; + + db.execSQL(sqlScreenSettings); + + final String sqlPerformanceFlags = + "CREATE TABLE IF NOT EXISTS " + DB_TABLE_PERFORMANCE + " (" + + ID + " INTEGER PRIMARY KEY, " + + DB_KEY_PERFORMANCE_RFX + " INTEGER, " + + DB_KEY_PERFORMANCE_GFX + " INTEGER, " + + DB_KEY_PERFORMANCE_H264 + " INTEGER, " + + DB_KEY_PERFORMANCE_WALLPAPER + " INTEGER, " + + DB_KEY_PERFORMANCE_THEME + " INTEGER, " + + DB_KEY_PERFORMANCE_DRAG + " INTEGER, " + + DB_KEY_PERFORMANCE_MENU_ANIMATIONS + " INTEGER, " + + DB_KEY_PERFORMANCE_FONTS + " INTEGER, " + + DB_KEY_PERFORMANCE_COMPOSITION + " INTEGER);"; + + db.execSQL(sqlPerformanceFlags); + + final String sqlManualBookmarks = getManualBookmarksCreationString(); + db.execSQL(sqlManualBookmarks); + } + + private void upgradeTables(SQLiteDatabase db) { + for (String table : DB_TABLES) { + final String tmpTable = DB_BACKUP_PREFIX + table; + + final List newColumns = GetColumns(db, table); + List columns = GetColumns(db, tmpTable); + + if (columns != null) { + columns.retainAll(newColumns); + + // restore data + final String cols = joinStrings(columns, ","); + final String query = String.format("INSERT INTO %s (%s) SELECT %s from '%s'", table, cols, cols, tmpTable); + db.execSQL(query); + } + } + } + + private void downgradeTables(SQLiteDatabase db) { + for (String table : DB_TABLES) { + final String tmpTable = DB_BACKUP_PREFIX + table; + + List oldColumns = GetColumns(db, table); + final List columns = GetColumns(db, tmpTable); + + if (oldColumns != null) { + oldColumns.retainAll(columns); + + // restore data + final String cols = joinStrings(oldColumns, ","); + final String query = String.format("INSERT INTO %s (%s) SELECT %s from '%s'", table, cols, cols, tmpTable); + db.execSQL(query); + } + } + } + + private List getTableNames(SQLiteDatabase db) { + final String query = "SELECT name FROM sqlite_master WHERE type='table'"; + Cursor cursor = db.rawQuery(query, null); + List list = new ArrayList<>(); + try { + if (cursor.moveToFirst() && (cursor.getCount() > 0)) { + while (!cursor.isAfterLast()) { + final String name = cursor.getString(cursor.getColumnIndex("name")); + list.add(name); + cursor.moveToNext(); + } + } + } finally { + cursor.close(); + } + + return list; + } + + private void insertDefault(SQLiteDatabase db) { + ContentValues screenValues = new ContentValues(); + screenValues.put(DB_KEY_SCREEN_COLORS, 32); + screenValues.put(DB_KEY_SCREEN_RESOLUTION, 1); + screenValues.put(DB_KEY_SCREEN_WIDTH, 1024); + screenValues.put(DB_KEY_SCREEN_HEIGHT, 768); + + final long idScreen = db.insert(DB_TABLE_SCREEN, null, screenValues); + final long idScreen3g = db.insert(DB_TABLE_SCREEN, null, screenValues); + + ContentValues performanceValues = new ContentValues(); + performanceValues.put(DB_KEY_PERFORMANCE_RFX, 1); + performanceValues.put(DB_KEY_PERFORMANCE_GFX, 1); + performanceValues.put(DB_KEY_PERFORMANCE_H264, 0); + performanceValues.put(DB_KEY_PERFORMANCE_WALLPAPER, 0); + performanceValues.put(DB_KEY_PERFORMANCE_THEME, 0); + performanceValues.put(DB_KEY_PERFORMANCE_DRAG, 0); + performanceValues.put(DB_KEY_PERFORMANCE_MENU_ANIMATIONS, 0); + performanceValues.put(DB_KEY_PERFORMANCE_FONTS, 0); + performanceValues.put(DB_KEY_PERFORMANCE_COMPOSITION, 0); + + final long idPerformance = db.insert(DB_TABLE_PERFORMANCE, null, performanceValues); + final long idPerformance3g = db.insert(DB_TABLE_PERFORMANCE, null, performanceValues); + + ContentValues bookmarkValues = new ContentValues(); + bookmarkValues.put(DB_KEY_BOOKMARK_LABEL, "Test Server"); + bookmarkValues.put(DB_KEY_BOOKMARK_HOSTNAME, "testservice.afreerdp.com"); + bookmarkValues.put(DB_KEY_BOOKMARK_USERNAME, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_PASSWORD, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_DOMAIN, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_PORT, "3389"); + + bookmarkValues.put(DB_KEY_SCREEN_SETTINGS, idScreen); + bookmarkValues.put(DB_KEY_SCREEN_SETTINGS_3G, idScreen3g); + bookmarkValues.put(DB_KEY_PERFORMANCE_FLAGS, idPerformance); + bookmarkValues.put(DB_KEY_PERFORMANCE_FLAGS_3G, idPerformance3g); + + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_SDCARD, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_SOUND, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_SECURITY, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REMOTE_PROGRAM, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_WORK_DIR, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_ASYNC_CHANNEL, 1); + bookmarkValues.put(DB_KEY_BOOKMARK_ASYNC_INPUT, 1); + bookmarkValues.put(DB_KEY_BOOKMARK_ASYNC_UPDATE, 1); + bookmarkValues.put(DB_KEY_BOOKMARK_CONSOLE_MODE, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_DEBUG_LEVEL, "INFO"); + + db.insert(DB_TABLE_BOOKMARK, null, bookmarkValues); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createDB(db); + insertDefault(db); + } + + private String getManualBookmarksCreationString() { + return ( + "CREATE TABLE IF NOT EXISTS " + DB_TABLE_BOOKMARK + " (" + + ID + " INTEGER PRIMARY KEY, " + + DB_KEY_BOOKMARK_LABEL + " TEXT NOT NULL, " + + DB_KEY_BOOKMARK_HOSTNAME + " TEXT NOT NULL, " + + DB_KEY_BOOKMARK_USERNAME + " TEXT NOT NULL, " + + DB_KEY_BOOKMARK_PASSWORD + " TEXT, " + + DB_KEY_BOOKMARK_DOMAIN + " TEXT, " + + DB_KEY_BOOKMARK_PORT + " TEXT, " + + DB_KEY_SCREEN_SETTINGS + " INTEGER NOT NULL, " + + DB_KEY_PERFORMANCE_FLAGS + " INTEGER NOT NULL, " + + + DB_KEY_BOOKMARK_GW_ENABLE + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_GW_HOSTNAME + " TEXT, " + + DB_KEY_BOOKMARK_GW_PORT + " INTEGER DEFAULT 443, " + + DB_KEY_BOOKMARK_GW_USERNAME + " TEXT, " + + DB_KEY_BOOKMARK_GW_PASSWORD + " TEXT, " + + DB_KEY_BOOKMARK_GW_DOMAIN + " TEXT, " + + + DB_KEY_BOOKMARK_3G_ENABLE + " INTEGER DEFAULT 0, " + + DB_KEY_SCREEN_SETTINGS_3G + " INTEGER NOT NULL, " + + DB_KEY_PERFORMANCE_FLAGS_3G + " INTEGER NOT NULL, " + + DB_KEY_BOOKMARK_REDIRECT_SDCARD + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_REDIRECT_SOUND + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_REDIRECT_MICROPHONE + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_SECURITY + " INTEGER, " + + DB_KEY_BOOKMARK_REMOTE_PROGRAM + " TEXT, " + + DB_KEY_BOOKMARK_WORK_DIR + " TEXT, " + + DB_KEY_BOOKMARK_ASYNC_CHANNEL + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_ASYNC_INPUT + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_ASYNC_UPDATE + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_CONSOLE_MODE + " INTEGER, " + + DB_KEY_BOOKMARK_DEBUG_LEVEL + " TEXT DEFAULT 'INFO', " + + + "FOREIGN KEY(" + DB_KEY_SCREEN_SETTINGS + ") REFERENCES " + DB_TABLE_SCREEN + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_PERFORMANCE_FLAGS + ") REFERENCES " + DB_TABLE_PERFORMANCE + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_SCREEN_SETTINGS_3G + ") REFERENCES " + DB_TABLE_SCREEN + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_PERFORMANCE_FLAGS_3G + ") REFERENCES " + DB_TABLE_PERFORMANCE + "(" + ID + ") " + + + ");"); + } + + private void recreateDB(SQLiteDatabase db) { + for (String table : DB_TABLES) { + final String query = "DROP TABLE IF EXISTS '" + table + "'"; + db.execSQL(query); + } + onCreate(db); + } + + private void upgradeDB(SQLiteDatabase db) { + db.beginTransaction(); + try { + /* Back up old tables. */ + dropOldTables(db); + backupTables(db); + createDB(db); + upgradeTables(db); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + dropOldTables(db); + } + } + + private void downgradeDB(SQLiteDatabase db) { + db.beginTransaction(); + try { + /* Back up old tables. */ + dropOldTables(db); + backupTables(db); + createDB(db); + downgradeTables(db); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + dropOldTables(db); + } + } + + // from http://stackoverflow.com/questions/3424156/upgrade-sqlite-database-from-one-version-to-another + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + switch (oldVersion) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + upgradeDB(db); + break; + default: + recreateDB(db); + break; + } + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + downgradeDB(db); + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java new file mode 100644 index 0000000..5d0035b --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java @@ -0,0 +1,120 @@ +/* + Suggestion Provider for RDP bookmarks + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; + +import java.util.ArrayList; + +public class FreeRDPSuggestionProvider extends ContentProvider { + + public static final Uri CONTENT_URI = Uri.parse("content://com.freerdp.afreerdp.services.freerdpsuggestionprovider"); + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getType(Uri uri) { + return "vnd.android.cursor.item/vnd.freerdp.remote"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + + String query = (selectionArgs != null && selectionArgs.length > 0) ? selectionArgs[0] : ""; + + // search history + ArrayList history = GlobalApp.getQuickConnectHistoryGateway().findHistory(query); + + // search bookmarks + ArrayList manualBookmarks; + if (query.length() > 0) + manualBookmarks = GlobalApp.getManualBookmarkGateway().findByLabelOrHostnameLike(query); + else + manualBookmarks = GlobalApp.getManualBookmarkGateway().findAll(); + + return createResultCursor(history, manualBookmarks); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + private void addBookmarksToCursor(ArrayList bookmarks, MatrixCursor resultCursor) { + Object[] row = new Object[5]; + for (BookmarkBase bookmark : bookmarks) { + row[0] = new Long(bookmark.getId()); + row[1] = bookmark.getLabel(); + row[2] = bookmark.get().getHostname(); + row[3] = ConnectionReference.getManualBookmarkReference(bookmark.getId()); + row[4] = "android.resource://" + getContext().getPackageName() + "/" + R.drawable.icon_star_on; + resultCursor.addRow(row); + } + } + + private void addHistoryToCursor(ArrayList history, MatrixCursor resultCursor) { + Object[] row = new Object[5]; + for (BookmarkBase bookmark : history) { + row[0] = new Integer(1); + row[1] = bookmark.getLabel(); + row[2] = bookmark.getLabel(); + row[3] = ConnectionReference.getHostnameReference(bookmark.getLabel()); + row[4] = "android.resource://" + getContext().getPackageName() + "/" + R.drawable.icon_star_off; + resultCursor.addRow(row); + } + } + + private Cursor createResultCursor(ArrayList history, ArrayList manualBookmarks) { + + // create result matrix cursor + int totalCount = history.size() + manualBookmarks.size(); + String[] columns = {android.provider.BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, SearchManager.SUGGEST_COLUMN_INTENT_DATA, + SearchManager.SUGGEST_COLUMN_ICON_2}; + MatrixCursor matrixCursor = new MatrixCursor(columns, totalCount); + + // populate result matrix + if (totalCount > 0) { + addHistoryToCursor(history, matrixCursor); + addBookmarksToCursor(manualBookmarks, matrixCursor); + } + return matrixCursor; + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java new file mode 100644 index 0000000..91bd8b7 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java @@ -0,0 +1,46 @@ +/* + Quick Connect History Database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class HistoryDB extends SQLiteOpenHelper { + + public static final String QUICK_CONNECT_TABLE_NAME = "quick_connect_history"; + public static final String QUICK_CONNECT_TABLE_COL_ITEM = "item"; + public static final String QUICK_CONNECT_TABLE_COL_TIMESTAMP = "timestamp"; + private static final int DB_VERSION = 1; + private static final String DB_NAME = "history.db"; + + public HistoryDB(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + + String sqlQuickConnectHistory = + "CREATE TABLE " + QUICK_CONNECT_TABLE_NAME + " (" + + QUICK_CONNECT_TABLE_COL_ITEM + " TEXT PRIMARY KEY, " + + QUICK_CONNECT_TABLE_COL_TIMESTAMP + " INTEGER" + + ");"; + + db.execSQL(sqlQuickConnectHistory); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // TODO Auto-generated method stub + + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java new file mode 100644 index 0000000..0d2bd2e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java @@ -0,0 +1,523 @@ +/* + Android FreeRDP JNI Wrapper + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.support.v4.util.LongSparseArray; +import android.util.Log; + +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; + +import java.util.ArrayList; + +public class LibFreeRDP { + private static final String TAG = "LibFreeRDP"; + private static EventListener listener; + private static boolean mHasH264 = true; + + private static final LongSparseArray mInstanceState = new LongSparseArray<>(); + + static { + final String h264 = "openh264"; + final String[] libraries = { + h264, "freerdp-openssl", "jpeg", "winpr2", + "freerdp2", "freerdp-client2", "freerdp-android2"}; + final String LD_PATH = System.getProperty("java.library.path"); + + for (String lib : libraries) { + try { + Log.v(TAG, "Trying to load library " + lib + " from LD_PATH: " + LD_PATH); + System.loadLibrary(lib); + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "Failed to load library " + lib + ": " + e.toString()); + if (lib.equals(h264)) { + mHasH264 = false; + } + } + } + } + + public static boolean hasH264Support() { + return mHasH264; + } + + private static native String freerdp_get_jni_version(); + + private static native String freerdp_get_version(); + + private static native String freerdp_get_build_date(); + + private static native String freerdp_get_build_revision(); + + private static native String freerdp_get_build_config(); + + private static native long freerdp_new(Context context); + + private static native void freerdp_free(long inst); + + private static native boolean freerdp_parse_arguments(long inst, String[] args); + + private static native boolean freerdp_connect(long inst); + + private static native boolean freerdp_disconnect(long inst); + + private static native boolean freerdp_update_graphics(long inst, + Bitmap bitmap, int x, int y, int width, int height); + + private static native boolean freerdp_send_cursor_event(long inst, int x, int y, int flags); + + private static native boolean freerdp_send_key_event(long inst, int keycode, boolean down); + + private static native boolean freerdp_send_unicodekey_event(long inst, int keycode); + + private static native boolean freerdp_send_clipboard_data(long inst, String data); + + public static void setEventListener(EventListener l) { + listener = l; + } + + public static long newInstance(Context context) { + return freerdp_new(context); + } + + public static void freeInstance(long inst) { + synchronized (mInstanceState) { + if (mInstanceState.get(inst, false)) { + freerdp_disconnect(inst); + } + while(mInstanceState.get(inst, false)) { + try { + mInstanceState.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(); + } + } + } + freerdp_free(inst); + } + + public static boolean connect(long inst) { + synchronized (mInstanceState) { + if (mInstanceState.get(inst, false)) { + throw new RuntimeException("instance already connected"); + } + } + return freerdp_connect(inst); + } + + public static boolean disconnect(long inst) { + synchronized (mInstanceState) { + if (mInstanceState.get(inst, false)) { + return freerdp_disconnect(inst); + } + return true; + } + } + + public static boolean cancelConnection(long inst) { + synchronized (mInstanceState) { + if (mInstanceState.get(inst, false)) { + return freerdp_disconnect(inst); + } + return true; + } + } + + private static String addFlag(String name, boolean enabled) { + if (enabled) { + return "+" + name; + } + return "-" + name; + } + + public static boolean setConnectionInfo(Context context, long inst, BookmarkBase bookmark) { + BookmarkBase.ScreenSettings screenSettings = bookmark.getActiveScreenSettings(); + BookmarkBase.AdvancedSettings advanced = bookmark.getAdvancedSettings(); + BookmarkBase.DebugSettings debug = bookmark.getDebugSettings(); + + String arg; + ArrayList args = new ArrayList(); + + args.add(TAG); + args.add("/gdi:sw"); + + final String clientName = ApplicationSettingsActivity.getClientName(context); + if (!clientName.isEmpty()) { + args.add("/client-hostname:" + clientName); + } + String certName = ""; + if (bookmark.getType() != BookmarkBase.TYPE_MANUAL) { + return false; + } + + int port = bookmark.get().getPort(); + String hostname = bookmark.get().getHostname(); + + args.add("/v:" + hostname); + args.add("/port:" + String.valueOf(port)); + + arg = bookmark.getUsername(); + if (!arg.isEmpty()) { + args.add("/u:" + arg); + } + arg = bookmark.getDomain(); + if (!arg.isEmpty()) { + args.add("/d:" + arg); + } + arg = bookmark.getPassword(); + if (!arg.isEmpty()) { + args.add("/p:" + arg); + } + + args.add(String.format("/size:%dx%d", screenSettings.getWidth(), + screenSettings.getHeight())); + args.add("/bpp:" + String.valueOf(screenSettings.getColors())); + + if (advanced.getConsoleMode()) { + args.add("/admin"); + } + + switch (advanced.getSecurity()) { + case 3: // NLA + args.add("/sec-nla"); + break; + case 2: // TLS + args.add("/sec-tls"); + break; + case 1: // RDP + args.add("/sec-rdp"); + break; + default: + break; + } + + if (!certName.isEmpty()) { + args.add("/cert-name:" + certName); + } + + BookmarkBase.PerformanceFlags flags = bookmark.getActivePerformanceFlags(); + if (flags.getRemoteFX()) { + args.add("/rfx"); + } + + if (flags.getGfx()) { + args.add("/gfx"); + } + + if (flags.getH264() && mHasH264) { + args.add("/gfx:AVC444"); + } + + args.add(addFlag("wallpaper", flags.getWallpaper())); + args.add(addFlag("window-drag", flags.getFullWindowDrag())); + args.add(addFlag("menu-anims", flags.getMenuAnimations())); + args.add(addFlag("themes", flags.getTheming())); + args.add(addFlag("fonts", flags.getFontSmoothing())); + args.add(addFlag("aero", flags.getDesktopComposition())); + args.add(addFlag("glyph-cache", false)); + + if (!advanced.getRemoteProgram().isEmpty()) { + args.add("/shell:" + advanced.getRemoteProgram()); + } + + if (!advanced.getWorkDir().isEmpty()) { + args.add("/shell-dir:" + advanced.getWorkDir()); + } + + args.add(addFlag("async-channels", debug.getAsyncChannel())); + args.add(addFlag("async-input", debug.getAsyncInput())); + args.add(addFlag("async-update", debug.getAsyncUpdate())); + + if (advanced.getRedirectSDCard()) { + String path = android.os.Environment.getExternalStorageDirectory().getPath(); + args.add("/drive:sdcard," + path); + } + + args.add("/clipboard"); + + // Gateway enabled? + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL && bookmark.get().getEnableGatewaySettings()) { + ManualBookmark.GatewaySettings gateway = bookmark.get().getGatewaySettings(); + + args.add(String.format("/g:%s:%d", gateway.getHostname(), gateway.getPort())); + + arg = gateway.getUsername(); + if (!arg.isEmpty()) { + args.add("/gu:" + arg); + } + arg = gateway.getDomain(); + if (!arg.isEmpty()) { + args.add("/gd:" + arg); + } + arg = gateway.getPassword(); + if (!arg.isEmpty()) { + args.add("/gp:" + arg); + } + } + + /* 0 ... local + 1 ... remote + 2 ... disable */ + args.add("/audio-mode:" + String.valueOf(advanced.getRedirectSound())); + if (advanced.getRedirectSound() == 0) { + args.add("/sound"); + } + + if (advanced.getRedirectMicrophone()) { + args.add("/microphone"); + } + + args.add("/cert-ignore"); + args.add("/log-level:" + debug.getDebugLevel()); + String[] arrayArgs = args.toArray(new String[args.size()]); + return freerdp_parse_arguments(inst, arrayArgs); + } + + public static boolean setConnectionInfo(Context context, long inst, Uri openUri) { + ArrayList args = new ArrayList<>(); + + // Parse URI from query string. Same key overwrite previous one + // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=- + + // Now we only support Software GDI + args.add(TAG); + args.add("/gdi:sw"); + + final String clientName = ApplicationSettingsActivity.getClientName(context); + if (!clientName.isEmpty()) { + args.add("/client-hostname:" + clientName); + } + + // Parse hostname and port. Set to 'v' argument + String hostname = openUri.getHost(); + int port = openUri.getPort(); + if (hostname != null) { + hostname = hostname + ((port == -1) ? "" : (":" + String.valueOf(port))); + args.add("/v:" + hostname); + } + + String user = openUri.getUserInfo(); + if (user != null) { + args.add("/u:" + user); + } + + for (String key : openUri.getQueryParameterNames()) { + String value = openUri.getQueryParameter(key); + + if (value.isEmpty()) { + // Query: key= + // To freerdp argument: /key + args.add("/" + key); + } else if (value.equals("-") || value.equals("+")) { + // Query: key=- or key=+ + // To freerdp argument: -key or +key + args.add(value + key); + } else { + // Query: key=value + // To freerdp argument: /key:value + if (key.equals("drive") && value.equals("sdcard")) { + // Special for sdcard redirect + String path = android.os.Environment.getExternalStorageDirectory().getPath(); + value = "sdcard," + path; + } + + args.add("/" + key + ":" + value); + } + } + + String[] arrayArgs = args.toArray(new String[args.size()]); + return freerdp_parse_arguments(inst, arrayArgs); + } + + public static boolean updateGraphics(long inst, Bitmap bitmap, int x, int y, int width, int height) { + return freerdp_update_graphics(inst, bitmap, x, y, width, height); + } + + public static boolean sendCursorEvent(long inst, int x, int y, int flags) { + return freerdp_send_cursor_event(inst, x, y, flags); + } + + public static boolean sendKeyEvent(long inst, int keycode, boolean down) { + return freerdp_send_key_event(inst, keycode, down); + } + + public static boolean sendUnicodeKeyEvent(long inst, int keycode) { + return freerdp_send_unicodekey_event(inst, keycode); + } + + public static boolean sendClipboardData(long inst, String data) { + return freerdp_send_clipboard_data(inst, data); + } + + private static void OnConnectionSuccess(long inst) { + if (listener != null) + listener.OnConnectionSuccess(inst); + synchronized (mInstanceState) { + mInstanceState.append(inst, true); + mInstanceState.notifyAll(); + } + } + + private static void OnConnectionFailure(long inst) { + if (listener != null) + listener.OnConnectionFailure(inst); + synchronized (mInstanceState) { + mInstanceState.remove(inst); + mInstanceState.notifyAll(); + } + } + + private static void OnPreConnect(long inst) { + if (listener != null) + listener.OnPreConnect(inst); + } + + private static void OnDisconnecting(long inst) { + if (listener != null) + listener.OnDisconnecting(inst); + } + + private static void OnDisconnected(long inst) { + if (listener != null) + listener.OnDisconnected(inst); + synchronized (mInstanceState) { + mInstanceState.remove(inst); + mInstanceState.notifyAll(); + } + } + + private static void OnSettingsChanged(long inst, int width, int height, int bpp) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnSettingsChanged(width, height, bpp); + } + + private static boolean OnAuthenticate(long inst, StringBuilder username, StringBuilder domain, StringBuilder password) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return false; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnAuthenticate(username, domain, password); + return false; + } + + private static boolean OnGatewayAuthenticate(long inst, StringBuilder username, StringBuilder + domain, StringBuilder password) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return false; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnGatewayAuthenticate(username, domain, password); + return false; + } + + private static int OnVerifyCertificate(long inst, String commonName, String subject, + String issuer, String fingerprint, boolean + hostMismatch) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return 0; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnVerifiyCertificate(commonName, subject, issuer, fingerprint, + hostMismatch); + return 0; + } + + private static int OnVerifyChangedCertificate(long inst, String commonName, String subject, + String issuer, String fingerprint, String oldSubject, + String oldIssuer, String oldFingerprint) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return 0; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnVerifyChangedCertificate(commonName, subject, issuer, + fingerprint, oldSubject, oldIssuer, oldFingerprint); + return 0; + } + + private static void OnGraphicsUpdate(long inst, int x, int y, int width, int height) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnGraphicsUpdate(x, y, width, height); + } + + private static void OnGraphicsResize(long inst, int width, int height, int bpp) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnGraphicsResize(width, height, bpp); + } + + private static void OnRemoteClipboardChanged(long inst, String data) { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnRemoteClipboardChanged(data); + } + + public static String getVersion() { + return freerdp_get_version(); + } + + public static interface EventListener { + void OnPreConnect(long instance); + + void OnConnectionSuccess(long instance); + + void OnConnectionFailure(long instance); + + void OnDisconnecting(long instance); + + void OnDisconnected(long instance); + } + + public static interface UIEventListener { + void OnSettingsChanged(int width, int height, int bpp); + + boolean OnAuthenticate(StringBuilder username, StringBuilder domain, StringBuilder password); + + boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain, StringBuilder + password); + + int OnVerifiyCertificate(String commonName, String subject, + String issuer, String fingerprint, boolean mismatch); + + int OnVerifyChangedCertificate(String commonName, String subject, + String issuer, String fingerprint, String oldSubject, + String oldIssuer, String oldFingerprint); + + void OnGraphicsUpdate(int x, int y, int width, int height); + + void OnGraphicsResize(int width, int height, int bpp); + + void OnRemoteClipboardChanged(String data); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java new file mode 100644 index 0000000..64df814 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java @@ -0,0 +1,113 @@ +/* + Manual bookmarks database gateway + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteOpenHelper; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ManualBookmark; + +import java.util.ArrayList; + +public class ManualBookmarkGateway extends BookmarkBaseGateway { + + public ManualBookmarkGateway(SQLiteOpenHelper bookmarkDB) { + super(bookmarkDB); + } + + @Override + protected BookmarkBase createBookmark() { + return new ManualBookmark(); + } + + @Override + protected String getBookmarkTableName() { + return BookmarkDB.DB_TABLE_BOOKMARK; + } + + @Override + protected void addBookmarkSpecificColumns(BookmarkBase bookmark, ContentValues columns) { + ManualBookmark bm = (ManualBookmark) bookmark; + columns.put(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME, bm.getHostname()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_PORT, bm.getPort()); + + // gateway settings + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE, bm.getEnableGatewaySettings()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME, bm.getGatewaySettings().getHostname()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT, bm.getGatewaySettings().getPort()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME, bm.getGatewaySettings().getUsername()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD, bm.getGatewaySettings().getPassword()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN, bm.getGatewaySettings().getDomain()); + } + + @Override + protected void addBookmarkSpecificColumns(ArrayList columns) { + columns.add(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_PORT); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN); + } + + @Override + protected void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor) { + ManualBookmark bm = (ManualBookmark) bookmark; + bm.setHostname(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME))); + bm.setPort(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_PORT))); + + bm.setEnableGatewaySettings(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE)) != 0); + readGatewaySettings(bm, cursor); + } + + public BookmarkBase findByLabelOrHostname(String pattern) { + if (pattern.length() == 0) + return null; + + Cursor cursor = queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " = '" + pattern + + "' OR " + BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME + " = '" + pattern + "'", + BookmarkDB.DB_KEY_BOOKMARK_LABEL); + BookmarkBase bookmark = null; + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + bookmark = getBookmarkFromCursor(cursor); + + cursor.close(); + return bookmark; + } + + public ArrayList findByLabelOrHostnameLike(String pattern) { + Cursor cursor = queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " LIKE '%" + + pattern + "%' OR " + BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME + " LIKE '%" + + pattern + "%'", BookmarkDB.DB_KEY_BOOKMARK_LABEL); + ArrayList bookmarks = new ArrayList(cursor.getCount()); + + if (cursor.moveToFirst() && (cursor.getCount() > 0)) { + do { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + private void readGatewaySettings(ManualBookmark bookmark, Cursor cursor) { + ManualBookmark.GatewaySettings gatewaySettings = bookmark.getGatewaySettings(); + gatewaySettings.setHostname(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME))); + gatewaySettings.setPort(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT))); + gatewaySettings.setUsername(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME))); + gatewaySettings.setPassword(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD))); + gatewaySettings.setDomain(cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN))); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java new file mode 100644 index 0000000..7d97243 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java @@ -0,0 +1,96 @@ +/* + Quick connect history gateway + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.QuickConnectBookmark; + +import java.util.ArrayList; + + +public class QuickConnectHistoryGateway { + private final static String TAG = "QuickConnectHistoryGateway"; + private SQLiteOpenHelper historyDB; + + + public QuickConnectHistoryGateway(SQLiteOpenHelper historyDB) { + this.historyDB = historyDB; + } + + public ArrayList findHistory(String filter) { + String[] column = {HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM}; + + SQLiteDatabase db = getReadableDatabase(); + String selection = (filter.length() > 0) ? (HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " LIKE '%" + filter + "%'") : null; + Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, selection, null, null, null, HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP); + + ArrayList result = new ArrayList(cursor.getCount()); + if (cursor.moveToFirst()) { + do { + String hostname = cursor.getString(cursor.getColumnIndex(HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM)); + QuickConnectBookmark bookmark = new QuickConnectBookmark(); + bookmark.setLabel(hostname); + bookmark.setHostname(hostname); + result.add(bookmark); + } while (cursor.moveToNext()); + } + cursor.close(); + return result; + } + + public void addHistoryItem(String item) { + String insertHistoryItem = "INSERT OR REPLACE INTO " + HistoryDB.QUICK_CONNECT_TABLE_NAME + " (" + + HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + ", " + HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP + ") VALUES('" + item + "', datetime('now'))"; + SQLiteDatabase db = getWritableDatabase(); + try { + db.execSQL(insertHistoryItem); + } catch (SQLException e) { + Log.v(TAG, e.toString()); + } + } + + public boolean historyItemExists(String item) { + String[] column = {HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM}; + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + item + "'", null, null, null, null); + boolean exists = (cursor.getCount() == 1); + cursor.close(); + return exists; + } + + public void removeHistoryItem(String hostname) { + SQLiteDatabase db = getWritableDatabase(); + db.delete(HistoryDB.QUICK_CONNECT_TABLE_NAME, HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + hostname + "'", null); + } + + // safety wrappers + // in case of getReadableDatabase it could happen that upgradeDB gets called which is + // a problem if the DB is only readable + private SQLiteDatabase getWritableDatabase() { + return historyDB.getWritableDatabase(); + } + + private SQLiteDatabase getReadableDatabase() { + SQLiteDatabase db; + try { + db = historyDB.getReadableDatabase(); + } catch (SQLiteException e) { + db = historyDB.getWritableDatabase(); + } + return db; + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java new file mode 100644 index 0000000..6239aca --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java @@ -0,0 +1,72 @@ +/* + Activity for handling connection requests + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.presentation.BookmarkActivity; +import com.freerdp.freerdpcore.presentation.SessionActivity; + + +public class SessionRequestHandlerActivity extends AppCompatActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + setIntent(intent); + handleIntent(intent); + } + + private void startSessionWithConnectionReference(String refStr) { + + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent sessionIntent = new Intent(this, SessionActivity.class); + sessionIntent.putExtras(bundle); + + startActivityForResult(sessionIntent, 0); + } + + private void editBookmarkWithConnectionReference(String refStr) { + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent bookmarkIntent = new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivityForResult(bookmarkIntent, 0); + } + + private void handleIntent(Intent intent) { + + String action = intent.getAction(); + if (Intent.ACTION_SEARCH.equals(action)) + startSessionWithConnectionReference(ConnectionReference.getHostnameReference(intent.getStringExtra(SearchManager.QUERY))); + else if (Intent.ACTION_VIEW.equals(action)) + startSessionWithConnectionReference(intent.getDataString()); + else if (Intent.ACTION_EDIT.equals(action)) + editBookmarkWithConnectionReference(intent.getDataString()); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + this.setResult(resultCode); + this.finish(); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..3e7aaa3 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java @@ -0,0 +1,107 @@ +package com.freerdp.freerdpcore.utils; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + @NonNull + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} \ No newline at end of file diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java new file mode 100644 index 0000000..4968c57 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java @@ -0,0 +1,117 @@ +/* + ArrayAdapter for bookmark lists + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.domain.PlaceholderBookmark; +import com.freerdp.freerdpcore.presentation.BookmarkActivity; + +import java.util.List; + +public class BookmarkArrayAdapter extends ArrayAdapter { + + public BookmarkArrayAdapter(Context context, int textViewResourceId, List objects) { + super(context, textViewResourceId, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View curView = convertView; + if (curView == null) { + LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + curView = vi.inflate(R.layout.bookmark_list_item, null); + } + + BookmarkBase bookmark = getItem(position); + TextView label = (TextView) curView.findViewById(R.id.bookmark_text1); + TextView hostname = (TextView) curView.findViewById(R.id.bookmark_text2); + ImageView star_icon = (ImageView) curView.findViewById(R.id.bookmark_icon2); + assert label != null; + assert hostname != null; + + label.setText(bookmark.getLabel()); + star_icon.setVisibility(View.VISIBLE); + + String refStr; + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL) { + hostname.setText(bookmark.get().getHostname()); + refStr = ConnectionReference.getManualBookmarkReference(bookmark.getId()); + star_icon.setImageResource(R.drawable.icon_star_on); + } else if (bookmark.getType() == BookmarkBase.TYPE_QUICKCONNECT) { + // just set an empty hostname (with a blank) - the hostname is already displayed in the label + // and in case we just set it to "" the textview will shrunk + hostname.setText(" "); + refStr = ConnectionReference.getHostnameReference(bookmark.getLabel()); + star_icon.setImageResource(R.drawable.icon_star_off); + } else if (bookmark.getType() == BookmarkBase.TYPE_PLACEHOLDER) { + hostname.setText(" "); + refStr = ConnectionReference.getPlaceholderReference(bookmark.get().getName()); + star_icon.setVisibility(View.GONE); + } else { + // unknown bookmark type... + refStr = ""; + assert false; + } + + star_icon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // start bookmark editor + Bundle bundle = new Bundle(); + String refStr = v.getTag().toString(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = new Intent(getContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + getContext().startActivity(bookmarkIntent); + } + }); + + curView.setTag(refStr); + star_icon.setTag(refStr); + + return curView; + } + + public void addItems(List newItems) { + for (BookmarkBase item : newItems) + add(item); + } + + public void replaceItems(List newItems) { + clear(); + for (BookmarkBase item : newItems) + add(item); + } + + public void remove(long bookmarkId) { + for (int i = 0; i < getCount(); i++) { + BookmarkBase bm = getItem(i); + if (bm.getId() == bookmarkId) { + remove(bm); + return; + } + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java new file mode 100644 index 0000000..7248932 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java @@ -0,0 +1,86 @@ +/* + Custom preference item showing a button on the right side + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.freerdp.freerdpcore.R; + +public class ButtonPreference extends Preference { + + private OnClickListener buttonOnClickListener; + private String buttonText; + private Button button; + + public ButtonPreference(Context context) { + super(context); + init(); + } + + public ButtonPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ButtonPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setLayoutResource(R.layout.button_preference); + button = null; + buttonText = null; + buttonOnClickListener = null; + } + + @Override + public View getView(View convertView, ViewGroup parent) { + View v = super.getView(convertView, parent); + button = (Button) v.findViewById(R.id.preference_button); + if (buttonText != null) + button.setText(buttonText); + if (buttonOnClickListener != null) + button.setOnClickListener(buttonOnClickListener); + + // additional init for ICS - make widget frame visible + // refer to http://stackoverflow.com/questions/8762984/custom-preference-broken-in-honeycomb-ics + LinearLayout widgetFrameView = ((LinearLayout) v.findViewById(android.R.id.widget_frame)); + widgetFrameView.setVisibility(View.VISIBLE); + + return v; + } + + public void setButtonText(int resId) { + buttonText = getContext().getResources().getString(resId); + if (button != null) + button.setText(buttonText); + } + + public void setButtonText(String text) { + buttonText = text; + if (button != null) + button.setText(text); + } + + public void setButtonOnClickListener(OnClickListener listener) { + if (button != null) + button.setOnClickListener(listener); + else + buttonOnClickListener = listener; + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java new file mode 100644 index 0000000..dce3b1b --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java @@ -0,0 +1,93 @@ +package com.freerdp.freerdpcore.utils; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; + +public abstract class ClipboardManagerProxy { + + public static ClipboardManagerProxy getClipboardManager(Context ctx) { + if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + return new PreHCClipboardManager(ctx); + else + return new HCClipboardManager(ctx); + } + + public abstract void setClipboardData(String data); + + public abstract void addClipboardChangedListener(OnClipboardChangedListener listener); + + public abstract void removeClipboardboardChangedListener(OnClipboardChangedListener listener); + + public static interface OnClipboardChangedListener { + void onClipboardChanged(String data); + } + + private static class PreHCClipboardManager extends ClipboardManagerProxy { + + public PreHCClipboardManager(Context ctx) { + + } + + @Override + public void setClipboardData(String data) { + } + + @Override + public void addClipboardChangedListener( + OnClipboardChangedListener listener) { + } + + @Override + public void removeClipboardboardChangedListener( + OnClipboardChangedListener listener) { + } + } + + @TargetApi(11) + private static class HCClipboardManager extends ClipboardManagerProxy implements ClipboardManager.OnPrimaryClipChangedListener { + private ClipboardManager mClipboardManager; + private OnClipboardChangedListener mListener; + + public HCClipboardManager(Context ctx) { + mClipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); + } + + @Override + public void setClipboardData(String data) { + mClipboardManager.setPrimaryClip(ClipData.newPlainText("rdp-clipboard", data == null ? "" : data)); + } + + @Override + public void onPrimaryClipChanged() { + ClipData clip = mClipboardManager.getPrimaryClip(); + String data = null; + + if (clip != null && clip.getItemCount() > 0) { + CharSequence cs = clip.getItemAt(0).getText(); + if (cs != null) + data = cs.toString(); + } + if (mListener != null) { + mListener.onClipboardChanged(data); + } + } + + @Override + public void addClipboardChangedListener( + OnClipboardChangedListener listener) { + mListener = listener; + mClipboardManager.addPrimaryClipChangedListener(this); + } + + @Override + public void removeClipboardboardChangedListener( + OnClipboardChangedListener listener) { + mListener = null; + mClipboardManager.removePrimaryClipChangedListener(this); + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java new file mode 100644 index 0000000..563bc61 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java @@ -0,0 +1,327 @@ +/* + 2 finger gesture detector + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; + +import com.freerdp.freerdpcore.utils.GestureDetector.OnGestureListener; + +public class DoubleGestureDetector { + // timeout during that the second finger has to touch the screen before the double finger detection is cancelled + private static final long DOUBLE_TOUCH_TIMEOUT = 100; + // timeout during that an UP event will trigger a single double touch event + private static final long SINGLE_DOUBLE_TOUCH_TIMEOUT = 1000; + // constants for Message.what used by GestureHandler below + private static final int TAP = 1; + // different detection modes + private static final int MODE_UNKNOWN = 0; + private static final int MODE_PINCH_ZOOM = 1; + private static final int MODE_SCROLL = 2; + private static final int SCROLL_SCORE_TO_REACH = 20; + private final OnDoubleGestureListener mListener; + private int mPointerDistanceSquare; + private int mCurrentMode; + private int mScrollDetectionScore; + private ScaleGestureDetector scaleGestureDetector; + private boolean mCancelDetection; + private boolean mDoubleInProgress; + private GestureHandler mHandler; + private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentDoubleDownEvent; + private MotionEvent mPreviousUpEvent; + private MotionEvent mPreviousPointerUpEvent; + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener) { + mListener = listener; + init(context, handler); + } + + private void init(Context context, Handler handler) { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + + if (handler != null) + mHandler = new GestureHandler(handler); + else + mHandler = new GestureHandler(); + + // we use 1cm distance to decide between scroll and pinch zoom + // - first convert cm to inches + // - then multiply inches by dots per inch + float distInches = 0.5f / 2.54f; + float distPixelsX = distInches * context.getResources().getDisplayMetrics().xdpi; + float distPixelsY = distInches * context.getResources().getDisplayMetrics().ydpi; + + mPointerDistanceSquare = (int) (distPixelsX * distPixelsX + distPixelsY * distPixelsY); + } + + /** + * Set scale gesture detector + * + * @param scaleGestureDetector + */ + public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) { + this.scaleGestureDetector = scaleGestureDetector; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) { + boolean handled = false; + final int action = ev.getAction(); + //dumpEvent(ev); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + if (mCurrentDownEvent != null) + mCurrentDownEvent.recycle(); + + mCurrentMode = MODE_UNKNOWN; + mCurrentDownEvent = MotionEvent.obtain(ev); + mCancelDetection = false; + mDoubleInProgress = false; + mScrollDetectionScore = 0; + handled = true; + break; + + case MotionEvent.ACTION_POINTER_UP: + if (mPreviousPointerUpEvent != null) + mPreviousPointerUpEvent.recycle(); + mPreviousPointerUpEvent = MotionEvent.obtain(ev); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + // more than 2 fingers down? cancel + // 2nd finger touched too late? cancel + if (ev.getPointerCount() > 2 || (ev.getEventTime() - mCurrentDownEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT) { + cancel(); + break; + } + + // detection cancelled? + if (mCancelDetection) + break; + + // double touch gesture in progress + mDoubleInProgress = true; + if (mCurrentDoubleDownEvent != null) + mCurrentDoubleDownEvent.recycle(); + mCurrentDoubleDownEvent = MotionEvent.obtain(ev); + + // set detection mode to unkown and send a TOUCH timeout event to detect single taps + mCurrentMode = MODE_UNKNOWN; + mHandler.sendEmptyMessageDelayed(TAP, SINGLE_DOUBLE_TOUCH_TIMEOUT); + + handled |= mListener.onDoubleTouchDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + + // detection cancelled or not active? + if (mCancelDetection || !mDoubleInProgress || ev.getPointerCount() != 2) + break; + + // determine mode + if (mCurrentMode == MODE_UNKNOWN) { + // did the pointer distance change? + if (pointerDistanceChanged(mCurrentDoubleDownEvent, ev)) { + handled |= scaleGestureDetector.onTouchEvent(mCurrentDownEvent); + MotionEvent e = MotionEvent.obtain(ev); + e.setAction(mCurrentDoubleDownEvent.getAction()); + handled |= scaleGestureDetector.onTouchEvent(e); + mCurrentMode = MODE_PINCH_ZOOM; + break; + } else { + mScrollDetectionScore++; + if (mScrollDetectionScore >= SCROLL_SCORE_TO_REACH) + mCurrentMode = MODE_SCROLL; + } + } + + switch (mCurrentMode) { + case MODE_PINCH_ZOOM: + if (scaleGestureDetector != null) + handled |= scaleGestureDetector.onTouchEvent(ev); + break; + + case MODE_SCROLL: + handled = mListener.onDoubleTouchScroll(mCurrentDownEvent, ev); + break; + + default: + handled = true; + break; + } + + break; + + case MotionEvent.ACTION_UP: + // fingers were not removed equally? cancel + if (mPreviousPointerUpEvent != null && (ev.getEventTime() - mPreviousPointerUpEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT) { + mPreviousPointerUpEvent.recycle(); + mPreviousPointerUpEvent = null; + cancel(); + break; + } + + // detection cancelled or not active? + if (mCancelDetection || !mDoubleInProgress) + break; + + boolean hasTapEvent = mHandler.hasMessages(TAP); + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mCurrentMode == MODE_UNKNOWN && hasTapEvent) + handled = mListener.onDoubleTouchSingleTap(mCurrentDoubleDownEvent); + else if (mCurrentMode == MODE_PINCH_ZOOM) + handled = scaleGestureDetector.onTouchEvent(ev); + + if (mPreviousUpEvent != null) + mPreviousUpEvent.recycle(); + + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + handled |= mListener.onDoubleTouchUp(ev); + break; + + case MotionEvent.ACTION_CANCEL: + cancel(); + break; + } + + if ((action == MotionEvent.ACTION_MOVE) && handled == false) + handled = true; + + return handled; + } + + private void cancel() { + mHandler.removeMessages(TAP); + mCurrentMode = MODE_UNKNOWN; + mCancelDetection = true; + mDoubleInProgress = false; + } + + // returns true of the distance between the two pointers changed + private boolean pointerDistanceChanged(MotionEvent oldEvent, MotionEvent newEvent) { + int deltaX1 = Math.abs((int) oldEvent.getX(0) - (int) oldEvent.getX(1)); + int deltaX2 = Math.abs((int) newEvent.getX(0) - (int) newEvent.getX(1)); + int distXSquare = (deltaX2 - deltaX1) * (deltaX2 - deltaX1); + + int deltaY1 = Math.abs((int) oldEvent.getY(0) - (int) oldEvent.getY(1)); + int deltaY2 = Math.abs((int) newEvent.getY(0) - (int) newEvent.getY(1)); + int distYSquare = (deltaY2 - deltaY1) * (deltaY2 - deltaY1); + + return (distXSquare + distYSquare) > mPointerDistanceSquare; + } + + /** + * The listener that is used to notify when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnGestureListener}. + */ + public interface OnDoubleGestureListener { + + /** + * Notified when a multi tap event starts + */ + boolean onDoubleTouchDown(MotionEvent e); + + /** + * Notified when a multi tap event ends + */ + boolean onDoubleTouchUp(MotionEvent e); + + /** + * Notified when a tap occurs with the up {@link MotionEvent} + * that triggered it. + * + * @param e The up motion event that completed the first tap + * @return true if the event is consumed, else false + */ + boolean onDoubleTouchSingleTap(MotionEvent e); + + /** + * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the + * current move {@link MotionEvent}. The distance in x and y is also supplied for + * convenience. + * + * @param e1 The first down motion event that started the scrolling. + * @param e2 The move motion event that triggered the current onScroll. + * @param distanceX The distance along the X axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @param distanceY The distance along the Y axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @return true if the event is consumed, else false + */ + boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2); + } + + /* + private void dumpEvent(MotionEvent event) { + String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" , + "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" }; + StringBuilder sb = new StringBuilder(); + int action = event.getAction(); + int actionCode = action & MotionEvent.ACTION_MASK; + sb.append("event ACTION_" ).append(names[actionCode]); + if (actionCode == MotionEvent.ACTION_POINTER_DOWN + || actionCode == MotionEvent.ACTION_POINTER_UP) { + sb.append("(pid " ).append( + action >> MotionEvent.ACTION_POINTER_ID_SHIFT); + sb.append(")" ); + } + sb.append("[" ); + for (int i = 0; i < event.getPointerCount(); i++) { + sb.append("#" ).append(i); + sb.append("(pid " ).append(event.getPointerId(i)); + sb.append(")=" ).append((int) event.getX(i)); + sb.append("," ).append((int) event.getY(i)); + if (i + 1 < event.getPointerCount()) + sb.append(";" ); + } + sb.append("]" ); + Log.d("DoubleDetector", sb.toString()); + } + */ + + private class GestureHandler extends Handler { + GestureHandler() { + super(); + } + + GestureHandler(Handler handler) { + super(handler.getLooper()); + } + + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java new file mode 100644 index 0000000..71c1e6f --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modified for aFreeRDP by Martin Fleisz (martin.fleisz@thincast.com) + */ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +public class GestureDetector { + + private static final int TAP_TIMEOUT = 100; + private static final int DOUBLE_TAP_TIMEOUT = 200; + // Distance a touch can wander before we think the user is the first touch in a sequence of double tap + private static final int LARGE_TOUCH_SLOP = 18; + // Distance between the first touch and second touch to still be considered a double tap + private static final int DOUBLE_TAP_SLOP = 100; + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + private static final int TAP = 3; + private final Handler mHandler; + private final OnGestureListener mListener; + private int mTouchSlopSquare; + private int mLargeTouchSlopSquare; + private int mDoubleTapSlopSquare; + private int mLongpressTimeout = 100; + private OnDoubleTapListener mDoubleTapListener; + private boolean mStillDown; + private boolean mInLongPress; + private boolean mAlwaysInTapRegion; + private boolean mAlwaysInBiggerTapRegion; + private MotionEvent mCurrentDownEvent; + private MotionEvent mPreviousUpEvent; + /** + * True when the user is still touching for the second tap (down, move, and + * up events). Can only be true if there is a double tap listener attached. + */ + private boolean mIsDoubleTapping; + private float mLastMotionY; + private float mLastMotionX; + private boolean mIsLongpressEnabled; + /** + * True if we are at a target API level of >= Froyo or the developer can + * explicitly set it. If true, input events with > 1 pointer will be ignored + * so we can work side by side with multitouch gesture detectors. + */ + private boolean mIgnoreMultitouch; + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener) { + this(context, listener, null); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler) { + this(context, listener, handler, context != null && + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * @param ignoreMultitouch whether events involving more than one pointer should + * be ignored. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler, + boolean ignoreMultitouch) { + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + init(context, ignoreMultitouch); + } + + private void init(Context context, boolean ignoreMultitouch) { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + mIgnoreMultitouch = ignoreMultitouch; + + // Fallback to support pre-donuts releases + int touchSlop, largeTouchSlop, doubleTapSlop; + if (context == null) { + //noinspection deprecation + touchSlop = ViewConfiguration.getTouchSlop(); + largeTouchSlop = touchSlop + 2; + doubleTapSlop = DOUBLE_TAP_SLOP; + } else { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final float density = metrics.density; + final ViewConfiguration configuration = ViewConfiguration.get(context); + touchSlop = configuration.getScaledTouchSlop(); + largeTouchSlop = (int) (density * LARGE_TOUCH_SLOP + 0.5f); + doubleTapSlop = configuration.getScaledDoubleTapSlop(); + } + mTouchSlopSquare = touchSlop * touchSlop; + mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop; + mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * Sets the listener which will be called for double-tap and related + * gestures. + * + * @param onDoubleTapListener the listener invoked for all the callbacks, or + * null to stop listening for double-tap gestures. + */ + public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { + mDoubleTapListener = onDoubleTapListener; + } + + /** + * Set whether longpress is enabled, if this is enabled when a user + * presses and holds down you get a longpress event and nothing further. + * If it's disabled the user can press and hold down and then later + * moved their finger and you will get scroll events. By default + * longpress is enabled. + * + * @param isLongpressEnabled whether longpress should be enabled. + */ + public void setIsLongpressEnabled(boolean isLongpressEnabled) { + mIsLongpressEnabled = isLongpressEnabled; + } + + /** + * @return true if longpress is enabled, else false. + */ + public boolean isLongpressEnabled() { + return mIsLongpressEnabled; + } + + public void setLongPressTimeout(int timeout) { + mLongpressTimeout = timeout; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + final float y = ev.getY(); + final float x = ev.getX(); + + boolean handled = false; + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_POINTER_DOWN: + if (mIgnoreMultitouch) { + // Multitouch event - abort. + cancel(); + } + break; + + case MotionEvent.ACTION_POINTER_UP: + // Ending a multitouch gesture and going back to 1 finger + if (mIgnoreMultitouch && ev.getPointerCount() == 2) { + int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0; + mLastMotionX = ev.getX(index); + mLastMotionY = ev.getY(index); + } + break; + + case MotionEvent.ACTION_DOWN: + if (mDoubleTapListener != null) { + boolean hadTapMessage = mHandler.hasMessages(TAP); + if (hadTapMessage) mHandler.removeMessages(TAP); + if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && + isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else { + // This is a first tap + mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + } + } + + mLastMotionX = x; + mLastMotionY = y; + if (mCurrentDownEvent != null) { + mCurrentDownEvent.recycle(); + } + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + + if (mIsLongpressEnabled) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + + TAP_TIMEOUT + mLongpressTimeout); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); + handled |= mListener.onDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + if (mIgnoreMultitouch && ev.getPointerCount() > 1) { + break; + } + final float scrollX = mLastMotionX - x; + final float scrollY = mLastMotionY - y; + if (mIsDoubleTapping) { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mAlwaysInTapRegion) { + final int deltaX = (int) (x - mCurrentDownEvent.getX()); + final int deltaY = (int) (y - mCurrentDownEvent.getY()); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + if (distance > mTouchSlopSquare) { + mLastMotionX = x; + mLastMotionY = y; + mAlwaysInTapRegion = false; + mHandler.removeMessages(TAP); + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + if (distance > mLargeTouchSlopSquare) { + mAlwaysInBiggerTapRegion = false; + } + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastMotionX = x; + mLastMotionY = y; + } + break; + + case MotionEvent.ACTION_UP: + mStillDown = false; + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mIsDoubleTapping) { + // Finally, give the up event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mInLongPress) { + mHandler.removeMessages(TAP); + mListener.onLongPressUp(ev); + mInLongPress = false; + } else if (mAlwaysInTapRegion) { + handled = mListener.onSingleTapUp(mCurrentDownEvent); + } else { + // A fling must travel the minimum tap distance + } + if (mPreviousUpEvent != null) { + mPreviousUpEvent.recycle(); + } + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + mIsDoubleTapping = false; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + handled |= mListener.onUp(ev); + break; + case MotionEvent.ACTION_CANCEL: + cancel(); + break; + } + return handled; + } + + private void cancel() { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + mAlwaysInTapRegion = false; // ensures that we won't receive an OnSingleTap notification when a 2-Finger tap is performed + mIsDoubleTapping = false; + mStillDown = false; + if (mInLongPress) { + mInLongPress = false; + } + } + + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, + MotionEvent secondDown) { + if (!mAlwaysInBiggerTapRegion) { + return false; + } + + if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) { + return false; + } + + int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); + int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); + return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); + } + + private void dispatchLongPress() { + mHandler.removeMessages(TAP); + mInLongPress = true; + mListener.onLongPress(mCurrentDownEvent); + } + + /** + * The listener that is used to notify when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnGestureListener}. + */ + public interface OnGestureListener { + + /** + * Notified when a tap occurs with the down {@link MotionEvent} + * that triggered it. This will be triggered immediately for + * every down event. All other events should be preceded by this. + * + * @param e The down motion event. + */ + boolean onDown(MotionEvent e); + + /** + * Notified when a tap finishes with the up {@link MotionEvent} + * that triggered it. This will be triggered immediately for + * every up event. All other events should be preceded by this. + * + * @param e The up motion event. + */ + boolean onUp(MotionEvent e); + + /** + * The user has performed a down {@link MotionEvent} and not performed + * a move or up yet. This event is commonly used to provide visual + * feedback to the user to let them know that their action has been + * recognized i.e. highlight an element. + * + * @param e The down motion event + */ + void onShowPress(MotionEvent e); + + /** + * Notified when a tap occurs with the up {@link MotionEvent} + * that triggered it. + * + * @param e The up motion event that completed the first tap + * @return true if the event is consumed, else false + */ + boolean onSingleTapUp(MotionEvent e); + + /** + * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the + * current move {@link MotionEvent}. The distance in x and y is also supplied for + * convenience. + * + * @param e1 The first down motion event that started the scrolling. + * @param e2 The move motion event that triggered the current onScroll. + * @param distanceX The distance along the X axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @param distanceY The distance along the Y axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @return true if the event is consumed, else false + */ + boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); + + /** + * Notified when a long press occurs with the initial on down {@link MotionEvent} + * that trigged it. + * + * @param e The initial on down motion event that started the longpress. + */ + void onLongPress(MotionEvent e); + + /** + * Notified when a long press ends with the final {@link MotionEvent}. + * + * @param e The up motion event that ended the longpress. + */ + void onLongPressUp(MotionEvent e); + } + + /** + * The listener that is used to notify when a double-tap or a confirmed + * single-tap occur. + */ + public interface OnDoubleTapListener { + /** + * Notified when a single-tap occurs. + *

+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this + * will only be called after the detector is confident that the user's + * first tap is not followed by a second tap leading to a double-tap + * gesture. + * + * @param e The down motion event of the single-tap. + * @return true if the event is consumed, else false + */ + boolean onSingleTapConfirmed(MotionEvent e); + + /** + * Notified when a double-tap occurs. + * + * @param e The down motion event of the first tap of the double-tap. + * @return true if the event is consumed, else false + */ + boolean onDoubleTap(MotionEvent e); + + /** + * Notified when an event within a double-tap gesture occurs, including + * the down, move, and up events. + * + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event is consumed, else false + */ + boolean onDoubleTapEvent(MotionEvent e); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of all the gestures. This implements all methods in the + * {@link OnGestureListener} and {@link OnDoubleTapListener} but does + * nothing and return {@code false} for all applicable methods. + */ + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener { + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + public void onLongPress(MotionEvent e) { + } + + public void onLongPressUp(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + return false; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onDown(MotionEvent e) { + return false; + } + + public boolean onUp(MotionEvent e) { + return false; + } + + public boolean onDoubleTap(MotionEvent e) { + return false; + } + + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + + public boolean onSingleTapConfirmed(MotionEvent e) { + return false; + } + } + + private class GestureHandler extends Handler { + GestureHandler() { + super(); + } + + GestureHandler(Handler handler) { + super(handler.getLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null && !mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } + break; + + default: + throw new RuntimeException("Unknown message " + msg); //never + } + } + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java new file mode 100644 index 0000000..a3e951c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java @@ -0,0 +1,87 @@ +/* + EditTextPreference to store/load integer values + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.EditTextPreference; +import android.util.AttributeSet; + +import com.freerdp.freerdpcore.R; + +public class IntEditTextPreference extends EditTextPreference { + + private int bounds_min, bounds_max, bounds_default; + + public IntEditTextPreference(Context context) { + super(context); + init(context, null); + } + + public IntEditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + if (attrs != null) { + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IntEditTextPreference, 0, 0); + bounds_min = array.getInt(R.styleable.IntEditTextPreference_bounds_min, Integer.MIN_VALUE); + bounds_max = array.getInt(R.styleable.IntEditTextPreference_bounds_max, Integer.MAX_VALUE); + bounds_default = array.getInt(R.styleable.IntEditTextPreference_bounds_default, 0); + array.recycle(); + } else { + bounds_min = Integer.MIN_VALUE; + bounds_max = Integer.MAX_VALUE; + bounds_default = 0; + } + } + + public void setBounds(int min, int max, int defaultValue) { + bounds_min = min; + bounds_max = max; + bounds_default = defaultValue; + } + + @Override + protected String getPersistedString(String defaultReturnValue) { + int value = getPersistedInt(-1); + if (value > bounds_max || value < bounds_min) + value = bounds_default; + return String.valueOf(value); + } + + @Override + protected boolean persistString(String value) { + return persistInt(Integer.valueOf(value)); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + // prevent exception when an empty value is persisted + if (getEditText().getText().length() == 0) + getEditText().setText("0"); + + // check bounds + int value = Integer.valueOf(getEditText().getText().toString()); + if (value > bounds_max || value < bounds_min) + value = bounds_default; + getEditText().setText(String.valueOf(value)); + } + + super.onDialogClosed(positiveResult); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java new file mode 100644 index 0000000..a641061 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java @@ -0,0 +1,35 @@ +/* + ListPreference to store/load integer values + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +public class IntListPreference extends ListPreference { + + public IntListPreference(Context context) { + super(context); + } + + public IntListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected String getPersistedString(String defaultReturnValue) { + return String.valueOf(getPersistedInt(-1)); + } + + @Override + protected boolean persistString(String value) { + return persistInt(Integer.valueOf(value)); + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java new file mode 100644 index 0000000..170f415 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java @@ -0,0 +1,641 @@ +/* + Android Keyboard Mapping + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.view.KeyEvent; + +import com.freerdp.freerdpcore.R; + +public class KeyboardMapper { + public static final int KEYBOARD_TYPE_FUNCTIONKEYS = 1; + public static final int KEYBOARD_TYPE_NUMPAD = 2; + public static final int KEYBOARD_TYPE_CURSOR = 3; + + // defines key states for modifier keys - locked means on and no auto-release if an other key is pressed + public static final int KEYSTATE_ON = 1; + public static final int KEYSTATE_LOCKED = 2; + public static final int KEYSTATE_OFF = 3; + final static int VK_LBUTTON = 0x01; + final static int VK_RBUTTON = 0x02; + final static int VK_CANCEL = 0x03; + final static int VK_MBUTTON = 0x04; + final static int VK_XBUTTON1 = 0x05; + final static int VK_XBUTTON2 = 0x06; + final static int VK_BACK = 0x08; + final static int VK_TAB = 0x09; + final static int VK_CLEAR = 0x0C; + final static int VK_RETURN = 0x0D; + final static int VK_SHIFT = 0x10; + final static int VK_CONTROL = 0x11; + final static int VK_MENU = 0x12; + final static int VK_PAUSE = 0x13; + final static int VK_CAPITAL = 0x14; + final static int VK_KANA = 0x15; + final static int VK_HANGUEL = 0x15; + final static int VK_HANGUL = 0x15; + final static int VK_JUNJA = 0x17; + final static int VK_FINAL = 0x18; + final static int VK_HANJA = 0x19; + final static int VK_KANJI = 0x19; + final static int VK_ESCAPE = 0x1B; + final static int VK_CONVERT = 0x1C; + final static int VK_NONCONVERT = 0x1D; + final static int VK_ACCEPT = 0x1E; + final static int VK_MODECHANGE = 0x1F; + final static int VK_SPACE = 0x20; + final static int VK_PRIOR = 0x21; + final static int VK_NEXT = 0x22; + final static int VK_END = 0x23; + final static int VK_HOME = 0x24; + final static int VK_LEFT = 0x25; + final static int VK_UP = 0x26; + final static int VK_RIGHT = 0x27; + final static int VK_DOWN = 0x28; + final static int VK_SELECT = 0x29; + final static int VK_PRINT = 0x2A; + final static int VK_EXECUTE = 0x2B; + final static int VK_SNAPSHOT = 0x2C; + final static int VK_INSERT = 0x2D; + final static int VK_DELETE = 0x2E; + final static int VK_HELP = 0x2F; + final static int VK_KEY_0 = 0x30; + final static int VK_KEY_1 = 0x31; + final static int VK_KEY_2 = 0x32; + final static int VK_KEY_3 = 0x33; + final static int VK_KEY_4 = 0x34; + final static int VK_KEY_5 = 0x35; + final static int VK_KEY_6 = 0x36; + final static int VK_KEY_7 = 0x37; + final static int VK_KEY_8 = 0x38; + final static int VK_KEY_9 = 0x39; + final static int VK_KEY_A = 0x41; + final static int VK_KEY_B = 0x42; + final static int VK_KEY_C = 0x43; + final static int VK_KEY_D = 0x44; + final static int VK_KEY_E = 0x45; + final static int VK_KEY_F = 0x46; + final static int VK_KEY_G = 0x47; + final static int VK_KEY_H = 0x48; + final static int VK_KEY_I = 0x49; + final static int VK_KEY_J = 0x4A; + final static int VK_KEY_K = 0x4B; + final static int VK_KEY_L = 0x4C; + final static int VK_KEY_M = 0x4D; + final static int VK_KEY_N = 0x4E; + final static int VK_KEY_O = 0x4F; + final static int VK_KEY_P = 0x50; + final static int VK_KEY_Q = 0x51; + final static int VK_KEY_R = 0x52; + final static int VK_KEY_S = 0x53; + final static int VK_KEY_T = 0x54; + final static int VK_KEY_U = 0x55; + final static int VK_KEY_V = 0x56; + final static int VK_KEY_W = 0x57; + final static int VK_KEY_X = 0x58; + final static int VK_KEY_Y = 0x59; + final static int VK_KEY_Z = 0x5A; + final static int VK_LWIN = 0x5B; + final static int VK_RWIN = 0x5C; + final static int VK_APPS = 0x5D; + final static int VK_SLEEP = 0x5F; + final static int VK_NUMPAD0 = 0x60; + final static int VK_NUMPAD1 = 0x61; + final static int VK_NUMPAD2 = 0x62; + final static int VK_NUMPAD3 = 0x63; + final static int VK_NUMPAD4 = 0x64; + final static int VK_NUMPAD5 = 0x65; + final static int VK_NUMPAD6 = 0x66; + final static int VK_NUMPAD7 = 0x67; + final static int VK_NUMPAD8 = 0x68; + final static int VK_NUMPAD9 = 0x69; + final static int VK_MULTIPLY = 0x6A; + final static int VK_ADD = 0x6B; + final static int VK_SEPARATOR = 0x6C; + final static int VK_SUBTRACT = 0x6D; + final static int VK_DECIMAL = 0x6E; + final static int VK_DIVIDE = 0x6F; + final static int VK_F1 = 0x70; + final static int VK_F2 = 0x71; + final static int VK_F3 = 0x72; + final static int VK_F4 = 0x73; + final static int VK_F5 = 0x74; + final static int VK_F6 = 0x75; + final static int VK_F7 = 0x76; + final static int VK_F8 = 0x77; + final static int VK_F9 = 0x78; + final static int VK_F10 = 0x79; + final static int VK_F11 = 0x7A; + final static int VK_F12 = 0x7B; + final static int VK_F13 = 0x7C; + final static int VK_F14 = 0x7D; + final static int VK_F15 = 0x7E; + final static int VK_F16 = 0x7F; + final static int VK_F17 = 0x80; + final static int VK_F18 = 0x81; + final static int VK_F19 = 0x82; + final static int VK_F20 = 0x83; + final static int VK_F21 = 0x84; + final static int VK_F22 = 0x85; + final static int VK_F23 = 0x86; + final static int VK_F24 = 0x87; + final static int VK_NUMLOCK = 0x90; + final static int VK_SCROLL = 0x91; + final static int VK_LSHIFT = 0xA0; + final static int VK_RSHIFT = 0xA1; + final static int VK_LCONTROL = 0xA2; + final static int VK_RCONTROL = 0xA3; + final static int VK_LMENU = 0xA4; + final static int VK_RMENU = 0xA5; + final static int VK_BROWSER_BACK = 0xA6; + final static int VK_BROWSER_FORWARD = 0xA7; + final static int VK_BROWSER_REFRESH = 0xA8; + final static int VK_BROWSER_STOP = 0xA9; + final static int VK_BROWSER_SEARCH = 0xAA; + final static int VK_BROWSER_FAVORITES = 0xAB; + final static int VK_BROWSER_HOME = 0xAC; + final static int VK_VOLUME_MUTE = 0xAD; + final static int VK_VOLUME_DOWN = 0xAE; + final static int VK_VOLUME_UP = 0xAF; + final static int VK_MEDIA_NEXT_TRACK = 0xB0; + final static int VK_MEDIA_PREV_TRACK = 0xB1; + final static int VK_MEDIA_STOP = 0xB2; + final static int VK_MEDIA_PLAY_PAUSE = 0xB3; + final static int VK_LAUNCH_MAIL = 0xB4; + final static int VK_LAUNCH_MEDIA_SELECT = 0xB5; + final static int VK_LAUNCH_APP1 = 0xB6; + final static int VK_LAUNCH_APP2 = 0xB7; + final static int VK_OEM_1 = 0xBA; + final static int VK_OEM_PLUS = 0xBB; + final static int VK_OEM_COMMA = 0xBC; + final static int VK_OEM_MINUS = 0xBD; + final static int VK_OEM_PERIOD = 0xBE; + final static int VK_OEM_2 = 0xBF; + final static int VK_OEM_3 = 0xC0; + final static int VK_ABNT_C1 = 0xC1; + final static int VK_ABNT_C2 = 0xC2; + final static int VK_OEM_4 = 0xDB; + final static int VK_OEM_5 = 0xDC; + final static int VK_OEM_6 = 0xDD; + final static int VK_OEM_7 = 0xDE; + final static int VK_OEM_8 = 0xDF; + final static int VK_OEM_102 = 0xE2; + final static int VK_PROCESSKEY = 0xE5; + final static int VK_PACKET = 0xE7; + final static int VK_ATTN = 0xF6; + final static int VK_CRSEL = 0xF7; + final static int VK_EXSEL = 0xF8; + final static int VK_EREOF = 0xF9; + final static int VK_PLAY = 0xFA; + final static int VK_ZOOM = 0xFB; + final static int VK_NONAME = 0xFC; + final static int VK_PA1 = 0xFD; + final static int VK_OEM_CLEAR = 0xFE; + final static int VK_UNICODE = 0x80000000; + final static int VK_EXT_KEY = 0x00000100; + // key codes to switch between custom keyboard + private final static int EXTKEY_KBFUNCTIONKEYS = 0x1100; + private final static int EXTKEY_KBNUMPAD = 0x1101; + private final static int EXTKEY_KBCURSOR = 0x1102; + // this flag indicates if we got a VK or a unicode character in our translation map + private static final int KEY_FLAG_UNICODE = 0x80000000; + // this flag indicates if the key is a toggle key (remains down when pressed and goes up if pressed again) + private static final int KEY_FLAG_TOGGLE = 0x40000000; + private static int[] keymapAndroid; + private static int[] keymapExt; + private static boolean initialized = false; + private KeyProcessingListener listener = null; + private boolean shiftPressed = false; + private boolean ctrlPressed = false; + private boolean altPressed = false; + private boolean winPressed = false; + private long lastModifierTime; + private int lastModifierKeyCode = -1; + private boolean isShiftLocked = false; + private boolean isCtrlLocked = false; + private boolean isAltLocked = false; + private boolean isWinLocked = false; + + public void init(Context context) { + if (initialized == true) + return; + + keymapAndroid = new int[256]; + + keymapAndroid[KeyEvent.KEYCODE_0] = VK_KEY_0; + keymapAndroid[KeyEvent.KEYCODE_1] = VK_KEY_1; + keymapAndroid[KeyEvent.KEYCODE_2] = VK_KEY_2; + keymapAndroid[KeyEvent.KEYCODE_3] = VK_KEY_3; + keymapAndroid[KeyEvent.KEYCODE_4] = VK_KEY_4; + keymapAndroid[KeyEvent.KEYCODE_5] = VK_KEY_5; + keymapAndroid[KeyEvent.KEYCODE_6] = VK_KEY_6; + keymapAndroid[KeyEvent.KEYCODE_7] = VK_KEY_7; + keymapAndroid[KeyEvent.KEYCODE_8] = VK_KEY_8; + keymapAndroid[KeyEvent.KEYCODE_9] = VK_KEY_9; + + keymapAndroid[KeyEvent.KEYCODE_A] = VK_KEY_A; + keymapAndroid[KeyEvent.KEYCODE_B] = VK_KEY_B; + keymapAndroid[KeyEvent.KEYCODE_C] = VK_KEY_C; + keymapAndroid[KeyEvent.KEYCODE_D] = VK_KEY_D; + keymapAndroid[KeyEvent.KEYCODE_E] = VK_KEY_E; + keymapAndroid[KeyEvent.KEYCODE_F] = VK_KEY_F; + keymapAndroid[KeyEvent.KEYCODE_G] = VK_KEY_G; + keymapAndroid[KeyEvent.KEYCODE_H] = VK_KEY_H; + keymapAndroid[KeyEvent.KEYCODE_I] = VK_KEY_I; + keymapAndroid[KeyEvent.KEYCODE_J] = VK_KEY_J; + keymapAndroid[KeyEvent.KEYCODE_K] = VK_KEY_K; + keymapAndroid[KeyEvent.KEYCODE_L] = VK_KEY_L; + keymapAndroid[KeyEvent.KEYCODE_M] = VK_KEY_M; + keymapAndroid[KeyEvent.KEYCODE_N] = VK_KEY_N; + keymapAndroid[KeyEvent.KEYCODE_O] = VK_KEY_O; + keymapAndroid[KeyEvent.KEYCODE_P] = VK_KEY_P; + keymapAndroid[KeyEvent.KEYCODE_Q] = VK_KEY_Q; + keymapAndroid[KeyEvent.KEYCODE_R] = VK_KEY_R; + keymapAndroid[KeyEvent.KEYCODE_S] = VK_KEY_S; + keymapAndroid[KeyEvent.KEYCODE_T] = VK_KEY_T; + keymapAndroid[KeyEvent.KEYCODE_U] = VK_KEY_U; + keymapAndroid[KeyEvent.KEYCODE_V] = VK_KEY_V; + keymapAndroid[KeyEvent.KEYCODE_W] = VK_KEY_W; + keymapAndroid[KeyEvent.KEYCODE_X] = VK_KEY_X; + keymapAndroid[KeyEvent.KEYCODE_Y] = VK_KEY_Y; + keymapAndroid[KeyEvent.KEYCODE_Z] = VK_KEY_Z; + + keymapAndroid[KeyEvent.KEYCODE_DEL] = VK_BACK; + keymapAndroid[KeyEvent.KEYCODE_ENTER] = VK_RETURN; + keymapAndroid[KeyEvent.KEYCODE_SPACE] = VK_SPACE; + keymapAndroid[KeyEvent.KEYCODE_TAB] = VK_TAB; +// keymapAndroid[KeyEvent.KEYCODE_SHIFT_LEFT] = VK_LSHIFT; +// keymapAndroid[KeyEvent.KEYCODE_SHIFT_RIGHT] = VK_RSHIFT; + +// keymapAndroid[KeyEvent.KEYCODE_DPAD_DOWN] = VK_DOWN; +// keymapAndroid[KeyEvent.KEYCODE_DPAD_LEFT] = VK_LEFT; +// keymapAndroid[KeyEvent.KEYCODE_DPAD_RIGHT] = VK_RIGHT; +// keymapAndroid[KeyEvent.KEYCODE_DPAD_UP] = VK_UP; + +// keymapAndroid[KeyEvent.KEYCODE_COMMA] = VK_OEM_COMMA; +// keymapAndroid[KeyEvent.KEYCODE_PERIOD] = VK_OEM_PERIOD; +// keymapAndroid[KeyEvent.KEYCODE_MINUS] = VK_OEM_MINUS; +// keymapAndroid[KeyEvent.KEYCODE_PLUS] = VK_OEM_PLUS; + +// keymapAndroid[KeyEvent.KEYCODE_ALT_LEFT] = VK_LMENU; +// keymapAndroid[KeyEvent.KEYCODE_ALT_RIGHT] = VK_RMENU; + +// keymapAndroid[KeyEvent.KEYCODE_AT] = (KEY_FLAG_UNICODE | 64); +// keymapAndroid[KeyEvent.KEYCODE_APOSTROPHE] = (KEY_FLAG_UNICODE | 39); +// keymapAndroid[KeyEvent.KEYCODE_BACKSLASH] = (KEY_FLAG_UNICODE | 92); +// keymapAndroid[KeyEvent.KEYCODE_COMMA] = (KEY_FLAG_UNICODE | 44); +// keymapAndroid[KeyEvent.KEYCODE_EQUALS] = (KEY_FLAG_UNICODE | 61); +// keymapAndroid[KeyEvent.KEYCODE_GRAVE] = (KEY_FLAG_UNICODE | 96); +// keymapAndroid[KeyEvent.KEYCODE_LEFT_BRACKET] = (KEY_FLAG_UNICODE | 91); +// keymapAndroid[KeyEvent.KEYCODE_RIGHT_BRACKET] = (KEY_FLAG_UNICODE | 93); +// keymapAndroid[KeyEvent.KEYCODE_MINUS] = (KEY_FLAG_UNICODE | 45); +// keymapAndroid[KeyEvent.KEYCODE_PERIOD] = (KEY_FLAG_UNICODE | 46); +// keymapAndroid[KeyEvent.KEYCODE_PLUS] = (KEY_FLAG_UNICODE | 43); +// keymapAndroid[KeyEvent.KEYCODE_POUND] = (KEY_FLAG_UNICODE | 35); +// keymapAndroid[KeyEvent.KEYCODE_SEMICOLON] = (KEY_FLAG_UNICODE | 59); +// keymapAndroid[KeyEvent.KEYCODE_SLASH] = (KEY_FLAG_UNICODE | 47); +// keymapAndroid[KeyEvent.KEYCODE_STAR] = (KEY_FLAG_UNICODE | 42); + + // special keys mapping + keymapExt = new int[256]; + keymapExt[context.getResources().getInteger(R.integer.keycode_F1)] = VK_F1; + keymapExt[context.getResources().getInteger(R.integer.keycode_F2)] = VK_F2; + keymapExt[context.getResources().getInteger(R.integer.keycode_F3)] = VK_F3; + keymapExt[context.getResources().getInteger(R.integer.keycode_F4)] = VK_F4; + keymapExt[context.getResources().getInteger(R.integer.keycode_F5)] = VK_F5; + keymapExt[context.getResources().getInteger(R.integer.keycode_F6)] = VK_F6; + keymapExt[context.getResources().getInteger(R.integer.keycode_F7)] = VK_F7; + keymapExt[context.getResources().getInteger(R.integer.keycode_F8)] = VK_F8; + keymapExt[context.getResources().getInteger(R.integer.keycode_F9)] = VK_F9; + keymapExt[context.getResources().getInteger(R.integer.keycode_F10)] = VK_F10; + keymapExt[context.getResources().getInteger(R.integer.keycode_F11)] = VK_F11; + keymapExt[context.getResources().getInteger(R.integer.keycode_F12)] = VK_F12; + keymapExt[context.getResources().getInteger(R.integer.keycode_tab)] = VK_TAB; + keymapExt[context.getResources().getInteger(R.integer.keycode_print)] = VK_PRINT; + keymapExt[context.getResources().getInteger(R.integer.keycode_insert)] = VK_INSERT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_delete)] = VK_DELETE | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_home)] = VK_HOME | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_end)] = VK_END | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_pgup)] = VK_PRIOR | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_pgdn)] = VK_NEXT | VK_EXT_KEY; + + // numpad mapping + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_0)] = VK_NUMPAD0; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_1)] = VK_NUMPAD1; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_2)] = VK_NUMPAD2; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_3)] = VK_NUMPAD3; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_4)] = VK_NUMPAD4; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_5)] = VK_NUMPAD5; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_6)] = VK_NUMPAD6; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_7)] = VK_NUMPAD7; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_8)] = VK_NUMPAD8; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_9)] = VK_NUMPAD9; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_numlock)] = VK_NUMLOCK; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_add)] = VK_ADD; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_comma)] = VK_DECIMAL; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_divide)] = VK_DIVIDE | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_enter)] = VK_RETURN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_multiply)] = VK_MULTIPLY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_subtract)] = VK_SUBTRACT; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_equals)] = (KEY_FLAG_UNICODE | 61); + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_left_paren)] = (KEY_FLAG_UNICODE | 40); + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_right_paren)] = (KEY_FLAG_UNICODE | 41); + + // cursor key codes + keymapExt[context.getResources().getInteger(R.integer.keycode_up)] = VK_UP | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_down)] = VK_DOWN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_left)] = VK_LEFT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_right)] = VK_RIGHT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_enter)] = VK_RETURN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_backspace)] = VK_BACK; + + // shared keys + keymapExt[context.getResources().getInteger(R.integer.keycode_win)] = VK_LWIN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_menu)] = VK_APPS | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_esc)] = VK_ESCAPE; + +/* keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_ctrl)] = VK_LCONTROL; + keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_alt)] = VK_LMENU; + keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_shift)] = VK_LSHIFT; +*/ + // get custom keyboard key codes + keymapExt[context.getResources().getInteger(R.integer.keycode_specialkeys_keyboard)] = EXTKEY_KBFUNCTIONKEYS; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_keyboard)] = EXTKEY_KBNUMPAD; + keymapExt[context.getResources().getInteger(R.integer.keycode_cursor_keyboard)] = EXTKEY_KBCURSOR; + + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_shift)] = (KEY_FLAG_TOGGLE | VK_LSHIFT); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_ctrl)] = (KEY_FLAG_TOGGLE | VK_LCONTROL); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_alt)] = (KEY_FLAG_TOGGLE | VK_LMENU); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_win)] = (KEY_FLAG_TOGGLE | VK_LWIN); + + initialized = true; + } + + public void reset(KeyProcessingListener listener) { + shiftPressed = false; + ctrlPressed = false; + altPressed = false; + winPressed = false; + setKeyProcessingListener(listener); + } + + public void setKeyProcessingListener(KeyProcessingListener listener) { + this.listener = listener; + } + + public boolean processAndroidKeyEvent(KeyEvent event) { + switch (event.getAction()) { + // we only process down events + case KeyEvent.ACTION_UP: { + return false; + } + + case KeyEvent.ACTION_DOWN: { + boolean modifierActive = isModifierPressed(); + // if a modifier is pressed we will send a VK event (if possible) so that key combinations will be + // recognized correctly. Otherwise we will send the unicode key. At the end we will reset all modifiers + // and notifiy our listener. + int vkcode = getVirtualKeyCode(event.getKeyCode()); + if ((vkcode & KEY_FLAG_UNICODE) != 0) + listener.processUnicodeKey(vkcode & (~KEY_FLAG_UNICODE)); + // if we got a valid vkcode send it - except for letters/numbers if a modifier is active + else if (vkcode > 0 && (event.getMetaState() & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) { + listener.processVirtualKey(vkcode, true); + listener.processVirtualKey(vkcode, false); + } else if (event.isShiftPressed() && vkcode != 0) { + listener.processVirtualKey(VK_LSHIFT, true); + listener.processVirtualKey(vkcode, true); + listener.processVirtualKey(vkcode, false); + listener.processVirtualKey(VK_LSHIFT, false); + } else if (event.getUnicodeChar() != 0) + listener.processUnicodeKey(event.getUnicodeChar()); + else + return false; + + // reset any pending toggle states if a modifier was pressed + if (modifierActive) + resetModifierKeysAfterInput(false); + return true; + } + + case KeyEvent.ACTION_MULTIPLE: { + String str = event.getCharacters(); + for (int i = 0; i < str.length(); i++) + listener.processUnicodeKey(str.charAt(i)); + return true; + } + + default: + break; + } + return false; + } + + public void processCustomKeyEvent(int keycode) { + int extCode = getExtendedKeyCode(keycode); + if (extCode == 0) + return; + + // toggle button pressed? + if ((extCode & KEY_FLAG_TOGGLE) != 0) { + processToggleButton(extCode & (~KEY_FLAG_TOGGLE)); + return; + } + + // keyboard switch button pressed? + if (extCode == EXTKEY_KBFUNCTIONKEYS || extCode == EXTKEY_KBNUMPAD || extCode == EXTKEY_KBCURSOR) { + switchKeyboard(extCode); + return; + } + + // nope - see if we got a unicode or vk + if ((extCode & KEY_FLAG_UNICODE) != 0) + listener.processUnicodeKey(extCode & (~KEY_FLAG_UNICODE)); + else { + listener.processVirtualKey(extCode, true); + listener.processVirtualKey(extCode, false); + } + + resetModifierKeysAfterInput(false); + } + + public void sendAltF4() { + listener.processVirtualKey(VK_LMENU, true); + listener.processVirtualKey(VK_F4, true); + listener.processVirtualKey(VK_F4, false); + listener.processVirtualKey(VK_LMENU, false); + } + + private boolean isModifierPressed() { + return (shiftPressed || ctrlPressed || altPressed || winPressed); + } + + public int getModifierState(int keycode) { + int modifierCode = getExtendedKeyCode(keycode); + + // check and get real modifier keycode + if ((modifierCode & KEY_FLAG_TOGGLE) == 0) + return -1; + modifierCode = modifierCode & (~KEY_FLAG_TOGGLE); + + switch (modifierCode) { + case VK_LSHIFT: { + return (shiftPressed ? (isShiftLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + case VK_LCONTROL: { + return (ctrlPressed ? (isCtrlLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + case VK_LMENU: { + return (altPressed ? (isAltLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + case VK_LWIN: { + return (winPressed ? (isWinLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + } + + return -1; + } + + private int getVirtualKeyCode(int keycode) { + if (keycode >= 0 && keycode <= 0xFF) + return keymapAndroid[keycode]; + return 0; + } + + private int getExtendedKeyCode(int keycode) { + if (keycode >= 0 && keycode <= 0xFF) + return keymapExt[keycode]; + return 0; + } + + private void processToggleButton(int keycode) { + switch (keycode) { + case VK_LSHIFT: { + if (!checkToggleModifierLock(VK_LSHIFT)) { + isShiftLocked = false; + shiftPressed = !shiftPressed; + listener.processVirtualKey(VK_LSHIFT, shiftPressed); + } else + isShiftLocked = true; + break; + } + case VK_LCONTROL: { + if (!checkToggleModifierLock(VK_LCONTROL)) { + isCtrlLocked = false; + ctrlPressed = !ctrlPressed; + listener.processVirtualKey(VK_LCONTROL, ctrlPressed); + } else + isCtrlLocked = true; + break; + } + case VK_LMENU: { + if (!checkToggleModifierLock(VK_LMENU)) { + isAltLocked = false; + altPressed = !altPressed; + listener.processVirtualKey(VK_LMENU, altPressed); + } else + isAltLocked = true; + break; + } + case VK_LWIN: { + if (!checkToggleModifierLock(VK_LWIN)) { + isWinLocked = false; + winPressed = !winPressed; + listener.processVirtualKey(VK_LWIN | VK_EXT_KEY, winPressed); + } else + isWinLocked = true; + break; + } + } + listener.modifiersChanged(); + } + + public void clearlAllModifiers() { + resetModifierKeysAfterInput(true); + } + + private void resetModifierKeysAfterInput(boolean force) { + if (shiftPressed && (!isShiftLocked || force)) { + listener.processVirtualKey(VK_LSHIFT, false); + shiftPressed = false; + } + if (ctrlPressed && (!isCtrlLocked || force)) { + listener.processVirtualKey(VK_LCONTROL, false); + ctrlPressed = false; + } + if (altPressed && (!isAltLocked || force)) { + listener.processVirtualKey(VK_LMENU, false); + altPressed = false; + } + if (winPressed && (!isWinLocked || force)) { + listener.processVirtualKey(VK_LWIN | VK_EXT_KEY, false); + winPressed = false; + } + + if (listener != null) + listener.modifiersChanged(); + } + + private void switchKeyboard(int keycode) { + switch (keycode) { + case EXTKEY_KBFUNCTIONKEYS: { + listener.switchKeyboard(KEYBOARD_TYPE_FUNCTIONKEYS); + break; + } + + case EXTKEY_KBNUMPAD: { + listener.switchKeyboard(KEYBOARD_TYPE_NUMPAD); + break; + } + + case EXTKEY_KBCURSOR: { + listener.switchKeyboard(KEYBOARD_TYPE_CURSOR); + break; + } + + default: + break; + } + } + + private boolean checkToggleModifierLock(int keycode) { + long now = System.currentTimeMillis(); + + // was the same modifier hit? + if (lastModifierKeyCode != keycode) { + lastModifierKeyCode = keycode; + lastModifierTime = now; + return false; + } + + // within a certain time interval? + if (lastModifierTime + 800 > now) { + lastModifierTime = 0; + return true; + } else { + lastModifierTime = now; + return false; + } + } + + // interface that gets called for input handling + public interface KeyProcessingListener { + abstract void processVirtualKey(int virtualKeyCode, boolean down); + + abstract void processUnicodeKey(int unicodeKey); + + abstract void switchKeyboard(int keyboardType); + + abstract void modifiersChanged(); + } + +} + diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java new file mode 100644 index 0000000..2461486 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java @@ -0,0 +1,59 @@ +/* + Android Mouse Input Mapping + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; + +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; + +public class Mouse { + + private final static int PTRFLAGS_LBUTTON = 0x1000; + private final static int PTRFLAGS_RBUTTON = 0x2000; + + private final static int PTRFLAGS_DOWN = 0x8000; + private final static int PTRFLAGS_MOVE = 0x0800; + + private final static int PTRFLAGS_WHEEL = 0x0200; + private final static int PTRFLAGS_WHEEL_NEGATIVE = 0x0100; + + public static int getLeftButtonEvent(Context context, boolean down) { + if (ApplicationSettingsActivity.getSwapMouseButtons(context)) + return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0)); + else + return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0)); + } + + public static int getRightButtonEvent(Context context, boolean down) { + if (ApplicationSettingsActivity.getSwapMouseButtons(context)) + return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0)); + else + return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0)); + } + + public static int getMoveEvent() { + return PTRFLAGS_MOVE; + } + + public static int getScrollEvent(Context context, boolean down) { + int flags = PTRFLAGS_WHEEL; + + // invert scrolling? + if (ApplicationSettingsActivity.getInvertScrolling(context)) + down = !down; + + if (down) + flags |= (PTRFLAGS_WHEEL_NEGATIVE | 0x0088); + else + flags |= 0x0078; + return flags; + } + +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java new file mode 100644 index 0000000..c45d41d --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java @@ -0,0 +1,91 @@ +/* + Simple .RDP file parser + + Copyright 2013 Blaz Bacnik + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; + +public class RDPFileParser { + + private static final int MAX_ERRORS = 20; + private static final int MAX_LINES = 500; + + private HashMap options; + + public RDPFileParser() { + init(); + } + + public RDPFileParser(String filename) throws IOException { + init(); + parse(filename); + } + + private void init() { + options = new HashMap(); + } + + public void parse(String filename) throws IOException { + BufferedReader br = new BufferedReader(new FileReader(filename)); + String line = null; + + int errors = 0; + int lines = 0; + boolean ok; + + while ((line = br.readLine()) != null) { + lines++; + ok = false; + + if (errors > MAX_ERRORS || lines > MAX_LINES) { + br.close(); + throw new IOException("Parsing limits exceeded"); + } + + String[] fields = line.split(":", 3); + + if (fields.length == 3) { + if (fields[1].equals("s")) { + options.put(fields[0].toLowerCase(Locale.ENGLISH), fields[2]); + ok = true; + } else if (fields[1].equals("i")) { + try { + Integer i = Integer.parseInt(fields[2]); + options.put(fields[0].toLowerCase(Locale.ENGLISH), i); + ok = true; + } catch (NumberFormatException e) { + } + } else if (fields[1].equals("b")) { + ok = true; + } + } + + if (!ok) errors++; + } + br.close(); + } + + public String getString(String optionName) { + if (options.get(optionName) instanceof String) + return (String) options.get(optionName); + else + return null; + } + + public Integer getInteger(String optionName) { + if (options.get(optionName) instanceof Integer) + return (Integer) options.get(optionName); + else + return null; + } +} diff --git a/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java new file mode 100644 index 0000000..7f8d8d2 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java @@ -0,0 +1,178 @@ +/* + Separated List Adapter + Taken from http://jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/ + + Copyright Jeff Sharkey + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; + +import com.freerdp.freerdpcore.R; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class SeparatedListAdapter extends BaseAdapter { + + public final static int TYPE_SECTION_HEADER = 0; + public final Map sections = new LinkedHashMap(); + public final ArrayAdapter headers; + + public SeparatedListAdapter(Context context) { + headers = new ArrayAdapter(context, R.layout.list_header); + } + + public void addSection(String section, Adapter adapter) { + this.headers.add(section); + this.sections.put(section, adapter); + } + + public void setSectionTitle(int section, String title) { + String oldTitle = this.headers.getItem(section); + + // remove/add to headers array + this.headers.remove(oldTitle); + this.headers.insert(title, section); + + // remove/add to section map + Adapter adapter = this.sections.get(oldTitle); + this.sections.remove(oldTitle); + this.sections.put(title, adapter); + } + + public Object getItem(int position) { + for (int i = 0; i < headers.getCount(); i++) { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // ignore empty sections + if (adapter.getCount() > 0) { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) return section; + if (position < size) return adapter.getItem(position - 1); + + // otherwise jump into next section + position -= size; + } + } + return null; + } + + public int getCount() { + // total together all sections, plus one for each section header (except if the section is empty) + int total = 0; + for (Adapter adapter : this.sections.values()) + total += ((adapter.getCount() > 0) ? adapter.getCount() + 1 : 0); + return total; + } + + public int getViewTypeCount() { + // assume that headers count as one, then total all sections + int total = 1; + for (Adapter adapter : this.sections.values()) + total += adapter.getViewTypeCount(); + return total; + } + + public int getItemViewType(int position) { + int type = 1; + for (int i = 0; i < headers.getCount(); i++) { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // skip empty sections + if (adapter.getCount() > 0) { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) return TYPE_SECTION_HEADER; + if (position < size) return type + adapter.getItemViewType(position - 1); + + // otherwise jump into next section + position -= size; + type += adapter.getViewTypeCount(); + } + } + return -1; + } + + public boolean areAllItemsSelectable() { + return false; + } + + public boolean isEnabled(int position) { + return (getItemViewType(position) != TYPE_SECTION_HEADER); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int sectionnum = 0; + for (int i = 0; i < headers.getCount(); i++) { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // skip empty sections + if (adapter.getCount() > 0) { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) return headers.getView(sectionnum, convertView, parent); + if (position < size) return adapter.getView(position - 1, null, parent); + + // otherwise jump into next section + position -= size; + } + sectionnum++; + } + return null; + } + + @Override + public long getItemId(int position) { + for (int i = 0; i < headers.getCount(); i++) { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + if (adapter.getCount() > 0) { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position < size) return adapter.getItemId(position - 1); + + // otherwise jump into next section + position -= size; + } + } + return -1; + } + + public String getSectionForPosition(int position) { + int curPos = 0; + for (int i = 0; i < headers.getCount(); i++) { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + if (adapter.getCount() > 0) { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position >= curPos && position < (curPos + size)) return section.toString(); + + // otherwise jump into next section + curPos += size; + } + } + return null; + } + +} \ No newline at end of file diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png new file mode 100644 index 0000000..19104c8 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png new file mode 100644 index 0000000..ae18557 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png new file mode 100644 index 0000000..2283a91 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..ff31f25 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png new file mode 100644 index 0000000..43cf97c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png new file mode 100644 index 0000000..46b50d6 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png new file mode 100644 index 0000000..82d4170 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png new file mode 100644 index 0000000..192c57b Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..2ed0352 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png new file mode 100644 index 0000000..8ba2751 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png new file mode 100644 index 0000000..d6ea16c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png new file mode 100644 index 0000000..8dcef3e Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..3d824d3 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..121b545 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png new file mode 100644 index 0000000..eeebc09 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png new file mode 100644 index 0000000..e6fef76 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png new file mode 100644 index 0000000..8ab6ad7 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..104c1b9 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..a6d8733 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..19b33da Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png new file mode 100644 index 0000000..cf2b963 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png new file mode 100644 index 0000000..312f5f9 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png new file mode 100644 index 0000000..421c3e8 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..49726f4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png new file mode 100644 index 0000000..d0c26e2 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png new file mode 100644 index 0000000..2eaa369 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png new file mode 100644 index 0000000..99c1ad4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png new file mode 100644 index 0000000..90a813e Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..02aeed3 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png new file mode 100644 index 0000000..5e4e17a Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png new file mode 100644 index 0000000..34beda8 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png new file mode 100644 index 0000000..61a2fdd Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..2fb6887 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..066ce3f Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png new file mode 100644 index 0000000..b0dce0e Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png new file mode 100644 index 0000000..b09dec7 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png new file mode 100644 index 0000000..8347635 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png new file mode 100644 index 0000000..07f95d5 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..064a2c2 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..7e7b3de Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png new file mode 100644 index 0000000..1d8a4ea Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png new file mode 100644 index 0000000..33c42e0 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png new file mode 100644 index 0000000..ae18557 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png new file mode 100644 index 0000000..60112c1 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..6b18c0a Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png new file mode 100644 index 0000000..04442c8 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png new file mode 100644 index 0000000..014ad59 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png new file mode 100644 index 0000000..9a78d64 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png new file mode 100644 index 0000000..7213223 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..f8a055b Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png new file mode 100644 index 0000000..36041a5 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png new file mode 100644 index 0000000..3a297aa Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png new file mode 100644 index 0000000..f78c41f Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..a7d5585 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..b76585a Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png new file mode 100644 index 0000000..f2cafb5 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png new file mode 100644 index 0000000..f203ce2 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png new file mode 100644 index 0000000..144481c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..98a2bc2 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..617d4e9 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..af0fea3 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png new file mode 100644 index 0000000..27e26fc Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml b/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..a6aeb24 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png new file mode 100644 index 0000000..99c1ad4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png new file mode 100644 index 0000000..53c5b36 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml b/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml new file mode 100644 index 0000000..4cd72ac --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png new file mode 100644 index 0000000..ec6959c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png new file mode 100644 index 0000000..53dc892 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png new file mode 100644 index 0000000..331fea5 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png new file mode 100644 index 0000000..a17bcd7 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png new file mode 100644 index 0000000..0e67f89 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png new file mode 100644 index 0000000..afc2b5c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png new file mode 100644 index 0000000..b296f33 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png new file mode 100644 index 0000000..fe5c072 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png new file mode 100644 index 0000000..d44ae11 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png new file mode 100644 index 0000000..25c011b Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png new file mode 100644 index 0000000..ca62134 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png new file mode 100644 index 0000000..96577f4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png new file mode 100644 index 0000000..8abbd6c Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png new file mode 100644 index 0000000..778ad4f Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png new file mode 100644 index 0000000..ac498e4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png new file mode 100644 index 0000000..98f9f5a Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png new file mode 100644 index 0000000..f1fff6d Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png new file mode 100644 index 0000000..08880bb Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png new file mode 100644 index 0000000..90f2b1b Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png new file mode 100644 index 0000000..dcb6904 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png new file mode 100644 index 0000000..b34b02e Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png new file mode 100644 index 0000000..c5275a4 Binary files /dev/null and b/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png differ diff --git a/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml b/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..1e22849 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml b/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml new file mode 100644 index 0000000..913b3ef --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml b/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml new file mode 100644 index 0000000..407f3ee --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Mac/cli/AppDelegate.h b/client/Mac/cli/AppDelegate.h new file mode 100644 index 0000000..91439c0 --- /dev/null +++ b/client/Mac/cli/AppDelegate.h @@ -0,0 +1,26 @@ +// +// AppDelegate.h +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import +#import +#import + +@interface AppDelegate : NSObject +{ +@public + NSWindow* window; + rdpContext* context; + MRDPView* mrdpView; +} + +- (void) rdpConnectError: (NSString*) customMessage; + +@property (assign) IBOutlet NSWindow *window; +@property (assign) rdpContext *context; + +@end diff --git a/client/Mac/cli/AppDelegate.m b/client/Mac/cli/AppDelegate.m new file mode 100644 index 0000000..9d4432b --- /dev/null +++ b/client/Mac/cli/AppDelegate.m @@ -0,0 +1,298 @@ +// +// AppDelegate.m +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import "AppDelegate.h" +#import "MacFreeRDP/mfreerdp.h" +#import "MacFreeRDP/mf_client.h" +#import "MacFreeRDP/MRDPView.h" +#import + +static AppDelegate* _singleDelegate = nil; +void AppDelegate_ConnectionResultEventHandler(void* context, + ConnectionResultEventArgs* e); +void AppDelegate_ErrorInfoEventHandler(void* ctx, ErrorInfoEventArgs* e); +void AppDelegate_EmbedWindowEventHandler(void* context, + EmbedWindowEventArgs* e); +void AppDelegate_ResizeWindowEventHandler(void* context, + ResizeWindowEventArgs* e); +void mac_set_view_size(rdpContext* context, MRDPView* view); + +@implementation AppDelegate + +- (void)dealloc +{ + [super dealloc]; +} + +@synthesize window = window; + + +@synthesize context = context; + +- (void) applicationDidFinishLaunching:(NSNotification*)aNotification +{ + int status; + mfContext* mfc; + _singleDelegate = self; + [self CreateContext]; + status = [self ParseCommandLineArguments]; + mfc = (mfContext*) context; + mfc->view = (void*) mrdpView; + + if (status < 0) + { + NSString* winTitle; + winTitle = [[NSString alloc] initWithCString:"ERROR"]; + [window setTitle:winTitle]; + } + else + { + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + NSRect screenFrame = [screen frame]; + + if (context->instance->settings->Fullscreen) + { + context->instance->settings->DesktopWidth = screenFrame.size.width; + context->instance->settings->DesktopHeight = screenFrame.size.height; + } + + PubSub_SubscribeConnectionResult(context->pubSub, + AppDelegate_ConnectionResultEventHandler); + PubSub_SubscribeErrorInfo(context->pubSub, AppDelegate_ErrorInfoEventHandler); + PubSub_SubscribeEmbedWindow(context->pubSub, + AppDelegate_EmbedWindowEventHandler); + PubSub_SubscribeResizeWindow(context->pubSub, + AppDelegate_ResizeWindowEventHandler); + freerdp_client_start(context); + NSString* winTitle; + + if (mfc->context.settings->WindowTitle && mfc->context.settings->WindowTitle[0]) + { + winTitle = [[NSString alloc] initWithCString: + mfc->context.settings->WindowTitle]; + } + else + { + winTitle = [[NSString alloc] initWithFormat:@"%@:%u", + [NSString stringWithCString:mfc->context.settings->ServerHostname encoding: + NSUTF8StringEncoding], + mfc->context.settings->ServerPort]; + } + + [window setTitle:winTitle]; + } +} + +- (void) applicationWillTerminate:(NSNotification*)notification +{ + NSLog(@"Stopping...\n"); + freerdp_client_stop(context); + [mrdpView releaseResources]; + _singleDelegate = nil; + NSLog(@"Stopped.\n"); +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender +{ + return YES; +} + +- (int) ParseCommandLineArguments +{ + int i; + int length; + int status; + char* cptr; + NSArray* args = [[NSProcessInfo processInfo] arguments]; + context->argc = (int) [args count]; + context->argv = malloc(sizeof(char*) * context->argc); + i = 0; + + for (NSString * str in args) + { + /* filter out some arguments added by XCode */ + if ([str isEqualToString:@"YES"]) + continue; + + if ([str isEqualToString:@"-NSDocumentRevisionsDebugMode"]) + continue; + + length = (int)([str length] + 1); + cptr = (char*) malloc(length); + sprintf_s(cptr, length, "%s", [str UTF8String]); + context->argv[i++] = cptr; + } + + context->argc = i; + status = freerdp_client_settings_parse_command_line(context->settings, + context->argc, context->argv, FALSE); + status = freerdp_client_settings_command_line_status_print(context->settings, + status, context->argc, context->argv); + return status; +} + +- (void) CreateContext +{ + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + ZeroMemory(&clientEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS); + clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION; + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); +} + +- (void) ReleaseContext +{ + mfContext* mfc; + MRDPView* view; + mfc = (mfContext*) context; + view = (MRDPView*) mfc->view; + [view exitFullScreenModeWithOptions:nil]; + [view releaseResources]; + [view release]; + mfc->view = nil; + freerdp_client_context_free(context); + context = nil; +} + + +/** ********************************************************************* + * called when we fail to connect to a RDP server - Make sure this is called from the main thread. + ***********************************************************************/ + +- (void) rdpConnectError : (NSString*) withMessage +{ + mfContext* mfc; + MRDPView* view; + mfc = (mfContext*) context; + view = (MRDPView*) mfc->view; + [view exitFullScreenModeWithOptions:nil]; + NSString* message = withMessage ? withMessage : @"Error connecting to server"; + NSAlert* alert = [[NSAlert alloc] init]; + [alert setMessageText:message]; + [alert beginSheetModalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) + contextInfo:nil]; +} + + +/** ********************************************************************* + * just a terminate selector for above call + ***********************************************************************/ + +- (void) alertDidEnd:(NSAlert*)a returnCode:(NSInteger)rc contextInfo:(void*)ci +{ + [NSApp terminate:nil]; +} + + +@end + +/** ********************************************************************* + * On connection error, display message and quit application + ***********************************************************************/ + +void AppDelegate_ConnectionResultEventHandler(void* ctx, + ConnectionResultEventArgs* e) +{ + NSLog(@"ConnectionResult event result:%d\n", e->result); + + if (_singleDelegate) + { + if (e->result != 0) + { + NSString* message = nil; + + if (connectErrorCode == AUTHENTICATIONERROR) + { + message = [NSString stringWithFormat:@"%@", + @"Authentication failure, check credentials."]; + } + + // Making sure this should be invoked on the main UI thread. + [_singleDelegate performSelectorOnMainThread:@selector(rdpConnectError:) + withObject:message waitUntilDone:FALSE]; + } + } +} + +void AppDelegate_ErrorInfoEventHandler(void* ctx, ErrorInfoEventArgs* e) +{ + NSLog(@"ErrorInfo event code:%d\n", e->code); + + if (_singleDelegate) + { + // Retrieve error message associated with error code + NSString* message = nil; + + if (e->code != ERRINFO_NONE) + { + const char* errorMessage = freerdp_get_error_info_string(e->code); + message = [[NSString alloc] initWithUTF8String:errorMessage]; + } + + // Making sure this should be invoked on the main UI thread. + [_singleDelegate performSelectorOnMainThread:@selector(rdpConnectError:) + withObject:message waitUntilDone:TRUE]; + [message release]; + } +} + +void AppDelegate_EmbedWindowEventHandler(void* ctx, EmbedWindowEventArgs* e) +{ + rdpContext* context = (rdpContext*) ctx; + + if (_singleDelegate) + { + mfContext* mfc = (mfContext*) context; + _singleDelegate->mrdpView = mfc->view; + + if (_singleDelegate->window) + { + [[_singleDelegate->window contentView] addSubview:mfc->view]; + } + + mac_set_view_size(context, mfc->view); + } +} + +void AppDelegate_ResizeWindowEventHandler(void* ctx, ResizeWindowEventArgs* e) +{ + rdpContext* context = (rdpContext*) ctx; + fprintf(stderr, "ResizeWindowEventHandler: %d %d\n", e->width, e->height); + + if (_singleDelegate) + { + mfContext* mfc = (mfContext*) context; + mac_set_view_size(context, mfc->view); + } +} + +void mac_set_view_size(rdpContext* context, MRDPView* view) +{ + // set client area to specified dimensions + NSRect innerRect; + innerRect.origin.x = 0; + innerRect.origin.y = 0; + innerRect.size.width = context->settings->DesktopWidth; + innerRect.size.height = context->settings->DesktopHeight; + [view setFrame:innerRect]; + // calculate window of same size, but keep position + NSRect outerRect = [[view window] frame]; + outerRect.size = [[view window] frameRectForContentRect:innerRect].size; + // we are not in RemoteApp mode, disable larger than resolution + [[view window] setContentMaxSize:innerRect.size]; + // set window to given area + [[view window] setFrame:outerRect display:YES]; + // set window to front + [NSApp activateIgnoringOtherApps:YES]; + + if (context->settings->Fullscreen) + [[view window] toggleFullScreen:nil]; +} diff --git a/client/Mac/cli/CMakeLists.txt b/client/Mac/cli/CMakeLists.txt new file mode 100644 index 0000000..d7b7b41 --- /dev/null +++ b/client/Mac/cli/CMakeLists.txt @@ -0,0 +1,113 @@ + +project(MacFreeRDP) + +set(MODULE_NAME "MacFreeRDP") +set(MODULE_OUTPUT_NAME "MacFreeRDP") +set(MODULE_PREFIX "FREERDP_CLIENT_MAC_CLIENT") + +# Import libraries +find_library(FOUNDATION_LIBRARY Foundation) +find_library(COCOA_LIBRARY Cocoa) +find_library(APPKIT_LIBRARY AppKit) + +set(MACOSX_BUNDLE_INFO_STRING "MacFreeRDP") +set(MACOSX_BUNDLE_ICON_FILE "FreeRDP.icns") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.freerdp.mac") +set(MACOSX_BUNDLE_BUNDLE_IDENTIFIER "FreeRDP-client.Mac") +set(MACOSX_BUNDLE_LONG_VERSION_STRING "MacFreeRDP Client Version 1.1.0") +set(MACOSX_BUNDLE_BUNDLE_NAME "MacFreeRDP") +set(MACOSX_BUNDLE_SHORT_VERSION_STRING 1.1.0) +set(MACOSX_BUNDLE_BUNDLE_VERSION 1.1.0) +set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013. All Rights Reserved.") + +set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "MainMenu") +set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication") + +mark_as_advanced(COCOA_LIBRARY FOUNDATION_LIBRARY APPKIT_LIBRARY) +set(APP_TYPE MACOSX_BUNDLE) + +set(${MODULE_PREFIX}_XIBS MainMenu.xib) + +set(${MODULE_PREFIX}_SOURCES "") + +set(${MODULE_PREFIX}_OBJECTIVE_SOURCES + main.m + AppDelegate.m) + +list(APPEND ${MODULE_PREFIX}_SOURCES ${${MODULE_PREFIX}_OBJECTIVE_SOURCES}) + +set(${MODULE_PREFIX}_HEADERS + AppDelegate.h) + +set(${MODULE_PREFIX}_RESOURCES ${MACOSX_BUNDLE_ICON_FILE}) + +# Include XIB file in Xcode resources. +if("${CMAKE_GENERATOR}" MATCHES "Xcode") + message(STATUS "Adding Xcode XIB resources for ${MODULE_NAME}") + set(${MODULE_PREFIX}_RESOURCES ${${MODULE_PREFIX}_RESOURCES} ${${MODULE_PREFIX}_XIBS}) +endif() + +add_executable(${MODULE_NAME} + ${APP_TYPE} + ${${MODULE_PREFIX}_HEADERS} + ${${MODULE_PREFIX}_SOURCES} + ${${MODULE_PREFIX}_RESOURCES}) + +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_OUTPUT_NAME}") + +# This is necessary for the xib file part below +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Info.plist ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + +# This allows for automatic xib to nib ibitool +set_target_properties(${MODULE_NAME} PROPERTIES RESOURCE "${${MODULE_PREFIX}_RESOURCES}") + +# Tell the compiler where to look for the FreeRDP framework +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -F../") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -F../") + +# Tell XCode where to look for the MacFreeRDP framework +set_target_properties(${MODULE_NAME} PROPERTIES XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS + "${XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS} ${CMAKE_CURRENT_BINARY_DIR}/../$(CONFIGURATION)") + +# Set the info plist to the custom instance +set_target_properties(${MODULE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + +# Disable transitive linking +target_link_libraries(${MODULE_NAME} ${COCOA_LIBRARY} ${FOUNDATION_LIBRARY} ${APPKIT_LIBRARY} MacFreeRDP-library) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Mac") + +# Embed the FreeRDP framework into the app bundle +add_custom_command(TARGET ${MODULE_NAME} POST_BUILD + COMMAND mkdir ARGS -p ${CMAKE_CURRENT_BINARY_DIR}/$(CONFIGURATION)/${MODULE_OUTPUT_NAME}.app/Contents/Frameworks + COMMAND ditto ${CMAKE_CURRENT_BINARY_DIR}/../$(CONFIGURATION)/MacFreeRDP.framework ${CMAKE_CURRENT_BINARY_DIR}/$(CONFIGURATION)/${MODULE_OUTPUT_NAME}.app/Contents/Frameworks/MacFreeRDP.framework + COMMAND install_name_tool -change "@executable_path/../Frameworks/MacFreeRDP.framework/Versions/${MAC_OS_X_BUNDLE_BUNDLE_VERSION}/MacFreeRDP" + "@executable_path/../Frameworks/MacFreeRDP.framework/Versions/Current/MacFreeRDP" + "${CMAKE_CURRENT_BINARY_DIR}/$(CONFIGURATION)/${MODULE_OUTPUT_NAME}.app/Contents/MacOS/${MODULE_NAME}" + COMMENT Setting install name for MacFreeRDP) + +# Add post-build NIB file generation in unix makefiles. XCode handles this implicitly. +if("${CMAKE_GENERATOR}" MATCHES "Unix Makefiles") + message(STATUS "Adding post-build NIB file generation event for ${MODULE_NAME}") + + # Make sure we can find the 'ibtool' program. If we can NOT find it we skip generation of this project + find_program(IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin") + if (${IBTOOL} STREQUAL "IBTOOL-NOTFOUND") + message(SEND_ERROR "ibtool can not be found and is needed to compile the .xib files. It should have been installed with + the Apple developer tools. The default system paths were searched in addition to ${OSX_DEVELOPER_ROOT}/usr/bin") + endif() + + # Make sure the 'Resources' Directory is correctly created before we build + add_custom_command(TARGET ${MODULE_NAME} PRE_BUILD COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/\${CONFIGURATION}/${MODULE_OUTPUT_NAME}.app/Contents/Resources) + + # Compile the .xib files using the 'ibtool' program with the destination being the app package + foreach(xib ${${MODULE_PREFIX}_XIBS}) + get_filename_component(XIB_WE ${xib} NAME_WE) + + add_custom_command (TARGET ${MODULE_NAME} POST_BUILD + COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text + --compile ${CMAKE_CURRENT_BINARY_DIR}/\${CONFIGURATION}/${MODULE_OUTPUT_NAME}.app/Contents/Resources/${XIB_WE}.nib ${CMAKE_CURRENT_SOURCE_DIR}/${xib} + COMMENT "Compiling ${xib}") + endforeach() + +endif() diff --git a/client/Mac/cli/FreeRDP.icns b/client/Mac/cli/FreeRDP.icns new file mode 100644 index 0000000..88bd44c Binary files /dev/null and b/client/Mac/cli/FreeRDP.icns differ diff --git a/client/Mac/cli/Info.plist b/client/Mac/cli/Info.plist new file mode 100644 index 0000000..cb69765 --- /dev/null +++ b/client/Mac/cli/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + + CFBundleIconFile + FreeRDP + CFBundleIdentifier + FreeRDP.Mac + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + + NSHumanReadableCopyright + Copyright © 2012 __MyCompanyName__. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/client/Mac/cli/MacClient2-Info.plist b/client/Mac/cli/MacClient2-Info.plist new file mode 100644 index 0000000..6efd7bd --- /dev/null +++ b/client/Mac/cli/MacClient2-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + awakecoding.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/client/Mac/cli/MacClient2-Prefix.pch b/client/Mac/cli/MacClient2-Prefix.pch new file mode 100644 index 0000000..f81d505 --- /dev/null +++ b/client/Mac/cli/MacClient2-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'MacClient2' target in the 'MacClient2' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/client/Mac/cli/MainMenu.xib b/client/Mac/cli/MainMenu.xib new file mode 100644 index 0000000..65e72b1 --- /dev/null +++ b/client/Mac/cli/MainMenu.xib @@ -0,0 +1,631 @@ + + + + 1070 + 12E55 + 3084 + 1187.39 + 626.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 3084 + + + NSCustomObject + NSMenu + NSMenuItem + NSView + NSWindowTemplate + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + + + FreeRDP + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + FreeRDP + + + + About FreeRDP + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide FreeRDP + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit FreeRDP + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + + + + Edit + + 1048576 + 2147483647 + + + + + + Format + + 2147483647 + + + + + + View + + 1048576 + 2147483647 + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + + + Mac Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{163, 10}, {1024, 768}} + 1954021376 + FreeRDP + NSWindow + + + {1024, 768} + {1024, 768} + + + 256 + + {1024, 768} + + + + {{0, 0}, {1920, 1178}} + {1024, 790} + {1024, 790} + 128 + YES + + + AppDelegate + + + NSFontManager + + + + + + + terminate: + + + + 449 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + delegate + + + + 568 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + performZoom: + + + + 240 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + showHelp: + + + + 493 + + + + window + + + + 570 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + + + + + + + + + + + + 19 + + + + + + + + 56 + + + + + + + + 217 + + + + + + 83 + + + + + + 57 + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 236 + + + + + 149 + + + + + 145 + + + + + 24 + + + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + + + + 371 + + + + + + + + 372 + + + + + + 375 + + + + + + 420 + + + + + 490 + + + + + + + + 491 + + + + + + + + 492 + + + + + 494 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + com.apple.InterfaceBuilder.CocoaPlugin + {{380, 496}, {480, 360}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 570 + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.macosx + + + YES + 3 + + {11, 11} + {10, 3} + + YES + + diff --git a/client/Mac/cli/en.lproj/Credits.rtf b/client/Mac/cli/en.lproj/Credits.rtf new file mode 100644 index 0000000..46576ef --- /dev/null +++ b/client/Mac/cli/en.lproj/Credits.rtf @@ -0,0 +1,29 @@ +{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw9840\paperh8400 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Some people\ +\ + +\b Human Interface Design: +\b0 \ + Some other people\ +\ + +\b Testing: +\b0 \ + Hopefully not nobody\ +\ + +\b Documentation: +\b0 \ + Whoever\ +\ + +\b With special thanks to: +\b0 \ + Mom\ +} diff --git a/client/Mac/cli/en.lproj/InfoPlist.strings b/client/Mac/cli/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/client/Mac/cli/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/client/Mac/cli/en.lproj/MainMenu.xib b/client/Mac/cli/en.lproj/MainMenu.xib new file mode 100644 index 0000000..dd4e190 --- /dev/null +++ b/client/Mac/cli/en.lproj/MainMenu.xib @@ -0,0 +1,3299 @@ + + + + 1080 + 12D78 + 3084 + 1187.37 + 626.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 3084 + + + IBNSLayoutConstraint + NSCustomObject + NSCustomView + NSMenu + NSMenuItem + NSView + NSWindowTemplate + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + + + MacClient2 + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + MacClient2 + + + + About MacClient2 + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide MacClient2 + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit MacClient2 + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + + + New + n + 1048576 + 2147483647 + + + + + + Open… + o + 1048576 + 2147483647 + + + + + + Open Recent + + 1048576 + 2147483647 + + + submenuAction: + + Open Recent + + + + Clear Menu + + 1048576 + 2147483647 + + + + + _NSRecentDocumentsMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Close + w + 1048576 + 2147483647 + + + + + + Save… + s + 1048576 + 2147483647 + + + + + + Revert to Saved + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Page Setup... + P + 1179648 + 2147483647 + + + + + + + Print… + p + 1048576 + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Paste and Match Style + V + 1572864 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find and Replace… + f + 1572864 + 2147483647 + + + 12 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + + + Show Spelling and Grammar + : + 1048576 + 2147483647 + + + + + + Check Document Now + ; + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + Correct Spelling Automatically + + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + + + Show Substitutions + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Dashes + + 2147483647 + + + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + Text Replacement + + 2147483647 + + + + + + + + + Transformations + + 2147483647 + + + submenuAction: + + Transformations + + + + Make Upper Case + + 2147483647 + + + + + + Make Lower Case + + 2147483647 + + + + + + Capitalize + + 2147483647 + + + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + Format + + 2147483647 + + + submenuAction: + + Format + + + + Font + + 2147483647 + + + submenuAction: + + Font + + + + Show Fonts + t + 1048576 + 2147483647 + + + + + + Bold + b + 1048576 + 2147483647 + + + 2 + + + + Italic + i + 1048576 + 2147483647 + + + 1 + + + + Underline + u + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bigger + + + 1048576 + 2147483647 + + + 3 + + + + Smaller + - + 1048576 + 2147483647 + + + 4 + + + + YES + YES + + + 2147483647 + + + + + + Kern + + 2147483647 + + + submenuAction: + + Kern + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Tighten + + 2147483647 + + + + + + Loosen + + 2147483647 + + + + + + + + + Ligatures + + 2147483647 + + + submenuAction: + + Ligatures + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Use All + + 2147483647 + + + + + + + + + Baseline + + 2147483647 + + + submenuAction: + + Baseline + + + + Use Default + + 2147483647 + + + + + + Superscript + + 2147483647 + + + + + + Subscript + + 2147483647 + + + + + + Raise + + 2147483647 + + + + + + Lower + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Colors + C + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Copy Style + c + 1572864 + 2147483647 + + + + + + Paste Style + v + 1572864 + 2147483647 + + + + + _NSFontMenu + + + + + Text + + 2147483647 + + + submenuAction: + + Text + + + + Align Left + { + 1048576 + 2147483647 + + + + + + Center + | + 1048576 + 2147483647 + + + + + + Justify + + 2147483647 + + + + + + Align Right + } + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Writing Direction + + 2147483647 + + + submenuAction: + + Writing Direction + + + + YES + Paragraph + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + Selection + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Ruler + + 2147483647 + + + + + + Copy Ruler + c + 1310720 + 2147483647 + + + + + + Paste Ruler + v + 1310720 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + + + MacClient2 Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{335, 390}, {480, 360}} + 1954021376 + MacClient2 + NSWindow + + + + + 256 + + + + 268 + {480, 360} + + _NS:9 + MRDPView + + + {480, 360} + + + + {{0, 0}, {1440, 878}} + {10000000000000, 10000000000000} + YES + + + AppDelegate + + + NSFontManager + + + + + + + terminate: + + + + 449 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + delegate + + + + 495 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + print: + + + + 86 + + + + runPageLayout: + + + + 87 + + + + clearRecentDocuments: + + + + 127 + + + + performClose: + + + + 193 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + saveDocument: + + + + 362 + + + + revertDocumentToSaved: + + + + 364 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + newDocument: + + + + 373 + + + + openDocument: + + + + 374 + + + + raiseBaseline: + + + + 426 + + + + lowerBaseline: + + + + 427 + + + + copyFont: + + + + 428 + + + + subscript: + + + + 429 + + + + superscript: + + + + 430 + + + + tightenKerning: + + + + 431 + + + + underline: + + + + 432 + + + + orderFrontColorPanel: + + + + 433 + + + + useAllLigatures: + + + + 434 + + + + loosenKerning: + + + + 435 + + + + pasteFont: + + + + 436 + + + + unscript: + + + + 437 + + + + useStandardKerning: + + + + 438 + + + + useStandardLigatures: + + + + 439 + + + + turnOffLigatures: + + + + 440 + + + + turnOffKerning: + + + + 441 + + + + toggleAutomaticSpellingCorrection: + + + + 456 + + + + orderFrontSubstitutionsPanel: + + + + 458 + + + + toggleAutomaticDashSubstitution: + + + + 461 + + + + toggleAutomaticTextReplacement: + + + + 463 + + + + uppercaseWord: + + + + 464 + + + + capitalizeWord: + + + + 467 + + + + lowercaseWord: + + + + 468 + + + + pasteAsPlainText: + + + + 486 + + + + performFindPanelAction: + + + + 487 + + + + performFindPanelAction: + + + + 488 + + + + performFindPanelAction: + + + + 489 + + + + showHelp: + + + + 493 + + + + alignCenter: + + + + 518 + + + + pasteRuler: + + + + 519 + + + + toggleRuler: + + + + 520 + + + + alignRight: + + + + 521 + + + + copyRuler: + + + + 522 + + + + alignJustified: + + + + 523 + + + + alignLeft: + + + + 524 + + + + makeBaseWritingDirectionNatural: + + + + 525 + + + + makeBaseWritingDirectionLeftToRight: + + + + 526 + + + + makeBaseWritingDirectionRightToLeft: + + + + 527 + + + + makeTextWritingDirectionNatural: + + + + 528 + + + + makeTextWritingDirectionLeftToRight: + + + + 529 + + + + makeTextWritingDirectionRightToLeft: + + + + 530 + + + + performFindPanelAction: + + + + 535 + + + + addFontTrait: + + + + 421 + + + + addFontTrait: + + + + 422 + + + + modifyFont: + + + + 423 + + + + orderFrontFontPanel: + + + + 424 + + + + modifyFont: + + + + 425 + + + + mrdpView + + + + 549 + + + + window + + + + 550 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + + + + + + + + + + + + 19 + + + + + + + + 56 + + + + + + + + 217 + + + + + + + + 83 + + + + + + + + 81 + + + + + + + + + + + + + + + + + 75 + + + + + 78 + + + + + 72 + + + + + 82 + + + + + 124 + + + + + + + + 77 + + + + + 73 + + + + + 79 + + + + + 112 + + + + + 74 + + + + + 125 + + + + + + + + 126 + + + + + 205 + + + + + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + + + + + + 216 + + + + + + + + 200 + + + + + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 57 + + + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + + + + + + 296 + + + + + + + + + 297 + + + + + 298 + + + + + 211 + + + + + + + + 212 + + + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + + + + + + 349 + + + + + + + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 371 + + + + + + + + 372 + + + + + 6 + 0 + + 6 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 4 + 0 + + 4 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 5 + 0 + + 5 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 3 + 0 + + 3 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + + + + 375 + + + + + + + + 376 + + + + + + + + + 377 + + + + + + + + 388 + + + + + + + + + + + + + + + + + + + + + + + 389 + + + + + 390 + + + + + 391 + + + + + 392 + + + + + 393 + + + + + 394 + + + + + 395 + + + + + 396 + + + + + 397 + + + + + + + + 398 + + + + + + + + 399 + + + + + + + + 400 + + + + + 401 + + + + + 402 + + + + + 403 + + + + + 404 + + + + + 405 + + + + + + + + + + + + 406 + + + + + 407 + + + + + 408 + + + + + 409 + + + + + 410 + + + + + 411 + + + + + + + + + + 412 + + + + + 413 + + + + + 414 + + + + + 415 + + + + + + + + + + + 416 + + + + + 417 + + + + + 418 + + + + + 419 + + + + + 420 + + + + + 450 + + + + + + + + 451 + + + + + + + + + + 452 + + + + + 453 + + + + + 454 + + + + + 457 + + + + + 459 + + + + + 460 + + + + + 462 + + + + + 465 + + + + + 466 + + + + + 485 + + + + + 490 + + + + + + + + 491 + + + + + + + + 492 + + + + + 494 + + + + + 496 + + + + + + + + 497 + + + + + + + + + + + + + + + + + 498 + + + + + 499 + + + + + 500 + + + + + 501 + + + + + 502 + + + + + 503 + + + + + + + + 504 + + + + + 505 + + + + + 506 + + + + + 507 + + + + + 508 + + + + + + + + + + + + + + + + 509 + + + + + 510 + + + + + 511 + + + + + 512 + + + + + 513 + + + + + 514 + + + + + 515 + + + + + 516 + + + + + 517 + + + + + 534 + + + + + 536 + + + + + 542 + + + + + 544 + + + + + 545 + + + + + 546 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{380, 496}, {480, 360}} + + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 550 + + + 0 + IBCocoaFramework + YES + 3 + + {11, 11} + {10, 3} + + YES + + diff --git a/client/Mac/cli/main.m b/client/Mac/cli/main.m new file mode 100644 index 0000000..4048b01 --- /dev/null +++ b/client/Mac/cli/main.m @@ -0,0 +1,14 @@ +// +// main.m +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char**) argv); +} diff --git a/client/Mac/en.lproj/InfoPlist.strings b/client/Mac/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/client/Mac/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/client/Mac/main.m b/client/Mac/main.m new file mode 100644 index 0000000..4cba6cf --- /dev/null +++ b/client/Mac/main.m @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **)argv); +} diff --git a/client/Mac/mf_client.h b/client/Mac/mf_client.h new file mode 100644 index 0000000..79712cc --- /dev/null +++ b/client/Mac/mf_client.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_MAC_CLIENT_H +#define FREERDP_CLIENT_MAC_CLIENT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FREERDP_API void mf_scale_mouse_event(void* context, rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +/** + * Client Interface + */ + +FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_MAC_CLIENT_H */ diff --git a/client/Mac/mf_client.m b/client/Mac/mf_client.m new file mode 100644 index 0000000..575d448 --- /dev/null +++ b/client/Mac/mf_client.m @@ -0,0 +1,176 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Interface + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mfreerdp.h" +#include +#include +#include + +/** + * Client Interface + */ + +static BOOL mfreerdp_client_global_init() +{ + freerdp_handle_signals(); + return TRUE; +} + +static void mfreerdp_client_global_uninit() +{ +} + +static int mfreerdp_client_start(rdpContext* context) +{ + MRDPView* view; + mfContext* mfc = (mfContext*) context; + + if (mfc->view == NULL) + { + // view not specified beforehand. Create view dynamically + mfc->view = [[MRDPView alloc] initWithFrame : NSMakeRect(0, 0, + context->settings->DesktopWidth, context->settings->DesktopHeight)]; + mfc->view_ownership = TRUE; + } + + view = (MRDPView*) mfc->view; + return [view rdpStart:context]; +} + +static int mfreerdp_client_stop(rdpContext* context) +{ + mfContext* mfc = (mfContext*) context; + + if (mfc->thread) + { + SetEvent(mfc->stopEvent); + WaitForSingleObject(mfc->thread, INFINITE); + CloseHandle(mfc->thread); + mfc->thread = NULL; + } + + if (mfc->view_ownership) + { + MRDPView* view = (MRDPView*) mfc->view; + [view releaseResources]; + [view release]; + mfc->view = nil; + } + + return 0; +} + +static BOOL mfreerdp_client_new(freerdp* instance, rdpContext* context) +{ + mfContext* mfc; + rdpSettings* settings; + mfc = (mfContext*) instance->context; + mfc->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + context->instance->PreConnect = mac_pre_connect; + context->instance->PostConnect = mac_post_connect; + context->instance->Authenticate = mac_authenticate; + settings = instance->settings; + settings->AsyncUpdate = TRUE; + settings->AsyncInput = TRUE; + return TRUE; +} + +static void mfreerdp_client_free(freerdp* instance, rdpContext* context) +{ + mfContext* mfc; + rdpSettings* settings; + + if (!instance || !context) + return; + + mfc = (mfContext*) instance->context; + CloseHandle(mfc->stopEvent); +} + +static void freerdp_client_mouse_event(rdpContext* cfc, DWORD flags, int x, + int y) +{ + int width, height; + rdpInput* input = cfc->instance->input; + rdpSettings* settings = cfc->instance->settings; + width = settings->DesktopWidth; + height = settings->DesktopHeight; + + if (x < 0) + x = 0; + + x = width - 1; + + if (y < 0) + y = 0; + + if (y >= height) + y = height - 1; + + freerdp_input_send_mouse_event(input, flags, x, y); +} + +void mf_scale_mouse_event(void* context, rdpInput* input, UINT16 flags, + UINT16 x, UINT16 y) +{ + mfContext* mfc = (mfContext*) context; + MRDPView* view = (MRDPView*) mfc->view; + int ww, wh, dw, dh; + ww = mfc->client_width; + wh = mfc->client_height; + dw = mfc->context.settings->DesktopWidth; + dh = mfc->context.settings->DesktopHeight; + // Convert to windows coordinates + y = [view frame].size.height - y; + + if (!mfc->context.settings->SmartSizing || ((ww == dw) && (wh == dh))) + { + y = y + mfc->yCurrentScroll; + + if (wh != dh) + { + y -= (dh - wh); + } + + freerdp_input_send_mouse_event(input, flags, x + mfc->xCurrentScroll, y); + } + else + { + y = y * dh / wh + mfc->yCurrentScroll; + freerdp_input_send_mouse_event(input, flags, x * dw / ww + mfc->xCurrentScroll, y); + } +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = mfreerdp_client_global_init; + pEntryPoints->GlobalUninit = mfreerdp_client_global_uninit; + pEntryPoints->ContextSize = sizeof(mfContext); + pEntryPoints->ClientNew = mfreerdp_client_new; + pEntryPoints->ClientFree = mfreerdp_client_free; + pEntryPoints->ClientStart = mfreerdp_client_start; + pEntryPoints->ClientStop = mfreerdp_client_stop; + return 0; +} diff --git a/client/Mac/mfreerdp.h b/client/Mac/mfreerdp.h new file mode 100644 index 0000000..1d21a96 --- /dev/null +++ b/client/Mac/mfreerdp.h @@ -0,0 +1,91 @@ +#ifndef FREERDP_CLIENT_MAC_FREERDP_H +#define FREERDP_CLIENT_MAC_FREERDP_H + +typedef struct mf_context mfContext; + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "MRDPView.h" +#include "Keyboard.h" +#include + +struct mf_context +{ + rdpContext context; + DEFINE_RDP_CLIENT_COMMON(); + + void* view; + BOOL view_ownership; + + int width; + int height; + int offset_x; + int offset_y; + int fs_toggle; + int fullscreen; + int percentscreen; + char window_title[64]; + int client_x; + int client_y; + int client_width; + int client_height; + + HANDLE stopEvent; + HANDLE keyboardThread; + enum APPLE_KEYBOARD_TYPE appleKeyboardType; + + DWORD mainThreadId; + DWORD keyboardThreadId; + + BOOL clipboardSync; + wClipboard* clipboard; + UINT32 numServerFormats; + UINT32 requestedFormatId; + HANDLE clipboardRequestEvent; + CLIPRDR_FORMAT* serverFormats; + CliprdrClientContext* cliprdr; + UINT32 clipboardCapabilities; + + rdpFile* connectionRdpFile; + + // Keep track of window size and position, disable when in fullscreen mode. + BOOL disablewindowtracking; + + // These variables are required for horizontal scrolling. + BOOL updating_scrollbars; + BOOL xScrollVisible; + int xMinScroll; // minimum horizontal scroll value + int xCurrentScroll; // current horizontal scroll value + int xMaxScroll; // maximum horizontal scroll value + + // These variables are required for vertical scrolling. + BOOL yScrollVisible; + int yMinScroll; // minimum vertical scroll value + int yCurrentScroll; // current vertical scroll value + int yMaxScroll; // maximum vertical scroll value + + CGEventFlags kbdFlags; +}; + +#endif /* FREERDP_CLIENT_MAC_FREERDP_H */ diff --git a/client/Sample/CMakeLists.txt b/client/Sample/CMakeLists.txt new file mode 100644 index 0000000..1e64733 --- /dev/null +++ b/client/Sample/CMakeLists.txt @@ -0,0 +1,47 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Sample UI cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "sfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_SAMPLE") + +set(${MODULE_PREFIX}_SRCS + freerdp.c) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set (WINPR_SRCS ${WINPR_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${CMAKE_DL_LIBS}) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp) +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Sample") diff --git a/client/Sample/ModuleOptions.cmake b/client/Sample/ModuleOptions.cmake new file mode 100644 index 0000000..d4d5a9e --- /dev/null +++ b/client/Sample/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_CLIENT_NAME "sfreerdp") +set(FREERDP_CLIENT_PLATFORM "Sample") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/client/Sample/freerdp.c b/client/Sample/freerdp.c new file mode 100644 index 0000000..cfbc30e --- /dev/null +++ b/client/Sample/freerdp.c @@ -0,0 +1,212 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Test UI + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Armin Novak + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG CLIENT_TAG("sample") + +struct tf_context +{ + rdpContext _p; +}; +typedef struct tf_context tfContext; + +static BOOL tf_context_new(freerdp* instance, rdpContext* context) +{ + return TRUE; +} + +static void tf_context_free(freerdp* instance, rdpContext* context) +{ +} + +static BOOL tf_begin_paint(rdpContext* context) +{ + rdpGdi* gdi = context->gdi; + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +static BOOL tf_end_paint(rdpContext* context) +{ + rdpGdi* gdi = context->gdi; + + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + return TRUE; +} + +static BOOL tf_pre_connect(freerdp* instance) +{ + rdpSettings* settings; + settings = instance->settings; + settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_PATBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = TRUE; + settings->OrderSupport[NEG_LINETO_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE; + settings->OrderSupport[NEG_MEMBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = TRUE; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = TRUE; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = TRUE; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = TRUE; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = TRUE; + return TRUE; +} + +static BOOL tf_post_connect(freerdp* instance) +{ + if (!gdi_init(instance, PIXEL_FORMAT_XRGB32)) + return FALSE; + + instance->update->BeginPaint = tf_begin_paint; + instance->update->EndPaint = tf_end_paint; + return TRUE; +} + +static DWORD WINAPI tf_client_thread_proc(LPVOID arg) +{ + freerdp* instance = (freerdp*)arg; + DWORD nCount; + DWORD status; + HANDLE handles[64]; + + if (!freerdp_connect(instance)) + { + WLog_ERR(TAG, "connection failure"); + return 0; + } + + while (!freerdp_shall_disconnect(instance)) + { + nCount = freerdp_get_event_handles(instance->context, &handles[0], 64); + + if (nCount == 0) + { + WLog_ERR(TAG, "%s: freerdp_get_event_handles failed", __FUNCTION__); + break; + } + + status = WaitForMultipleObjects(nCount, handles, FALSE, 100); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "%s: WaitForMultipleObjects failed with %"PRIu32"", __FUNCTION__, + status); + break; + } + + if (!freerdp_check_event_handles(instance->context)) + { + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "Failed to check FreeRDP event handles"); + + break; + } + } + + freerdp_disconnect(instance); + ExitThread(0); + return 0; +} + +int main(int argc, char* argv[]) +{ + int status; + HANDLE thread; + freerdp* instance; + instance = freerdp_new(); + + if (!instance) + { + WLog_ERR(TAG, "Couldn't create instance"); + return 1; + } + + instance->PreConnect = tf_pre_connect; + instance->PostConnect = tf_post_connect; + instance->ContextSize = sizeof(tfContext); + instance->ContextNew = tf_context_new; + instance->ContextFree = tf_context_free; + freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); + + if (!freerdp_context_new(instance)) + { + WLog_ERR(TAG, "Couldn't create context"); + return 1; + } + + status = freerdp_client_settings_parse_command_line(instance->settings, argc, + argv, FALSE); + + if (status < 0) + { + return 0; + } + + if (!freerdp_client_load_addins(instance->context->channels, + instance->settings)) + return -1; + + if (!(thread = CreateThread(NULL, 0, tf_client_thread_proc, instance, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create client thread"); + } + else + { + WaitForSingleObject(thread, INFINITE); + } + + freerdp_context_free(instance); + freerdp_free(instance); + return 0; +} diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt new file mode 100644 index 0000000..bc66f28 --- /dev/null +++ b/client/Wayland/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Wayland Client cmake build script +# +# Copyright 2014 Manuel Bachmann +# Copyright 2015 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "wlfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND") + +include_directories(${WAYLAND_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/uwac/include) + +set(${MODULE_PREFIX}_SRCS + wlfreerdp.c + wlfreerdp.h + wlf_input.c + wlf_input.h + wlf_channels.c + wlf_channels.h + ) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp uwac) +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland") +configure_file(wlfreerdp.1.in ${CMAKE_CURRENT_BINARY_DIR}/wlfreerdp.1) +install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/wlfreerdp.1 1) diff --git a/client/Wayland/wlf_channels.c b/client/Wayland/wlf_channels.c new file mode 100644 index 0000000..c8a4c9d --- /dev/null +++ b/client/Wayland/wlf_channels.c @@ -0,0 +1,123 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "wlf_channels.h" +#include "wlfreerdp.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_encomsp_participant_created(EncomspClientContext* context, + ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + return CHANNEL_RC_OK; +} + +static void wlf_encomsp_init(wlfContext* wlf, EncomspClientContext* encomsp) +{ + wlf->encomsp = encomsp; + encomsp->custom = (void*) wlf; + encomsp->ParticipantCreated = wlf_encomsp_participant_created; +} + +static void wlf_encomsp_uninit(wlfContext* wlf, EncomspClientContext* encomsp) +{ + if (encomsp) + { + encomsp->custom = NULL; + encomsp->ParticipantCreated = NULL; + } + + if (wlf) + wlf->encomsp = NULL; +} + + +void wlf_OnChannelConnectedEventHandler(void* context, + ChannelConnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*) context; + rdpSettings* settings; + + settings = wlf->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + wlf->rdpei = (RdpeiClientContext*) e->pInterface; + } + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_init(wlf->context.gdi, (RdpgfxClientContext*) e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + wlf_encomsp_init(wlf, (EncomspClientContext*) e->pInterface); + } +} + +void wlf_OnChannelDisconnectedEventHandler(void* context, + ChannelDisconnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*) context; + rdpSettings* settings; + + settings = wlf->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + wlf->rdpei = NULL; + } + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_uninit(wlf->context.gdi, + (RdpgfxClientContext*) e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + wlf_encomsp_uninit(wlf, (EncomspClientContext*) e->pInterface); + } +} diff --git a/client/Wayland/wlf_channels.h b/client/Wayland/wlf_channels.h new file mode 100644 index 0000000..1b6c591 --- /dev/null +++ b/client/Wayland/wlf_channels.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CHANNELS_H +#define FREERDP_CLIENT_WAYLAND_CHANNELS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +int wlf_on_channel_connected(freerdp* instance, const char* name, + void* pInterface); +int wlf_on_channel_disconnected(freerdp* instance, const char* name, + void* pInterface); + +void wlf_OnChannelConnectedEventHandler(void* context, + ChannelConnectedEventArgs* e); +void wlf_OnChannelDisconnectedEventHandler(void* context, + ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_WAYLAND_CHANNELS_H */ diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c new file mode 100644 index 0000000..2516cb4 --- /dev/null +++ b/client/Wayland/wlf_input.c @@ -0,0 +1,133 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann + * Copyright 2015 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +#include + +#include "wlf_input.h" + +BOOL wlf_handle_pointer_enter(freerdp* instance, UwacPointerEnterLeaveEvent* ev) +{ + if (!instance || !ev || !instance->input) + return FALSE; + + return freerdp_input_send_mouse_event(instance->input, PTR_FLAGS_MOVE, ev->x, ev->y); +} + +BOOL wlf_handle_pointer_motion(freerdp* instance, UwacPointerMotionEvent* ev) +{ + if (!instance || !ev || !instance->input) + return FALSE; + + return freerdp_input_send_mouse_event(instance->input, PTR_FLAGS_MOVE, ev->x, ev->y); +} + +BOOL wlf_handle_pointer_buttons(freerdp* instance, UwacPointerButtonEvent* ev) +{ + rdpInput* input; + UINT16 flags; + + if (!instance || !ev || !instance->input) + return FALSE; + + input = instance->input; + + if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED) + flags = PTR_FLAGS_DOWN; + else + flags = 0; + + switch (ev->button) + { + case BTN_LEFT: + flags |= PTR_FLAGS_BUTTON1; + break; + + case BTN_RIGHT: + flags |= PTR_FLAGS_BUTTON2; + break; + + case BTN_MIDDLE: + flags |= PTR_FLAGS_BUTTON3; + break; + + default: + return TRUE; + } + + return freerdp_input_send_mouse_event(input, flags, ev->x, ev->y); +} + + +BOOL wlf_handle_pointer_axis(freerdp* instance, UwacPointerAxisEvent* ev) +{ + rdpInput* input; + UINT16 flags; + int direction; + + if (!instance || !ev || !instance->input) + return FALSE; + + input = instance->input; + flags = PTR_FLAGS_WHEEL; + + if (ev->axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + { + direction = wl_fixed_to_int(ev->value); + + if (direction < 0) + flags |= 0x0078; + else + flags |= PTR_FLAGS_WHEEL_NEGATIVE | 0x0088; + } + + return freerdp_input_send_mouse_event(input, flags, ev->x, ev->y); +} + +BOOL wlf_handle_key(freerdp* instance, UwacKeyEvent* ev) +{ + rdpInput* input; + DWORD rdp_scancode; + + if (!instance || !ev || !instance->input) + return FALSE; + + input = instance->input; + rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(ev->raw_key + 8); + + if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + return TRUE; + + return freerdp_input_send_keyboard_event_ex(input, ev->pressed, rdp_scancode); +} + +BOOL wlf_keyboard_enter(freerdp* instance, UwacKeyboardEnterLeaveEvent* ev) +{ + rdpInput* input; + + if (!instance || !ev || !instance->input) + return FALSE; + + input = instance->input; + return freerdp_input_send_focus_in_event(input, 0) && + freerdp_input_send_mouse_event(input, PTR_FLAGS_MOVE, 0, 0); +} diff --git a/client/Wayland/wlf_input.h b/client/Wayland/wlf_input.h new file mode 100644 index 0000000..701edf5 --- /dev/null +++ b/client/Wayland/wlf_input.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann + * Copyright 2015 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_INPUT_H +#define FREERDP_CLIENT_WAYLAND_INPUT_H + +#include +#include +#include +#include + +BOOL wlf_handle_pointer_enter(freerdp* instance, + UwacPointerEnterLeaveEvent* ev); +BOOL wlf_handle_pointer_motion(freerdp* instance, UwacPointerMotionEvent* ev); +BOOL wlf_handle_pointer_buttons(freerdp* instance, UwacPointerButtonEvent* ev); +BOOL wlf_handle_pointer_axis(freerdp* instance, UwacPointerAxisEvent* ev); + +BOOL wlf_handle_key(freerdp* instance, UwacKeyEvent* ev); +BOOL wlf_keyboard_enter(freerdp* instance, UwacKeyboardEnterLeaveEvent* ev); + +#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */ diff --git a/client/Wayland/wlfreerdp.1.in b/client/Wayland/wlfreerdp.1.in new file mode 100644 index 0000000..c268546 --- /dev/null +++ b/client/Wayland/wlfreerdp.1.in @@ -0,0 +1,38 @@ +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac +.TH wlfreerdp 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP" +.SH NAME +wlfreerdp \- FreeRDP wayland client +.SH SYNOPSIS +.B wlfreerdp +[file] +[\fIdefault_client_options\fP] +[\fB/v\fP:[:port]] +[\fB/version\fP] +[\fB/help\fP] +.SH DESCRIPTION +.B wlfreerdp +is a wayland Remote Desktop Protocol (RDP) client which is part of the FreeRDP project. A RDP server is built-in to many editions of Windows. Alternative servers included xrdp and VRDP (VirtualBox). +.SH OPTIONS +The wayland client also supports a lot of the \fIdefault client options\fP which are not described here. For details on those see the xfreerdp(1) man page. +.IP \fB/v:\fP\fI[:port]\fP +The server hostname or IP, and optionally the port, to connect to. +.IP /version +Print the version and exit. +.IP /help +Print the help and exit. +.SH EXIT STATUS +.TP +.B 0 +Successful program execution. +.TP +.B not 0 +On failure. + +.SH SEE ALSO +xfreerdp(1) wlog(7) + +.SH AUTHOR +FreeRDP diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c new file mode 100644 index 0000000..eed827f --- /dev/null +++ b/client/Wayland/wlfreerdp.c @@ -0,0 +1,502 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "wlfreerdp.h" +#include "wlf_input.h" +#include "wlf_channels.h" + +static BOOL wl_update_content(wlfContext* context_w) +{ + if (!context_w) + return FALSE; + + if (!context_w->waitingFrameDone && context_w->haveDamage) + { + UwacWindowSubmitBuffer(context_w->window, true); + context_w->waitingFrameDone = TRUE; + context_w->haveDamage = FALSE; + } + + return TRUE; +} + +static BOOL wl_begin_paint(rdpContext* context) +{ + rdpGdi* gdi; + + if (!context || !context->gdi) + return FALSE; + + gdi = context->gdi; + + if (!gdi->primary) + return FALSE; + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + + +static BOOL wl_end_paint(rdpContext* context) +{ + rdpGdi* gdi; + char* data; + wlfContext* context_w; + INT32 x, y; + UINT32 w, h; + UINT32 i; + + if (!context || !context->gdi || !context->gdi->primary) + return FALSE; + + gdi = context->gdi; + + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + x = gdi->primary->hdc->hwnd->invalid->x; + y = gdi->primary->hdc->hwnd->invalid->y; + w = gdi->primary->hdc->hwnd->invalid->w; + h = gdi->primary->hdc->hwnd->invalid->h; + context_w = (wlfContext*) context; + data = UwacWindowGetDrawingBuffer(context_w->window); + + if (!data) + return FALSE; + + for (i = 0; i < h; i++) + { + memcpy(data + ((i + y) * (gdi->width * GetBytesPerPixel( + gdi->dstFormat))) + x * GetBytesPerPixel(gdi->dstFormat), + gdi->primary_buffer + ((i + y) * (gdi->width * GetBytesPerPixel( + gdi->dstFormat))) + x * GetBytesPerPixel(gdi->dstFormat), + w * GetBytesPerPixel(gdi->dstFormat)); + } + + if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS) + return FALSE; + + context_w->haveDamage = TRUE; + return wl_update_content(context_w); +} + + +static BOOL wl_pre_connect(freerdp* instance) +{ + rdpSettings* settings; + wlfContext* context; + UwacOutput* output; + UwacSize resolution; + + if (!instance) + return FALSE; + + context = (wlfContext*) instance->context; + settings = instance->settings; + + if (!context || !settings) + return FALSE; + + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_NATIVE_WAYLAND; + ZeroMemory(settings->OrderSupport, 32); + settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_PATBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_LINETO_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE; + settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE; + PubSub_SubscribeChannelConnected(instance->context->pubSub, + wlf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + wlf_OnChannelDisconnectedEventHandler); + + if (settings->Fullscreen) + { + // Use the resolution of the first display output + output = UwacDisplayGetOutput(context->display, 1); + + if (output != NULL && UwacOutputGetResolution(output, &resolution) == UWAC_SUCCESS) + { + settings->DesktopWidth = (UINT32) resolution.width; + settings->DesktopHeight = (UINT32) resolution.height; + } + else + { + WLog_WARN(TAG, "Failed to get output resolution! Check your display settings"); + } + } + + if (!freerdp_client_load_addins(instance->context->channels, + instance->settings)) + return FALSE; + + return TRUE; +} + +static BOOL wl_post_connect(freerdp* instance) +{ + rdpGdi* gdi; + UwacWindow* window; + wlfContext* context; + + if (!instance || !instance->context) + return FALSE; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + gdi = instance->context->gdi; + + if (!gdi) + return FALSE; + + context = (wlfContext*) instance->context; + context->window = window = UwacCreateWindowShm(context->display, gdi->width, + gdi->height, WL_SHM_FORMAT_XRGB8888); + + if (!window) + return FALSE; + + UwacWindowSetFullscreenState(window, NULL, instance->context->settings->Fullscreen); + UwacWindowSetTitle(window, "FreeRDP"); + UwacWindowSetOpaqueRegion(context->window, 0, 0, gdi->width, gdi->height); + instance->update->BeginPaint = wl_begin_paint; + instance->update->EndPaint = wl_end_paint; + memcpy(UwacWindowGetDrawingBuffer(context->window), gdi->primary_buffer, + gdi->width * gdi->height * 4); + UwacWindowAddDamage(context->window, 0, 0, gdi->width, gdi->height); + context->haveDamage = TRUE; + freerdp_keyboard_init(instance->context->settings->KeyboardLayout); + return wl_update_content(context); +} + +static void wl_post_disconnect(freerdp* instance) +{ + wlfContext* context; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (wlfContext*) instance->context; + gdi_free(instance); + + if (context->window) + UwacDestroyWindow(&context->window); +} + +static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display) +{ + UwacEvent event; + wlfContext* context; + + if (UwacDisplayDispatch(display, 1) < 0) + return FALSE; + + while (UwacHasEvent(display)) + { + if (UwacNextEvent(display, &event) != UWAC_SUCCESS) + return FALSE; + + /*printf("UWAC event type %d\n", event.type);*/ + switch (event.type) + { + case UWAC_EVENT_FRAME_DONE: + if (!instance) + continue; + + context = (wlfContext*)instance->context; + context->waitingFrameDone = FALSE; + + if (context->haveDamage && !wl_update_content(context)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_ENTER: + if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_MOTION: + if (!wlf_handle_pointer_motion(instance, &event.mouse_motion)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_BUTTONS: + if (!wlf_handle_pointer_buttons(instance, &event.mouse_button)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_AXIS: + if (!wlf_handle_pointer_axis(instance, &event.mouse_axis)) + return FALSE; + + break; + + case UWAC_EVENT_KEY: + if (!wlf_handle_key(instance, &event.key)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_ENTER: + if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave)) + return FALSE; + + break; + + default: + break; + } + } + + return TRUE; +} + +static int wlfreerdp_run(freerdp* instance) +{ + wlfContext* context; + DWORD count; + HANDLE handles[64]; + DWORD status = WAIT_ABANDONED; + + if (!instance) + return -1; + + context = (wlfContext*)instance->context; + + if (!context) + return -1; + + if (!freerdp_connect(instance)) + { + printf("Failed to connect\n"); + return -1; + } + + while (!freerdp_shall_disconnect(instance)) + { + handles[0] = context->displayHandle; + count = freerdp_get_event_handles(instance->context, &handles[1], 63) + 1; + + if (count <= 1) + { + printf("Failed to get FreeRDP file descriptor\n"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + printf("%s: WaitForMultipleObjects failed\n", __FUNCTION__); + break; + } + + if (!handle_uwac_events(instance, context->display)) + { + printf("error handling UWAC events\n"); + break; + } + + if (freerdp_check_event_handles(instance->context) != TRUE) + { + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + printf("Failed to check FreeRDP file descriptor\n"); + + break; + } + } + + freerdp_disconnect(instance); + return status; +} + +static BOOL wlf_client_global_init(void) +{ + setlocale(LC_ALL, ""); + + if (freerdp_handle_signals() != 0) + return FALSE; + + return TRUE; +} + +static void wlf_client_global_uninit(void) +{ +} + +static int wlf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + wlfContext* wlf; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + wlf = (wlfContext*) instance->context; + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +static BOOL wlf_client_new(freerdp* instance, rdpContext* context) +{ + UwacReturnCode status; + wlfContext* wfl = (wlfContext*) context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = wl_pre_connect; + instance->PostConnect = wl_post_connect; + instance->PostDisconnect = wl_post_disconnect; + instance->Authenticate = client_cli_authenticate; + instance->GatewayAuthenticate = client_cli_gw_authenticate; + instance->VerifyCertificate = client_cli_verify_certificate; + instance->VerifyChangedCertificate = client_cli_verify_changed_certificate; + instance->LogonErrorInfo = wlf_logon_error_info; + wfl->display = UwacOpenDisplay(NULL, &status); + + if (!wfl->display || (status != UWAC_SUCCESS)) + return FALSE; + + wfl->displayHandle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, + UwacDisplayGetFd(wfl->display), WINPR_FD_READ); + + if (!wfl->displayHandle) + return FALSE; + + return TRUE; +} + + +static void wlf_client_free(freerdp* instance, rdpContext* context) +{ + wlfContext* wlf = (wlfContext*) instance->context; + + if (!context) + return; + + if (wlf->display) + UwacCloseDisplay(&wlf->display); + + if (wlf->displayHandle) + CloseHandle(wlf->displayHandle); +} + +static int wfl_client_start(rdpContext* context) +{ + return 0; +} + +static int wfl_client_stop(rdpContext* context) +{ + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = wlf_client_global_init; + pEntryPoints->GlobalUninit = wlf_client_global_uninit; + pEntryPoints->ContextSize = sizeof(wlfContext); + pEntryPoints->ClientNew = wlf_client_new; + pEntryPoints->ClientFree = wlf_client_free; + pEntryPoints->ClientStart = wfl_client_start; + pEntryPoints->ClientStop = wfl_client_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + DWORD status; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* context; + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + goto fail; + + status = freerdp_client_settings_parse_command_line(context->settings, argc, + argv, FALSE); + status = freerdp_client_settings_command_line_status_print(context->settings, + status, argc, argv); + + if (status) + return 0; + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = wlfreerdp_run(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h new file mode 100644 index 0000000..7605e46 --- /dev/null +++ b/client/Wayland/wlfreerdp.h @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_FREERDP_H +#define FREERDP_CLIENT_WAYLAND_FREERDP_H + +#include +#include +#include +#include +#include +#include +#include + +#define TAG CLIENT_TAG("wayland") + +typedef struct wlf_context wlfContext; + + +struct wlf_context +{ + rdpContext context; + + UwacDisplay* display; + HANDLE displayHandle; + UwacWindow* window; + + BOOL waitingFrameDone; + BOOL haveDamage; + + /* Channels */ + RdpeiClientContext* rdpei; + RdpgfxClientContext* gfx; + EncomspClientContext* encomsp; +}; + +#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */ + diff --git a/client/Windows/CMakeLists.txt b/client/Windows/CMakeLists.txt new file mode 100644 index 0000000..c645baf --- /dev/null +++ b/client/Windows/CMakeLists.txt @@ -0,0 +1,97 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "wfreerdp-client") +set(MODULE_PREFIX "FREERDP_CLIENT_WINDOWS_CONTROL") + +set(${MODULE_PREFIX}_SRCS + wf_gdi.c + wf_gdi.h + wf_event.c + wf_event.h + wf_channels.c + wf_channels.h + wf_graphics.c + wf_graphics.h + wf_cliprdr.c + wf_cliprdr.h + wf_rail.c + wf_rail.h + wf_client.c + wf_client.h + wf_floatbar.c + wf_floatbar.h + wfreerdp.rc + resource.h) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32 AND BUILD_SHARED_LIBS) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + if(WITH_CLIENT_INTERFACE) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + else() + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + endif() + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + + +if(WITH_CLIENT_INTERFACE) + if(CLIENT_INTERFACE_SHARED) + add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS}) + else() + add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + endif() + if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) + endif() + +else() + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/wfreerdp.c cli/wfreerdp.h) + add_executable(${MODULE_NAME} WIN32 ${${MODULE_PREFIX}_SRCS}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "wfreerdp") +endif() + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +if(WITH_CLIENT_INTERFACE) + install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries) + if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols) + endif() + add_subdirectory(cli) +else() + install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) + if (WITH_DEBUG_SYMBOLS AND MSVC) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) + endif() +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Windows") diff --git a/client/Windows/FreeRDP.ico b/client/Windows/FreeRDP.ico new file mode 100644 index 0000000..0864d1c Binary files /dev/null and b/client/Windows/FreeRDP.ico differ diff --git a/client/Windows/ModuleOptions.cmake b/client/Windows/ModuleOptions.cmake new file mode 100644 index 0000000..a0fcaec --- /dev/null +++ b/client/Windows/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_CLIENT_NAME "wfreerdp") +set(FREERDP_CLIENT_PLATFORM "Windows") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/client/Windows/cli/CMakeLists.txt b/client/Windows/cli/CMakeLists.txt new file mode 100644 index 0000000..0272611 --- /dev/null +++ b/client/Windows/cli/CMakeLists.txt @@ -0,0 +1,54 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "wfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WINDOWS") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS + wfreerdp.c + wfreerdp.h + ../wfreerdp.rc) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() +add_executable(${MODULE_NAME} WIN32 ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} wfreerdp-client) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Windows") diff --git a/client/Windows/cli/wfreerdp.c b/client/Windows/cli/wfreerdp.c new file mode 100644 index 0000000..ad9160c --- /dev/null +++ b/client/Windows/cli/wfreerdp.c @@ -0,0 +1,142 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "resource.h" + +#include "wf_client.h" + +#include + +INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + int status; + HANDLE thread; + wfContext* wfc; + DWORD dwExitCode; + rdpContext* context; + rdpSettings* settings; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + int ret = 1; + int argc = 0, i; + LPWSTR* args; + LPWSTR cmd; + char** argv; + ZeroMemory(&clientEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + 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; + + cmd = GetCommandLineW(); + + if (!cmd) + goto out; + + args = CommandLineToArgvW(cmd, &argc); + + if (!args) + goto out; + + argv = calloc(argc, sizeof(char*)); + + if (!argv) + goto out; + + for (i = 0; i < argc; i++) + { + int size = WideCharToMultiByte(CP_UTF8, 0, args[i], -1, NULL, 0, NULL, NULL); + argv[i] = calloc(size, sizeof(char)); + + if (!argv[i]) + goto out; + + if (WideCharToMultiByte(CP_UTF8, 0, args[i], -1, argv[i], size, NULL, + NULL) != size) + goto out; + } + + settings = context->settings; + wfc = (wfContext*) context; + + if (!settings || !wfc) + goto out; + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, + FALSE); + + if (status) + { + freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + goto out; + } + + if (freerdp_client_start(context) != 0) + goto out; + + thread = freerdp_client_get_thread(context); + + if (thread) + { + if (WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0) + { + GetExitCodeThread(thread, &dwExitCode); + ret = dwExitCode; + } + } + + if (freerdp_client_stop(context) != 0) + goto out; + +out: + freerdp_client_context_free(context); + + if (argv) + { + for (i = 0; i < argc; i++) + free(argv[i]); + + free(argv); + } + + LocalFree(args); + return ret; +} diff --git a/client/Windows/cli/wfreerdp.h b/client/Windows/cli/wfreerdp.h new file mode 100644 index 0000000..2bb57bc --- /dev/null +++ b/client/Windows/cli/wfreerdp.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_FREERDP_H +#define FREERDP_CLIENT_WIN_FREERDP_H + +#include "wf_interface.h" + +#endif /* FREERDP_CLIENT_WIN_FREERDP_H */ diff --git a/client/Windows/resource.h b/client/Windows/resource.h new file mode 100644 index 0000000..89c1e79 --- /dev/null +++ b/client/Windows/resource.h @@ -0,0 +1,13 @@ + +#define IDI_ICON1 101 +#define IDB_BACKGROUND 102 +#define IDB_MINIMIZE 103 +#define IDB_MINIMIZE_ACT 104 +#define IDB_LOCK 105 +#define IDB_LOCK_ACT 106 +#define IDB_UNLOCK 107 +#define IDB_UNLOCK_ACT 108 +#define IDB_CLOSE 109 +#define IDB_CLOSE_ACT 100 +#define IDB_RESTORE 111 +#define IDB_RESTORE_ACT 112 diff --git a/client/Windows/resource/bg.bmp b/client/Windows/resource/bg.bmp new file mode 100644 index 0000000..7e90ec6 Binary files /dev/null and b/client/Windows/resource/bg.bmp differ diff --git a/client/Windows/resource/close.bmp b/client/Windows/resource/close.bmp new file mode 100644 index 0000000..f9bf31e Binary files /dev/null and b/client/Windows/resource/close.bmp differ diff --git a/client/Windows/resource/close_active.bmp b/client/Windows/resource/close_active.bmp new file mode 100644 index 0000000..43dc5b2 Binary files /dev/null and b/client/Windows/resource/close_active.bmp differ diff --git a/client/Windows/resource/lock.bmp b/client/Windows/resource/lock.bmp new file mode 100644 index 0000000..6857787 Binary files /dev/null and b/client/Windows/resource/lock.bmp differ diff --git a/client/Windows/resource/lock_active.bmp b/client/Windows/resource/lock_active.bmp new file mode 100644 index 0000000..b4fa35f Binary files /dev/null and b/client/Windows/resource/lock_active.bmp differ diff --git a/client/Windows/resource/minimize.bmp b/client/Windows/resource/minimize.bmp new file mode 100644 index 0000000..7fce92d Binary files /dev/null and b/client/Windows/resource/minimize.bmp differ diff --git a/client/Windows/resource/minimize_active.bmp b/client/Windows/resource/minimize_active.bmp new file mode 100644 index 0000000..6f0b74d Binary files /dev/null and b/client/Windows/resource/minimize_active.bmp differ diff --git a/client/Windows/resource/restore.bmp b/client/Windows/resource/restore.bmp new file mode 100644 index 0000000..b2ae47b Binary files /dev/null and b/client/Windows/resource/restore.bmp differ diff --git a/client/Windows/resource/restore_active.bmp b/client/Windows/resource/restore_active.bmp new file mode 100644 index 0000000..a0516af Binary files /dev/null and b/client/Windows/resource/restore_active.bmp differ diff --git a/client/Windows/resource/unlock.bmp b/client/Windows/resource/unlock.bmp new file mode 100644 index 0000000..f59b72a Binary files /dev/null and b/client/Windows/resource/unlock.bmp differ diff --git a/client/Windows/resource/unlock_active.bmp b/client/Windows/resource/unlock_active.bmp new file mode 100644 index 0000000..a5d9c38 Binary files /dev/null and b/client/Windows/resource/unlock_active.bmp differ diff --git a/client/Windows/wf_channels.c b/client/Windows/wf_channels.c new file mode 100644 index 0000000..73aac5c --- /dev/null +++ b/client/Windows/wf_channels.c @@ -0,0 +1,88 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "wf_channels.h" + +#include "wf_rail.h" +#include "wf_cliprdr.h" + +#include + +#include +#define TAG CLIENT_TAG("windows") + +void wf_OnChannelConnectedEventHandler(void* context, + ChannelConnectedEventArgs* e) +{ + wfContext* wfc = (wfContext*) context; + rdpSettings* settings = wfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (!settings->SoftwareGdi) + WLog_WARN(TAG, + "Channel "RDPGFX_DVC_CHANNEL_NAME" does not support hardware acceleration, using fallback."); + + gdi_graphics_pipeline_init(wfc->context.gdi, (RdpgfxClientContext*) e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + wf_rail_init(wfc, (RailClientContext*) e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wf_cliprdr_init(wfc, (CliprdrClientContext*) e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } +} + +void wf_OnChannelDisconnectedEventHandler(void* context, + ChannelDisconnectedEventArgs* e) +{ + wfContext* wfc = (wfContext*) context; + rdpSettings* settings = wfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + gdi_graphics_pipeline_uninit(wfc->context.gdi, + (RdpgfxClientContext*) e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + wf_rail_uninit(wfc, (RailClientContext*) e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wf_cliprdr_uninit(wfc, (CliprdrClientContext*) e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } +} diff --git a/client/Windows/wf_channels.h b/client/Windows/wf_channels.h new file mode 100644 index 0000000..90ee0da --- /dev/null +++ b/client/Windows/wf_channels.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_CHANNELS_H +#define FREERDP_CLIENT_WIN_CHANNELS_H + +#include +#include +#include +#include +#include +#include + +#include "wf_client.h" + +void wf_OnChannelConnectedEventHandler(void* context, + ChannelConnectedEventArgs* e); +void wf_OnChannelDisconnectedEventHandler(void* context, + ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_WIN_CHANNELS_H */ diff --git a/client/Windows/wf_client.c b/client/Windows/wf_client.c new file mode 100644 index 0000000..6369ddf --- /dev/null +++ b/client/Windows/wf_client.c @@ -0,0 +1,1026 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wf_gdi.h" +#include "wf_rail.h" +#include "wf_channels.h" +#include "wf_graphics.h" +#include "wf_cliprdr.h" + +#include "wf_client.h" + +#include "resource.h" + +#define TAG CLIENT_TAG("windows") + +static int wf_create_console(void) +{ + if (!AllocConsole()) + return 1; + + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + WLog_INFO(TAG, "Debug console created."); + return 0; +} + +static BOOL wf_end_paint(rdpContext* context) +{ + int i; + rdpGdi* gdi; + int ninvalid; + RECT updateRect; + HGDI_RGN cinvalid; + REGION16 invalidRegion; + RECTANGLE_16 invalidRect; + const RECTANGLE_16* extents; + wfContext* wfc = (wfContext*)context; + gdi = context->gdi; + ninvalid = gdi->primary->hdc->hwnd->ninvalid; + cinvalid = gdi->primary->hdc->hwnd->cinvalid; + + if (ninvalid < 1) + return TRUE; + + region16_init(&invalidRegion); + + for (i = 0; i < ninvalid; i++) + { + invalidRect.left = cinvalid[i].x; + invalidRect.top = cinvalid[i].y; + invalidRect.right = cinvalid[i].x + cinvalid[i].w; + invalidRect.bottom = cinvalid[i].y + cinvalid[i].h; + region16_union_rect(&invalidRegion, &invalidRegion, &invalidRect); + } + + if (!region16_is_empty(&invalidRegion)) + { + extents = region16_extents(&invalidRegion); + updateRect.left = extents->left; + updateRect.top = extents->top; + updateRect.right = extents->right; + updateRect.bottom = extents->bottom; + InvalidateRect(wfc->hwnd, &updateRect, FALSE); + + if (wfc->rail) + wf_rail_invalidate_region(wfc, &invalidRegion); + } + + region16_uninit(&invalidRegion); + return TRUE; +} + +static BOOL wf_begin_paint(rdpContext* context) +{ + HGDI_DC hdc; + + if (!context || !context->gdi || !context->gdi->primary || !context->gdi->primary->hdc) + return FALSE; + + hdc = context->gdi->primary->hdc; + + if (!hdc || !hdc->hwnd || !hdc->hwnd->invalid) + return FALSE; + + hdc->hwnd->invalid->null = TRUE; + hdc->hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL wf_desktop_resize(rdpContext* context) +{ + BOOL same; + RECT rect; + rdpSettings* settings; + wfContext* wfc = (wfContext*)context; + + if (!context || !context->settings) + return FALSE; + + settings = context->settings; + + if (wfc->primary) + { + same = (wfc->primary == wfc->drawing) ? TRUE : FALSE; + wf_image_free(wfc->primary); + wfc->primary = wf_image_new(wfc, settings->DesktopWidth, + settings->DesktopHeight, context->gdi->dstFormat, NULL); + } + + if (!gdi_resize_ex(context->gdi, settings->DesktopWidth, + settings->DesktopHeight, 0, + context->gdi->dstFormat, wfc->primary->pdata, NULL)) + return FALSE; + + if (same) + wfc->drawing = wfc->primary; + + if (wfc->fullscreen != TRUE) + { + if (wfc->hwnd) + SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1, settings->DesktopWidth + wfc->diff.x, + settings->DesktopHeight + wfc->diff.y, SWP_NOMOVE); + } + else + { + wf_update_offset(wfc); + GetWindowRect(wfc->hwnd, &rect); + InvalidateRect(wfc->hwnd, &rect, TRUE); + } + + return TRUE; +} + +static BOOL wf_pre_connect(freerdp* instance) +{ + wfContext* wfc; + int desktopWidth; + int desktopHeight; + rdpContext* context; + rdpSettings* settings; + + if (!instance || !instance->context || !instance->settings) + return FALSE; + + context = instance->context; + wfc = (wfContext*) instance->context; + settings = instance->settings; + settings->OsMajorType = OSMAJORTYPE_WINDOWS; + settings->OsMinorType = OSMINORTYPE_WINDOWS_NT; + settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_PATBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_LINETO_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE; + settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = TRUE; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE; + wfc->fullscreen = settings->Fullscreen; + wfc->floatbar_active = settings->Floatbar; + + if (wfc->fullscreen) + wfc->fs_toggle = 1; + + desktopWidth = settings->DesktopWidth; + desktopHeight = settings->DesktopHeight; + + if (wfc->percentscreen > 0) + { + desktopWidth = (GetSystemMetrics(SM_CXSCREEN) * wfc->percentscreen) / 100; + settings->DesktopWidth = desktopWidth; + desktopHeight = (GetSystemMetrics(SM_CYSCREEN) * wfc->percentscreen) / 100; + settings->DesktopHeight = desktopHeight; + } + + if (wfc->fullscreen) + { + if (settings->UseMultimon) + { + desktopWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + desktopHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else + { + desktopWidth = GetSystemMetrics(SM_CXSCREEN); + desktopHeight = GetSystemMetrics(SM_CYSCREEN); + } + } + + /* FIXME: desktopWidth has a limitation that it should be divisible by 4, + * otherwise the screen will crash when connecting to an XP desktop.*/ + desktopWidth = (desktopWidth + 3) & (~3); + + if (desktopWidth != settings->DesktopWidth) + { + freerdp_set_param_uint32(settings, FreeRDP_DesktopWidth, desktopWidth); + } + + if (desktopHeight != settings->DesktopHeight) + { + freerdp_set_param_uint32(settings, FreeRDP_DesktopHeight, desktopHeight); + } + + if ((settings->DesktopWidth < 64) || (settings->DesktopHeight < 64) || + (settings->DesktopWidth > 4096) || (settings->DesktopHeight > 4096)) + { + WLog_ERR(TAG, "invalid dimensions %lu %lu", settings->DesktopWidth, + settings->DesktopHeight); + return FALSE; + } + + if (!freerdp_client_load_addins(context->channels, instance->settings)) + return -1; + + freerdp_set_param_uint32(settings, FreeRDP_KeyboardLayout, + (int) GetKeyboardLayout(0) & 0x0000FFFF); + PubSub_SubscribeChannelConnected(instance->context->pubSub, + wf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + wf_OnChannelDisconnectedEventHandler); + return TRUE; +} + +static void wf_add_system_menu(wfContext* wfc) +{ + HMENU hMenu = GetSystemMenu(wfc->hwnd, FALSE); + MENUITEMINFO item_info; + ZeroMemory(&item_info, sizeof(MENUITEMINFO)); + item_info.fMask = MIIM_CHECKMARKS | MIIM_FTYPE | MIIM_ID | MIIM_STRING | + MIIM_DATA; + item_info.cbSize = sizeof(MENUITEMINFO); + item_info.wID = SYSCOMMAND_ID_SMARTSIZING; + item_info.fType = MFT_STRING; + item_info.dwTypeData = _wcsdup(_T("Smart sizing")); + item_info.cch = (UINT) _wcslen(_T("Smart sizing")); + item_info.dwItemData = (ULONG_PTR) wfc; + InsertMenuItem(hMenu, 6, TRUE, &item_info); + + if (wfc->context.settings->SmartSizing) + { + CheckMenuItem(hMenu, SYSCOMMAND_ID_SMARTSIZING, MF_CHECKED); + } +} + +static BOOL wf_post_connect(freerdp* instance) +{ + rdpGdi* gdi; + DWORD dwStyle; + rdpCache* cache; + wfContext* wfc; + rdpContext* context; + WCHAR lpWindowName[512]; + rdpSettings* settings; + EmbedWindowEventArgs e; + const UINT32 format = PIXEL_FORMAT_BGRX32; + settings = instance->settings; + context = instance->context; + wfc = (wfContext*) instance->context; + cache = instance->context->cache; + wfc->primary = wf_image_new(wfc, settings->DesktopWidth, + settings->DesktopHeight, format, NULL); + + if (!gdi_init_ex(instance, format, 0, wfc->primary->pdata, NULL)) + return FALSE; + + gdi = instance->context->gdi; + + if (!settings->SoftwareGdi) + { + wf_gdi_register_update_callbacks(instance->update); + } + + if (settings->WindowTitle != NULL) + _snwprintf_s(lpWindowName, ARRAYSIZE(lpWindowName), _TRUNCATE, L"%S", settings->WindowTitle); + else if (settings->ServerPort == 3389) + _snwprintf_s(lpWindowName, ARRAYSIZE(lpWindowName), _TRUNCATE, L"FreeRDP: %S", + settings->ServerHostname); + else + _snwprintf_s(lpWindowName, ARRAYSIZE(lpWindowName), _TRUNCATE, L"FreeRDP: %S:%u", + settings->ServerHostname, settings->ServerPort); + + if (settings->EmbeddedWindow) + settings->Decorations = FALSE; + + if (wfc->fullscreen) + dwStyle = WS_POPUP; + else if (!settings->Decorations) + dwStyle = WS_CHILD | WS_BORDER; + else + dwStyle = WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX + | WS_MAXIMIZEBOX; + + if (!wfc->hwnd) + { + wfc->hwnd = CreateWindowEx((DWORD) NULL, wfc->wndClassName, lpWindowName, + dwStyle, + 0, 0, 0, 0, wfc->hWndParent, NULL, wfc->hInstance, NULL); + SetWindowLongPtr(wfc->hwnd, GWLP_USERDATA, (LONG_PTR) wfc); + } + + wf_resize_window(wfc); + wf_add_system_menu(wfc); + BitBlt(wfc->primary->hdc, 0, 0, settings->DesktopWidth, settings->DesktopHeight, + NULL, 0, 0, BLACKNESS); + wfc->drawing = wfc->primary; + EventArgsInit(&e, "wfreerdp"); + e.embed = FALSE; + e.handle = (void*) wfc->hwnd; + PubSub_OnEmbedWindow(context->pubSub, context, &e); + ShowWindow(wfc->hwnd, SW_SHOWNORMAL); + UpdateWindow(wfc->hwnd); + instance->update->BeginPaint = wf_begin_paint; + instance->update->DesktopResize = wf_desktop_resize; + instance->update->EndPaint = wf_end_paint; + wf_register_pointer(context->graphics); + + if (!settings->SoftwareGdi) + { + wf_register_graphics(context->graphics); + wf_gdi_register_update_callbacks(instance->update); + brush_cache_register_callbacks(instance->update); + glyph_cache_register_callbacks(instance->update); + bitmap_cache_register_callbacks(instance->update); + offscreen_cache_register_callbacks(instance->update); + palette_cache_register_callbacks(instance->update); + } + + if (wfc->fullscreen) + floatbar_window_create(wfc); + + return TRUE; +} + +static BOOL wf_post_disconnect(freerdp* instance) +{ + return TRUE; +} + +static CREDUI_INFOA wfUiInfo = +{ + sizeof(CREDUI_INFOA), + NULL, + "Enter your credentials", + "Remote Desktop Security", + NULL +}; + +static BOOL wf_authenticate_raw(freerdp* instance, const char* title, + char** username, char** password, char** domain) +{ + BOOL fSave; + DWORD status; + DWORD dwFlags; + char UserName[CREDUI_MAX_USERNAME_LENGTH + 1]; + char Password[CREDUI_MAX_PASSWORD_LENGTH + 1]; + char User[CREDUI_MAX_USERNAME_LENGTH + 1]; + char Domain[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1]; + fSave = FALSE; + ZeroMemory(UserName, sizeof(UserName)); + ZeroMemory(Password, sizeof(Password)); + dwFlags = CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_EXCLUDE_CERTIFICATES; + status = CredUIPromptForCredentialsA(&wfUiInfo, title, NULL, 0, + UserName, CREDUI_MAX_USERNAME_LENGTH + 1, + Password, CREDUI_MAX_PASSWORD_LENGTH + 1, &fSave, dwFlags); + + if (status != NO_ERROR) + { + WLog_ERR(TAG, "CredUIPromptForCredentials unexpected status: 0x%08lX", status); + return FALSE; + } + + ZeroMemory(User, sizeof(User)); + ZeroMemory(Domain, sizeof(Domain)); + status = CredUIParseUserNameA(UserName, User, sizeof(User), Domain, + sizeof(Domain)); + //WLog_ERR(TAG, "User: %s Domain: %s Password: %s", User, Domain, Password); + *username = _strdup(User); + + if (!(*username)) + { + WLog_ERR(TAG, "strdup failed", status); + return FALSE; + } + + if (strlen(Domain) > 0) + *domain = _strdup(Domain); + else + *domain = _strdup("\0"); + + if (!(*domain)) + { + free(*username); + WLog_ERR(TAG, "strdup failed", status); + return FALSE; + } + + *password = _strdup(Password); + + if (!(*password)) + { + free(*username); + free(*domain); + return FALSE; + } + + return TRUE; +} + +static BOOL wf_authenticate(freerdp* instance, + char** username, char** password, char** domain) +{ + return wf_authenticate_raw(instance, instance->settings->ServerHostname, + username, password, domain); +} + +static BOOL wf_gw_authenticate(freerdp* instance, + char** username, char** password, char** domain) +{ + char tmp[MAX_PATH]; + sprintf_s(tmp, sizeof(tmp), "Gateway %s", instance->settings->GatewayHostname); + return wf_authenticate_raw(instance, tmp, username, password, domain); +} + +static DWORD wf_verify_certificate(freerdp* instance, + const char* common_name, + const char* subject, + const char* issuer, + const char* fingerprint, + BOOL host_mismatch) +{ +#if 0 + DWORD mode; + int read_size; + DWORD read_count; + TCHAR answer[2]; + TCHAR* read_buffer; + HANDLE input_handle; +#endif + WLog_INFO(TAG, "Certificate details:"); + WLog_INFO(TAG, "\tCommonName: %s", common_name); + WLog_INFO(TAG, "\tSubject: %s", subject); + WLog_INFO(TAG, "\tIssuer: %s", issuer); + WLog_INFO(TAG, "\tThumbprint: %s", fingerprint); + WLog_INFO(TAG, "\tHostMismatch: %s", host_mismatch ? "Yes" : "No"); + WLog_INFO(TAG, + "The above X.509 certificate could not be verified, possibly because you do not have " + "the CA certificate in your certificate store, or the certificate has expired. " + "Please look at the OpenSSL documentation on how to add a private CA to the store.\n"); + /* TODO: ask for user validation */ +#if 0 + input_handle = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(input_handle, &mode); + mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT; + SetConsoleMode(input_handle, mode); +#endif + /* return 1 to accept and store a certificate, 2 to accept + * a certificate only for this session, 0 otherwise */ + return 2; +} + +static DWORD wf_verify_changed_certificate(freerdp* instance, + const char* common_name, + const char* subject, const char* issuer, + const char* fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint) +{ + WLog_ERR(TAG, "!!! Certificate has changed !!!"); + WLog_ERR(TAG, "New Certificate details:"); + WLog_ERR(TAG, "\tSubject: %s", subject); + WLog_ERR(TAG, "\tIssuer: %s", issuer); + WLog_ERR(TAG, "\tThumbprint: %s", fingerprint); + WLog_ERR(TAG, "Old Certificate details:"); + WLog_ERR(TAG, "\tSubject: %s", old_subject); + WLog_ERR(TAG, "\tIssuer: %s", old_issuer); + WLog_ERR(TAG, "\tThumbprint: %s", old_fingerprint); + WLog_ERR(TAG, + "The above X.509 certificate does not match the certificate used for previous connections. " + "This may indicate that the certificate has been tampered with." + "Please contact the administrator of the RDP server and clarify."); + return 0; +} + +static DWORD WINAPI wf_input_thread(LPVOID arg) +{ + int status; + wMessage message; + wMessageQueue* queue; + freerdp* instance = (freerdp*) arg; + assert(NULL != instance); + status = 1; + queue = freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + + while (MessageQueue_Wait(queue)) + { + while (MessageQueue_Peek(queue, &message, TRUE)) + { + status = freerdp_message_queue_process_message(instance, + FREERDP_INPUT_MESSAGE_QUEUE, &message); + + if (!status) + break; + } + + if (!status) + break; + } + + ExitThread(0); + return 0; +} + +static DWORD WINAPI wf_client_thread(LPVOID lpParam) +{ + MSG msg; + int width; + int height; + BOOL msg_ret; + int quit_msg; + DWORD nCount; + DWORD error; + HANDLE handles[64]; + wfContext* wfc; + freerdp* instance; + rdpContext* context; + rdpChannels* channels; + rdpSettings* settings; + BOOL async_input; + HANDLE input_thread; + instance = (freerdp*) lpParam; + context = instance->context; + wfc = (wfContext*) instance->context; + + if (!freerdp_connect(instance)) + goto end; + + channels = instance->context->channels; + settings = instance->context->settings; + async_input = settings->AsyncInput; + + if (async_input) + { + if (!(input_thread = CreateThread(NULL, 0, wf_input_thread, + instance, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create async input thread."); + goto disconnect; + } + } + + while (1) + { + nCount = 0; + + if (freerdp_focus_required(instance)) + { + wf_event_focus_in(wfc); + wf_event_focus_in(wfc); + } + + { + DWORD tmp = freerdp_get_event_handles(context, &handles[nCount], 64 - nCount); + + if (tmp == 0) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + nCount += tmp; + } + + if (MsgWaitForMultipleObjects(nCount, handles, FALSE, 1000, + QS_ALLINPUT) == WAIT_FAILED) + { + WLog_ERR(TAG, "wfreerdp_run: WaitForMultipleObjects failed: 0x%08lX", + GetLastError()); + break; + } + + { + if (!freerdp_check_event_handles(context)) + { + if (client_auto_reconnect(instance)) + continue; + + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + if (freerdp_shall_disconnect(instance)) + break; + + quit_msg = FALSE; + + while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) + { + msg_ret = GetMessage(&msg, NULL, 0, 0); + + if (instance->settings->EmbeddedWindow) + { + if ((msg.message == WM_SETFOCUS) && (msg.lParam == 1)) + { + PostMessage(wfc->hwnd, WM_SETFOCUS, 0, 0); + } + else if ((msg.message == WM_KILLFOCUS) && (msg.lParam == 1)) + { + PostMessage(wfc->hwnd, WM_KILLFOCUS, 0, 0); + } + } + + if (msg.message == WM_SIZE) + { + width = LOWORD(msg.lParam); + height = HIWORD(msg.lParam); + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED); + } + + if ((msg_ret == 0) || (msg_ret == -1)) + { + quit_msg = TRUE; + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (quit_msg) + break; + } + + /* cleanup */ + if (async_input) + { + wMessageQueue* input_queue; + input_queue = freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + + if (MessageQueue_PostQuit(input_queue, 0)) + WaitForSingleObject(input_thread, INFINITE); + } + +disconnect: + freerdp_disconnect(instance); + + if (async_input) + CloseHandle(input_thread); + +end: + error = freerdp_get_last_error(instance->context); + WLog_DBG(TAG, "Main thread exited with %" PRIu32, error); + ExitThread(error); + return error; +} + +static DWORD WINAPI wf_keyboard_thread(LPVOID lpParam) +{ + MSG msg; + BOOL status; + wfContext* wfc; + HHOOK hook_handle; + wfc = (wfContext*) lpParam; + assert(NULL != wfc); + hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, wf_ll_kbd_proc, wfc->hInstance, + 0); + + if (hook_handle) + { + while ((status = GetMessage(&msg, NULL, 0, 0)) != 0) + { + if (status == -1) + { + WLog_ERR(TAG, "keyboard thread error getting message"); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + UnhookWindowsHookEx(hook_handle); + } + else + { + WLog_ERR(TAG, "failed to install keyboard hook"); + } + + WLog_DBG(TAG, "Keyboard thread exited."); + ExitThread(0); + return (DWORD) NULL; +} + +static rdpSettings* freerdp_client_get_settings(wfContext* wfc) +{ + return wfc->context.settings; +} + +static int freerdp_client_focus_in(wfContext* wfc) +{ + PostThreadMessage(wfc->mainThreadId, WM_SETFOCUS, 0, 1); + return 0; +} + +static int freerdp_client_focus_out(wfContext* wfc) +{ + PostThreadMessage(wfc->mainThreadId, WM_KILLFOCUS, 0, 1); + return 0; +} + +static int freerdp_client_set_window_size(wfContext* wfc, int width, int height) +{ + WLog_DBG(TAG, "freerdp_client_set_window_size %d, %d", width, height); + + if ((width != wfc->client_width) || (height != wfc->client_height)) + { + PostThreadMessage(wfc->mainThreadId, WM_SIZE, SIZE_RESTORED, + ((UINT) height << 16) | (UINT) width); + } + + return 0; +} + +void wf_size_scrollbars(wfContext* wfc, UINT32 client_width, + UINT32 client_height) +{ + if (wfc->disablewindowtracking) + return; + + // prevent infinite message loop + wfc->disablewindowtracking = TRUE; + + if (wfc->context.settings->SmartSizing) + { + wfc->xCurrentScroll = 0; + wfc->yCurrentScroll = 0; + + if (wfc->xScrollVisible || wfc->yScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_BOTH, FALSE)) + { + wfc->xScrollVisible = FALSE; + wfc->yScrollVisible = FALSE; + } + } + } + else + { + SCROLLINFO si; + BOOL horiz = wfc->xScrollVisible; + BOOL vert = wfc->yScrollVisible;; + + if (!horiz && client_width < wfc->context.settings->DesktopWidth) + { + horiz = TRUE; + } + else if (horiz + && client_width >= + wfc->context.settings->DesktopWidth/* - GetSystemMetrics(SM_CXVSCROLL)*/) + { + horiz = FALSE; + } + + if (!vert && client_height < wfc->context.settings->DesktopHeight) + { + vert = TRUE; + } + else if (vert + && client_height >= + wfc->context.settings->DesktopHeight/* - GetSystemMetrics(SM_CYHSCROLL)*/) + { + vert = FALSE; + } + + if (horiz == vert && (horiz != wfc->xScrollVisible + && vert != wfc->yScrollVisible)) + { + if (ShowScrollBar(wfc->hwnd, SB_BOTH, horiz)) + { + wfc->xScrollVisible = horiz; + wfc->yScrollVisible = vert; + } + } + + if (horiz != wfc->xScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_HORZ, horiz)) + { + wfc->xScrollVisible = horiz; + } + } + + if (vert != wfc->yScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_VERT, vert)) + { + wfc->yScrollVisible = vert; + } + } + + if (horiz) + { + // The horizontal scrolling range is defined by + // (bitmap_width) - (client_width). The current horizontal + // scroll value remains within the horizontal scrolling range. + wfc->xMaxScroll = MAX(wfc->context.settings->DesktopWidth - client_width, 0); + wfc->xCurrentScroll = MIN(wfc->xCurrentScroll, wfc->xMaxScroll); + si.cbSize = sizeof(si); + si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + si.nMin = wfc->xMinScroll; + si.nMax = wfc->context.settings->DesktopWidth; + si.nPage = client_width; + si.nPos = wfc->xCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_HORZ, &si, TRUE); + } + + if (vert) + { + // The vertical scrolling range is defined by + // (bitmap_height) - (client_height). The current vertical + // scroll value remains within the vertical scrolling range. + wfc->yMaxScroll = MAX(wfc->context.settings->DesktopHeight - client_height, + 0); + wfc->yCurrentScroll = MIN(wfc->yCurrentScroll, wfc->yMaxScroll); + si.cbSize = sizeof(si); + si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + si.nMin = wfc->yMinScroll; + si.nMax = wfc->context.settings->DesktopHeight; + si.nPage = client_height; + si.nPos = wfc->yCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_VERT, &si, TRUE); + } + } + + wfc->disablewindowtracking = FALSE; + wf_update_canvas_diff(wfc); +} + +static BOOL wfreerdp_client_global_init(void) +{ + WSADATA wsaData; + + if (!getenv("HOME")) + { + char home[MAX_PATH * 2] = "HOME="; + strcat(home, getenv("HOMEDRIVE")); + strcat(home, getenv("HOMEPATH")); + _putenv(home); + } + + WSAStartup(0x101, &wsaData); +#if defined(WITH_DEBUG) || defined(_DEBUG) + wf_create_console(); +#endif + freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); + return TRUE; +} + +static void wfreerdp_client_global_uninit(void) +{ + WSACleanup(); +} + +static BOOL wfreerdp_client_new(freerdp* instance, rdpContext* context) +{ + if (!(wfreerdp_client_global_init())) + return FALSE; + + instance->PreConnect = wf_pre_connect; + instance->PostConnect = wf_post_connect; + instance->PostDisconnect = wf_post_disconnect; + instance->Authenticate = wf_authenticate; + instance->GatewayAuthenticate = wf_gw_authenticate; + instance->VerifyCertificate = wf_verify_certificate; + instance->VerifyChangedCertificate = wf_verify_changed_certificate; + return TRUE; +} + +static void wfreerdp_client_free(freerdp* instance, rdpContext* context) +{ + if (!context) + return; +} + +static int wfreerdp_client_start(rdpContext* context) +{ + HWND hWndParent; + HINSTANCE hInstance; + wfContext* wfc = (wfContext*) context; + freerdp* instance = context->instance; + hInstance = GetModuleHandle(NULL); + hWndParent = (HWND) instance->settings->ParentWindowId; + instance->settings->EmbeddedWindow = (hWndParent) ? TRUE : FALSE; + wfc->hWndParent = hWndParent; + wfc->hInstance = hInstance; + wfc->cursor = LoadCursor(NULL, IDC_ARROW); + wfc->icon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1)); + wfc->wndClassName = _tcsdup(_T("FreeRDP")); + wfc->wndClass.cbSize = sizeof(WNDCLASSEX); + wfc->wndClass.style = CS_HREDRAW | CS_VREDRAW; + wfc->wndClass.lpfnWndProc = wf_event_proc; + wfc->wndClass.cbClsExtra = 0; + wfc->wndClass.cbWndExtra = 0; + wfc->wndClass.hCursor = wfc->cursor; + wfc->wndClass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wfc->wndClass.lpszMenuName = NULL; + wfc->wndClass.lpszClassName = wfc->wndClassName; + wfc->wndClass.hInstance = hInstance; + wfc->wndClass.hIcon = wfc->icon; + wfc->wndClass.hIconSm = wfc->icon; + RegisterClassEx(&(wfc->wndClass)); + wfc->keyboardThread = CreateThread(NULL, 0, wf_keyboard_thread, (void*) wfc, 0, + &wfc->keyboardThreadId); + + if (!wfc->keyboardThread) + return -1; + + wfc->thread = CreateThread(NULL, 0, wf_client_thread, (void*) instance, 0, + &wfc->mainThreadId); + + if (!wfc->thread) + return -1; + + return 0; +} + +static int wfreerdp_client_stop(rdpContext* context) +{ + wfContext* wfc = (wfContext*) context; + + if (wfc->thread) + { + PostThreadMessage(wfc->mainThreadId, WM_QUIT, 0, 0); + WaitForSingleObject(wfc->thread, INFINITE); + CloseHandle(wfc->thread); + wfc->thread = NULL; + wfc->mainThreadId = 0; + } + + if (wfc->keyboardThread) + { + PostThreadMessage(wfc->keyboardThreadId, WM_QUIT, 0, 0); + WaitForSingleObject(wfc->keyboardThread, INFINITE); + CloseHandle(wfc->keyboardThread); + wfc->keyboardThread = NULL; + wfc->keyboardThreadId = 0; + } + + return 0; +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = wfreerdp_client_global_init; + pEntryPoints->GlobalUninit = wfreerdp_client_global_uninit; + pEntryPoints->ContextSize = sizeof(wfContext); + pEntryPoints->ClientNew = wfreerdp_client_new; + pEntryPoints->ClientFree = wfreerdp_client_free; + pEntryPoints->ClientStart = wfreerdp_client_start; + pEntryPoints->ClientStop = wfreerdp_client_stop; + return 0; +} diff --git a/client/Windows/wf_client.h b/client/Windows/wf_client.h new file mode 100644 index 0000000..a7c0658 --- /dev/null +++ b/client/Windows/wf_client.h @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_INTERFACE_H +#define FREERDP_CLIENT_WIN_INTERFACE_H + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct wf_context wfContext; + +#include "wf_channels.h" +#include "wf_floatbar.h" +#include "wf_event.h" +#include "wf_cliprdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// System menu constants +#define SYSCOMMAND_ID_SMARTSIZING 1000 + +struct wf_bitmap +{ + rdpBitmap _bitmap; + HDC hdc; + HBITMAP bitmap; + HBITMAP org_bitmap; + BYTE* pdata; +}; +typedef struct wf_bitmap wfBitmap; + +struct wf_pointer +{ + rdpPointer pointer; + HCURSOR cursor; +}; +typedef struct wf_pointer wfPointer; + +struct wf_context +{ + rdpContext context; + DEFINE_RDP_CLIENT_COMMON(); + + int offset_x; + int offset_y; + int fs_toggle; + int fullscreen; + int floatbar_active; + int percentscreen; + char window_title[64]; + int client_x; + int client_y; + int client_width; + int client_height; + + HANDLE keyboardThread; + + HICON icon; + HWND hWndParent; + HINSTANCE hInstance; + WNDCLASSEX wndClass; + LPCTSTR wndClassName; + HCURSOR hDefaultCursor; + + HWND hwnd; + POINT diff; + + wfBitmap* primary; + wfBitmap* drawing; + HCURSOR cursor; + HBRUSH brush; + HBRUSH org_brush; + RECT update_rect; + RECT scale_update_rect; + + DWORD mainThreadId; + DWORD keyboardThreadId; + + rdpFile* connectionRdpFile; + + BOOL disablewindowtracking; + + BOOL updating_scrollbars; + BOOL xScrollVisible; + int xMinScroll; + int xCurrentScroll; + int xMaxScroll; + + BOOL yScrollVisible; + int yMinScroll; + int yCurrentScroll; + int yMaxScroll; + + void* clipboard; + CliprdrClientContext* cliprdr; + + FloatBar* floatbar; + + RailClientContext* rail; + wHashTable* railWindows; +}; + +/** + * Client Interface + */ + +FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); +FREERDP_API int freerdp_client_set_window_size(wfContext* wfc, int width, + int height); +FREERDP_API void wf_size_scrollbars(wfContext* wfc, UINT32 client_width, + UINT32 client_height); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_WIN_INTERFACE_H */ diff --git a/client/Windows/wf_cliprdr.c b/client/Windows/wf_cliprdr.c new file mode 100644 index 0000000..88413a2 --- /dev/null +++ b/client/Windows/wf_cliprdr.c @@ -0,0 +1,2581 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Clipboard Redirection + * + * Copyright 2012 Jason Champion + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CINTERFACE +#define COBJMACROS + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "wf_cliprdr.h" + +#define TAG CLIENT_TAG("windows") + +#ifdef WITH_DEBUG_CLIPRDR +#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_CLIPRDR(...) do { } while (0) +#endif + +typedef BOOL (WINAPI* fnAddClipboardFormatListener)(HWND hwnd); +typedef BOOL (WINAPI* fnRemoveClipboardFormatListener)(HWND hwnd); +typedef BOOL (WINAPI* fnGetUpdatedClipboardFormats)(PUINT lpuiFormats, + UINT cFormats, PUINT pcFormatsOut); + +struct format_mapping +{ + UINT32 remote_format_id; + UINT32 local_format_id; + WCHAR* name; +}; +typedef struct format_mapping formatMapping; + +struct _CliprdrEnumFORMATETC +{ + IEnumFORMATETC iEnumFORMATETC; + + LONG m_lRefCount; + LONG m_nIndex; + LONG m_nNumFormats; + FORMATETC* m_pFormatEtc; +}; +typedef struct _CliprdrEnumFORMATETC CliprdrEnumFORMATETC; + +struct _CliprdrStream +{ + IStream iStream; + + LONG m_lRefCount; + LONG m_lIndex; + ULARGE_INTEGER m_lSize; + ULARGE_INTEGER m_lOffset; + FILEDESCRIPTORW m_Dsc; + void* m_pData; +}; +typedef struct _CliprdrStream CliprdrStream; + +struct _CliprdrDataObject +{ + IDataObject iDataObject; + + LONG m_lRefCount; + FORMATETC* m_pFormatEtc; + STGMEDIUM* m_pStgMedium; + LONG m_nNumFormats; + LONG m_nStreams; + IStream** m_pStream; + void* m_pData; +}; +typedef struct _CliprdrDataObject CliprdrDataObject; + +struct wf_clipboard +{ + wfContext* wfc; + rdpChannels* channels; + CliprdrClientContext* context; + + BOOL sync; + UINT32 capabilities; + + size_t map_size; + size_t map_capacity; + formatMapping* format_mappings; + + UINT32 requestedFormatId; + + HWND hwnd; + HANDLE hmem; + HANDLE thread; + HANDLE response_data_event; + + LPDATAOBJECT data_obj; + ULONG req_fsize; + char* req_fdata; + HANDLE req_fevent; + + size_t nFiles; + size_t file_array_size; + WCHAR** file_names; + FILEDESCRIPTORW** fileDescriptor; + + BOOL legacyApi; + HMODULE hUser32; + HWND hWndNextViewer; + fnAddClipboardFormatListener AddClipboardFormatListener; + fnRemoveClipboardFormatListener RemoveClipboardFormatListener; + fnGetUpdatedClipboardFormats GetUpdatedClipboardFormats; +}; +typedef struct wf_clipboard wfClipboard; + +#define WM_CLIPRDR_MESSAGE (WM_USER + 156) +#define OLE_SETCLIPBOARD 1 + +static BOOL wf_create_file_obj(wfClipboard* cliprdrrdr, + IDataObject** ppDataObject); +static void wf_destroy_file_obj(IDataObject* instance); +static UINT32 get_remote_format_id(wfClipboard* clipboard, UINT32 local_format); +static UINT cliprdr_send_data_request(wfClipboard* clipboard, UINT32 format); +static UINT cliprdr_send_lock(wfClipboard* clipboard); +static UINT cliprdr_send_unlock(wfClipboard* clipboard); +static UINT cliprdr_send_request_filecontents(wfClipboard* clipboard, + void* streamid, + int index, int flag, DWORD positionhigh, + DWORD positionlow, ULONG request); + +static void CliprdrDataObject_Delete(CliprdrDataObject* instance); + +static CliprdrEnumFORMATETC* CliprdrEnumFORMATETC_New(int nFormats, + FORMATETC* pFormatEtc); +static void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC* instance); + +static void CliprdrStream_Delete(CliprdrStream* instance); + +/** + * IStream + */ + +static HRESULT STDMETHODCALLTYPE CliprdrStream_QueryInterface(IStream* This, + REFIID riid, void** ppvObject) +{ + if (IsEqualIID(riid, &IID_IStream) || IsEqualIID(riid, &IID_IUnknown)) + { + IStream_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_AddRef(IStream* This) +{ + CliprdrStream* instance = (CliprdrStream*) This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_Release(IStream* This) +{ + LONG count; + CliprdrStream* instance = (CliprdrStream*) This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrStream_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream* This, void* pv, + ULONG cb, ULONG* pcbRead) +{ + int ret; + CliprdrStream* instance = (CliprdrStream*) This; + wfClipboard* clipboard; + + if (!pv || !pcbRead || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard*) instance->m_pData; + *pcbRead = 0; + + if (instance->m_lOffset.QuadPart >= instance->m_lSize.QuadPart) + return S_FALSE; + + ret = cliprdr_send_request_filecontents(clipboard, (void*) This, + instance->m_lIndex, FILECONTENTS_RANGE, + instance->m_lOffset.HighPart, instance->m_lOffset.LowPart, cb); + + if (ret < 0) + return E_FAIL; + + if (clipboard->req_fdata) + { + CopyMemory(pv, clipboard->req_fdata, clipboard->req_fsize); + free(clipboard->req_fdata); + } + + *pcbRead = clipboard->req_fsize; + instance->m_lOffset.QuadPart += clipboard->req_fsize; + + if (clipboard->req_fsize < cb) + return S_FALSE; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Write(IStream* This, + const void* pv, + ULONG cb, ULONG* pcbWritten) +{ + (void)This; + (void)pv; + (void)cb; + (void)pcbWritten; + return STG_E_ACCESSDENIED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Seek(IStream* This, + LARGE_INTEGER dlibMove, + DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition) +{ + ULONGLONG newoffset; + CliprdrStream* instance = (CliprdrStream*) This; + + if (!instance) + return E_INVALIDARG; + + newoffset = instance->m_lOffset.QuadPart; + + switch (dwOrigin) + { + case STREAM_SEEK_SET: + newoffset = dlibMove.QuadPart; + break; + + case STREAM_SEEK_CUR: + newoffset += dlibMove.QuadPart; + break; + + case STREAM_SEEK_END: + newoffset = instance->m_lSize.QuadPart + dlibMove.QuadPart; + break; + + default: + return E_INVALIDARG; + } + + if (newoffset < 0 || newoffset >= instance->m_lSize.QuadPart) + return E_FAIL; + + instance->m_lOffset.QuadPart = newoffset; + + if (plibNewPosition) + plibNewPosition->QuadPart = instance->m_lOffset.QuadPart; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_SetSize(IStream* This, + ULARGE_INTEGER libNewSize) +{ + (void)This; + (void)libNewSize; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_CopyTo(IStream* This, + IStream* pstm, + ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, + ULARGE_INTEGER* pcbWritten) +{ + (void)This; + (void)pstm; + (void)cb; + (void)pcbRead; + (void)pcbWritten; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Commit(IStream* This, + DWORD grfCommitFlags) +{ + (void)This; + (void)grfCommitFlags; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Revert(IStream* This) +{ + (void)This; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_LockRegion(IStream* This, + ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_UnlockRegion(IStream* This, + ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Stat(IStream* This, + STATSTG* pstatstg, DWORD grfStatFlag) +{ + CliprdrStream* instance = (CliprdrStream*) This; + + if (!instance) + return E_INVALIDARG; + + if (pstatstg == NULL) + return STG_E_INVALIDPOINTER; + + ZeroMemory(pstatstg, sizeof(STATSTG)); + + switch (grfStatFlag) + { + case STATFLAG_DEFAULT: + return STG_E_INSUFFICIENTMEMORY; + + case STATFLAG_NONAME: + pstatstg->cbSize.QuadPart = instance->m_lSize.QuadPart; + pstatstg->grfLocksSupported = LOCK_EXCLUSIVE; + pstatstg->grfMode = GENERIC_READ; + pstatstg->grfStateBits = 0; + pstatstg->type = STGTY_STREAM; + break; + + case STATFLAG_NOOPEN: + return STG_E_INVALIDFLAG; + + default: + return STG_E_INVALIDFLAG; + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Clone(IStream* This, + IStream** ppstm) +{ + (void)This; + (void)ppstm; + return E_NOTIMPL; +} + +static CliprdrStream* CliprdrStream_New(LONG index, void* pData, + const FILEDESCRIPTORW* dsc) +{ + IStream* iStream; + BOOL success = FALSE; + BOOL isDir = FALSE; + CliprdrStream* instance; + wfClipboard* clipboard = (wfClipboard*) pData; + instance = (CliprdrStream*) calloc(1, sizeof(CliprdrStream)); + + if (instance) + { + instance->m_Dsc = *dsc; + iStream = &instance->iStream; + iStream->lpVtbl = (IStreamVtbl*) calloc(1, sizeof(IStreamVtbl)); + + if (iStream->lpVtbl) + { + iStream->lpVtbl->QueryInterface = CliprdrStream_QueryInterface; + iStream->lpVtbl->AddRef = CliprdrStream_AddRef; + iStream->lpVtbl->Release = CliprdrStream_Release; + iStream->lpVtbl->Read = CliprdrStream_Read; + iStream->lpVtbl->Write = CliprdrStream_Write; + iStream->lpVtbl->Seek = CliprdrStream_Seek; + iStream->lpVtbl->SetSize = CliprdrStream_SetSize; + iStream->lpVtbl->CopyTo = CliprdrStream_CopyTo; + iStream->lpVtbl->Commit = CliprdrStream_Commit; + iStream->lpVtbl->Revert = CliprdrStream_Revert; + iStream->lpVtbl->LockRegion = CliprdrStream_LockRegion; + iStream->lpVtbl->UnlockRegion = CliprdrStream_UnlockRegion; + iStream->lpVtbl->Stat = CliprdrStream_Stat; + iStream->lpVtbl->Clone = CliprdrStream_Clone; + instance->m_lRefCount = 1; + instance->m_lIndex = index; + instance->m_pData = pData; + instance->m_lOffset.QuadPart = 0; + + if (instance->m_Dsc.dwFlags & FD_ATTRIBUTES) + { + if (instance->m_Dsc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + isDir = TRUE; + } + + if (((instance->m_Dsc.dwFlags & FD_FILESIZE) == 0) && !isDir) + { + /* get content size of this stream */ + if (cliprdr_send_request_filecontents(clipboard, (void*) instance, + instance->m_lIndex, + FILECONTENTS_SIZE, 0, 0, 8) == CHANNEL_RC_OK) + { + success = TRUE; + } + + instance->m_lSize.QuadPart = *((LONGLONG*) clipboard->req_fdata); + free(clipboard->req_fdata); + } + else + success = TRUE; + } + } + + if (!success) + { + CliprdrStream_Delete(instance); + instance = NULL; + } + + return instance; +} + +void CliprdrStream_Delete(CliprdrStream* instance) +{ + if (instance) + { + free(instance->iStream.lpVtbl); + free(instance); + } +} + +/** + * IDataObject + */ + +static int cliprdr_lookup_format(CliprdrDataObject* instance, + FORMATETC* pFormatEtc) +{ + int i; + + if (!instance || !pFormatEtc) + return -1; + + for (i = 0; i < instance->m_nNumFormats; i++) + { + if ((pFormatEtc->tymed & instance->m_pFormatEtc[i].tymed) && + pFormatEtc->cfFormat == instance->m_pFormatEtc[i].cfFormat && + pFormatEtc->dwAspect & instance->m_pFormatEtc[i].dwAspect) + { + return i; + } + } + + return -1; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryInterface( + IDataObject* This, REFIID riid, void** ppvObject) +{ + (void)This; + + if (!ppvObject) + return E_INVALIDARG; + + if (IsEqualIID(riid, &IID_IDataObject) || IsEqualIID(riid, &IID_IUnknown)) + { + IDataObject_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_AddRef(IDataObject* This) +{ + CliprdrDataObject* instance = (CliprdrDataObject*) This; + + if (!instance) + return E_INVALIDARG; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_Release(IDataObject* This) +{ + LONG count; + CliprdrDataObject* instance = (CliprdrDataObject*) This; + + if (!instance) + return E_INVALIDARG; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrDataObject_Delete(instance); + return 0; + } + else + return count; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData( + IDataObject* This, FORMATETC* pFormatEtc, STGMEDIUM* pMedium) +{ + int i, idx; + CliprdrDataObject* instance = (CliprdrDataObject*) This; + wfClipboard* clipboard; + + if (!pFormatEtc || !pMedium || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard*) instance->m_pData; + + if (!clipboard) + return E_INVALIDARG; + + if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1) + return DV_E_FORMATETC; + + pMedium->tymed = instance->m_pFormatEtc[idx].tymed; + pMedium->pUnkForRelease = 0; + + if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat( + CFSTR_FILEDESCRIPTORW)) + { + FILEGROUPDESCRIPTOR* dsc; + DWORD remote = get_remote_format_id(clipboard, + instance->m_pFormatEtc[idx].cfFormat); + + if (cliprdr_send_data_request(clipboard, remote) != 0) + return E_UNEXPECTED; + + pMedium->hGlobal = + clipboard->hmem; /* points to a FILEGROUPDESCRIPTOR structure */ + /* GlobalLock returns a pointer to the first byte of the memory block, + * in which is a FILEGROUPDESCRIPTOR structure, whose first UINT member + * is the number of FILEDESCRIPTOR's */ + dsc = (FILEGROUPDESCRIPTOR*) GlobalLock(clipboard->hmem); + instance->m_nStreams = dsc->cItems; + GlobalUnlock(clipboard->hmem); + + if (instance->m_nStreams > 0) + { + if (!instance->m_pStream) + { + instance->m_pStream = (LPSTREAM*) calloc(instance->m_nStreams, + sizeof(LPSTREAM)); + + if (instance->m_pStream) + { + for (i = 0; i < instance->m_nStreams; i++) + { + instance->m_pStream[i] = (IStream*) CliprdrStream_New(i, clipboard, + &dsc->fgd[i]); + + if (!instance->m_pStream[i]) + return E_OUTOFMEMORY; + } + } + } + } + + if (!instance->m_pStream) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + + pMedium->hGlobal = NULL; + return E_OUTOFMEMORY; + } + } + else if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat( + CFSTR_FILECONTENTS)) + { + if (pFormatEtc->lindex < instance->m_nStreams) + { + pMedium->pstm = instance->m_pStream[pFormatEtc->lindex]; + IDataObject_AddRef(instance->m_pStream[pFormatEtc->lindex]); + } + else + return E_INVALIDARG; + } + else + return E_UNEXPECTED; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetDataHere( + IDataObject* This, FORMATETC* pformatetc, STGMEDIUM* pmedium) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryGetData( + IDataObject* This, FORMATETC* pformatetc) +{ + CliprdrDataObject* instance = (CliprdrDataObject*) This; + + if (!pformatetc) + return E_INVALIDARG; + + if (cliprdr_lookup_format(instance, pformatetc) == -1) + return DV_E_FORMATETC; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetCanonicalFormatEtc( + IDataObject* This, FORMATETC* pformatectIn, FORMATETC* pformatetcOut) +{ + (void)This; + (void)pformatectIn; + + if (!pformatetcOut) + return E_INVALIDARG; + + pformatetcOut->ptd = NULL; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_SetData( + IDataObject* This, FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + (void)fRelease; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumFormatEtc( + IDataObject* This, DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc) +{ + CliprdrDataObject* instance = (CliprdrDataObject*) This; + + if (!instance || !ppenumFormatEtc) + return E_INVALIDARG; + + if (dwDirection == DATADIR_GET) + { + *ppenumFormatEtc = (IEnumFORMATETC*) CliprdrEnumFORMATETC_New( + instance->m_nNumFormats, instance->m_pFormatEtc); + return (*ppenumFormatEtc) ? S_OK : E_OUTOFMEMORY; + } + else + { + return E_NOTIMPL; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DAdvise( + IDataObject* This, FORMATETC* pformatetc, DWORD advf, + IAdviseSink* pAdvSink, DWORD* pdwConnection) +{ + (void)This; + (void)pformatetc; + (void)advf; + (void)pAdvSink; + (void)pdwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DUnadvise( + IDataObject* This, DWORD dwConnection) +{ + (void)This; + (void)dwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumDAdvise( + IDataObject* This, IEnumSTATDATA** ppenumAdvise) +{ + (void)This; + (void)ppenumAdvise; + return OLE_E_ADVISENOTSUPPORTED; +} + +static CliprdrDataObject* CliprdrDataObject_New( + FORMATETC* fmtetc, STGMEDIUM* stgmed, int count, void* data) +{ + int i; + CliprdrDataObject* instance; + IDataObject* iDataObject; + instance = (CliprdrDataObject*) calloc(1, sizeof(CliprdrDataObject)); + + if (!instance) + goto error; + + iDataObject = &instance->iDataObject; + iDataObject->lpVtbl = (IDataObjectVtbl*) calloc(1, sizeof(IDataObjectVtbl)); + + if (!iDataObject->lpVtbl) + goto error; + + iDataObject->lpVtbl->QueryInterface = CliprdrDataObject_QueryInterface; + iDataObject->lpVtbl->AddRef = CliprdrDataObject_AddRef; + iDataObject->lpVtbl->Release = CliprdrDataObject_Release; + iDataObject->lpVtbl->GetData = CliprdrDataObject_GetData; + iDataObject->lpVtbl->GetDataHere = CliprdrDataObject_GetDataHere; + iDataObject->lpVtbl->QueryGetData = CliprdrDataObject_QueryGetData; + iDataObject->lpVtbl->GetCanonicalFormatEtc = + CliprdrDataObject_GetCanonicalFormatEtc; + iDataObject->lpVtbl->SetData = CliprdrDataObject_SetData; + iDataObject->lpVtbl->EnumFormatEtc = CliprdrDataObject_EnumFormatEtc; + iDataObject->lpVtbl->DAdvise = CliprdrDataObject_DAdvise; + iDataObject->lpVtbl->DUnadvise = CliprdrDataObject_DUnadvise; + iDataObject->lpVtbl->EnumDAdvise = CliprdrDataObject_EnumDAdvise; + instance->m_lRefCount = 1; + instance->m_nNumFormats = count; + instance->m_pData = data; + instance->m_nStreams = 0; + instance->m_pStream = NULL; + + if (count > 0) + { + instance->m_pFormatEtc = (FORMATETC*) calloc(count, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + instance->m_pStgMedium = (STGMEDIUM*) calloc(count, sizeof(STGMEDIUM)); + + if (!instance->m_pStgMedium) + goto error; + + for (i = 0; i < count; i++) + { + instance->m_pFormatEtc[i] = fmtetc[i]; + instance->m_pStgMedium[i] = stgmed[i]; + } + } + + return instance; +error: + CliprdrDataObject_Delete(instance); + return NULL; +} + +void CliprdrDataObject_Delete(CliprdrDataObject* instance) +{ + if (instance) + { + free(instance->iDataObject.lpVtbl); + free(instance->m_pFormatEtc); + free(instance->m_pStgMedium); + + if (instance->m_pStream) + { + LONG i; + + for (i = 0; i < instance->m_nStreams; i++) + CliprdrStream_Release(instance->m_pStream[i]); + + free(instance->m_pStream); + } + + free(instance); + } +} + +static BOOL wf_create_file_obj(wfClipboard* clipboard, + IDataObject** ppDataObject) +{ + FORMATETC fmtetc[2]; + STGMEDIUM stgmeds[2]; + + if (!ppDataObject) + return FALSE; + + fmtetc[0].cfFormat = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + fmtetc[0].dwAspect = DVASPECT_CONTENT; + fmtetc[0].lindex = 0; + fmtetc[0].ptd = NULL; + fmtetc[0].tymed = TYMED_HGLOBAL; + stgmeds[0].tymed = TYMED_HGLOBAL; + stgmeds[0].hGlobal = NULL; + stgmeds[0].pUnkForRelease = NULL; + fmtetc[1].cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + fmtetc[1].dwAspect = DVASPECT_CONTENT; + fmtetc[1].lindex = 0; + fmtetc[1].ptd = NULL; + fmtetc[1].tymed = TYMED_ISTREAM; + stgmeds[1].tymed = TYMED_ISTREAM; + stgmeds[1].pstm = NULL; + stgmeds[1].pUnkForRelease = NULL; + *ppDataObject = (IDataObject*) CliprdrDataObject_New(fmtetc, stgmeds, 2, + clipboard); + return (*ppDataObject) ? TRUE : FALSE; +} + +static void wf_destroy_file_obj(IDataObject* instance) +{ + if (instance) + IDataObject_Release(instance); +} + +/** + * IEnumFORMATETC + */ + +static void cliprdr_format_deep_copy(FORMATETC* dest, FORMATETC* source) +{ + *dest = *source; + + if (source->ptd) + { + dest->ptd = (DVTARGETDEVICE*) CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + + if (dest->ptd) + *(dest->ptd) = *(source->ptd); + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_QueryInterface( + IEnumFORMATETC* This, REFIID riid, void** ppvObject) +{ + (void)This; + + if (IsEqualIID(riid, &IID_IEnumFORMATETC) || IsEqualIID(riid, &IID_IUnknown)) + { + IEnumFORMATETC_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_AddRef(IEnumFORMATETC* This) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_Release( + IEnumFORMATETC* This) +{ + LONG count; + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrEnumFORMATETC_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Next(IEnumFORMATETC* This, + ULONG celt, + FORMATETC* rgelt, ULONG* pceltFetched) +{ + ULONG copied = 0; + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance || !celt || !rgelt) + return E_INVALIDARG; + + while ((instance->m_nIndex < instance->m_nNumFormats) && (copied < celt)) + { + cliprdr_format_deep_copy(&rgelt[copied++], + &instance->m_pFormatEtc[instance->m_nIndex++]); + } + + if (pceltFetched != 0) + *pceltFetched = copied; + + return (copied == celt) ? S_OK : E_FAIL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Skip(IEnumFORMATETC* This, + ULONG celt) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance) + return E_INVALIDARG; + + if (instance->m_nIndex + (LONG) celt > instance->m_nNumFormats) + return E_FAIL; + + instance->m_nIndex += celt; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Reset( + IEnumFORMATETC* This) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance) + return E_INVALIDARG; + + instance->m_nIndex = 0; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Clone( + IEnumFORMATETC* This, IEnumFORMATETC** ppEnum) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*) This; + + if (!instance || !ppEnum) + return E_INVALIDARG; + + *ppEnum = (IEnumFORMATETC*) CliprdrEnumFORMATETC_New(instance->m_nNumFormats, + instance->m_pFormatEtc); + + if (!*ppEnum) + return E_OUTOFMEMORY; + + ((CliprdrEnumFORMATETC*) *ppEnum)->m_nIndex = instance->m_nIndex; + return S_OK; +} + +CliprdrEnumFORMATETC* CliprdrEnumFORMATETC_New(int nFormats, + FORMATETC* pFormatEtc) +{ + int i; + CliprdrEnumFORMATETC* instance; + IEnumFORMATETC* iEnumFORMATETC; + + if ((nFormats != 0) && !pFormatEtc) + return NULL; + + instance = (CliprdrEnumFORMATETC*) calloc(1, sizeof(CliprdrEnumFORMATETC)); + + if (!instance) + goto error; + + iEnumFORMATETC = &instance->iEnumFORMATETC; + iEnumFORMATETC->lpVtbl = (IEnumFORMATETCVtbl*) calloc(1, + sizeof(IEnumFORMATETCVtbl)); + + if (!iEnumFORMATETC->lpVtbl) + goto error; + + iEnumFORMATETC->lpVtbl->QueryInterface = CliprdrEnumFORMATETC_QueryInterface; + iEnumFORMATETC->lpVtbl->AddRef = CliprdrEnumFORMATETC_AddRef; + iEnumFORMATETC->lpVtbl->Release = CliprdrEnumFORMATETC_Release; + iEnumFORMATETC->lpVtbl->Next = CliprdrEnumFORMATETC_Next; + iEnumFORMATETC->lpVtbl->Skip = CliprdrEnumFORMATETC_Skip; + iEnumFORMATETC->lpVtbl->Reset = CliprdrEnumFORMATETC_Reset; + iEnumFORMATETC->lpVtbl->Clone = CliprdrEnumFORMATETC_Clone; + instance->m_lRefCount = 0; + instance->m_nIndex = 0; + instance->m_nNumFormats = nFormats; + + if (nFormats > 0) + { + instance->m_pFormatEtc = (FORMATETC*) calloc(nFormats, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + for (i = 0; i < nFormats; i++) + cliprdr_format_deep_copy(&instance->m_pFormatEtc[i], &pFormatEtc[i]); + } + + return instance; +error: + CliprdrEnumFORMATETC_Delete(instance); + return NULL; +} + +void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC* instance) +{ + LONG i; + + if (instance) + { + free(instance->iEnumFORMATETC.lpVtbl); + + if (instance->m_pFormatEtc) + { + for (i = 0; i < instance->m_nNumFormats; i++) + { + if (instance->m_pFormatEtc[i].ptd) + CoTaskMemFree(instance->m_pFormatEtc[i].ptd); + } + + free(instance->m_pFormatEtc); + } + + free(instance); + } +} + +/***********************************************************************************/ + +static UINT32 get_local_format_id_by_name(wfClipboard* clipboard, + const TCHAR* format_name) +{ + size_t i; + formatMapping* map; + WCHAR* unicode_name; +#if !defined(UNICODE) + size_t size; +#endif + + if (!clipboard || !format_name) + return 0; + +#if defined(UNICODE) + unicode_name = _wcsdup(format_name); +#else + size = _tcslen(format_name); + unicode_name = calloc(size + 1, sizeof(WCHAR)); + + if (!unicode_name) + return 0; + + MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), unicode_name, + size); +#endif + + if (!unicode_name) + return 0; + + for (i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->name) + { + if (wcscmp(map->name, unicode_name) == 0) + { + free(unicode_name); + return map->local_format_id; + } + } + } + + free(unicode_name); + return 0; +} + +static INLINE BOOL file_transferring(wfClipboard* clipboard) +{ + return get_local_format_id_by_name(clipboard, + CFSTR_FILEDESCRIPTORW) ? TRUE : FALSE; +} + +static UINT32 get_remote_format_id(wfClipboard* clipboard, UINT32 local_format) +{ + UINT32 i; + formatMapping* map; + + if (!clipboard) + return 0; + + for (i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->local_format_id == local_format) + return map->remote_format_id; + } + + return local_format; +} + +static void map_ensure_capacity(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + if (clipboard->map_size >= clipboard->map_capacity) + { + size_t new_size; + formatMapping* new_map; + new_size = clipboard->map_capacity * 2; + new_map = (formatMapping*) realloc(clipboard->format_mappings, + sizeof(formatMapping) * new_size); + + if (!new_map) + return; + + clipboard->format_mappings = new_map; + clipboard->map_capacity = new_size; + } +} + +static BOOL clear_format_map(wfClipboard* clipboard) +{ + size_t i; + formatMapping* map; + + if (!clipboard) + return FALSE; + + if (clipboard->format_mappings) + { + for (i = 0; i < clipboard->map_capacity; i++) + { + map = &clipboard->format_mappings[i]; + map->remote_format_id = 0; + map->local_format_id = 0; + free(map->name); + map->name = NULL; + } + } + + clipboard->map_size = 0; + return TRUE; +} + +static UINT cliprdr_send_tempdir(wfClipboard* clipboard) +{ + CLIPRDR_TEMP_DIRECTORY tempDirectory; + + if (!clipboard) + return -1; + + if (GetEnvironmentVariableA("TEMP", tempDirectory.szTempDir, + sizeof(tempDirectory.szTempDir)) == 0) + return -1; + + return clipboard->context->TempDirectory(clipboard->context, &tempDirectory); +} + +BOOL cliprdr_GetUpdatedClipboardFormats(wfClipboard* clipboard, + PUINT lpuiFormats, UINT cFormats, PUINT pcFormatsOut) +{ + UINT index = 0; + UINT format = 0; + BOOL clipboardOpen = FALSE; + + if (!clipboard->legacyApi) + return clipboard->GetUpdatedClipboardFormats(lpuiFormats, cFormats, + pcFormatsOut); + + clipboardOpen = OpenClipboard(clipboard->hwnd); + + if (!clipboardOpen) + return FALSE; + + while (index < cFormats) + { + format = EnumClipboardFormats(format); + + if (!format) + break; + + lpuiFormats[index] = format; + index++; + } + + *pcFormatsOut = index; + CloseClipboard(); + return TRUE; +} + +static UINT cliprdr_send_format_list(wfClipboard* clipboard) +{ + UINT rc; + int count; + UINT32 index; + UINT32 numFormats; + UINT32 formatId = 0; + char formatName[1024]; + CLIPRDR_FORMAT* formats; + CLIPRDR_FORMAT_LIST formatList; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + + if (!OpenClipboard(clipboard->hwnd)) + return ERROR_INTERNAL_ERROR; + + count = CountClipboardFormats(); + numFormats = (UINT32) count; + formats = (CLIPRDR_FORMAT*) calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + CloseClipboard(); + return CHANNEL_RC_NO_MEMORY; + } + + index = 0; + + if (IsClipboardFormatAvailable(CF_HDROP)) + { + formats[index++].formatId = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + formats[index++].formatId = RegisterClipboardFormat(CFSTR_FILECONTENTS); + } + else + { + while (formatId = EnumClipboardFormats(formatId)) + formats[index++].formatId = formatId; + } + + numFormats = index; + + if (!CloseClipboard()) + { + free(formats); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < numFormats; index++) + { + if(GetClipboardFormatNameA(formats[index].formatId, formatName, + sizeof(formatName))) + { + formats[index].formatName = _strdup(formatName); + } + } + + formatList.numFormats = numFormats; + formatList.formats = formats; + rc = clipboard->context->ClientFormatList(clipboard->context, &formatList); + + for (index = 0; index < numFormats; index++) + free(formats[index].formatName); + + free(formats); + return rc; +} + +static UINT cliprdr_send_data_request(wfClipboard* clipboard, UINT32 formatId) +{ + UINT rc; + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + + if (!clipboard || !clipboard->context || + !clipboard->context->ClientFormatDataRequest) + return ERROR_INTERNAL_ERROR; + + formatDataRequest.requestedFormatId = formatId; + clipboard->requestedFormatId = formatId; + rc = clipboard->context->ClientFormatDataRequest(clipboard->context, + &formatDataRequest); + + if (WaitForSingleObject(clipboard->response_data_event, + INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->response_data_event)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +static UINT cliprdr_send_request_filecontents(wfClipboard* clipboard, + const void* streamid, + int index, int flag, DWORD positionhigh, + DWORD positionlow, ULONG nreq) +{ + UINT rc; + CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest; + + if (!clipboard || !clipboard->context || + !clipboard->context->ClientFileContentsRequest) + return ERROR_INTERNAL_ERROR; + + fileContentsRequest.streamId = (UINT32) streamid; + fileContentsRequest.listIndex = index; + fileContentsRequest.dwFlags = flag; + fileContentsRequest.nPositionLow = positionlow; + fileContentsRequest.nPositionHigh = positionhigh; + fileContentsRequest.cbRequested = nreq; + fileContentsRequest.clipDataId = 0; + fileContentsRequest.msgFlags = 0; + rc = clipboard->context->ClientFileContentsRequest(clipboard->context, + &fileContentsRequest); + + if (WaitForSingleObject(clipboard->req_fevent, INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->req_fevent)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +static UINT cliprdr_send_response_filecontents(wfClipboard* clipboard, + UINT32 streamId, UINT32 size, BYTE* data) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE fileContentsResponse; + + if (!clipboard || !clipboard->context || + !clipboard->context->ClientFileContentsResponse) + return ERROR_INTERNAL_ERROR; + + fileContentsResponse.streamId = streamId; + fileContentsResponse.cbRequested = size; + fileContentsResponse.requestedData = data; + fileContentsResponse.msgFlags = CB_RESPONSE_OK; + return clipboard->context->ClientFileContentsResponse(clipboard->context, + &fileContentsResponse); +} + +static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) +{ + static wfClipboard* clipboard = NULL; + + switch (Msg) + { + case WM_CREATE: + DEBUG_CLIPRDR("info: WM_CREATE"); + clipboard = (wfClipboard*)((CREATESTRUCT*) lParam)->lpCreateParams; + clipboard->hwnd = hWnd; + + if (!clipboard->legacyApi) + clipboard->AddClipboardFormatListener(hWnd); + else + clipboard->hWndNextViewer = SetClipboardViewer(hWnd); + + break; + + case WM_CLOSE: + DEBUG_CLIPRDR("info: WM_CLOSE"); + + if (!clipboard->legacyApi) + clipboard->RemoveClipboardFormatListener(hWnd); + + break; + + case WM_DESTROY: + if (clipboard->legacyApi) + ChangeClipboardChain(hWnd, clipboard->hWndNextViewer); + + break; + + case WM_CLIPBOARDUPDATE: + DEBUG_CLIPRDR("info: WM_CLIPBOARDUPDATE"); + + if (clipboard->sync) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + + cliprdr_send_format_list(clipboard); + } + } + + break; + + case WM_RENDERALLFORMATS: + DEBUG_CLIPRDR("info: WM_RENDERALLFORMATS"); + + /* discard all contexts in clipboard */ + if (!OpenClipboard(clipboard->hwnd)) + { + DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); + break; + } + + EmptyClipboard(); + CloseClipboard(); + break; + + case WM_RENDERFORMAT: + DEBUG_CLIPRDR("info: WM_RENDERFORMAT"); + + if (cliprdr_send_data_request(clipboard, (UINT32) wParam) != 0) + { + DEBUG_CLIPRDR("error: cliprdr_send_data_request failed."); + break; + } + + if (!SetClipboardData((UINT) wParam, clipboard->hmem)) + { + DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError()); + + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = NULL; + } + } + + /* Note: GlobalFree() is not needed when success */ + break; + + case WM_DRAWCLIPBOARD: + if (clipboard->legacyApi) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + cliprdr_send_format_list(clipboard); + } + + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CHANGECBCHAIN: + if (clipboard->legacyApi) + { + HWND hWndCurrViewer = (HWND) wParam; + HWND hWndNextViewer = (HWND) lParam; + + if (hWndCurrViewer == clipboard->hWndNextViewer) + clipboard->hWndNextViewer = hWndNextViewer; + else if (clipboard->hWndNextViewer) + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CLIPRDR_MESSAGE: + DEBUG_CLIPRDR("info: WM_CLIPRDR_MESSAGE"); + + switch (wParam) + { + case OLE_SETCLIPBOARD: + DEBUG_CLIPRDR("info: OLE_SETCLIPBOARD"); + + if (wf_create_file_obj(clipboard, &clipboard->data_obj)) + { + if (OleSetClipboard(clipboard->data_obj) != S_OK) + { + wf_destroy_file_obj(clipboard->data_obj); + clipboard->data_obj = NULL; + } + } + + break; + + default: + break; + } + + break; + + case WM_DESTROYCLIPBOARD: + case WM_ASKCBFORMATNAME: + case WM_HSCROLLCLIPBOARD: + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_VSCROLLCLIPBOARD: + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static int create_cliprdr_window(wfClipboard* clipboard) +{ + WNDCLASSEX wnd_cls; + ZeroMemory(&wnd_cls, sizeof(WNDCLASSEX)); + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_OWNDC; + wnd_cls.lpfnWndProc = cliprdr_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = NULL; + wnd_cls.hCursor = NULL; + wnd_cls.hbrBackground = NULL; + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = _T("ClipboardHiddenMessageProcessor"); + wnd_cls.hInstance = GetModuleHandle(NULL); + wnd_cls.hIconSm = NULL; + RegisterClassEx(&wnd_cls); + clipboard->hwnd = CreateWindowEx(WS_EX_LEFT, + _T("ClipboardHiddenMessageProcessor"), + _T("rdpclip"), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), + clipboard); + + if (!clipboard->hwnd) + { + DEBUG_CLIPRDR("error: CreateWindowEx failed with %x.", GetLastError()); + return -1; + } + + return 0; +} + +static DWORD WINAPI cliprdr_thread_func(LPVOID arg) +{ + int ret; + MSG msg; + BOOL mcode; + wfClipboard* clipboard = (wfClipboard*) arg; + OleInitialize(0); + + if ((ret = create_cliprdr_window(clipboard)) != 0) + { + DEBUG_CLIPRDR("error: create clipboard window failed."); + return 0; + } + + while ((mcode = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (mcode == -1) + { + DEBUG_CLIPRDR("error: clipboard thread GetMessage failed."); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + OleUninitialize(); + return 0; +} + +static void clear_file_array(wfClipboard* clipboard) +{ + size_t i; + + if (!clipboard) + return; + + /* clear file_names array */ + if (clipboard->file_names) + { + for (i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->file_names[i]); + clipboard->file_names[i] = NULL; + } + + free(clipboard->file_names); + clipboard->file_names = NULL; + } + + /* clear fileDescriptor array */ + if (clipboard->fileDescriptor) + { + for (i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->fileDescriptor[i]); + clipboard->fileDescriptor[i] = NULL; + } + + free(clipboard->fileDescriptor); + clipboard->fileDescriptor = NULL; + } + + clipboard->file_array_size = 0; + clipboard->nFiles = 0; +} + +static BOOL wf_cliprdr_get_file_contents(WCHAR* file_name, BYTE* buffer, + LONG positionLow, LONG positionHigh, + DWORD nRequested, DWORD* puSize) +{ + BOOL res = FALSE; + HANDLE hFile; + DWORD nGet, rc; + + if (!file_name || !buffer || !puSize) + { + WLog_ERR(TAG, "get file contents Invalid Arguments."); + return FALSE; + } + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + rc = SetFilePointer(hFile, positionLow, &positionHigh, FILE_BEGIN); + + if (rc == INVALID_SET_FILE_POINTER) + goto error; + + if (!ReadFile(hFile, buffer, nRequested, &nGet, NULL)) + { + DEBUG_CLIPRDR("ReadFile failed with 0x%08lX.", GetLastError()); + goto error; + } + + res = TRUE; +error: + + if (!CloseHandle(hFile)) + res = FALSE; + + if (res) + *puSize = nGet; + + return res; +} + +/* path_name has a '\' at the end. e.g. c:\newfolder\, file_name is c:\newfolder\new.txt */ +static FILEDESCRIPTORW* wf_cliprdr_get_file_descriptor(WCHAR* file_name, + size_t pathLen) +{ + HANDLE hFile; + FILEDESCRIPTORW* fd; + fd = (FILEDESCRIPTORW*) calloc(1, sizeof(FILEDESCRIPTORW)); + + if (!fd) + return NULL; + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + free(fd); + return NULL; + } + + fd->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI; + fd->dwFileAttributes = GetFileAttributes(file_name); + + if (!GetFileTime(hFile, NULL, NULL, &fd->ftLastWriteTime)) + { + fd->dwFlags &= ~FD_WRITESTIME; + } + + fd->nFileSizeLow = GetFileSize(hFile, &fd->nFileSizeHigh); + wcscpy_s(fd->cFileName, sizeof(fd->cFileName) / 2, file_name + pathLen); + CloseHandle(hFile); + return fd; +} + +static BOOL wf_cliprdr_array_ensure_capacity(wfClipboard* clipboard) +{ + if (!clipboard) + return FALSE; + + if (clipboard->nFiles == clipboard->file_array_size) + { + size_t new_size; + FILEDESCRIPTORW** new_fd; + WCHAR** new_name; + new_size = (clipboard->file_array_size + 1) * 2; + new_fd = (FILEDESCRIPTORW**) realloc(clipboard->fileDescriptor, + new_size * sizeof(FILEDESCRIPTORW*)); + + if (new_fd) + clipboard->fileDescriptor = new_fd; + + new_name = (WCHAR**) realloc(clipboard->file_names, new_size * sizeof(WCHAR*)); + + if (new_name) + clipboard->file_names = new_name; + + if (!new_fd || !new_name) + return FALSE; + + clipboard->file_array_size = new_size; + } + + return TRUE; +} + +static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard* clipboard, + WCHAR* full_file_name, size_t pathLen) +{ + if (!wf_cliprdr_array_ensure_capacity(clipboard)) + return FALSE; + + /* add to name array */ + clipboard->file_names[clipboard->nFiles] = (LPWSTR) malloc(MAX_PATH * 2); + + if (!clipboard->file_names[clipboard->nFiles]) + return FALSE; + + wcscpy_s(clipboard->file_names[clipboard->nFiles], MAX_PATH, full_file_name); + /* add to descriptor array */ + clipboard->fileDescriptor[clipboard->nFiles] = + wf_cliprdr_get_file_descriptor(full_file_name, pathLen); + + if (!clipboard->fileDescriptor[clipboard->nFiles]) + { + free(clipboard->file_names[clipboard->nFiles]); + return FALSE; + } + + clipboard->nFiles++; + return TRUE; +} + +static BOOL wf_cliprdr_traverse_directory(wfClipboard* clipboard, + WCHAR* Dir, size_t pathLen) +{ + HANDLE hFind; + WCHAR DirSpec[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + + if (!clipboard || !Dir) + return FALSE; + + StringCchCopy(DirSpec, MAX_PATH, Dir); + StringCchCat(DirSpec, MAX_PATH, TEXT("\\*")); + hFind = FindFirstFile(DirSpec, &FindFileData); + + if (hFind == INVALID_HANDLE_VALUE) + { + DEBUG_CLIPRDR("FindFirstFile failed with 0x%x.", GetLastError()); + return FALSE; + } + + while (FindNextFile(hFind, &FindFileData)) + { + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 + && wcscmp(FindFileData.cFileName, _T(".")) == 0 + || wcscmp(FindFileData.cFileName, _T("..")) == 0) + { + continue; + } + + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + WCHAR DirAdd[MAX_PATH]; + StringCchCopy(DirAdd, MAX_PATH, Dir); + StringCchCat(DirAdd, MAX_PATH, _T("\\")); + StringCchCat(DirAdd, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, DirAdd, pathLen)) + return FALSE; + + if (!wf_cliprdr_traverse_directory(clipboard, DirAdd, pathLen)) + return FALSE; + } + else + { + WCHAR fileName[MAX_PATH]; + StringCchCopy(fileName, MAX_PATH, Dir); + StringCchCat(fileName, MAX_PATH, _T("\\")); + StringCchCat(fileName, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, fileName, pathLen)) + return FALSE; + } + } + + FindClose(hFind); + return TRUE; +} + +static UINT wf_cliprdr_send_client_capabilities(wfClipboard* clipboard) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + if (!clipboard || !clipboard->context || + !clipboard->context->ClientCapabilities) + return ERROR_INTERNAL_ERROR; + + 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 | + CB_STREAM_FILECLIP_ENABLED | + CB_FILECLIP_NO_FILE_PATHS; + return clipboard->context->ClientCapabilities(clipboard->context, + &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_monitor_ready(CliprdrClientContext* context, + CLIPRDR_MONITOR_READY* monitorReady) +{ + UINT rc; + wfClipboard* clipboard = (wfClipboard*) context->custom; + + if (!context || !monitorReady) + return ERROR_INTERNAL_ERROR; + + clipboard->sync = TRUE; + rc = wf_cliprdr_send_client_capabilities(clipboard); + + if (rc != CHANNEL_RC_OK) + return rc; + + return cliprdr_send_format_list(clipboard); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_capabilities(CliprdrClientContext* context, + CLIPRDR_CAPABILITIES* capabilities) +{ + UINT32 index; + CLIPRDR_CAPABILITY_SET* capabilitySet; + wfClipboard* clipboard = (wfClipboard*) context->custom; + + if (!context || !capabilities) + return ERROR_INTERNAL_ERROR; + + for (index = 0; index < capabilities->cCapabilitiesSets; index++) + { + capabilitySet = &(capabilities->capabilitySets[index]); + + if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) && + (capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN)) + { + CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet + = (CLIPRDR_GENERAL_CAPABILITY_SET*) capabilitySet; + clipboard->capabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_list(CliprdrClientContext* context, + CLIPRDR_FORMAT_LIST* formatList) +{ + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 i; + formatMapping* mapping; + CLIPRDR_FORMAT* format; + wfClipboard* clipboard = (wfClipboard*) context->custom; + + if (!clear_format_map(clipboard)) + return ERROR_INTERNAL_ERROR; + + for (i = 0; i < formatList->numFormats; i++) + { + format = &(formatList->formats[i]); + mapping = &(clipboard->format_mappings[i]); + mapping->remote_format_id = format->formatId; + + if (format->formatName) + { + int size = MultiByteToWideChar(CP_UTF8, 0, format->formatName, + strlen(format->formatName), + NULL, 0); + mapping->name = calloc(size + 1, sizeof(WCHAR)); + + if (mapping->name) + { + MultiByteToWideChar(CP_UTF8, 0, format->formatName, strlen(format->formatName), + mapping->name, size); + mapping->local_format_id = RegisterClipboardFormatW((LPWSTR) mapping->name); + } + } + else + { + mapping->name = NULL; + mapping->local_format_id = mapping->remote_format_id; + } + + clipboard->map_size++; + map_ensure_capacity(clipboard); + } + + if (file_transferring(clipboard)) + { + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, 0)) + rc = CHANNEL_RC_OK; + } + else + { + if (!OpenClipboard(clipboard->hwnd)) + return ERROR_INTERNAL_ERROR; + + if (EmptyClipboard()) + { + for (i = 0; i < (UINT32) clipboard->map_size; i++) + SetClipboardData(clipboard->format_mappings[i].local_format_id, NULL); + + rc = CHANNEL_RC_OK; + } + + if (!CloseClipboard() && GetLastError()) + return ERROR_INTERNAL_ERROR; + } + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_list_response(CliprdrClientContext* + context, + CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + (void)context; + (void)formatListResponse; + + if (formatListResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_lock_clipboard_data(CliprdrClientContext* context, + CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + (void)context; + (void)lockClipboardData; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_unlock_clipboard_data(CliprdrClientContext* + context, + CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + (void)context; + (void)unlockClipboardData; + return CHANNEL_RC_OK; +} + +static BOOL wf_cliprdr_process_filename(wfClipboard* clipboard, + WCHAR* wFileName, size_t str_len) +{ + size_t pathLen; + size_t offset = str_len; + + if (!clipboard || !wFileName) + return FALSE; + + /* find the last '\' in full file name */ + while (offset > 0) + { + if (wFileName[offset] == L'\\') + break; + else + offset--; + } + + pathLen = offset + 1; + + if (!wf_cliprdr_add_to_file_arrays(clipboard, wFileName, pathLen)) + return FALSE; + + if ((clipboard->fileDescriptor[clipboard->nFiles - 1]->dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY) != 0) + { + /* this is a directory */ + if (!wf_cliprdr_traverse_directory(clipboard, wFileName, pathLen)) + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_data_request(CliprdrClientContext* context, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + UINT rc; + size_t size = 0; + void* buff = NULL; + char* globlemem = NULL; + HANDLE hClipdata = NULL; + UINT32 requestedFormatId; + CLIPRDR_FORMAT_DATA_RESPONSE response; + wfClipboard* clipboard; + + if (!context || !formatDataRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard*) context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + requestedFormatId = formatDataRequest->requestedFormatId; + + if (requestedFormatId == RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)) + { + size_t len; + size_t i; + WCHAR* wFileName; + HRESULT result; + LPDATAOBJECT dataObj; + FORMATETC format_etc; + STGMEDIUM stg_medium; + DROPFILES* dropFiles; + FILEGROUPDESCRIPTORW* groupDsc; + result = OleGetClipboard(&dataObj); + + if (FAILED(result)) + return ERROR_INTERNAL_ERROR; + + ZeroMemory(&format_etc, sizeof(FORMATETC)); + ZeroMemory(&stg_medium, sizeof(STGMEDIUM)); + /* get DROPFILES struct from OLE */ + format_etc.cfFormat = CF_HDROP; + format_etc.tymed = TYMED_HGLOBAL; + format_etc.dwAspect = 1; + format_etc.lindex = -1; + result = IDataObject_GetData(dataObj, &format_etc, &stg_medium); + + if (FAILED(result)) + { + DEBUG_CLIPRDR("dataObj->GetData failed."); + goto exit; + } + + dropFiles = (DROPFILES*) GlobalLock(stg_medium.hGlobal); + + if (!dropFiles) + { + GlobalUnlock(stg_medium.hGlobal); + ReleaseStgMedium(&stg_medium); + clipboard->nFiles = 0; + goto exit; + } + + clear_file_array(clipboard); + + if (dropFiles->fWide) + { + /* dropFiles contains file names */ + for (wFileName = (WCHAR*)((char*)dropFiles + dropFiles->pFiles); + (len = wcslen(wFileName)) > 0; wFileName += len + 1) + { + wf_cliprdr_process_filename(clipboard, wFileName, wcslen(wFileName)); + } + } + else + { + char* p; + + for (p = (char*)((char*)dropFiles + dropFiles->pFiles); + (len = strlen(p)) > 0; p += len + 1, clipboard->nFiles++) + { + int cchWideChar; + WCHAR* wFileName; + cchWideChar = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, NULL, 0); + wFileName = (LPWSTR) calloc(cchWideChar, sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, len, wFileName, cchWideChar); + wf_cliprdr_process_filename(clipboard, wFileName, cchWideChar); + } + } + + GlobalUnlock(stg_medium.hGlobal); + ReleaseStgMedium(&stg_medium); + exit: + size = 4 + clipboard->nFiles * sizeof(FILEDESCRIPTORW); + groupDsc = (FILEGROUPDESCRIPTORW*) malloc(size); + + if (groupDsc) + { + groupDsc->cItems = clipboard->nFiles; + + for (i = 0; i < clipboard->nFiles; i++) + { + if (clipboard->fileDescriptor[i]) + groupDsc->fgd[i] = *clipboard->fileDescriptor[i]; + } + + buff = groupDsc; + } + + IDataObject_Release(dataObj); + } + else + { + if (!OpenClipboard(clipboard->hwnd)) + return ERROR_INTERNAL_ERROR; + + hClipdata = GetClipboardData(requestedFormatId); + + if (!hClipdata) + { + CloseClipboard(); + return ERROR_INTERNAL_ERROR; + } + + globlemem = (char*) GlobalLock(hClipdata); + size = (int) GlobalSize(hClipdata); + buff = malloc(size); + CopyMemory(buff, globlemem, size); + GlobalUnlock(hClipdata); + CloseClipboard(); + } + + response.msgFlags = CB_RESPONSE_OK; + response.dataLen = size; + response.requestedFormatData = (BYTE*) buff; + rc = clipboard->context->ClientFormatDataResponse(clipboard->context, + &response); + free(buff); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_data_response(CliprdrClientContext* + context, + CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + BYTE* data; + HANDLE hMem; + wfClipboard* clipboard; + + if (!context || !formatDataResponse) + return ERROR_INTERNAL_ERROR; + + if (formatDataResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + clipboard = (wfClipboard*) context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + hMem = GlobalAlloc(GMEM_MOVEABLE, formatDataResponse->dataLen); + + if (!hMem) + return ERROR_INTERNAL_ERROR; + + data = (BYTE*) GlobalLock(hMem); + + if (!data) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + CopyMemory(data, formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + + if (!GlobalUnlock(hMem) && GetLastError()) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + clipboard->hmem = hMem; + + if (!SetEvent(clipboard->response_data_event)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_file_contents_request(CliprdrClientContext* + context, + CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + DWORD uSize = 0; + BYTE* pData = NULL; + HRESULT hRet = S_OK; + FORMATETC vFormatEtc; + LPDATAOBJECT pDataObj = NULL; + STGMEDIUM vStgMedium; + BOOL bIsStreamFile = TRUE; + static LPSTREAM pStreamStc = NULL; + static UINT32 uStreamIdStc = 0; + wfClipboard* clipboard; + UINT rc = ERROR_INTERNAL_ERROR; + UINT sRc; + + if (!context || !fileContentsRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard*) context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + fileContentsRequest->cbRequested = sizeof(UINT64); + + pData = (BYTE*) calloc(1, fileContentsRequest->cbRequested); + + if (!pData) + goto error; + + hRet = OleGetClipboard(&pDataObj); + + if (FAILED(hRet)) + { + WLog_ERR(TAG, "filecontents: get ole clipboard failed."); + goto error; + } + + ZeroMemory(&vFormatEtc, sizeof(FORMATETC)); + ZeroMemory(&vStgMedium, sizeof(STGMEDIUM)); + vFormatEtc.cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + vFormatEtc.tymed = TYMED_ISTREAM; + vFormatEtc.dwAspect = 1; + vFormatEtc.lindex = fileContentsRequest->listIndex; + vFormatEtc.ptd = NULL; + + if ((uStreamIdStc != fileContentsRequest->streamId) || !pStreamStc) + { + LPENUMFORMATETC pEnumFormatEtc; + ULONG CeltFetched; + FORMATETC vFormatEtc2; + + if (pStreamStc) + { + IStream_Release(pStreamStc); + pStreamStc = NULL; + } + + bIsStreamFile = FALSE; + hRet = IDataObject_EnumFormatEtc(pDataObj, DATADIR_GET, &pEnumFormatEtc); + + if (hRet == S_OK) + { + do + { + hRet = IEnumFORMATETC_Next(pEnumFormatEtc, 1, &vFormatEtc2, &CeltFetched); + + if (hRet == S_OK) + { + if (vFormatEtc2.cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS)) + { + hRet = IDataObject_GetData(pDataObj, &vFormatEtc, &vStgMedium); + + if (hRet == S_OK) + { + pStreamStc = vStgMedium.pstm; + uStreamIdStc = fileContentsRequest->streamId; + bIsStreamFile = TRUE; + } + + break; + } + } + } + while (hRet == S_OK); + } + } + + if (bIsStreamFile == TRUE) + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + STATSTG vStatStg; + ZeroMemory(&vStatStg, sizeof(STATSTG)); + hRet = IStream_Stat(pStreamStc, &vStatStg, STATFLAG_NONAME); + + if (hRet == S_OK) + { + *((UINT32*) &pData[0]) = vStatStg.cbSize.LowPart; + *((UINT32*) &pData[4]) = vStatStg.cbSize.HighPart; + uSize = fileContentsRequest->cbRequested; + } + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + LARGE_INTEGER dlibMove; + ULARGE_INTEGER dlibNewPosition; + dlibMove.HighPart = fileContentsRequest->nPositionHigh; + dlibMove.LowPart = fileContentsRequest->nPositionLow; + hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); + + if (SUCCEEDED(hRet)) + hRet = IStream_Read(pStreamStc, pData, fileContentsRequest->cbRequested, + (PULONG) &uSize); + } + } + else + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + *((UINT32*) &pData[0]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeLow; + *((UINT32*) &pData[4]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeHigh; + uSize = fileContentsRequest->cbRequested; + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + BOOL bRet; + bRet = wf_cliprdr_get_file_contents( + clipboard->file_names[fileContentsRequest->listIndex], pData, + fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, + fileContentsRequest->cbRequested, &uSize); + + if (bRet == FALSE) + { + WLog_ERR(TAG, "get file contents failed."); + uSize = 0; + goto error; + } + } + } + + rc = CHANNEL_RC_OK; +error: + + if (pDataObj) + IDataObject_Release(pDataObj); + + if (uSize == 0) + { + free(pData); + pData = NULL; + } + + sRc = cliprdr_send_response_filecontents(clipboard, + fileContentsRequest->streamId, + uSize, pData); + free(pData); + + if (sRc != CHANNEL_RC_OK) + return sRc; + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_file_contents_response(CliprdrClientContext* + context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wfClipboard* clipboard; + + if (!context || !fileContentsResponse) + return ERROR_INTERNAL_ERROR; + + if (fileContentsResponse->msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + clipboard = (wfClipboard*) context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + clipboard->req_fsize = fileContentsResponse->cbRequested; + clipboard->req_fdata = (char*) malloc(fileContentsResponse->cbRequested); + + if (!clipboard->req_fdata) + return ERROR_INTERNAL_ERROR; + + CopyMemory(clipboard->req_fdata, fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + + if (!SetEvent(clipboard->req_fevent)) + { + free(clipboard->req_fdata); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +BOOL wf_cliprdr_init(wfContext* wfc, CliprdrClientContext* cliprdr) +{ + wfClipboard* clipboard; + rdpContext* context = (rdpContext*) wfc; + + if (!context || !cliprdr) + return FALSE; + + wfc->clipboard = (wfClipboard*) calloc(1, sizeof(wfClipboard)); + + if (!wfc->clipboard) + return FALSE; + + clipboard = wfc->clipboard; + clipboard->wfc = wfc; + clipboard->context = cliprdr; + clipboard->channels = context->channels; + clipboard->sync = FALSE; + clipboard->map_capacity = 32; + clipboard->map_size = 0; + clipboard->hUser32 = LoadLibraryA("user32.dll"); + + if (clipboard->hUser32) + { + clipboard->AddClipboardFormatListener = (fnAddClipboardFormatListener) + GetProcAddress(clipboard->hUser32, "AddClipboardFormatListener"); + clipboard->RemoveClipboardFormatListener = (fnRemoveClipboardFormatListener) + GetProcAddress(clipboard->hUser32, "RemoveClipboardFormatListener"); + clipboard->GetUpdatedClipboardFormats = (fnGetUpdatedClipboardFormats) + GetProcAddress(clipboard->hUser32, "GetUpdatedClipboardFormats"); + } + + if (!(clipboard->hUser32 && clipboard->AddClipboardFormatListener + && clipboard->RemoveClipboardFormatListener + && clipboard->GetUpdatedClipboardFormats)) + clipboard->legacyApi = TRUE; + + if (!(clipboard->format_mappings = (formatMapping*) calloc(clipboard->map_capacity, + sizeof(formatMapping)))) + goto error; + + if (!(clipboard->response_data_event = CreateEvent(NULL, TRUE, FALSE, + _T("response_data_event")))) + goto error; + + if (!(clipboard->req_fevent = CreateEvent(NULL, TRUE, FALSE, + _T("request_filecontents_event")))) + goto error; + + if (!(clipboard->thread = CreateThread(NULL, 0, cliprdr_thread_func, clipboard, 0, NULL))) + goto error; + + cliprdr->MonitorReady = wf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = wf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = wf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = wf_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = wf_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = wf_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = wf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = wf_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = wf_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = wf_cliprdr_server_file_contents_response; + cliprdr->custom = (void*) wfc->clipboard; + return TRUE; +error: + wf_cliprdr_uninit(wfc, cliprdr); + return FALSE; +} + +BOOL wf_cliprdr_uninit(wfContext* wfc, CliprdrClientContext* cliprdr) +{ + wfClipboard* clipboard; + + if (!wfc || !cliprdr) + return FALSE; + + clipboard = wfc->clipboard; + + if (!clipboard) + return FALSE; + + cliprdr->custom = NULL; + + if (!clipboard) + return FALSE; + + if (clipboard->hwnd) + PostMessage(clipboard->hwnd, WM_QUIT, 0, 0); + + if (clipboard->thread) + { + WaitForSingleObject(clipboard->thread, INFINITE); + CloseHandle(clipboard->thread); + clipboard->thread = NULL; + } + + if (clipboard->response_data_event) + { + CloseHandle(clipboard->response_data_event); + clipboard->response_data_event = NULL; + } + + clear_file_array(clipboard); + clear_format_map(clipboard); + free(clipboard->format_mappings); + free(clipboard); + return TRUE; +} diff --git a/client/Windows/wf_cliprdr.h b/client/Windows/wf_cliprdr.h new file mode 100644 index 0000000..3a6b4a1 --- /dev/null +++ b/client/Windows/wf_cliprdr.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Clipboard Redirection + * + * Copyright 2012 Jason Champion + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_CLIPRDR_H +#define FREERDP_CLIENT_WIN_CLIPRDR_H + +#include "wf_client.h" + +BOOL wf_cliprdr_init(wfContext* wfc, CliprdrClientContext* cliprdr); +BOOL wf_cliprdr_uninit(wfContext* wfc, CliprdrClientContext* cliprdr); + +#endif /* FREERDP_CLIENT_WIN_CLIPRDR_H */ diff --git a/client/Windows/wf_event.c b/client/Windows/wf_event.c new file mode 100644 index 0000000..8057999 --- /dev/null +++ b/client/Windows/wf_event.c @@ -0,0 +1,778 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Event Handling + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "wf_client.h" + +#include "wf_gdi.h" +#include "wf_event.h" + +#include + +static HWND g_focus_hWnd; + +#define X_POS(lParam) ((UINT16) (lParam & 0xFFFF)) +#define Y_POS(lParam) ((UINT16) ((lParam >> 16) & 0xFFFF)) + +static BOOL wf_scale_blt(wfContext* wfc, HDC hdc, int x, int y, int w, int h, + HDC hdcSrc, int x1, int y1, DWORD rop); +static BOOL wf_scale_mouse_event(wfContext* wfc, rdpInput* input, UINT16 flags, + UINT16 x, UINT16 y); +#if (_WIN32_WINNT >= 0x0500) +static BOOL wf_scale_mouse_event_ex(wfContext* wfc, rdpInput* input, UINT16 flags, + UINT16 buttonMask, UINT16 x, UINT16 y); +#endif + +static BOOL g_flipping_in; +static BOOL g_flipping_out; + +static BOOL alt_ctrl_down() +{ + return ((GetAsyncKeyState(VK_CONTROL) & 0x8000) || + (GetAsyncKeyState(VK_MENU) & 0x8000)); +} + +LRESULT CALLBACK wf_ll_kbd_proc(int nCode, WPARAM wParam, LPARAM lParam) +{ + wfContext* wfc; + DWORD rdp_scancode; + rdpInput* input; + PKBDLLHOOKSTRUCT p; + DEBUG_KBD("Low-level keyboard hook, hWnd %X nCode %X wParam %X", g_focus_hWnd, + nCode, wParam); + + if (g_flipping_in) + { + if (!alt_ctrl_down()) + g_flipping_in = FALSE; + + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + + if (g_focus_hWnd && (nCode == HC_ACTION)) + { + switch (wParam) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + wfc = (wfContext*) GetWindowLongPtr(g_focus_hWnd, GWLP_USERDATA); + p = (PKBDLLHOOKSTRUCT) lParam; + + if (!wfc || !p) + return 1; + + input = wfc->context.input; + rdp_scancode = MAKE_RDP_SCANCODE((BYTE) p->scanCode, p->flags & LLKHF_EXTENDED); + DEBUG_KBD("keydown %d scanCode 0x%08lX flags 0x%08lX vkCode 0x%08lX", + (wParam == WM_KEYDOWN), p->scanCode, p->flags, p->vkCode); + + if (wfc->fs_toggle && + ((p->vkCode == VK_RETURN) || (p->vkCode == VK_CANCEL)) && + (GetAsyncKeyState(VK_CONTROL) & 0x8000) && + (GetAsyncKeyState(VK_MENU) & 0x8000)) /* could also use flags & LLKHF_ALTDOWN */ + { + if (wParam == WM_KEYDOWN) + { + wf_toggle_fullscreen(wfc); + return 1; + } + } + + if (rdp_scancode == RDP_SCANCODE_NUMLOCK_EXTENDED) + { + /* Windows sends NumLock as extended - rdp doesn't */ + DEBUG_KBD("hack: NumLock (x45) should not be extended"); + rdp_scancode = RDP_SCANCODE_NUMLOCK; + } + else if (rdp_scancode == RDP_SCANCODE_NUMLOCK) + { + /* Windows sends Pause as if it was a RDP NumLock (handled above). + * It must however be sent as a one-shot Ctrl+NumLock */ + if (wParam == WM_KEYDOWN) + { + DEBUG_KBD("Pause, sent as Ctrl+NumLock"); + freerdp_input_send_keyboard_event_ex(input, TRUE, RDP_SCANCODE_LCONTROL); + freerdp_input_send_keyboard_event_ex(input, TRUE, RDP_SCANCODE_NUMLOCK); + freerdp_input_send_keyboard_event_ex(input, FALSE, RDP_SCANCODE_LCONTROL); + freerdp_input_send_keyboard_event_ex(input, FALSE, RDP_SCANCODE_NUMLOCK); + } + else + { + DEBUG_KBD("Pause up"); + } + + return 1; + } + else if (rdp_scancode == RDP_SCANCODE_RSHIFT_EXTENDED) + { + DEBUG_KBD("right shift (x36) should not be extended"); + rdp_scancode = RDP_SCANCODE_RSHIFT; + } + + freerdp_input_send_keyboard_event_ex(input, !(p->flags & LLKHF_UP), + rdp_scancode); + + if (p->vkCode == VK_NUMLOCK || p->vkCode == VK_CAPITAL + || p->vkCode == VK_SCROLL || p->vkCode == VK_KANA) + DEBUG_KBD("lock keys are processed on client side too to toggle their indicators"); + else + return 1; + + break; + } + } + + if (g_flipping_out) + { + if (!alt_ctrl_down()) + { + g_flipping_out = FALSE; + g_focus_hWnd = NULL; + } + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); +} + +void wf_event_focus_in(wfContext* wfc) +{ + UINT16 syncFlags; + rdpInput* input; + POINT pt; + RECT rc; + input = wfc->context.input; + syncFlags = 0; + + if (GetKeyState(VK_NUMLOCK)) + syncFlags |= KBD_SYNC_NUM_LOCK; + + if (GetKeyState(VK_CAPITAL)) + syncFlags |= KBD_SYNC_CAPS_LOCK; + + if (GetKeyState(VK_SCROLL)) + syncFlags |= KBD_SYNC_SCROLL_LOCK; + + if (GetKeyState(VK_KANA)) + syncFlags |= KBD_SYNC_KANA_LOCK; + + input->FocusInEvent(input, syncFlags); + /* send pointer position if the cursor is currently inside our client area */ + GetCursorPos(&pt); + ScreenToClient(wfc->hwnd, &pt); + GetClientRect(wfc->hwnd, &rc); + + if (pt.x >= rc.left && pt.x < rc.right && pt.y >= rc.top && pt.y < rc.bottom) + input->MouseEvent(input, PTR_FLAGS_MOVE, (UINT16)pt.x, (UINT16)pt.y); +} + +static BOOL wf_event_process_WM_MOUSEWHEEL(wfContext* wfc, HWND hWnd, UINT Msg, + WPARAM wParam, LPARAM lParam, BOOL horizontal, UINT16 x, UINT16 y) +{ + int delta; + UINT16 flags = 0; + rdpInput* input; + DefWindowProc(hWnd, Msg, wParam, lParam); + input = wfc->context.input; + delta = ((signed short) HIWORD(wParam)); /* GET_WHEEL_DELTA_WPARAM(wParam); */ + + if (horizontal) + flags |= PTR_FLAGS_HWHEEL; + else + flags |= PTR_FLAGS_WHEEL; + + if (delta < 0) + { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + delta = -delta; + } + flags |= delta; + + return wf_scale_mouse_event(wfc, input, flags, x, y); +} + +static void wf_sizing(wfContext* wfc, WPARAM wParam, LPARAM lParam) +{ + rdpSettings* settings = wfc->context.settings; + // Holding the CTRL key down while resizing the window will force the desktop aspect ratio. + LPRECT rect; + + if (settings->SmartSizing && (GetAsyncKeyState(VK_CONTROL) & 0x8000)) + { + rect = (LPRECT) wParam; + + switch (lParam) + { + case WMSZ_LEFT: + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + // Adjust height + rect->bottom = rect->top + settings->DesktopHeight * (rect->right - + rect->left) / settings->DesktopWidth; + break; + + case WMSZ_TOP: + case WMSZ_BOTTOM: + case WMSZ_TOPRIGHT: + // Adjust width + rect->right = rect->left + settings->DesktopWidth * (rect->bottom - rect->top) / + settings->DesktopHeight; + break; + + case WMSZ_BOTTOMLEFT: + case WMSZ_TOPLEFT: + // adjust width + rect->left = rect->right - (settings->DesktopWidth * (rect->bottom - + rect->top) / settings->DesktopHeight); + break; + } + } +} + +LRESULT CALLBACK wf_event_proc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) +{ + HDC hdc; + LONG_PTR ptr; + wfContext* wfc; + int x, y, w, h; + PAINTSTRUCT ps; + BOOL processed; + RECT windowRect; + MINMAXINFO* minmax; + SCROLLINFO si; + processed = TRUE; + ptr = GetWindowLongPtr(hWnd, GWLP_USERDATA); + wfc = (wfContext*) ptr; + + if (wfc != NULL) + { + rdpInput* input = wfc->context.input; + rdpSettings* settings = wfc->context.settings; + + switch (Msg) + { + case WM_MOVE: + if (!wfc->disablewindowtracking) + { + int x = (int)(short) LOWORD(lParam); + int y = (int)(short) HIWORD(lParam); + wfc->client_x = x; + wfc->client_y = y; + } + + break; + + case WM_GETMINMAXINFO: + if (wfc->context.settings->SmartSizing) + { + processed = FALSE; + } + else + { + // Set maximum window size for resizing + minmax = (MINMAXINFO*) lParam; + + //always use the last determined canvas diff, because it could be + //that the window is minimized when this gets called + //wf_update_canvas_diff(wfc); + + if (!wfc->fullscreen) + { + // add window decoration + minmax->ptMaxTrackSize.x = settings->DesktopWidth + wfc->diff.x; + minmax->ptMaxTrackSize.y = settings->DesktopHeight + wfc->diff.y; + } + } + + break; + + case WM_SIZING: + wf_sizing(wfc, lParam, wParam); + break; + + case WM_SIZE: + GetWindowRect(wfc->hwnd, &windowRect); + + if (!wfc->fullscreen) + { + wfc->client_width = LOWORD(lParam); + wfc->client_height = HIWORD(lParam); + wfc->client_x = windowRect.left; + wfc->client_y = windowRect.top; + } + + if (wfc->client_width && wfc->client_height) + { + wf_size_scrollbars(wfc, LOWORD(lParam), HIWORD(lParam)); + + // Workaround: when the window is maximized, the call to "ShowScrollBars" returns TRUE but has no effect. + if (wParam == SIZE_MAXIMIZED && !wfc->fullscreen) + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, windowRect.right - windowRect.left, + windowRect.bottom - windowRect.top, SWP_NOMOVE | SWP_FRAMECHANGED); + } + + break; + + case WM_EXITSIZEMOVE: + wf_size_scrollbars(wfc, wfc->client_width, wfc->client_height); + break; + + case WM_ERASEBKGND: + /* Say we handled it - prevents flickering */ + return (LRESULT) 1; + + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + x = ps.rcPaint.left; + y = ps.rcPaint.top; + w = ps.rcPaint.right - ps.rcPaint.left + 1; + h = ps.rcPaint.bottom - ps.rcPaint.top + 1; + wf_scale_blt(wfc, hdc, x, y, w, h, wfc->primary->hdc, + x - wfc->offset_x + wfc->xCurrentScroll, + y - wfc->offset_y + wfc->yCurrentScroll, SRCCOPY); + EndPaint(hWnd, &ps); + break; + +#if (_WIN32_WINNT >= 0x0500) + case WM_XBUTTONDOWN: + wf_scale_mouse_event_ex(wfc, input, PTR_XFLAGS_DOWN, GET_XBUTTON_WPARAM(wParam), + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_XBUTTONUP: + wf_scale_mouse_event_ex(wfc, input, 0, GET_XBUTTON_WPARAM(wParam), + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; +#endif + + case WM_MBUTTONDOWN: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON3, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_MBUTTONUP: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_BUTTON3, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_LBUTTONDOWN: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON1, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_LBUTTONUP: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_BUTTON1, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_RBUTTONDOWN: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON2, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_RBUTTONUP: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_BUTTON2, + X_POS(lParam) - wfc->offset_x, Y_POS(lParam) - wfc->offset_y); + break; + + case WM_MOUSEMOVE: + wf_scale_mouse_event(wfc, input, PTR_FLAGS_MOVE, X_POS(lParam) - wfc->offset_x, + Y_POS(lParam) - wfc->offset_y); + break; + +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + case WM_MOUSEWHEEL: + wf_event_process_WM_MOUSEWHEEL(wfc, hWnd, Msg, wParam, lParam, FALSE, + X_POS(lParam) - wfc->offset_x, + Y_POS(lParam) - wfc->offset_y); + break; +#endif + +#if (_WIN32_WINNT >= 0x0600) + case WM_MOUSEHWHEEL: + wf_event_process_WM_MOUSEWHEEL(wfc, hWnd, Msg, wParam, lParam, TRUE, + X_POS(lParam) - wfc->offset_x, + Y_POS(lParam) - wfc->offset_y); + break; +#endif + + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) + SetCursor(wfc->cursor); + else + DefWindowProc(hWnd, Msg, wParam, lParam); + + break; + + case WM_HSCROLL: + { + int xDelta; // xDelta = new_pos - current_pos + int xNewPos; // new position + int yDelta = 0; + + switch (LOWORD(wParam)) + { + // User clicked the scroll bar shaft left of the scroll box. + case SB_PAGEUP: + xNewPos = wfc->xCurrentScroll - 50; + break; + + // User clicked the scroll bar shaft right of the scroll box. + case SB_PAGEDOWN: + xNewPos = wfc->xCurrentScroll + 50; + break; + + // User clicked the left arrow. + case SB_LINEUP: + xNewPos = wfc->xCurrentScroll - 5; + break; + + // User clicked the right arrow. + case SB_LINEDOWN: + xNewPos = wfc->xCurrentScroll + 5; + break; + + // User dragged the scroll box. + case SB_THUMBPOSITION: + xNewPos = HIWORD(wParam); + + // user is dragging the scrollbar + case SB_THUMBTRACK : + xNewPos = HIWORD(wParam); + break; + + default: + xNewPos = wfc->xCurrentScroll; + } + + // New position must be between 0 and the screen width. + xNewPos = MAX(0, xNewPos); + xNewPos = MIN(wfc->xMaxScroll, xNewPos); + + // If the current position does not change, do not scroll. + if (xNewPos == wfc->xCurrentScroll) + break; + + // Determine the amount scrolled (in pixels). + xDelta = xNewPos - wfc->xCurrentScroll; + // Reset the current scroll position. + wfc->xCurrentScroll = xNewPos; + // Scroll the window. (The system repaints most of the + // client area when ScrollWindowEx is called; however, it is + // necessary to call UpdateWindow in order to repaint the + // rectangle of pixels that were invalidated.) + ScrollWindowEx(wfc->hwnd, -xDelta, -yDelta, (CONST RECT*) NULL, + (CONST RECT*) NULL, (HRGN) NULL, (PRECT) NULL, + SW_INVALIDATE); + UpdateWindow(wfc->hwnd); + // Reset the scroll bar. + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + si.nPos = wfc->xCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_HORZ, &si, TRUE); + } + break; + + case WM_VSCROLL: + { + int xDelta = 0; + int yDelta; // yDelta = new_pos - current_pos + int yNewPos; // new position + + switch (LOWORD(wParam)) + { + // User clicked the scroll bar shaft above the scroll box. + case SB_PAGEUP: + yNewPos = wfc->yCurrentScroll - 50; + break; + + // User clicked the scroll bar shaft below the scroll box. + case SB_PAGEDOWN: + yNewPos = wfc->yCurrentScroll + 50; + break; + + // User clicked the top arrow. + case SB_LINEUP: + yNewPos = wfc->yCurrentScroll - 5; + break; + + // User clicked the bottom arrow. + case SB_LINEDOWN: + yNewPos = wfc->yCurrentScroll + 5; + break; + + // User dragged the scroll box. + case SB_THUMBPOSITION: + yNewPos = HIWORD(wParam); + break; + + // user is dragging the scrollbar + case SB_THUMBTRACK : + yNewPos = HIWORD(wParam); + break; + + default: + yNewPos = wfc->yCurrentScroll; + } + + // New position must be between 0 and the screen height. + yNewPos = MAX(0, yNewPos); + yNewPos = MIN(wfc->yMaxScroll, yNewPos); + + // If the current position does not change, do not scroll. + if (yNewPos == wfc->yCurrentScroll) + break; + + // Determine the amount scrolled (in pixels). + yDelta = yNewPos - wfc->yCurrentScroll; + // Reset the current scroll position. + wfc->yCurrentScroll = yNewPos; + // Scroll the window. (The system repaints most of the + // client area when ScrollWindowEx is called; however, it is + // necessary to call UpdateWindow in order to repaint the + // rectangle of pixels that were invalidated.) + ScrollWindowEx(wfc->hwnd, -xDelta, -yDelta, (CONST RECT*) NULL, + (CONST RECT*) NULL, (HRGN) NULL, (PRECT) NULL, + SW_INVALIDATE); + UpdateWindow(wfc->hwnd); + // Reset the scroll bar. + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + si.nPos = wfc->yCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_VERT, &si, TRUE); + } + break; + + case WM_SYSCOMMAND: + { + if (wParam == SYSCOMMAND_ID_SMARTSIZING) + { + HMENU hMenu = GetSystemMenu(wfc->hwnd, FALSE); + freerdp_set_param_bool(wfc->context.settings, FreeRDP_SmartSizing, + !wfc->context.settings->SmartSizing); + CheckMenuItem(hMenu, SYSCOMMAND_ID_SMARTSIZING, + wfc->context.settings->SmartSizing ? MF_CHECKED : MF_UNCHECKED); + } + else + { + processed = FALSE; + } + } + break; + + default: + processed = FALSE; + break; + } + } + else + { + processed = FALSE; + } + + if (processed) + return 0; + + switch (Msg) + { + case WM_DESTROY: + PostQuitMessage(WM_QUIT); + break; + + case WM_SETFOCUS: + DEBUG_KBD("getting focus %X", hWnd); + + if (alt_ctrl_down()) + g_flipping_in = TRUE; + + g_focus_hWnd = hWnd; + freerdp_set_focus(wfc->context.instance); + break; + + case WM_KILLFOCUS: + if (g_focus_hWnd == hWnd && wfc && !wfc->fullscreen) + { + DEBUG_KBD("loosing focus %X", hWnd); + + if (alt_ctrl_down()) + g_flipping_out = TRUE; + else + g_focus_hWnd = NULL; + } + + break; + + case WM_ACTIVATE: + { + int activate = (int)(short) LOWORD(wParam); + + if (activate != WA_INACTIVE) + { + if (alt_ctrl_down()) + g_flipping_in = TRUE; + + g_focus_hWnd = hWnd; + } + else + { + if (alt_ctrl_down()) + g_flipping_out = TRUE; + else + g_focus_hWnd = NULL; + } + } + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + break; + } + + return 0; +} + +BOOL wf_scale_blt(wfContext* wfc, HDC hdc, int x, int y, int w, int h, + HDC hdcSrc, int x1, int y1, DWORD rop) +{ + rdpSettings* settings; + UINT32 ww, wh, dw, dh; + settings = wfc->context.settings; + + if (!wfc->client_width) + wfc->client_width = settings->DesktopWidth; + + if (!wfc->client_height) + wfc->client_height = settings->DesktopHeight; + + ww = wfc->client_width; + wh = wfc->client_height; + dw = settings->DesktopWidth; + dh = settings->DesktopHeight; + + if (!ww) + ww = dw; + + if (!wh) + wh = dh; + + if (wfc->fullscreen || !wfc->context.settings->SmartSizing || (ww == dw + && wh == dh)) + { + return BitBlt(hdc, x, y, w, h, wfc->primary->hdc, x1, y1, SRCCOPY); + } + else + { + SetStretchBltMode(hdc, HALFTONE); + SetBrushOrgEx(hdc, 0, 0, NULL); + return StretchBlt(hdc, 0, 0, ww, wh, wfc->primary->hdc, 0, 0, dw, dh, SRCCOPY); + } + + return TRUE; +} + +static BOOL wf_scale_mouse_pos(wfContext* wfc, UINT16* x, UINT16* y) +{ + int ww, wh, dw, dh; + rdpContext* context; + rdpSettings* settings; + + if (!wfc || !x || !y) + return FALSE; + + settings = wfc->context.settings; + if (!settings) + return FALSE; + + if (!wfc->client_width) + wfc->client_width = settings->DesktopWidth; + + if (!wfc->client_height) + wfc->client_height = settings->DesktopHeight; + + ww = wfc->client_width; + wh = wfc->client_height; + dw = settings->DesktopWidth; + dh = settings->DesktopHeight; + + if (!settings->SmartSizing || ((ww == dw) && (wh == dh))) + { + *x += wfc->xCurrentScroll; + *y += wfc->yCurrentScroll; + } + else + { + *x = *x * dw / ww + wfc->xCurrentScroll; + *y = *y * dh / wh + wfc->yCurrentScroll; + } + + return TRUE; +} + +static BOOL wf_scale_mouse_event(wfContext* wfc, rdpInput* input, UINT16 flags, + UINT16 x, UINT16 y) +{ + MouseEventEventArgs eventArgs; + + if (!wf_scale_mouse_pos(wfc, &x, &y)) + return FALSE; + + if (freerdp_input_send_mouse_event(input, flags, x, y)) + return FALSE; + + eventArgs.flags = flags; + eventArgs.x = x; + eventArgs.y = y; + PubSub_OnMouseEvent(wfc->context.pubSub, &wfc->context, &eventArgs); + return TRUE; +} + +#if(_WIN32_WINNT >= 0x0500) +static BOOL wf_scale_mouse_event_ex(wfContext* wfc, rdpInput* input, UINT16 flags, UINT16 buttonMask, + UINT16 x, UINT16 y) +{ + MouseEventExEventArgs eventArgs; + + if (buttonMask & XBUTTON1) + flags |= PTR_XFLAGS_BUTTON1; + if (buttonMask & XBUTTON2) + flags |= PTR_XFLAGS_BUTTON2; + + if (!wf_scale_mouse_pos(wfc, &x, &y)) + return FALSE; + + if (freerdp_input_send_extended_mouse_event(input, flags, x, y)) + return FALSE; + + eventArgs.flags = flags; + eventArgs.x = x; + eventArgs.y = y; + PubSub_OnMouseEventEx(wfc->context.pubSub, &wfc->context, &eventArgs); + return TRUE; +} +#endif diff --git a/client/Windows/wf_event.h b/client/Windows/wf_event.h new file mode 100644 index 0000000..a9d7108 --- /dev/null +++ b/client/Windows/wf_event.h @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Event Handling + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_EVENT_H +#define FREERDP_CLIENT_WIN_EVENT_H + +#include "wf_client.h" +#include + +LRESULT CALLBACK wf_ll_kbd_proc(int nCode, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK wf_event_proc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam); + +void wf_event_focus_in(wfContext* wfc); + +#define KBD_TAG CLIENT_TAG("windows") +#ifdef WITH_DEBUG_KBD +#define DEBUG_KBD(...) WLog_DBG(KBD_TAG, __VA_ARGS__) +#else +#define DEBUG_KBD(...) do { } while (0) +#endif + +#endif /* FREERDP_CLIENT_WIN_EVENT_H */ diff --git a/client/Windows/wf_floatbar.c b/client/Windows/wf_floatbar.c new file mode 100644 index 0000000..71f9898 --- /dev/null +++ b/client/Windows/wf_floatbar.c @@ -0,0 +1,480 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Float Bar + * + * Copyright 2013 Zhang Zhaolong + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +#include "resource.h" + +#include "wf_client.h" +#include "wf_floatbar.h" +#include "wf_gdi.h" + +typedef struct _Button Button; + +/* TIMERs */ +#define TIMER_HIDE 1 +#define TIMER_ANIMAT_SHOW 2 +#define TIMER_ANIMAT_HIDE 3 + +/* Button Type */ +#define BUTTON_LOCKPIN 0 +#define BUTTON_MINIMIZE 1 +#define BUTTON_RESTORE 2 +#define BUTTON_CLOSE 3 +#define BTN_MAX 4 + +/* bmp size */ +#define BACKGROUND_W 581 +#define BACKGROUND_H 29 +#define LOCK_X 13 +#define MINIMIZE_X (BACKGROUND_W - 91) +#define CLOSE_X (BACKGROUND_W - 37) +#define RESTORE_X (BACKGROUND_W - 64) + +#define BUTTON_Y 2 +#define BUTTON_WIDTH 24 +#define BUTTON_HEIGHT 24 + +struct _Button +{ + FloatBar* floatbar; + int type; + int x, y, h, w; + int active; + HBITMAP bmp; + HBITMAP bmp_act; + + /* Lock Specified */ + HBITMAP locked_bmp; + HBITMAP locked_bmp_act; + HBITMAP unlocked_bmp; + HBITMAP unlocked_bmp_act; +}; + +struct _FloatBar +{ + HWND parent; + HWND hwnd; + RECT rect; + LONG width; + LONG height; + wfContext* wfc; + Button* buttons[BTN_MAX]; + BOOL shown; + BOOL locked; + HDC hdcmem; + HBITMAP background; +}; + +static int button_hit(Button* button) +{ + FloatBar* floatbar = button->floatbar; + + switch (button->type) + { + case BUTTON_LOCKPIN: + if (!floatbar->locked) + { + button->bmp = button->locked_bmp; + button->bmp_act = button->locked_bmp_act; + } + else + { + button->bmp = button->unlocked_bmp; + button->bmp_act = button->unlocked_bmp_act; + } + + floatbar->locked = ~floatbar->locked; + InvalidateRect(button->floatbar->hwnd, NULL, FALSE); + UpdateWindow(button->floatbar->hwnd); + break; + + case BUTTON_MINIMIZE: + ShowWindow(floatbar->parent, SW_MINIMIZE); + break; + + case BUTTON_RESTORE: + wf_toggle_fullscreen(floatbar->wfc); + break; + + case BUTTON_CLOSE: + SendMessage(floatbar->parent, WM_DESTROY, 0 , 0); + break; + + default: + return 0; + } + + return 0; +} + +static int button_paint(Button* button, HDC hdc) +{ + FloatBar* floatbar = button->floatbar; + SelectObject(floatbar->hdcmem, button->active ? button->bmp_act : button->bmp); + StretchBlt(hdc, button->x, button->y, button->w, button->h, floatbar->hdcmem, 0, + 0, button->w, button->h, SRCCOPY); + return 0; +} + +static Button* floatbar_create_button(FloatBar* floatbar, int type, int resid, + int resid_act, int x, int y, int h, int w) +{ + Button* button; + button = (Button*)malloc(sizeof(Button)); + + if (!button) + return NULL; + + button->floatbar = floatbar; + button->type = type; + button->x = x; + button->y = y; + button->w = w; + button->h = h; + button->active = FALSE; + button->bmp = (HBITMAP)LoadImage(floatbar->wfc->hInstance, + MAKEINTRESOURCE(resid), IMAGE_BITMAP, w, h, LR_DEFAULTCOLOR); + button->bmp_act = (HBITMAP)LoadImage(floatbar->wfc->hInstance, + MAKEINTRESOURCE(resid_act), IMAGE_BITMAP, w, h, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_create_lock_button(FloatBar* floatbar, + int unlock_resid, int unlock_resid_act, + int lock_resid, int lock_resid_act, + int x, int y, int h, int w) +{ + Button* button; + button = floatbar_create_button(floatbar, BUTTON_LOCKPIN, unlock_resid, + unlock_resid_act, x, y, h, w); + + if (!button) + return NULL; + + button->unlocked_bmp = button->bmp; + button->unlocked_bmp_act = button->bmp_act; + button->locked_bmp = (HBITMAP)LoadImage(floatbar->wfc->hInstance, + MAKEINTRESOURCE(lock_resid), IMAGE_BITMAP, w, h, LR_DEFAULTCOLOR); + button->locked_bmp_act = (HBITMAP)LoadImage(floatbar->wfc->hInstance, + MAKEINTRESOURCE(lock_resid_act), IMAGE_BITMAP, w, h, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_get_button(FloatBar* floatbar, int x, int y) +{ + int i; + + if (y > BUTTON_Y && y < BUTTON_Y + BUTTON_HEIGHT) + for (i = 0; i < BTN_MAX; i++) + if (x > floatbar->buttons[i]->x + && x < floatbar->buttons[i]->x + floatbar->buttons[i]->w) + return floatbar->buttons[i]; + + return NULL; +} + +static int floatbar_paint(FloatBar* floatbar, HDC hdc) +{ + int i; + /* paint background */ + SelectObject(floatbar->hdcmem, floatbar->background); + StretchBlt(hdc, 0, 0, BACKGROUND_W, BACKGROUND_H, floatbar->hdcmem, 0, 0, + BACKGROUND_W, BACKGROUND_H, SRCCOPY); + + /* paint buttons */ + for (i = 0; i < BTN_MAX; i++) + button_paint(floatbar->buttons[i], hdc); + + return 0; +} + +static int floatbar_animation(FloatBar* floatbar, BOOL show) +{ + SetTimer(floatbar->hwnd, show ? TIMER_ANIMAT_SHOW : TIMER_ANIMAT_HIDE, 10, + NULL); + floatbar->shown = show; + return 0; +} + +LRESULT CALLBACK floatbar_proc(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) +{ + static int dragging = FALSE; + static int lbtn_dwn = FALSE; + static int btn_dwn_x = 0; + static FloatBar* floatbar; + static TRACKMOUSEEVENT tme; + PAINTSTRUCT ps; + Button* button; + HDC hdc; + int pos_x; + int pos_y; + int xScreen = GetSystemMetrics(SM_CXSCREEN); + + switch (Msg) + { + case WM_CREATE: + floatbar = (FloatBar*)((CREATESTRUCT*)lParam)->lpCreateParams; + floatbar->hwnd = hWnd; + floatbar->parent = GetParent(hWnd); + GetWindowRect(floatbar->hwnd, &floatbar->rect); + floatbar->width = floatbar->rect.right - floatbar->rect.left; + floatbar->height = floatbar->rect.bottom - floatbar->rect.top; + hdc = GetDC(hWnd); + floatbar->hdcmem = CreateCompatibleDC(hdc); + ReleaseDC(hWnd, hdc); + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + SetTimer(hWnd, TIMER_HIDE, 3000, NULL); + break; + + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + floatbar_paint(floatbar, hdc); + EndPaint(hWnd, &ps); + break; + + case WM_LBUTTONDOWN: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (!button) + { + SetCapture(hWnd); + dragging = TRUE; + btn_dwn_x = lParam & 0xffff; + } + else + lbtn_dwn = TRUE; + + break; + + case WM_LBUTTONUP: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + ReleaseCapture(); + dragging = FALSE; + + if (lbtn_dwn) + { + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (button) + button_hit(button); + + lbtn_dwn = FALSE; + } + + break; + + case WM_MOUSEMOVE: + KillTimer(hWnd, TIMER_HIDE); + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + + if (!floatbar->shown) + floatbar_animation(floatbar, TRUE); + + if (dragging) + { + floatbar->rect.left = floatbar->rect.left + (lParam & 0xffff) - btn_dwn_x; + + if (floatbar->rect.left < 0) + floatbar->rect.left = 0; + else if (floatbar->rect.left > xScreen - floatbar->width) + floatbar->rect.left = xScreen - floatbar->width; + + MoveWindow(hWnd, floatbar->rect.left, floatbar->rect.top, floatbar->width, + floatbar->height, TRUE); + } + else + { + int i; + + for (i = 0; i < BTN_MAX; i++) + floatbar->buttons[i]->active = FALSE; + + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (button) + button->active = TRUE; + + InvalidateRect(hWnd, NULL, FALSE); + UpdateWindow(hWnd); + } + + TrackMouseEvent(&tme); + break; + + case WM_CAPTURECHANGED: + dragging = FALSE; + break; + + case WM_MOUSELEAVE: + { + int i; + + for (i = 0; i < BTN_MAX; i++) + floatbar->buttons[i]->active = FALSE; + + InvalidateRect(hWnd, NULL, FALSE); + UpdateWindow(hWnd); + SetTimer(hWnd, TIMER_HIDE, 3000, NULL); + break; + } + + case WM_TIMER: + switch (wParam) + { + case TIMER_HIDE: + { + KillTimer(hWnd, TIMER_HIDE); + + if (!floatbar->locked) + floatbar_animation(floatbar, FALSE); + + break; + } + + case TIMER_ANIMAT_SHOW: + { + static int y = 0; + MoveWindow(floatbar->hwnd, floatbar->rect.left, (y++ - floatbar->height), + floatbar->width, floatbar->height, TRUE); + + if (y == floatbar->height) + { + y = 0; + KillTimer(hWnd, wParam); + } + + break; + } + + case TIMER_ANIMAT_HIDE: + { + static int y = 0; + MoveWindow(floatbar->hwnd, floatbar->rect.left, -y++, floatbar->width, + floatbar->height, TRUE); + + if (y == floatbar->height) + { + y = 0; + KillTimer(hWnd, wParam); + } + + break; + } + + default: + break; + } + + break; + + case WM_DESTROY: + DeleteDC(floatbar->hdcmem); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static FloatBar* floatbar_create(wfContext* wfc) +{ + FloatBar* floatbar; + floatbar = (FloatBar*)malloc(sizeof(FloatBar)); + + if (!floatbar) + return NULL; + + floatbar->locked = FALSE; + floatbar->shown = TRUE; + floatbar->hwnd = NULL; + floatbar->parent = wfc->hwnd; + floatbar->wfc = wfc; + floatbar->hdcmem = NULL; + floatbar->background = (HBITMAP)LoadImage(wfc->hInstance, + MAKEINTRESOURCE(IDB_BACKGROUND), IMAGE_BITMAP, BACKGROUND_W, BACKGROUND_H, + LR_DEFAULTCOLOR); + floatbar->buttons[0] = floatbar_create_button(floatbar, BUTTON_MINIMIZE, + IDB_MINIMIZE, IDB_MINIMIZE_ACT, MINIMIZE_X, BUTTON_Y, BUTTON_HEIGHT, + BUTTON_WIDTH); + floatbar->buttons[1] = floatbar_create_button(floatbar, BUTTON_RESTORE, + IDB_RESTORE, IDB_RESTORE_ACT, RESTORE_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + floatbar->buttons[2] = floatbar_create_button(floatbar, BUTTON_CLOSE, IDB_CLOSE, + IDB_CLOSE_ACT, CLOSE_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + floatbar->buttons[3] = floatbar_create_lock_button(floatbar, IDB_UNLOCK, + IDB_UNLOCK_ACT, IDB_LOCK, IDB_LOCK_ACT, LOCK_X, BUTTON_Y, BUTTON_HEIGHT, + BUTTON_WIDTH); + return floatbar; +} + +int floatbar_hide(FloatBar* floatbar) +{ + KillTimer(floatbar->hwnd, TIMER_HIDE); + MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->height, + floatbar->width, floatbar->height, TRUE); + return 0; +} + +int floatbar_show(FloatBar* floatbar) +{ + SetTimer(floatbar->hwnd, TIMER_HIDE, 3000, NULL); + MoveWindow(floatbar->hwnd, floatbar->rect.left, floatbar->rect.top, + floatbar->width, floatbar->height, TRUE); + return 0; +} + +void floatbar_window_create(wfContext* wfc) +{ + WNDCLASSEX wnd_cls; + HWND barWnd; + int x = (GetSystemMetrics(SM_CXSCREEN) - BACKGROUND_W) / 2; + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wnd_cls.lpfnWndProc = floatbar_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wnd_cls.hCursor = LoadCursor(wfc->hInstance, IDC_ARROW); + wnd_cls.hbrBackground = NULL; + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = L"floatbar"; + wnd_cls.hInstance = wfc->hInstance; + wnd_cls.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + wfc->floatbar = floatbar_create(wfc); + barWnd = CreateWindowEx(WS_EX_TOPMOST, L"floatbar", L"floatbar", WS_CHILD, x, 0, + BACKGROUND_W, BACKGROUND_H, wfc->hwnd, NULL, wfc->hInstance, wfc->floatbar); + + if (barWnd == NULL) + return; + + ShowWindow(barWnd, SW_SHOWNORMAL); +} diff --git a/client/Windows/wf_floatbar.h b/client/Windows/wf_floatbar.h new file mode 100644 index 0000000..9867e9d --- /dev/null +++ b/client/Windows/wf_floatbar.h @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Float Bar + * + * Copyright 2013 Zhang Zhaolong + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_FLOATBAR_H +#define FREERDP_CLIENT_WIN_FLOATBAR_H + +typedef struct _FloatBar FloatBar; +typedef struct wf_context wfContext; + +void floatbar_window_create(wfContext* wfc); +int floatbar_show(FloatBar* floatbar); +int floatbar_hide(FloatBar* floatbar); + +#endif /* FREERDP_CLIENT_WIN_FLOATBAR_H */ diff --git a/client/Windows/wf_gdi.c b/client/Windows/wf_gdi.c new file mode 100644 index 0000000..2306575 --- /dev/null +++ b/client/Windows/wf_gdi.c @@ -0,0 +1,835 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows GDI + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wf_client.h" +#include "wf_graphics.h" +#include "wf_gdi.h" + +#define TAG CLIENT_TAG("windows.gdi") + +static const BYTE wf_rop2_table[] = +{ + R2_BLACK, /* 0 */ + R2_NOTMERGEPEN, /* DPon */ + R2_MASKNOTPEN, /* DPna */ + R2_NOTCOPYPEN, /* Pn */ + R2_MASKPENNOT, /* PDna */ + R2_NOT, /* Dn */ + R2_XORPEN, /* DPx */ + R2_NOTMASKPEN, /* DPan */ + R2_MASKPEN, /* DPa */ + R2_NOTXORPEN, /* DPxn */ + R2_NOP, /* D */ + R2_MERGENOTPEN, /* DPno */ + R2_COPYPEN, /* P */ + R2_MERGEPENNOT, /* PDno */ + R2_MERGEPEN, /* PDo */ + R2_WHITE, /* 1 */ +}; + +static BOOL wf_decode_color(wfContext* wfc, const UINT32 srcColor, + COLORREF* color, UINT32* format) +{ + rdpGdi* gdi; + rdpSettings* settings; + UINT32 SrcFormat, DstFormat; + + if (!wfc) + return FALSE; + + gdi = wfc->context.gdi; + settings = wfc->context.settings; + + if (!gdi || !settings) + return FALSE; + + SrcFormat = gdi_get_pixel_format(gdi->context->settings->ColorDepth); + + if (format) + *format = SrcFormat; + + switch (GetBitsPerPixel(gdi->dstFormat)) + { + case 32: + DstFormat = PIXEL_FORMAT_ABGR32; + break; + + case 24: + DstFormat = PIXEL_FORMAT_BGR24; + break; + + case 16: + DstFormat = PIXEL_FORMAT_RGB16; + break; + + default: + return FALSE; + } + + *color = FreeRDPConvertColor(srcColor, SrcFormat, + DstFormat, &gdi->palette); + return TRUE; +} + +static BOOL wf_set_rop2(HDC hdc, int rop2) +{ + if ((rop2 < 0x01) || (rop2 > 0x10)) + { + WLog_ERR(TAG, "Unsupported ROP2: %d", rop2); + return FALSE; + } + + SetROP2(hdc, wf_rop2_table[rop2 - 1]); + return TRUE; +} + +static wfBitmap* wf_glyph_new(wfContext* wfc, GLYPH_DATA* glyph) +{ + wfBitmap* glyph_bmp; + glyph_bmp = wf_image_new(wfc, glyph->cx, glyph->cy, PIXEL_FORMAT_MONO, + glyph->aj); + return glyph_bmp; +} + +static void wf_glyph_free(wfBitmap* glyph) +{ + wf_image_free(glyph); +} + +static BYTE* wf_glyph_convert(wfContext* wfc, int width, int height, BYTE* data) +{ + int indexx; + int indexy; + BYTE* src; + BYTE* dst; + BYTE* cdata; + int src_bytes_per_row; + int dst_bytes_per_row; + src_bytes_per_row = (width + 7) / 8; + dst_bytes_per_row = src_bytes_per_row + (src_bytes_per_row % 2); + cdata = (BYTE*) malloc(dst_bytes_per_row * height); + src = data; + + for (indexy = 0; indexy < height; indexy++) + { + dst = cdata + indexy * dst_bytes_per_row; + + for (indexx = 0; indexx < dst_bytes_per_row; indexx++) + { + if (indexx < src_bytes_per_row) + *dst++ = *src++; + else + *dst++ = 0; + } + } + + return cdata; +} + +static HBRUSH wf_create_brush(wfContext* wfc, rdpBrush* brush, UINT32 color, + UINT32 bpp) +{ + UINT32 i; + HBRUSH br; + LOGBRUSH lbr; + BYTE* cdata; + BYTE ipattern[8]; + HBITMAP pattern = NULL; + lbr.lbStyle = brush->style; + + if (lbr.lbStyle == BS_DIBPATTERN || lbr.lbStyle == BS_DIBPATTERN8X8 + || lbr.lbStyle == BS_DIBPATTERNPT) + lbr.lbColor = DIB_RGB_COLORS; + else + lbr.lbColor = color; + + if (lbr.lbStyle == BS_PATTERN || lbr.lbStyle == BS_PATTERN8X8) + { + if (brush->bpp > 1) + { + UINT32 format = gdi_get_pixel_format(bpp); + pattern = wf_create_dib(wfc, 8, 8, format, brush->data, NULL); + lbr.lbHatch = (ULONG_PTR) pattern; + } + else + { + for (i = 0; i != 8; i++) + ipattern[7 - i] = brush->data[i]; + + cdata = wf_glyph_convert(wfc, 8, 8, ipattern); + pattern = CreateBitmap(8, 8, 1, 1, cdata); + lbr.lbHatch = (ULONG_PTR) pattern; + free(cdata); + } + } + else if (lbr.lbStyle == BS_HATCHED) + { + lbr.lbHatch = brush->hatch; + } + else + { + lbr.lbHatch = 0; + } + + br = CreateBrushIndirect(&lbr); + SetBrushOrgEx(wfc->drawing->hdc, brush->x, brush->y, NULL); + + if (pattern != NULL) + DeleteObject(pattern); + + return br; +} + +static BOOL wf_scale_rect(wfContext* wfc, RECT* source) +{ + UINT32 ww, wh, dw, dh; + rdpSettings* settings; + + if (!wfc || !source || !wfc->context.settings) + return FALSE; + + settings = wfc->context.settings; + + if (!settings) + return FALSE; + + dw = settings->DesktopWidth; + dh = settings->DesktopHeight; + + if (!wfc->client_width) + wfc->client_width = dw; + + if (!wfc->client_height) + wfc->client_height = dh; + + ww = wfc->client_width; + wh = wfc->client_height; + + if (!ww) + ww = dw; + + if (!wh) + wh = dh; + + if (wfc->context.settings->SmartSizing && (ww != dw || wh != dh)) + { + source->bottom = source->bottom * wh / dh + 20; + source->top = source->top * wh / dh - 20; + source->left = source->left * ww / dw - 20; + source->right = source->right * ww / dw + 20; + } + + source->bottom -= wfc->yCurrentScroll; + source->top -= wfc->yCurrentScroll; + source->left -= wfc->xCurrentScroll; + source->right -= wfc->xCurrentScroll; + return TRUE; +} + +void wf_invalidate_region(wfContext* wfc, UINT32 x, UINT32 y, UINT32 width, + UINT32 height) +{ + RECT rect; + rdpGdi* gdi = wfc->context.gdi; + wfc->update_rect.left = x + wfc->offset_x; + wfc->update_rect.top = y + wfc->offset_y; + wfc->update_rect.right = wfc->update_rect.left + width; + wfc->update_rect.bottom = wfc->update_rect.top + height; + wf_scale_rect(wfc, &(wfc->update_rect)); + InvalidateRect(wfc->hwnd, &(wfc->update_rect), FALSE); + rect.left = x; + rect.right = width; + rect.top = y; + rect.bottom = height; + wf_scale_rect(wfc, &rect); + gdi_InvalidateRegion(gdi->primary->hdc, rect.left, rect.top, rect.right, + rect.bottom); +} + +void wf_update_offset(wfContext* wfc) +{ + rdpSettings* settings; + settings = wfc->context.settings; + + if (wfc->fullscreen) + { + if (wfc->context.settings->UseMultimon) + { + int x = GetSystemMetrics(SM_XVIRTUALSCREEN); + int y = GetSystemMetrics(SM_YVIRTUALSCREEN); + int w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + wfc->offset_x = (w - settings->DesktopWidth) / 2; + + if (wfc->offset_x < x) + wfc->offset_x = x; + + wfc->offset_y = (h - settings->DesktopHeight) / 2; + + if (wfc->offset_y < y) + wfc->offset_y = y; + } + else + { + wfc->offset_x = (GetSystemMetrics(SM_CXSCREEN) - settings->DesktopWidth) / 2; + + if (wfc->offset_x < 0) + wfc->offset_x = 0; + + wfc->offset_y = (GetSystemMetrics(SM_CYSCREEN) - settings->DesktopHeight) / 2; + + if (wfc->offset_y < 0) + wfc->offset_y = 0; + } + } + else + { + wfc->offset_x = 0; + wfc->offset_y = 0; + } +} + +void wf_resize_window(wfContext* wfc) +{ + rdpSettings* settings; + settings = wfc->context.settings; + + if (wfc->fullscreen) + { + if (wfc->context.settings->UseMultimon) + { + int x = GetSystemMetrics(SM_XVIRTUALSCREEN); + int y = GetSystemMetrics(SM_YVIRTUALSCREEN); + int w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_POPUP); + SetWindowPos(wfc->hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED); + } + else + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_POPUP); + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), SWP_FRAMECHANGED); + } + } + else if (!wfc->context.settings->Decorations) + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_CHILD); + if (settings->EmbeddedWindow) + { + wf_update_canvas_diff(wfc); + } + else + { + /* Now resize to get full canvas size and room for caption and borders */ + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, settings->DesktopWidth, + settings->DesktopHeight, SWP_FRAMECHANGED); + wf_update_canvas_diff(wfc); + SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1, settings->DesktopWidth + wfc->diff.x, + settings->DesktopHeight + wfc->diff.y, SWP_NOMOVE | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, + WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX | + WS_MAXIMIZEBOX); + + if (!wfc->client_height) + wfc->client_height = settings->DesktopHeight; + + if (!wfc->client_width) + wfc->client_width = settings->DesktopWidth; + + if (!wfc->client_x) + wfc->client_x = 10; + + if (!wfc->client_y) + wfc->client_y = 10; + + wf_update_canvas_diff(wfc); + /* Now resize to get full canvas size and room for caption and borders */ + SetWindowPos(wfc->hwnd, HWND_TOP, wfc->client_x, wfc->client_y, + wfc->client_width + wfc->diff.x, wfc->client_height + wfc->diff.y, + 0 /*SWP_FRAMECHANGED*/); + //wf_size_scrollbars(wfc, wfc->client_width, wfc->client_height); + } + + wf_update_offset(wfc); +} + +void wf_toggle_fullscreen(wfContext* wfc) +{ + ShowWindow(wfc->hwnd, SW_HIDE); + wfc->fullscreen = !wfc->fullscreen; + + if (wfc->fullscreen) + { + wfc->disablewindowtracking = TRUE; + } + + if (wfc->fullscreen && wfc->floatbar_active) + floatbar_show(wfc->floatbar); + else + floatbar_hide(wfc->floatbar); + + SetParent(wfc->hwnd, wfc->fullscreen ? NULL : wfc->hWndParent); + wf_resize_window(wfc); + ShowWindow(wfc->hwnd, SW_SHOW); + SetForegroundWindow(wfc->hwnd); + + if (!wfc->fullscreen) + { + // Reenable window tracking AFTER resizing it back, otherwise it can lean to repositioning errors. + wfc->disablewindowtracking = FALSE; + } +} + +static BOOL wf_gdi_palette_update(rdpContext* context, + const PALETTE_UPDATE* palette) +{ + return TRUE; +} + +void wf_set_null_clip_rgn(wfContext* wfc) +{ + SelectClipRgn(wfc->drawing->hdc, NULL); +} + +void wf_set_clip_rgn(wfContext* wfc, int x, int y, int width, int height) +{ + HRGN clip; + clip = CreateRectRgn(x, y, x + width, y + height); + SelectClipRgn(wfc->drawing->hdc, clip); + DeleteObject(clip); +} + +static BOOL wf_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) +{ + HRGN hrgn; + wfContext* wfc = (wfContext*)context; + + if (!context || !bounds) + return FALSE; + + if (bounds != NULL) + { + hrgn = CreateRectRgn(bounds->left, bounds->top, bounds->right + 1, + bounds->bottom + 1); + SelectClipRgn(wfc->drawing->hdc, hrgn); + DeleteObject(hrgn); + } + else + SelectClipRgn(wfc->drawing->hdc, NULL); + + return TRUE; +} + +static BOOL wf_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt) +{ + wfContext* wfc = (wfContext*)context; + + if (!context || !dstblt) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, dstblt->nLeftRect, dstblt->nTopRect, + dstblt->nWidth, dstblt->nHeight, NULL, 0, 0, gdi_rop3_code(dstblt->bRop))) + return FALSE; + + wf_invalidate_region(wfc, dstblt->nLeftRect, dstblt->nTopRect, + dstblt->nWidth, dstblt->nHeight); + return TRUE; +} + +static BOOL wf_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) +{ + HBRUSH brush; + HBRUSH org_brush; + int org_bkmode; + UINT32 fgcolor; + UINT32 bgcolor; + COLORREF org_bkcolor; + COLORREF org_textcolor; + BOOL rc; + wfContext* wfc = (wfContext*)context; + + if (!context || !patblt) + return FALSE; + + if (!wf_decode_color(wfc, patblt->foreColor, &fgcolor, NULL)) + return FALSE; + + if (!wf_decode_color(wfc, patblt->backColor, &bgcolor, NULL)) + return FALSE; + + brush = wf_create_brush(wfc, &patblt->brush, fgcolor, + context->settings->ColorDepth); + org_bkmode = SetBkMode(wfc->drawing->hdc, OPAQUE); + org_bkcolor = SetBkColor(wfc->drawing->hdc, bgcolor); + org_textcolor = SetTextColor(wfc->drawing->hdc, fgcolor); + org_brush = (HBRUSH)SelectObject(wfc->drawing->hdc, brush); + rc = PatBlt(wfc->drawing->hdc, patblt->nLeftRect, patblt->nTopRect, + patblt->nWidth, patblt->nHeight, gdi_rop3_code(patblt->bRop)); + SelectObject(wfc->drawing->hdc, org_brush); + DeleteObject(brush); + SetBkMode(wfc->drawing->hdc, org_bkmode); + SetBkColor(wfc->drawing->hdc, org_bkcolor); + SetTextColor(wfc->drawing->hdc, org_textcolor); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, + patblt->nHeight); + + return rc; +} + +static BOOL wf_gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt) +{ + wfContext* wfc = (wfContext*)context; + + if (!context || !scrblt || !wfc->drawing) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, scrblt->nLeftRect, scrblt->nTopRect, + scrblt->nWidth, scrblt->nHeight, wfc->primary->hdc, + scrblt->nXSrc, scrblt->nYSrc, gdi_rop3_code(scrblt->bRop))) + return FALSE; + + wf_invalidate_region(wfc, scrblt->nLeftRect, scrblt->nTopRect, + scrblt->nWidth, scrblt->nHeight); + return TRUE; +} + +static BOOL wf_gdi_opaque_rect(rdpContext* context, + const OPAQUE_RECT_ORDER* opaque_rect) +{ + RECT rect; + HBRUSH brush; + UINT32 brush_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !opaque_rect) + return FALSE; + + if (!wf_decode_color(wfc, opaque_rect->color, &brush_color, NULL)) + return FALSE; + + rect.left = opaque_rect->nLeftRect; + rect.top = opaque_rect->nTopRect; + rect.right = opaque_rect->nLeftRect + opaque_rect->nWidth; + rect.bottom = opaque_rect->nTopRect + opaque_rect->nHeight; + brush = CreateSolidBrush(brush_color); + FillRect(wfc->drawing->hdc, &rect, brush); + DeleteObject(brush); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, rect.left, rect.top, rect.right - rect.left + 1, + rect.bottom - rect.top + 1); + + return TRUE; +} + +static BOOL wf_gdi_multi_opaque_rect(rdpContext* context, + const MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect) +{ + UINT32 i; + RECT rect; + HBRUSH brush; + UINT32 brush_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !multi_opaque_rect) + return FALSE; + + if (!wf_decode_color(wfc, multi_opaque_rect->color, &brush_color, + NULL)) + return FALSE; + + for (i = 0; i < multi_opaque_rect->numRectangles; i++) + { + const DELTA_RECT* rectangle = &multi_opaque_rect->rectangles[i]; + rect.left = rectangle->left; + rect.top = rectangle->top; + rect.right = rectangle->left + rectangle->width; + rect.bottom = rectangle->top + rectangle->height; + brush = CreateSolidBrush(brush_color); + FillRect(wfc->drawing->hdc, &rect, brush); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, rect.left, rect.top, rect.right - rect.left + 1, + rect.bottom - rect.top + 1); + + DeleteObject(brush); + } + + return TRUE; +} + +static BOOL wf_gdi_line_to(rdpContext* context, const LINE_TO_ORDER* line_to) +{ + HPEN pen; + HPEN org_pen; + int x, y, w, h; + UINT32 pen_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !line_to) + return FALSE; + + if (!wf_decode_color(wfc, line_to->penColor, &pen_color, NULL)) + return FALSE; + + pen = CreatePen(line_to->penStyle, line_to->penWidth, pen_color); + wf_set_rop2(wfc->drawing->hdc, line_to->bRop2); + org_pen = (HPEN) SelectObject(wfc->drawing->hdc, pen); + MoveToEx(wfc->drawing->hdc, line_to->nXStart, line_to->nYStart, NULL); + LineTo(wfc->drawing->hdc, line_to->nXEnd, line_to->nYEnd); + x = (line_to->nXStart < line_to->nXEnd) ? line_to->nXStart : line_to->nXEnd; + y = (line_to->nYStart < line_to->nYEnd) ? line_to->nYStart : line_to->nYEnd; + w = (line_to->nXStart < line_to->nXEnd) ? (line_to->nXEnd - line_to->nXStart) + : (line_to->nXStart - line_to->nXEnd); + h = (line_to->nYStart < line_to->nYEnd) ? (line_to->nYEnd - line_to->nYStart) + : (line_to->nYStart - line_to->nYEnd); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, x, y, w, h); + + SelectObject(wfc->drawing->hdc, org_pen); + DeleteObject(pen); + return TRUE; +} + +static BOOL wf_gdi_polyline(rdpContext* context, const POLYLINE_ORDER* polyline) +{ + int org_rop2; + HPEN hpen; + HPEN org_hpen; + UINT32 pen_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !polyline) + return FALSE; + + if (!wf_decode_color(wfc, polyline->penColor, &pen_color, NULL)) + return FALSE; + + hpen = CreatePen(0, 1, pen_color); + org_rop2 = wf_set_rop2(wfc->drawing->hdc, polyline->bRop2); + org_hpen = (HPEN) SelectObject(wfc->drawing->hdc, hpen); + + if (polyline->numDeltaEntries > 0) + { + POINT* pts; + POINT temp; + int numPoints; + int i; + numPoints = polyline->numDeltaEntries + 1; + pts = (POINT*) malloc(sizeof(POINT) * numPoints); + pts[0].x = temp.x = polyline->xStart; + pts[0].y = temp.y = polyline->yStart; + + for (i = 0; i < (int) polyline->numDeltaEntries; i++) + { + temp.x += polyline->points[i].x; + temp.y += polyline->points[i].y; + pts[i + 1].x = temp.x; + pts[i + 1].y = temp.y; + } + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, wfc->client_x, wfc->client_y, wfc->client_width, + wfc->client_height); + + Polyline(wfc->drawing->hdc, pts, numPoints); + free(pts); + } + + SelectObject(wfc->drawing->hdc, org_hpen); + wf_set_rop2(wfc->drawing->hdc, org_rop2); + DeleteObject(hpen); + return TRUE; +} + +static BOOL wf_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) +{ + wfBitmap* bitmap; + wfContext* wfc = (wfContext*)context; + + if (!context || !memblt) + return FALSE; + + bitmap = (wfBitmap*) memblt->bitmap; + + if (!bitmap || !wfc->drawing || !wfc->drawing->hdc) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, memblt->nLeftRect, memblt->nTopRect, + memblt->nWidth, memblt->nHeight, bitmap->hdc, + memblt->nXSrc, memblt->nYSrc, gdi_rop3_code(memblt->bRop))) + return FALSE; + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, memblt->nLeftRect, memblt->nTopRect, memblt->nWidth, + memblt->nHeight); + + return TRUE; +} + +static BOOL wf_gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt) +{ + BOOL rc = FALSE; + HDC hdc; + wfBitmap* bitmap; + wfContext* wfc = (wfContext*)context; + COLORREF fgcolor, bgcolor, orgColor; + HBRUSH orgBrush = NULL, brush = NULL; + + if (!context || !mem3blt) + return FALSE; + + bitmap = (wfBitmap*) mem3blt->bitmap; + + if (!bitmap || !wfc->drawing || !wfc->drawing->hdc) + return FALSE; + + hdc = wfc->drawing->hdc; + + if (!wf_decode_color(wfc, mem3blt->foreColor, &fgcolor, NULL)) + return FALSE; + + if (!wf_decode_color(wfc, mem3blt->backColor, &bgcolor, NULL)) + return FALSE; + + orgColor = SetTextColor(hdc, fgcolor); + + switch (mem3blt->brush.style) + { + case GDI_BS_SOLID: + brush = CreateSolidBrush(fgcolor); + break; + + case GDI_BS_HATCHED: + case GDI_BS_PATTERN: + { + HBITMAP bmp = CreateBitmap(8, 8, 1, mem3blt->brush.bpp, mem3blt->brush.data); + brush = CreatePatternBrush(bmp); + } + break; + + default: + goto fail; + } + + orgBrush = SelectObject(hdc, brush); + + if (!BitBlt(hdc, mem3blt->nLeftRect, mem3blt->nTopRect, + mem3blt->nWidth, mem3blt->nHeight, bitmap->hdc, + mem3blt->nXSrc, mem3blt->nYSrc, gdi_rop3_code(mem3blt->bRop))) + goto fail; + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, mem3blt->nLeftRect, mem3blt->nTopRect, + mem3blt->nWidth, + mem3blt->nHeight); + + rc = TRUE; +fail: + + if (brush) + SelectObject(hdc, orgBrush); + + SetTextColor(hdc, orgColor); + return rc; +} + +static BOOL wf_gdi_surface_frame_marker(rdpContext* context, + const SURFACE_FRAME_MARKER* surface_frame_marker) +{ + rdpSettings* settings; + + if (!context || !surface_frame_marker || !context->instance) + return FALSE; + + settings = context->instance->settings; + + if (!settings) + return FALSE; + + if (surface_frame_marker->frameAction == SURFACECMD_FRAMEACTION_END + && settings->FrameAcknowledge > 0) + { + IFCALL(context->instance->update->SurfaceFrameAcknowledge, context, + surface_frame_marker->frameId); + } + + return TRUE; +} + +void wf_gdi_register_update_callbacks(rdpUpdate* update) +{ + rdpPrimaryUpdate* primary = update->primary; + update->Palette = wf_gdi_palette_update; + update->SetBounds = wf_gdi_set_bounds; + primary->DstBlt = wf_gdi_dstblt; + primary->PatBlt = wf_gdi_patblt; + primary->ScrBlt = wf_gdi_scrblt; + primary->OpaqueRect = wf_gdi_opaque_rect; + primary->MultiOpaqueRect = wf_gdi_multi_opaque_rect; + primary->LineTo = wf_gdi_line_to; + primary->Polyline = wf_gdi_polyline; + primary->MemBlt = wf_gdi_memblt; + primary->Mem3Blt = wf_gdi_mem3blt; + update->SurfaceFrameMarker = wf_gdi_surface_frame_marker; +} + +void wf_update_canvas_diff(wfContext* wfc) +{ + RECT rc_client, rc_wnd; + int dx, dy; + GetClientRect(wfc->hwnd, &rc_client); + GetWindowRect(wfc->hwnd, &rc_wnd); + dx = (rc_wnd.right - rc_wnd.left) - rc_client.right; + dy = (rc_wnd.bottom - rc_wnd.top) - rc_client.bottom; + + if (!wfc->disablewindowtracking) + { + wfc->diff.x = dx; + wfc->diff.y = dy; + } +} diff --git a/client/Windows/wf_gdi.h b/client/Windows/wf_gdi.h new file mode 100644 index 0000000..3ceec49 --- /dev/null +++ b/client/Windows/wf_gdi.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows GDI + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_GDI_H +#define FREERDP_CLIENT_WIN_GDI_H + +#include "wf_client.h" + +void wf_invalidate_region(wfContext* wfc, UINT32 x, UINT32 y, UINT32 width, + UINT32 height); +wfBitmap* wf_image_new(wfContext* wfc, UINT32 width, UINT32 height, UINT32 bpp, + const BYTE* data); +void wf_image_free(wfBitmap* image); +void wf_update_offset(wfContext* wfc); +void wf_resize_window(wfContext* wfc); +void wf_toggle_fullscreen(wfContext* wfc); + +void wf_gdi_register_update_callbacks(rdpUpdate* update); + +void wf_update_canvas_diff(wfContext* wfc); + +#endif /* FREERDP_CLIENT_WIN_GDI_H */ diff --git a/client/Windows/wf_graphics.c b/client/Windows/wf_graphics.c new file mode 100644 index 0000000..28682cd --- /dev/null +++ b/client/Windows/wf_graphics.c @@ -0,0 +1,377 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Graphical Objects + * + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "wf_gdi.h" +#include "wf_graphics.h" + +#define TAG CLIENT_TAG("windows") + +HBITMAP wf_create_dib(wfContext* wfc, UINT32 width, UINT32 height, + UINT32 srcFormat, const BYTE* data, BYTE** pdata) +{ + HDC hdc; + int negHeight; + HBITMAP bitmap; + BITMAPINFO bmi; + BYTE* cdata = NULL; + UINT32 dstFormat = srcFormat; + /** + * See: http://msdn.microsoft.com/en-us/library/dd183376 + * if biHeight is positive, the bitmap is bottom-up + * if biHeight is negative, the bitmap is top-down + * Since we get top-down bitmaps, let's keep it that way + */ + negHeight = (height < 0) ? height : height * (-1); + hdc = GetDC(NULL); + bmi.bmiHeader.biSize = sizeof(BITMAPINFO); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = negHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = GetBitsPerPixel(dstFormat); + bmi.bmiHeader.biCompression = BI_RGB; + bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**) &cdata, NULL, 0); + + if (data) + freerdp_image_copy(cdata, dstFormat, 0, 0, 0, width, height, data, srcFormat, 0, + 0, 0, &wfc->context.gdi->palette, FREERDP_FLIP_NONE); + + if (pdata) + *pdata = cdata; + + ReleaseDC(NULL, hdc); + GdiFlush(); + return bitmap; +} + +wfBitmap* wf_image_new(wfContext* wfc, UINT32 width, UINT32 height, + UINT32 format, const BYTE* data) +{ + HDC hdc; + wfBitmap* image; + hdc = GetDC(NULL); + image = (wfBitmap*) malloc(sizeof(wfBitmap)); + image->hdc = CreateCompatibleDC(hdc); + image->bitmap = wf_create_dib(wfc, width, height, format, data, + &(image->pdata)); + image->org_bitmap = (HBITMAP) SelectObject(image->hdc, image->bitmap); + ReleaseDC(NULL, hdc); + return image; +} + +void wf_image_free(wfBitmap* image) +{ + if (image != 0) + { + SelectObject(image->hdc, image->org_bitmap); + DeleteObject(image->bitmap); + DeleteDC(image->hdc); + free(image); + } +} + +/* Bitmap Class */ + +static BOOL wf_Bitmap_New(rdpContext* context, rdpBitmap* bitmap) +{ + HDC hdc; + wfContext* wfc = (wfContext*)context; + wfBitmap* wf_bitmap = (wfBitmap*) bitmap; + + if (!context || !bitmap) + return FALSE; + + wf_bitmap = (wfBitmap*) bitmap; + hdc = GetDC(NULL); + wf_bitmap->hdc = CreateCompatibleDC(hdc); + + if (!bitmap->data) + wf_bitmap->bitmap = CreateCompatibleBitmap(hdc, bitmap->width, bitmap->height); + else + wf_bitmap->bitmap = wf_create_dib(wfc, bitmap->width, bitmap->height, + bitmap->format, bitmap->data, NULL); + + wf_bitmap->org_bitmap = (HBITMAP) SelectObject(wf_bitmap->hdc, + wf_bitmap->bitmap); + ReleaseDC(NULL, hdc); + return TRUE; +} + +static void wf_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap) +{ + wfBitmap* wf_bitmap = (wfBitmap*) bitmap; + + if (wf_bitmap != 0) + { + SelectObject(wf_bitmap->hdc, wf_bitmap->org_bitmap); + DeleteObject(wf_bitmap->bitmap); + DeleteDC(wf_bitmap->hdc); + + _aligned_free(wf_bitmap->_bitmap.data); + wf_bitmap->_bitmap.data = NULL; + } +} + +static BOOL wf_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap) +{ + BOOL rc; + UINT32 width, height; + wfContext* wfc = (wfContext*)context; + wfBitmap* wf_bitmap = (wfBitmap*) bitmap; + + if (!context || !bitmap) + return FALSE; + + width = bitmap->right - bitmap->left + 1; + height = bitmap->bottom - bitmap->top + 1; + rc = BitBlt(wfc->primary->hdc, bitmap->left, bitmap->top, + width, height, wf_bitmap->hdc, 0, 0, SRCCOPY); + wf_invalidate_region(wfc, bitmap->left, bitmap->top, width, height); + return rc; +} + +static BOOL wf_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, + BOOL primary) +{ + wfContext* wfc = (wfContext*)context; + wfBitmap* bmp = (wfBitmap*) bitmap; + rdpGdi* gdi = context->gdi; + + if (!gdi || !wfc) + return FALSE; + + if (primary) + wfc->drawing = wfc->primary; + else if (!bmp) + return FALSE; + else + wfc->drawing = bmp; + + return TRUE; +} + +/* Pointer Class */ + +static BOOL flip_bitmap(const BYTE* src, BYTE* dst, UINT32 scanline, + UINT32 nHeight) +{ + UINT32 x; + BYTE* bottomLine = dst + scanline * (nHeight - 1); + + for (x = 0; x < nHeight; x++) + { + memcpy(bottomLine, src, scanline); + src += scanline; + bottomLine -= scanline; + } + + return TRUE; +} + +static BOOL wf_Pointer_New(rdpContext* context, const rdpPointer* pointer) +{ + HCURSOR hCur; + ICONINFO info; + rdpGdi* gdi; + BOOL rc = FALSE; + + if (!context || !pointer) + return FALSE; + + gdi = context->gdi; + + if (!gdi) + return FALSE; + + info.fIcon = FALSE; + info.xHotspot = pointer->xPos; + info.yHotspot = pointer->yPos; + + if (pointer->xorBpp == 1) + { + BYTE* pdata = (BYTE*) _aligned_malloc(pointer->lengthAndMask + + pointer->lengthXorMask, 16); + + if (!pdata) + goto fail; + + CopyMemory(pdata, pointer->andMaskData, pointer->lengthAndMask); + CopyMemory(pdata + pointer->lengthAndMask, pointer->xorMaskData, + pointer->lengthXorMask); + info.hbmMask = CreateBitmap(pointer->width, pointer->height * 2, 1, 1, pdata); + _aligned_free(pdata); + info.hbmColor = NULL; + } + else + { + UINT32 srcFormat; + BYTE* pdata = (BYTE*) _aligned_malloc(pointer->lengthAndMask, 16); + + if (!pdata) + goto fail; + + flip_bitmap(pointer->andMaskData, pdata, (pointer->width + 7) / 8, + pointer->height); + info.hbmMask = CreateBitmap(pointer->width, pointer->height, 1, 1, pdata); + _aligned_free(pdata); + + /* currently color xorBpp is only 24 per [T128] section 8.14.3 */ + srcFormat = gdi_get_pixel_format(pointer->xorBpp); + + if (!srcFormat) + goto fail; + + info.hbmColor = wf_create_dib((wfContext*)context, pointer->width, pointer->height, srcFormat, NULL, &pdata); + + if (!info.hbmColor) + goto fail; + + if (!freerdp_image_copy_from_pointer_data(pdata, gdi->dstFormat, 0, 0, 0, + pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, + pointer->andMaskData, pointer->lengthAndMask, pointer->xorBpp, &gdi->palette)) + { + goto fail; + } + } + + hCur = CreateIconIndirect(&info); + ((wfPointer*) pointer)->cursor = hCur; + rc = TRUE; +fail: + + if (info.hbmMask) + DeleteObject(info.hbmMask); + + if (info.hbmColor) + DeleteObject(info.hbmColor); + + return rc; +} + +static BOOL wf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + HCURSOR hCur; + + if (!context || !pointer) + return FALSE; + + hCur = ((wfPointer*) pointer)->cursor; + + if (hCur != 0) + DestroyIcon(hCur); + + return TRUE; +} + +static BOOL wf_Pointer_Set(rdpContext* context, const rdpPointer* pointer) +{ + HCURSOR hCur; + wfContext* wfc = (wfContext*)context; + + if (!context || !pointer) + return FALSE; + + hCur = ((wfPointer*) pointer)->cursor; + + if (hCur != NULL) + { + SetCursor(hCur); + wfc->cursor = hCur; + } + + return TRUE; +} + +static BOOL wf_Pointer_SetNull(rdpContext* context) +{ + if (!context) + return FALSE; + + return TRUE; +} + +static BOOL wf_Pointer_SetDefault(rdpContext* context) +{ + if (!context) + return FALSE; + + return TRUE; +} + +static BOOL wf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + if (!context) + return FALSE; + + return TRUE; +} + +BOOL wf_register_pointer(rdpGraphics* graphics) +{ + wfContext* wfc; + rdpPointer pointer; + + if (!graphics) + return FALSE; + + wfc = (wfContext*) graphics->context; + ZeroMemory(&pointer, sizeof(rdpPointer)); + pointer.size = sizeof(wfPointer); + pointer.New = wf_Pointer_New; + pointer.Free = wf_Pointer_Free; + pointer.Set = wf_Pointer_Set; + pointer.SetNull = wf_Pointer_SetNull; + pointer.SetDefault = wf_Pointer_SetDefault; + pointer.SetPosition = wf_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} + +/* Graphics Module */ + +BOOL wf_register_graphics(rdpGraphics* graphics) +{ + wfContext* wfc; + rdpGlyph glyph; + rdpBitmap bitmap; + + if (!graphics) + return FALSE; + + wfc = (wfContext*) graphics->context; + bitmap = *graphics->Bitmap_Prototype; + bitmap.size = sizeof(wfBitmap); + bitmap.New = wf_Bitmap_New; + bitmap.Free = wf_Bitmap_Free; + bitmap.Paint = wf_Bitmap_Paint; + bitmap.SetSurface = wf_Bitmap_SetSurface; + graphics_register_bitmap(graphics, &bitmap); + glyph = *graphics->Glyph_Prototype; + graphics_register_glyph(graphics, &glyph); + return TRUE; +} diff --git a/client/Windows/wf_graphics.h b/client/Windows/wf_graphics.h new file mode 100644 index 0000000..da2e36e --- /dev/null +++ b/client/Windows/wf_graphics.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Graphical Objects + * + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_GRAPHICS_H +#define FREERDP_CLIENT_WIN_GRAPHICS_H + +#include "wf_client.h" + +HBITMAP wf_create_dib(wfContext* wfc, UINT32 width, UINT32 height, + UINT32 format, const BYTE* data, BYTE** pdata); +wfBitmap* wf_image_new(wfContext* wfc, UINT32 width, UINT32 height, + UINT32 format, const BYTE* data); +void wf_image_free(wfBitmap* image); + +BOOL wf_register_pointer(rdpGraphics* graphics); +BOOL wf_register_graphics(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_WIN_GRAPHICS_H */ diff --git a/client/Windows/wf_rail.c b/client/Windows/wf_rail.c new file mode 100644 index 0000000..87f3be1 --- /dev/null +++ b/client/Windows/wf_rail.c @@ -0,0 +1,1040 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "wf_rail.h" + +#define TAG CLIENT_TAG("windows") + +#define GET_X_LPARAM(lParam) ((UINT16) (lParam & 0xFFFF)) +#define GET_Y_LPARAM(lParam) ((UINT16) ((lParam >> 16) & 0xFFFF)) + +/* RemoteApp Core Protocol Extension */ + +struct _WINDOW_STYLE +{ + UINT32 style; + const char* name; + BOOL multi; +}; +typedef struct _WINDOW_STYLE WINDOW_STYLE; + +static const WINDOW_STYLE WINDOW_STYLES[] = +{ + { WS_BORDER, "WS_BORDER", FALSE }, + { WS_CAPTION, "WS_CAPTION", FALSE }, + { WS_CHILD, "WS_CHILD", FALSE }, + { WS_CLIPCHILDREN, "WS_CLIPCHILDREN", FALSE }, + { WS_CLIPSIBLINGS, "WS_CLIPSIBLINGS", FALSE }, + { WS_DISABLED, "WS_DISABLED", FALSE }, + { WS_DLGFRAME, "WS_DLGFRAME", FALSE }, + { WS_GROUP, "WS_GROUP", FALSE }, + { WS_HSCROLL, "WS_HSCROLL", FALSE }, + { WS_ICONIC, "WS_ICONIC", FALSE }, + { WS_MAXIMIZE, "WS_MAXIMIZE", FALSE }, + { WS_MAXIMIZEBOX, "WS_MAXIMIZEBOX", FALSE }, + { WS_MINIMIZE, "WS_MINIMIZE", FALSE }, + { WS_MINIMIZEBOX, "WS_MINIMIZEBOX", FALSE }, + { WS_OVERLAPPED, "WS_OVERLAPPED", FALSE }, + { WS_OVERLAPPEDWINDOW, "WS_OVERLAPPEDWINDOW", TRUE }, + { WS_POPUP, "WS_POPUP", FALSE }, + { WS_POPUPWINDOW, "WS_POPUPWINDOW", TRUE }, + { WS_SIZEBOX, "WS_SIZEBOX", FALSE }, + { WS_SYSMENU, "WS_SYSMENU", FALSE }, + { WS_TABSTOP, "WS_TABSTOP", FALSE }, + { WS_THICKFRAME, "WS_THICKFRAME", FALSE }, + { WS_VISIBLE, "WS_VISIBLE", FALSE } +}; + +static const WINDOW_STYLE EXTENDED_WINDOW_STYLES[] = +{ + { WS_EX_ACCEPTFILES, "WS_EX_ACCEPTFILES", FALSE }, + { WS_EX_APPWINDOW, "WS_EX_APPWINDOW", FALSE }, + { WS_EX_CLIENTEDGE, "WS_EX_CLIENTEDGE", FALSE }, + { WS_EX_COMPOSITED, "WS_EX_COMPOSITED", FALSE }, + { WS_EX_CONTEXTHELP, "WS_EX_CONTEXTHELP", FALSE }, + { WS_EX_CONTROLPARENT, "WS_EX_CONTROLPARENT", FALSE }, + { WS_EX_DLGMODALFRAME, "WS_EX_DLGMODALFRAME", FALSE }, + { WS_EX_LAYERED, "WS_EX_LAYERED", FALSE }, + { WS_EX_LAYOUTRTL, "WS_EX_LAYOUTRTL", FALSE }, + { WS_EX_LEFT, "WS_EX_LEFT", FALSE }, + { WS_EX_LEFTSCROLLBAR, "WS_EX_LEFTSCROLLBAR", FALSE }, + { WS_EX_LTRREADING, "WS_EX_LTRREADING", FALSE }, + { WS_EX_MDICHILD, "WS_EX_MDICHILD", FALSE }, + { WS_EX_NOACTIVATE, "WS_EX_NOACTIVATE", FALSE }, + { WS_EX_NOINHERITLAYOUT, "WS_EX_NOINHERITLAYOUT", FALSE }, + { WS_EX_NOPARENTNOTIFY, "WS_EX_NOPARENTNOTIFY", FALSE }, + { WS_EX_OVERLAPPEDWINDOW, "WS_EX_OVERLAPPEDWINDOW", TRUE }, + { WS_EX_PALETTEWINDOW, "WS_EX_PALETTEWINDOW", TRUE }, + { WS_EX_RIGHT, "WS_EX_RIGHT", FALSE }, + { WS_EX_RIGHTSCROLLBAR, "WS_EX_RIGHTSCROLLBAR", FALSE }, + { WS_EX_RTLREADING, "WS_EX_RTLREADING", FALSE }, + { WS_EX_STATICEDGE, "WS_EX_STATICEDGE", FALSE }, + { WS_EX_TOOLWINDOW, "WS_EX_TOOLWINDOW", FALSE }, + { WS_EX_TOPMOST, "WS_EX_TOPMOST", FALSE }, + { WS_EX_TRANSPARENT, "WS_EX_TRANSPARENT", FALSE }, + { WS_EX_WINDOWEDGE, "WS_EX_WINDOWEDGE", FALSE } +}; + +void PrintWindowStyles(UINT32 style) +{ + int i; + WLog_INFO(TAG, "\tWindow Styles:\t{"); + + for (i = 0; i < ARRAYSIZE(WINDOW_STYLES); i++) + { + if (style & WINDOW_STYLES[i].style) + { + if (WINDOW_STYLES[i].multi) + { + if ((style & WINDOW_STYLES[i].style) != WINDOW_STYLES[i].style) + continue; + } + + WLog_INFO(TAG, "\t\t%s", WINDOW_STYLES[i].name); + } + } +} + +void PrintExtendedWindowStyles(UINT32 style) +{ + int i; + WLog_INFO(TAG, "\tExtended Window Styles:\t{"); + + for (i = 0; i < ARRAYSIZE(EXTENDED_WINDOW_STYLES); i++) + { + if (style & EXTENDED_WINDOW_STYLES[i].style) + { + if (EXTENDED_WINDOW_STYLES[i].multi) + { + if ((style & EXTENDED_WINDOW_STYLES[i].style) != + EXTENDED_WINDOW_STYLES[i].style) + continue; + } + + WLog_INFO(TAG, "\t\t%s", EXTENDED_WINDOW_STYLES[i].name); + } + } +} + +void PrintRailWindowState(WINDOW_ORDER_INFO* orderInfo, + WINDOW_STATE_ORDER* windowState) +{ + if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW) + WLog_INFO(TAG, "WindowCreate: WindowId: 0x%08X", orderInfo->windowId); + else + WLog_INFO(TAG, "WindowUpdate: WindowId: 0x%08X", orderInfo->windowId); + + WLog_INFO(TAG, "{"); + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + WLog_INFO(TAG, "\tOwnerWindowId: 0x%08X", windowState->ownerWindowId); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + WLog_INFO(TAG, "\tStyle: 0x%08X ExtendedStyle: 0x%08X", + windowState->style, windowState->extendedStyle); + PrintWindowStyles(windowState->style); + PrintExtendedWindowStyles(windowState->extendedStyle); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + WLog_INFO(TAG, "\tShowState: %u", windowState->showState); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = NULL; + ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) windowState->titleInfo.string, + windowState->titleInfo.length / 2, &title, 0, NULL, NULL); + WLog_INFO(TAG, "\tTitleInfo: %s (length = %hu)", title, + windowState->titleInfo.length); + free(title); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) + { + WLog_INFO(TAG, "\tClientOffsetX: %d ClientOffsetY: %d", + windowState->clientOffsetX, windowState->clientOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) + { + WLog_INFO(TAG, "\tClientAreaWidth: %u ClientAreaHeight: %u", + windowState->clientAreaWidth, windowState->clientAreaHeight); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) + { + WLog_INFO(TAG, "\tRPContent: %u", windowState->RPContent); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) + { + WLog_INFO(TAG, "\tRootParentHandle: 0x%08X", windowState->rootParentHandle); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + WLog_INFO(TAG, "\tWindowOffsetX: %d WindowOffsetY: %d", + windowState->windowOffsetX, windowState->windowOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) + { + WLog_INFO(TAG, "\tWindowClientDeltaX: %d WindowClientDeltaY: %d", + windowState->windowClientDeltaX, windowState->windowClientDeltaY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + WLog_INFO(TAG, "\tWindowWidth: %u WindowHeight: %u", + windowState->windowWidth, windowState->windowHeight); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + UINT32 index; + RECTANGLE_16* rect; + WLog_INFO(TAG, "\tnumWindowRects: %u", windowState->numWindowRects); + + for (index = 0; index < windowState->numWindowRects; index++) + { + rect = &windowState->windowRects[index]; + WLog_INFO(TAG, "\twindowRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", + index, rect->left, rect->top, rect->right, rect->bottom); + } + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) + { + WLog_INFO(TAG, "\tvisibileOffsetX: %d visibleOffsetY: %d", + windowState->visibleOffsetX, windowState->visibleOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) + { + UINT32 index; + RECTANGLE_16* rect; + WLog_INFO(TAG, "\tnumVisibilityRects: %u", windowState->numVisibilityRects); + + for (index = 0; index < windowState->numVisibilityRects; index++) + { + rect = &windowState->visibilityRects[index]; + WLog_INFO(TAG, "\tvisibilityRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", + index, rect->left, rect->top, rect->right, rect->bottom); + } + } + + WLog_INFO(TAG, "}"); +} + +static void PrintRailIconInfo(WINDOW_ORDER_INFO* orderInfo, ICON_INFO* iconInfo) +{ + WLog_INFO(TAG, "ICON_INFO"); + WLog_INFO(TAG, "{"); + WLog_INFO(TAG, "\tbigIcon: %s", + (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ICON_BIG) ? "true" : "false"); + WLog_INFO(TAG, "\tcacheEntry; 0x%08X", iconInfo->cacheEntry); + WLog_INFO(TAG, "\tcacheId: 0x%08X", iconInfo->cacheId); + WLog_INFO(TAG, "\tbpp: %u", iconInfo->bpp); + WLog_INFO(TAG, "\twidth: %u", iconInfo->width); + WLog_INFO(TAG, "\theight: %u", iconInfo->height); + WLog_INFO(TAG, "\tcbColorTable: %u", iconInfo->cbColorTable); + WLog_INFO(TAG, "\tcbBitsMask: %u", iconInfo->cbBitsMask); + WLog_INFO(TAG, "\tcbBitsColor: %u", iconInfo->cbBitsColor); + WLog_INFO(TAG, "\tcolorTable: %p", (void*) iconInfo->colorTable); + WLog_INFO(TAG, "\tbitsMask: %p", (void*) iconInfo->bitsMask); + WLog_INFO(TAG, "\tbitsColor: %p", (void*) iconInfo->bitsColor); + WLog_INFO(TAG, "}"); +} + +LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam) +{ + HDC hDC; + int x, y; + int width; + int height; + UINT32 xPos; + UINT32 yPos; + PAINTSTRUCT ps; + UINT32 inputFlags; + wfContext* wfc = NULL; + rdpInput* input = NULL; + rdpContext* context = NULL; + wfRailWindow* railWindow; + railWindow = (wfRailWindow*) GetWindowLongPtr(hWnd, GWLP_USERDATA); + + if (railWindow) + wfc = railWindow->wfc; + + if (wfc) + context = (rdpContext*) wfc; + + if (context) + input = context->input; + + switch (msg) + { + case WM_PAINT: + { + if (!wfc) + return 0; + + hDC = BeginPaint(hWnd, &ps); + x = ps.rcPaint.left; + y = ps.rcPaint.top; + width = ps.rcPaint.right - ps.rcPaint.left + 1; + height = ps.rcPaint.bottom - ps.rcPaint.top + 1; + BitBlt(hDC, x, y, width, height, wfc->primary->hdc, + railWindow->x + x, railWindow->y + y, SRCCOPY); + EndPaint(hWnd, &ps); + } + break; + + case WM_LBUTTONDOWN: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON1; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_LBUTTONUP: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_BUTTON1; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_RBUTTONDOWN: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON2; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_RBUTTONUP: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_BUTTON2; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_MOUSEMOVE: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_MOVE; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_MOUSEWHEEL: + break; + + case WM_CLOSE: + DestroyWindow(hWnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, msg, wParam, lParam); + } + + return 0; +} + +#define RAIL_DISABLED_WINDOW_STYLES (WS_BORDER | WS_THICKFRAME | WS_DLGFRAME | WS_CAPTION | \ + WS_OVERLAPPED | WS_VSCROLL | WS_HSCROLL | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) +#define RAIL_DISABLED_EXTENDED_WINDOW_STYLES (WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE) + +static BOOL wf_rail_window_common(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, WINDOW_STATE_ORDER* windowState) +{ + wfRailWindow* railWindow = NULL; + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + UINT32 fieldFlags = orderInfo->fieldFlags; + PrintRailWindowState(orderInfo, windowState); + + if (fieldFlags & WINDOW_ORDER_STATE_NEW) + { + HANDLE hInstance; + WCHAR* titleW = NULL; + WNDCLASSEX wndClassEx; + railWindow = (wfRailWindow*) calloc(1, sizeof(wfRailWindow)); + + if (!railWindow) + return FALSE; + + railWindow->wfc = wfc; + railWindow->dwStyle = windowState->style; + railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; + railWindow->dwExStyle = windowState->extendedStyle; + railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; + railWindow->x = windowState->windowOffsetX; + railWindow->y = windowState->windowOffsetY; + railWindow->width = windowState->windowWidth; + railWindow->height = windowState->windowHeight; + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = NULL; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + /* error handled below */ + } + } + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) windowState->titleInfo.string, + windowState->titleInfo.length / 2, &title, 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert window title"); + /* error handled below */ + } + + railWindow->title = title; + } + else + { + if (!(railWindow->title = _strdup("RdpRailWindow"))) + WLog_ERR(TAG, "failed to duplicate default window title string"); + } + + if (!railWindow->title) + { + free(railWindow); + return FALSE; + } + + ConvertToUnicode(CP_UTF8, 0, railWindow->title, -1, &titleW, 0); + hInstance = GetModuleHandle(NULL); + ZeroMemory(&wndClassEx, sizeof(WNDCLASSEX)); + wndClassEx.cbSize = sizeof(WNDCLASSEX); + wndClassEx.style = 0; + wndClassEx.lpfnWndProc = wf_RailWndProc; + wndClassEx.cbClsExtra = 0; + wndClassEx.cbWndExtra = 0; + wndClassEx.hIcon = NULL; + wndClassEx.hCursor = NULL; + wndClassEx.hbrBackground = NULL; + wndClassEx.lpszMenuName = NULL; + wndClassEx.lpszClassName = _T("RdpRailWindow"); + wndClassEx.hInstance = hInstance; + wndClassEx.hIconSm = NULL; + RegisterClassEx(&wndClassEx); + railWindow->hWnd = CreateWindowExW( + railWindow->dwExStyle, /* dwExStyle */ + _T("RdpRailWindow"), /* lpClassName */ + titleW, /* lpWindowName */ + railWindow->dwStyle, /* dwStyle */ + railWindow->x, /* x */ + railWindow->y, /* y */ + railWindow->width, /* nWidth */ + railWindow->height, /* nHeight */ + NULL, /* hWndParent */ + NULL, /* hMenu */ + hInstance, /* hInstance */ + NULL /* lpParam */ + ); + SetWindowLongPtr(railWindow->hWnd, GWLP_USERDATA, (LONG_PTR) railWindow); + HashTable_Add(wfc->railWindows, (void*)(UINT_PTR) orderInfo->windowId, + (void*) railWindow); + free(titleW); + UpdateWindow(railWindow->hWnd); + return TRUE; + } + else + { + railWindow = (wfRailWindow*) HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR) orderInfo->windowId); + } + + if (!railWindow) + return TRUE; + + if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)) + { + if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + railWindow->x = windowState->windowOffsetX; + railWindow->y = windowState->windowOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + railWindow->width = windowState->windowWidth; + railWindow->height = windowState->windowHeight; + } + + SetWindowPos(railWindow->hWnd, NULL, + railWindow->x, + railWindow->y, + railWindow->width, + railWindow->height, + 0); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + railWindow->dwStyle = windowState->style; + railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; + railWindow->dwExStyle = windowState->extendedStyle; + railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; + SetWindowLongPtr(railWindow->hWnd, GWL_STYLE, (LONG) railWindow->dwStyle); + SetWindowLongPtr(railWindow->hWnd, GWL_EXSTYLE, (LONG) railWindow->dwExStyle); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + ShowWindow(railWindow->hWnd, windowState->showState); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = NULL; + WCHAR* titleW = NULL; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + return FALSE; + } + } + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) windowState->titleInfo.string, + windowState->titleInfo.length / 2, &title, 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert window title"); + return FALSE; + } + + free(railWindow->title); + railWindow->title = title; + ConvertToUnicode(CP_UTF8, 0, railWindow->title, -1, &titleW, 0); + SetWindowTextW(railWindow->hWnd, titleW); + free(titleW); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + UINT32 index; + HRGN hWndRect; + HRGN hWndRects; + RECTANGLE_16* rect; + + if (windowState->numWindowRects > 0) + { + rect = &(windowState->windowRects[0]); + hWndRects = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); + + for (index = 1; index < windowState->numWindowRects; index++) + { + rect = &(windowState->windowRects[index]); + hWndRect = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); + CombineRgn(hWndRects, hWndRects, hWndRect, RGN_OR); + DeleteObject(hWndRect); + } + + SetWindowRgn(railWindow->hWnd, hWndRects, TRUE); + DeleteObject(hWndRects); + } + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) + { + } + + UpdateWindow(railWindow->hWnd); + return TRUE; +} + +static BOOL wf_rail_window_delete(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo) +{ + wfRailWindow* railWindow = NULL; + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailWindowDelete"); + railWindow = (wfRailWindow*) HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR) orderInfo->windowId); + + if (!railWindow) + return TRUE; + + HashTable_Remove(wfc->railWindows, (void*)(UINT_PTR) orderInfo->windowId); + DestroyWindow(railWindow->hWnd); + free(railWindow); + return TRUE; +} + +static BOOL wf_rail_window_icon(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, WINDOW_ICON_ORDER* windowIcon) +{ + HDC hDC; + int bpp; + int width; + int height; + HICON hIcon; + BOOL bigIcon; + ICONINFO iconInfo; + BITMAPINFO bitmapInfo; + wfRailWindow* railWindow; + BITMAPINFOHEADER* bitmapInfoHeader; + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailWindowIcon"); + PrintRailIconInfo(orderInfo, windowIcon->iconInfo); + railWindow = (wfRailWindow*) HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR) orderInfo->windowId); + + if (!railWindow) + return TRUE; + + bigIcon = (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ICON_BIG) ? TRUE : FALSE; + hDC = GetDC(railWindow->hWnd); + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO)); + bitmapInfoHeader = &(bitmapInfo.bmiHeader); + bpp = windowIcon->iconInfo->bpp; + width = windowIcon->iconInfo->width; + height = windowIcon->iconInfo->height; + bitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER); + bitmapInfoHeader->biWidth = width; + bitmapInfoHeader->biHeight = height; + bitmapInfoHeader->biPlanes = 1; + bitmapInfoHeader->biBitCount = bpp; + bitmapInfoHeader->biCompression = 0; + bitmapInfoHeader->biSizeImage = height * width * ((bpp + 7) / 8); + bitmapInfoHeader->biXPelsPerMeter = width; + bitmapInfoHeader->biYPelsPerMeter = height; + bitmapInfoHeader->biClrUsed = 0; + bitmapInfoHeader->biClrImportant = 0; + iconInfo.hbmMask = CreateDIBitmap(hDC, + bitmapInfoHeader, CBM_INIT, + windowIcon->iconInfo->bitsMask, + &bitmapInfo, DIB_RGB_COLORS); + iconInfo.hbmColor = CreateDIBitmap(hDC, + bitmapInfoHeader, CBM_INIT, + windowIcon->iconInfo->bitsColor, + &bitmapInfo, DIB_RGB_COLORS); + hIcon = CreateIconIndirect(&iconInfo); + + if (hIcon) + { + WPARAM wParam; + LPARAM lParam; + wParam = (WPARAM) bigIcon ? ICON_BIG : ICON_SMALL; + lParam = (LPARAM) hIcon; + SendMessage(railWindow->hWnd, WM_SETICON, wParam, lParam); + } + + ReleaseDC(NULL, hDC); + + if (windowIcon->iconInfo->cacheEntry != 0xFFFF) + { + /* icon should be cached */ + } + + return TRUE; +} + +static BOOL wf_rail_window_cached_icon(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + WLog_DBG(TAG, "RailWindowCachedIcon"); + return TRUE; +} + +static void wf_rail_notify_icon_common(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, 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) + { + ICON_INFO* iconInfo = &(notifyIconState->icon); + PrintRailIconInfo(orderInfo, iconInfo); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) + { + } +} + +static BOOL wf_rail_notify_icon_create(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconCreate"); + wf_rail_notify_icon_common(context, orderInfo, notifyIconState); + return TRUE; +} + +static BOOL wf_rail_notify_icon_update(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconUpdate"); + wf_rail_notify_icon_common(context, orderInfo, notifyIconState); + return TRUE; +} + +static BOOL wf_rail_notify_icon_delete(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo) +{ + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconDelete"); + return TRUE; +} + +static BOOL wf_rail_monitored_desktop(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailMonitorDesktop"); + return TRUE; +} + +static BOOL wf_rail_non_monitored_desktop(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo) +{ + wfContext* wfc = (wfContext*) context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNonMonitorDesktop"); + return TRUE; +} + +void wf_rail_register_update_callbacks(rdpUpdate* update) +{ + rdpWindowUpdate* window = update->window; + window->WindowCreate = wf_rail_window_common; + window->WindowUpdate = wf_rail_window_common; + window->WindowDelete = wf_rail_window_delete; + window->WindowIcon = wf_rail_window_icon; + window->WindowCachedIcon = wf_rail_window_cached_icon; + window->NotifyIconCreate = wf_rail_notify_icon_create; + window->NotifyIconUpdate = wf_rail_notify_icon_update; + window->NotifyIconDelete = wf_rail_notify_icon_delete; + window->MonitoredDesktop = wf_rail_monitored_desktop; + window->NonMonitoredDesktop = wf_rail_non_monitored_desktop; +} + +/* RemoteApp Virtual Channel Extension */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_execute_result(RailClientContext* context, + RAIL_EXEC_RESULT_ORDER* execResult) +{ + WLog_DBG(TAG, "RailServerExecuteResult: 0x%08X", execResult->rawResult); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_system_param(RailClientContext* context, + RAIL_SYSPARAM_ORDER* sysparam) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake(RailClientContext* context, + RAIL_HANDSHAKE_ORDER* handshake) +{ + RAIL_EXEC_ORDER exec; + RAIL_SYSPARAM_ORDER sysparam; + RAIL_HANDSHAKE_ORDER clientHandshake; + RAIL_CLIENT_STATUS_ORDER clientStatus; + wfContext* wfc = (wfContext*) context->custom; + rdpSettings* settings = wfc->context.settings; + clientHandshake.buildNumber = 0x00001DB0; + context->ClientHandshake(context, &clientHandshake); + ZeroMemory(&clientStatus, sizeof(RAIL_CLIENT_STATUS_ORDER)); + clientStatus.flags = RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; + context->ClientInformation(context, &clientStatus); + + if (settings->RemoteAppLanguageBarSupported) + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ + context->ClientLanguageBarInfo(context, &langBarInfo); + } + + ZeroMemory(&sysparam, sizeof(RAIL_SYSPARAM_ORDER)); + sysparam.params = 0; + sysparam.params |= SPI_MASK_SET_HIGH_CONTRAST; + sysparam.highContrast.colorScheme.string = NULL; + sysparam.highContrast.colorScheme.length = 0; + sysparam.highContrast.flags = 0x7E; + sysparam.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; + sysparam.mouseButtonSwap = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_PREF; + sysparam.keyboardPref = FALSE; + sysparam.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; + sysparam.dragFullWindows = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_CUES; + sysparam.keyboardCues = FALSE; + sysparam.params |= SPI_MASK_SET_WORK_AREA; + sysparam.workArea.left = 0; + sysparam.workArea.top = 0; + sysparam.workArea.right = settings->DesktopWidth; + sysparam.workArea.bottom = settings->DesktopHeight; + sysparam.dragFullWindows = FALSE; + context->ClientSystemParam(context, &sysparam); + ZeroMemory(&exec, sizeof(RAIL_EXEC_ORDER)); + exec.RemoteApplicationProgram = settings->RemoteApplicationProgram; + exec.RemoteApplicationWorkingDir = settings->ShellWorkingDirectory; + exec.RemoteApplicationArguments = settings->RemoteApplicationCmdLine; + context->ClientExecute(context, &exec); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake_ex(RailClientContext* context, + RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_local_move_size(RailClientContext* context, + RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_min_max_info(RailClientContext* context, + RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_language_bar_info(RailClientContext* context, + RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_get_appid_response(RailClientContext* context, + RAIL_GET_APPID_RESP_ORDER* getAppIdResp) +{ + return CHANNEL_RC_OK; +} + +void wf_rail_invalidate_region(wfContext* wfc, REGION16* invalidRegion) +{ + int index; + int count; + RECT updateRect; + RECTANGLE_16 windowRect; + ULONG_PTR* pKeys = NULL; + wfRailWindow* railWindow; + const RECTANGLE_16* extents; + REGION16 windowInvalidRegion; + region16_init(&windowInvalidRegion); + count = HashTable_GetKeys(wfc->railWindows, &pKeys); + + for (index = 0; index < count; index++) + { + railWindow = (wfRailWindow*) HashTable_GetItemValue(wfc->railWindows, + (void*) pKeys[index]); + + if (railWindow) + { + windowRect.left = railWindow->x; + windowRect.top = railWindow->y; + windowRect.right = railWindow->x + railWindow->width; + windowRect.bottom = railWindow->y + railWindow->height; + region16_clear(&windowInvalidRegion); + region16_intersect_rect(&windowInvalidRegion, invalidRegion, &windowRect); + + if (!region16_is_empty(&windowInvalidRegion)) + { + extents = region16_extents(&windowInvalidRegion); + updateRect.left = extents->left - railWindow->x; + updateRect.top = extents->top - railWindow->y; + updateRect.right = extents->right - railWindow->x; + updateRect.bottom = extents->bottom - railWindow->y; + InvalidateRect(railWindow->hWnd, &updateRect, FALSE); + } + } + } + + region16_uninit(&windowInvalidRegion); +} + +BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail) +{ + rdpContext* context = (rdpContext*) wfc; + wfc->rail = rail; + rail->custom = (void*) wfc; + rail->ServerExecuteResult = wf_rail_server_execute_result; + rail->ServerSystemParam = wf_rail_server_system_param; + rail->ServerHandshake = wf_rail_server_handshake; + rail->ServerHandshakeEx = wf_rail_server_handshake_ex; + rail->ServerLocalMoveSize = wf_rail_server_local_move_size; + rail->ServerMinMaxInfo = wf_rail_server_min_max_info; + rail->ServerLanguageBarInfo = wf_rail_server_language_bar_info; + rail->ServerGetAppIdResponse = wf_rail_server_get_appid_response; + wf_rail_register_update_callbacks(context->update); + wfc->railWindows = HashTable_New(TRUE); + return (wfc->railWindows != NULL); +} + +void wf_rail_uninit(wfContext* wfc, RailClientContext* rail) +{ + wfc->rail = NULL; + rail->custom = NULL; + HashTable_Free(wfc->railWindows); +} diff --git a/client/Windows/wf_rail.h b/client/Windows/wf_rail.h new file mode 100644 index 0000000..1bf8c27 --- /dev/null +++ b/client/Windows/wf_rail.h @@ -0,0 +1,49 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_WIN_RAIL_H +#define FREERDP_CLIENT_WIN_RAIL_H + +typedef struct wf_rail_window wfRailWindow; + +#include "wf_client.h" + +#include + +struct wf_rail_window +{ + wfContext* wfc; + + HWND hWnd; + + DWORD dwStyle; + DWORD dwExStyle; + + int x; + int y; + int width; + int height; + char* title; +}; + +BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail); +void wf_rail_uninit(wfContext* wfc, RailClientContext* rail); + +void wf_rail_invalidate_region(wfContext* wfc, REGION16* invalidRegion); + +#endif /* FREERDP_CLIENT_WIN_RAIL_H */ diff --git a/client/Windows/wfreerdp.rc b/client/Windows/wfreerdp.rc new file mode 100644 index 0000000..135f641 Binary files /dev/null and b/client/Windows/wfreerdp.rc differ diff --git a/client/X11/.gitignore b/client/X11/.gitignore new file mode 100644 index 0000000..2f903d6 --- /dev/null +++ b/client/X11/.gitignore @@ -0,0 +1,2 @@ +xfreerdp-argument.1.xml +generate_argument_docbook diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt new file mode 100644 index 0000000..48e5af9 --- /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 +# Copyright 2013 Corey Clayton +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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-client") +set(MODULE_PREFIX "FREERDP_CLIENT_X11_CONTROL") + +include(FindDocBookXSL) +include_directories(${X11_INCLUDE_DIRS}) +include_directories(${OPENSSL_INCLUDE_DIR}) + +set(${MODULE_PREFIX}_SRCS + xf_gdi.c + xf_gdi.h + xf_gfx.c + xf_gfx.h + xf_rail.c + xf_rail.h + xf_tsmf.c + xf_tsmf.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(WITH_CLIENT_INTERFACE) + if(CLIENT_INTERFACE_SHARED) + add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS}) + if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) + endif() + else() + add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + endif() + +else() + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/xfreerdp.c xfreerdp.h) + add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp") + include_directories(..) +endif() + +set(${MODULE_PREFIX}_LIBS + ${X11_LIBRARIES}) + +if(WITH_MANPAGES) + find_program(XSLTPROC_EXECUTABLE NAMES xsltproc) + + if(DOCBOOKXSL_FOUND AND XSLTPROC_EXECUTABLE) + + # We need the variable ${MAN_TODAY} to contain the current date in ISO + # format to replace it in the configure_file step. + include(today) + + TODAY(MAN_TODAY) + + configure_file(xfreerdp.1.xml.in xfreerdp.1.xml @ONLY IMMEDIATE) + + # Compile the helper tool with default compiler settings. + # We need the include paths though. + get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) + set(GENERATE_INCLUDES "") + foreach(dir ${dirs}) + set(GENERATE_INCLUDES ${GENERATE_INCLUDES} -I${dir}) + endforeach(dir) + + add_custom_command(OUTPUT xfreerdp.1 + COMMAND ${CMAKE_C_COMPILER} ${GENERATE_INCLUDES} + ${CMAKE_CURRENT_SOURCE_DIR}/generate_argument_docbook.c + -o ${CMAKE_CURRENT_BINARY_DIR}/generate_argument_docbook + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/generate_argument_docbook + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-channels.1.xml ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-examples.1.xml ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-envvar.1.xml ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${XSLTPROC_EXECUTABLE} ${DOCBOOKXSL_DIR}/manpages/docbook.xsl xfreerdp.1.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/xfreerdp.1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-examples.1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-channels.1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/xfreerdp-envvar.1.xml) + + add_custom_target(xfreerdp.manpage ALL + DEPENDS xfreerdp.1) + + install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/xfreerdp.1 1) + else() + message(WARNING "WITH_MANPAGES was set, but xsltproc was not found. man-pages will not be installed") + endif() +endif(WITH_MANPAGES) + +set(XSHM_FEATURE_TYPE "REQUIRED") +set(XSHM_FEATURE_PURPOSE "X11 shared memory") +set(XSHM_FEATURE_DESCRIPTION "X11 shared memory extension") + +set(XINERAMA_FEATURE_TYPE "RECOMMENDED") +set(XINERAMA_FEATURE_PURPOSE "multi-monitor") +set(XINERAMA_FEATURE_DESCRIPTION "X11 multi-monitor extension") + +set(XEXT_FEATURE_TYPE "RECOMMENDED") +set(XEXT_FEATURE_PURPOSE "X11 extension") +set(XEXT_FEATURE_DESCRIPTION "X11 core extensions") + +set(XCURSOR_FEATURE_TYPE "RECOMMENDED") +set(XCURSOR_FEATURE_PURPOSE "cursor") +set(XCURSOR_FEATURE_DESCRIPTION "X11 cursor extension") + +set(XV_FEATURE_TYPE "RECOMMENDED") +set(XV_FEATURE_PURPOSE "video") +set(XV_FEATURE_DESCRIPTION "X11 video extension") + +set(XI_FEATURE_TYPE "RECOMMENDED") +set(XI_FEATURE_PURPOSE "input") +set(XI_FEATURE_DESCRIPTION "X11 input extension") + +set(XRENDER_FEATURE_TYPE "RECOMMENDED") +set(XRENDER_FEATURE_PURPOSE "rendering") +set(XRENDER_FEATURE_DESCRIPTION "X11 render extension") + +set(XRANDR_FEATURE_TYPE "RECOMMENDED") +set(XRANDR_FEATURE_PURPOSE "tracking output configuration") +set(XRANDR_FEATURE_DESCRIPTION "X11 randr extension") + +set(XFIXES_FEATURE_TYPE "RECOMMENDED") +set(XFIXES_FEATURE_PURPOSE "X11 xfixes extension") +set(XFIXES_FEATURE_DESCRIPTION "Useful additions to the X11 core protocol") + +find_feature(XShm ${XSHM_FEATURE_TYPE} ${XSHM_FEATURE_PURPOSE} ${XSHM_FEATURE_DESCRIPTION}) +find_feature(Xinerama ${XINERAMA_FEATURE_TYPE} ${XINERAMA_FEATURE_PURPOSE} ${XINERAMA_FEATURE_DESCRIPTION}) +find_feature(Xext ${XEXT_FEATURE_TYPE} ${XEXT_FEATURE_PURPOSE} ${XEXT_FEATURE_DESCRIPTION}) +find_feature(Xcursor ${XCURSOR_FEATURE_TYPE} ${XCURSOR_FEATURE_PURPOSE} ${XCURSOR_FEATURE_DESCRIPTION}) +find_feature(Xv ${XV_FEATURE_TYPE} ${XV_FEATURE_PURPOSE} ${XV_FEATURE_DESCRIPTION}) +find_feature(Xi ${XI_FEATURE_TYPE} ${XI_FEATURE_PURPOSE} ${XI_FEATURE_DESCRIPTION}) +find_feature(Xrender ${XRENDER_FEATURE_TYPE} ${XRENDER_FEATURE_PURPOSE} ${XRENDER_FEATURE_DESCRIPTION}) +find_feature(XRandR ${XRANDR_FEATURE_TYPE} ${XRANDR_FEATURE_PURPOSE} ${XRANDR_FEATURE_DESCRIPTION}) +find_feature(Xfixes ${XFIXES_FEATURE_TYPE} ${XFIXES_FEATURE_PURPOSE} ${XFIXES_FEATURE_DESCRIPTION}) + +if(WITH_XINERAMA) + add_definitions(-DWITH_XINERAMA) + include_directories(${XINERAMA_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XINERAMA_LIBRARIES}) +endif() + +if(WITH_XEXT) + add_definitions(-DWITH_XEXT) + include_directories(${XEXT_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XEXT_LIBRARIES}) +endif() + +if(WITH_XCURSOR) + add_definitions(-DWITH_XCURSOR) + include_directories(${XCURSOR_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XCURSOR_LIBRARIES}) +endif() + +if(WITH_XV) + add_definitions(-DWITH_XV) + include_directories(${XV_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XV_LIBRARIES}) +endif() + +if(WITH_XI) + add_definitions(-DWITH_XI) + include_directories(${XI_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XI_LIBRARIES}) +endif() + +if(WITH_XRENDER) + add_definitions(-DWITH_XRENDER) + include_directories(${XRENDER_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XRENDER_LIBRARIES}) +endif() + +if(WITH_XRANDR) + add_definitions(-DWITH_XRANDR) + include_directories(${XRANDR_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XRANDR_LIBRARIES}) +endif() + +if(WITH_XFIXES) + add_definitions(-DWITH_XFIXES) + include_directories(${XFIXES_INCLUDE_DIRS}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XFIXES_LIBRARIES}) +endif() + +include_directories(${CMAKE_SOURCE_DIR}/resources) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp m) +if (NOT APPLE) + list(APPEND ${MODULE_PREFIX}_LIBS rt) +endif() +target_link_libraries(${MODULE_NAME} ${PRIVATE_KEYWORD} ${${MODULE_PREFIX}_LIBS}) + +if(WITH_IPP) + target_link_libraries(${MODULE_NAME} ${PRIVATE_KEYWORD} ${IPP_LIBRARY_LIST}) +endif() + +if(WITH_CLIENT_INTERFACE) + install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries) + add_subdirectory(cli) +else() + install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) +endif() + +set_property(TARGET ${MODULE_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/.gitignore b/client/X11/cli/.gitignore new file mode 100644 index 0000000..6ddebc6 --- /dev/null +++ b/client/X11/cli/.gitignore @@ -0,0 +1,2 @@ +xfreerdp + diff --git a/client/X11/cli/CMakeLists.txt b/client/X11/cli/CMakeLists.txt new file mode 100644 index 0000000..5f805c2 --- /dev/null +++ b/client/X11/cli/CMakeLists.txt @@ -0,0 +1,38 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP X11 cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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(${MODULE_PREFIX}_SRCS + xfreerdp.c) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "xfreerdp" RUNTIME_OUTPUT_DIRECTORY "..") + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} xfreerdp-client freerdp-client) + +if(OPENBSD) + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} ossaudio) +else() + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) +endif() + +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..663d49b --- /dev/null +++ b/client/X11/cli/xfreerdp.c @@ -0,0 +1,83 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client + * + * Copyright 2011 Marc-Andre Moreau + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "../xf_client.h" +#include "../xfreerdp.h" + +int main(int argc, char* argv[]) +{ + int status; + HANDLE thread; + xfContext* xfc; + DWORD dwExitCode; + rdpContext* context; + rdpSettings* settings; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + + ZeroMemory(&clientEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + 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); + + status = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (status) + { + if (settings->ListMonitors) + xf_list_monitors(xfc); + + freerdp_client_context_free(context); + return 0; + } + + freerdp_client_start(context); + + thread = freerdp_client_get_thread(context); + + WaitForSingleObject(thread, INFINITE); + GetExitCodeThread(thread, &dwExitCode); + + freerdp_client_stop(context); + + freerdp_client_context_free(context); + + return xf_exit_code_from_disconnect_reason(dwExitCode); +} diff --git a/client/X11/generate_argument_docbook.c b/client/X11/generate_argument_docbook.c new file mode 100644 index 0000000..b700539 --- /dev/null +++ b/client/X11/generate_argument_docbook.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include + +#include "../common/cmdline.h" + +#define TAG FREERDP_TAG("generate_argument_docbook") +LPSTR tr_esc_str(LPCSTR arg, bool format) +{ + LPSTR tmp = NULL; + size_t cs = 0, x, ds, len; + size_t s; + + if (NULL == arg) + return NULL; + + s = strlen(arg); + + /* Find trailing whitespaces */ + while ((s > 0) && isspace(arg[s - 1])) + s--; + + /* Prepare a initial buffer with the size of the result string. */ + ds = s + 1; + + if (s) + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not allocate string buffer.\n"); + exit(-2); + } + + /* Copy character for character and check, if it is necessary to escape. */ + memset(tmp, 0, ds * sizeof(CHAR)); + + for (x = 0; x < s; x++) + { + switch (arg[x]) + { + case '<': + len = format ? 13 : 4; + ds += len - 1; + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not reallocate string buffer.\n"); + exit(-3); + } + + if (format) + /* coverity[buffer_size] */ + strncpy(&tmp[cs], "", len); + else + /* coverity[buffer_size] */ + strncpy(&tmp[cs], "<", len); + + cs += len; + break; + + case '>': + len = format ? 14 : 4; + ds += len - 1; + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not reallocate string buffer.\n"); + exit(-4); + } + + if (format) + /* coverity[buffer_size] */ + strncpy(&tmp[cs], "", len); + else + /* coverity[buffer_size] */ + strncpy(&tmp[cs], "<", len); + + cs += len; + break; + + case '\'': + ds += 5; + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not reallocate string buffer.\n"); + exit(-5); + } + + tmp[cs++] = '&'; + tmp[cs++] = 'a'; + tmp[cs++] = 'p'; + tmp[cs++] = 'o'; + tmp[cs++] = 's'; + tmp[cs++] = ';'; + break; + + case '"': + ds += 5; + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not reallocate string buffer.\n"); + exit(-6); + } + + tmp[cs++] = '&'; + tmp[cs++] = 'q'; + tmp[cs++] = 'u'; + tmp[cs++] = 'o'; + tmp[cs++] = 't'; + tmp[cs++] = ';'; + break; + + case '&': + ds += 4; + tmp = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + + if (NULL == tmp) + { + fprintf(stderr, "Could not reallocate string buffer.\n"); + exit(-7); + } + + tmp[cs++] = '&'; + tmp[cs++] = 'a'; + tmp[cs++] = 'm'; + tmp[cs++] = 'p'; + tmp[cs++] = ';'; + break; + + default: + tmp[cs++] = arg[x]; + break; + } + + /* Assure, the string is '\0' terminated. */ + tmp[ds - 1] = '\0'; + } + + return tmp; +} + +int main(int argc, char* argv[]) +{ + size_t elements = sizeof(args) / sizeof(args[0]); + size_t x; + const char* fname = "xfreerdp-argument.1.xml"; + FILE* fp = NULL; + /* Open output file for writing, truncate if existing. */ + fp = fopen(fname, "w"); + + if (NULL == fp) + { + fprintf(stderr, "Could not open '%s' for writing.\n", fname); + return -1; + } + + /* The tag used as header in the manpage */ + fprintf(fp, "\n"); + fprintf(fp, "\tOptions\n"); + fprintf(fp, "\t\t\n"); + + /* Iterate over argument struct and write data to docbook 4.5 + * compatible XML */ + if (elements < 2) + { + fprintf(stderr, "The argument array 'args' is empty, writing an empty file.\n"); + elements = 1; + } + + for (x = 0; x < elements - 1; x++) + { + const COMMAND_LINE_ARGUMENT_A* arg = &args[x]; + char* name = tr_esc_str((LPSTR) arg->Name, FALSE); + char* alias = tr_esc_str((LPSTR) arg->Alias, FALSE); + char* format = tr_esc_str(arg->Format, TRUE); + char* text = tr_esc_str((LPSTR) arg->Text, FALSE); + fprintf(fp, "\t\t\t\n"); + + do + { + fprintf(fp, "\t\t\t\t", name); + + if (format) + { + if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL) + fprintf(fp, "["); + + fprintf(fp, ":%s", format); + + if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL) + fprintf(fp, "]"); + } + + fprintf(fp, "\n"); + + if (alias == name) + break; + + free(name); + name = alias; + } + while (alias); + + if (text) + { + fprintf(fp, "\t\t\t\t\n"); + fprintf(fp, "\t\t\t\t\t"); + + if (text) + fprintf(fp, "%s", text); + + if (arg->Flags == COMMAND_LINE_VALUE_BOOL) + fprintf(fp, " (default:%s)", arg->Default ? "on" : "off"); + else if (arg->Default) + { + char* value = tr_esc_str((LPSTR) arg->Default, FALSE); + fprintf(fp, " (default:%s)", value); + free(value); + } + + fprintf(fp, "\n"); + fprintf(fp, "\t\t\t\t\n"); + } + + fprintf(fp, "\t\t\t\n"); + free(name); + free(format); + free(text); + } + + fprintf(fp, "\t\t\n"); + fprintf(fp, "\t\n"); + fclose(fp); + return 0; +} + 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..ef91504 --- /dev/null +++ b/client/X11/xf_channels.c @@ -0,0 +1,141 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "xf_channels.h" + +#include "xf_client.h" +#include "xfreerdp.h" + +#include "xf_gfx.h" +#include "xf_tsmf.h" +#include "xf_rail.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_video.h" + +void xf_OnChannelConnectedEventHandler(void* context, ChannelConnectedEventArgs* e) +{ + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = xfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + xfc->rdpei = (RdpeiClientContext*) e->pInterface; + } + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_init(xfc, (TsmfClientContext*) e->pInterface); + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_init(xfc->context.gdi, (RdpgfxClientContext*) e->pInterface); + else + 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, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + xf_encomsp_init(xfc, (EncomspClientContext*) 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, GEOMETRY_DVC_CHANNEL_NAME) == 0) + { + gdi_video_geometry_init(xfc->context.gdi, (GeometryClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_video_control_init(xfc->context.gdi, (VideoClientContext*)e->pInterface); + else + xf_video_control_init(xfc, (VideoClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0) + { + gdi_video_data_init(xfc->context.gdi, (VideoClientContext*)e->pInterface); + } +} + +void xf_OnChannelDisconnectedEventHandler(void* context, ChannelDisconnectedEventArgs* e) +{ + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = xfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + xfc->rdpei = NULL; + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + xf_disp_uninit(xfc->xfDisp, (DispClientContext*)e->pInterface); + } + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_uninit(xfc, (TsmfClientContext*) e->pInterface); + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_uninit(xfc->context.gdi, (RdpgfxClientContext*) e->pInterface); + else + 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, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + xf_encomsp_uninit(xfc, (EncomspClientContext*) e->pInterface); + } + else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0) + { + gdi_video_geometry_uninit(xfc->context.gdi, (GeometryClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_video_control_uninit(xfc->context.gdi, (VideoClientContext*)e->pInterface); + else + xf_video_control_uninit(xfc, (VideoClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0) + { + gdi_video_data_uninit(xfc->context.gdi, (VideoClientContext*)e->pInterface); + } +} diff --git a/client/X11/xf_channels.h b/client/X11/xf_channels.h new file mode 100644 index 0000000..8a8c3b1 --- /dev/null +++ b/client/X11/xf_channels.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void xf_OnChannelConnectedEventHandler(void* context, ChannelConnectedEventArgs* e); +void xf_OnChannelDisconnectedEventHandler(void* context, 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..4a3e0d2 --- /dev/null +++ b/client/X11/xf_client.c @@ -0,0 +1,2005 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Interface + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2013 Corey Clayton + * Copyright 2014 Thincast Technologies GmbH + * Copyright 2014 Norbert Federa + * Copyright 2016 Armin Novak + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#ifdef WITH_XRENDER +#include +#include +#endif + +#ifdef WITH_XI +#include +#include +#endif + +#ifdef WITH_XCURSOR +#include +#endif + +#ifdef WITH_XINERAMA +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "xf_gdi.h" +#include "xf_rail.h" +#include "xf_tsmf.h" +#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_input.h" +#include "xf_channels.h" +#include "xfreerdp.h" + + +#include +#define TAG CLIENT_TAG("x11") + +static int (*_def_error_handler)(Display*, XErrorEvent*); +static int _xf_error_handler(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; + Picture primaryPicture; + XRenderPictureAttributes pa; + XRenderPictFormat* picFormat; + double xScalingFactor; + double yScalingFactor; + int x2; + int y2; + rdpSettings* settings = xfc->context.settings; + + if (xfc->scaledWidth <= 0 || xfc->scaledHeight <= 0) + { + WLog_ERR(TAG, "the current window dimensions are invalid"); + return; + } + + if (settings->DesktopWidth <= 0 || settings->DesktopHeight <= 0) + { + WLog_ERR(TAG, "the window dimensions are invalid"); + return; + } + + xScalingFactor = settings->DesktopWidth / (double)xfc->scaledWidth; + yScalingFactor = settings->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); + XRenderSetPictureFilter(xfc->display, primaryPicture, FilterBilinear, 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 = floor(x / xScalingFactor) - 1; + y = floor(y / yScalingFactor) - 1; + w = ceil(x2 / xScalingFactor) + 1 - x; + h = 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 = xfc->context.settings; + + if (xfc->offset_x || xfc->offset_y || + xfc->scaledWidth != settings->DesktopWidth || + xfc->scaledHeight != settings->DesktopHeight) + { + return TRUE; + } + + return FALSE; +} +#endif /* WITH_XRENDER defined */ + +void xf_draw_screen(xfContext* xfc, int x, int y, int w, int h) +{ + if (w == 0 || h == 0) + { + WLog_WARN(TAG, "invalid width and/or height specified: w=%d h=%d", w, h); + 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; + xfContext* xfc = (xfContext*) context; + settings = context->settings; + + if (xfc->primary) + { + BOOL same = (xfc->primary == xfc->drawing) ? TRUE : FALSE; + XFreePixmap(xfc->display, xfc->primary); + + if (!(xfc->primary = XCreatePixmap( + xfc->display, xfc->drawable, + settings->DesktopWidth, + settings->DesktopHeight, xfc->depth))) + return FALSE; + + if (same) + xfc->drawing = xfc->primary; + } + +#ifdef WITH_XRENDER + + if (!xfc->context.settings->SmartSizing) + { + xfc->scaledWidth = settings->DesktopWidth; + xfc->scaledHeight = settings->DesktopHeight; + } + +#endif + + if (!xfc->fullscreen) + { + xf_ResizeDesktopWindow(xfc, xfc->window, settings->DesktopWidth, settings->DesktopHeight); + } + else + { +#ifdef WITH_XRENDER + + if (!xfc->context.settings->SmartSizing) +#endif + { + /* Update the saved width and height values the window will be + * resized to when toggling out of fullscreen */ + xfc->savedWidth = settings->DesktopWidth; + xfc->savedHeight = settings->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, xfc->window->width, + xfc->window->height); + } + + return TRUE; +} + +static BOOL xf_sw_end_paint(rdpContext* context) +{ + int i; + INT32 x, y; + UINT32 w, h; + int ninvalid; + HGDI_RGN cinvalid; + xfContext* xfc = (xfContext*) context; + rdpGdi* gdi = context->gdi; + + if (gdi->suppressOutput) + return TRUE; + + x = gdi->primary->hdc->hwnd->invalid->x; + y = gdi->primary->hdc->hwnd->invalid->y; + w = gdi->primary->hdc->hwnd->invalid->w; + h = gdi->primary->hdc->hwnd->invalid->h; + ninvalid = gdi->primary->hdc->hwnd->ninvalid; + cinvalid = gdi->primary->hdc->hwnd->cinvalid; + + if (!xfc->remote_app) + { + if (!xfc->complex_regions) + { + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + xf_lock_x11(xfc, FALSE); + XPutImage(xfc->display, xfc->primary, xfc->gc, xfc->image, + x, y, x, y, w, h); + xf_draw_screen(xfc, x, y, w, h); + xf_unlock_x11(xfc, FALSE); + } + else + { + if (gdi->primary->hdc->hwnd->ninvalid < 1) + return TRUE; + + xf_lock_x11(xfc, FALSE); + + for (i = 0; i < ninvalid; i++) + { + x = cinvalid[i].x; + y = cinvalid[i].y; + w = cinvalid[i].w; + h = cinvalid[i].h; + XPutImage(xfc->display, xfc->primary, xfc->gc, + xfc->image, x, y, x, y, w, h); + xf_draw_screen(xfc, x, y, w, h); + } + + XFlush(xfc->display); + xf_unlock_x11(xfc, FALSE); + } + } + else + { + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + xf_lock_x11(xfc, FALSE); + xf_rail_paint(xfc, x, y, x + w, y + h); + xf_unlock_x11(xfc, FALSE); + } + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->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, TRUE); + + if (!gdi_resize(gdi, settings->DesktopWidth, settings->DesktopHeight)) + goto out; + + if (xfc->image) + { + xfc->image->data = NULL; + XDestroyImage(xfc->image); + } + + 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, TRUE); + return ret; +} + +static BOOL xf_hw_end_paint(rdpContext* context) +{ + INT32 x, y; + UINT32 w, h; + xfContext* xfc = (xfContext*) context; + + if (xfc->context.gdi->suppressOutput) + return TRUE; + + if (!xfc->remote_app) + { + if (!xfc->complex_regions) + { + if (xfc->hdc->hwnd->invalid->null) + return TRUE; + + x = xfc->hdc->hwnd->invalid->x; + y = xfc->hdc->hwnd->invalid->y; + w = xfc->hdc->hwnd->invalid->w; + h = xfc->hdc->hwnd->invalid->h; + xf_lock_x11(xfc, FALSE); + xf_draw_screen(xfc, x, y, w, h); + xf_unlock_x11(xfc, FALSE); + } + else + { + int i; + int ninvalid; + HGDI_RGN cinvalid; + + if (xfc->hdc->hwnd->ninvalid < 1) + return TRUE; + + ninvalid = xfc->hdc->hwnd->ninvalid; + cinvalid = xfc->hdc->hwnd->cinvalid; + xf_lock_x11(xfc, FALSE); + + for (i = 0; i < ninvalid; i++) + { + x = cinvalid[i].x; + y = cinvalid[i].y; + w = cinvalid[i].w; + h = cinvalid[i].h; + xf_draw_screen(xfc, x, y, w, h); + } + + XFlush(xfc->display); + xf_unlock_x11(xfc, FALSE); + } + } + else + { + if (xfc->hdc->hwnd->invalid->null) + return TRUE; + + x = xfc->hdc->hwnd->invalid->x; + y = xfc->hdc->hwnd->invalid->y; + w = xfc->hdc->hwnd->invalid->w; + h = xfc->hdc->hwnd->invalid->h; + xf_lock_x11(xfc, FALSE); + xf_rail_paint(xfc, x, y, x + w, y + h); + xf_unlock_x11(xfc, FALSE); + } + + xfc->hdc->hwnd->invalid->null = TRUE; + xfc->hdc->hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL xf_hw_desktop_resize(rdpContext* context) +{ + rdpGdi* gdi = context->gdi; + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = context->settings; + BOOL ret = FALSE; + xf_lock_x11(xfc, TRUE); + + if (!gdi_resize(gdi, settings->DesktopWidth, settings->DesktopHeight)) + goto out; + + ret = xf_desktop_resize(context); +out: + xf_unlock_x11(xfc, TRUE); + return ret; +} + +static BOOL xf_process_x_events(freerdp* instance) +{ + BOOL status; + XEvent xevent; + int pending_status; + xfContext* xfc = (xfContext*) instance->context; + status = TRUE; + pending_status = TRUE; + + while (pending_status) + { + xf_lock_x11(xfc, FALSE); + pending_status = XPending(xfc->display); + xf_unlock_x11(xfc, FALSE); + + if (pending_status) + { + ZeroMemory(&xevent, sizeof(xevent)); + XNextEvent(xfc->display, &xevent); + status = xf_event_process(instance, &xevent); + + if (!status) + return status; + } + } + + return status; +} + +BOOL xf_create_window(xfContext* xfc) +{ + XGCValues gcv; + XEvent xevent; + int width, height; + char* windowTitle; + rdpGdi* gdi; + rdpSettings* settings; + settings = xfc->context.settings; + gdi = xfc->context.gdi; + ZeroMemory(&xevent, sizeof(xevent)); + width = settings->DesktopWidth; + height = settings->DesktopHeight; + + if (!xfc->hdc) + if (!(xfc->hdc = gdi_CreateDC(gdi->dstFormat))) + return 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.colormap = xfc->colormap; + xfc->attribs.bit_gravity = NorthWestGravity; + xfc->attribs.win_gravity = NorthWestGravity; +#ifdef WITH_XRENDER + xfc->offset_x = 0; + xfc->offset_y = 0; +#endif + + if (settings->WindowTitle) + { + windowTitle = _strdup(settings->WindowTitle); + + if (!windowTitle) + return FALSE; + } + else if (settings->ServerPort == 3389) + { + size_t size = 1 + sizeof("FreeRDP: ") + strlen( + settings->ServerHostname); + windowTitle = malloc(size); + + if (!windowTitle) + return FALSE; + + sprintf_s(windowTitle, size, "FreeRDP: %s", settings->ServerHostname); + } + else + { + size_t size = 1 + sizeof("FreeRDP: ") + strlen(settings->ServerHostname) + + sizeof(":00000"); + windowTitle = malloc(size); + + if (!windowTitle) + return FALSE; + + sprintf_s(windowTitle, size, "FreeRDP: %s:%i", settings->ServerHostname, + settings->ServerPort); + } + +#ifdef WITH_XRENDER + + if (settings->SmartSizing && !xfc->fullscreen) + { + if (settings->SmartSizingWidth) + width = settings->SmartSizingWidth; + + if (settings->SmartSizingHeight) + height = settings->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->drawable = xf_CreateDummyWindow(xfc); + } + + ZeroMemory(&gcv, sizeof(gcv)); + + if (xfc->modifierMap) + XFreeModifiermap(xfc->modifierMap); + + xfc->modifierMap = XGetModifierMapping(xfc->display); + + if (!xfc->gc) + xfc->gc = XCreateGC(xfc->display, xfc->drawable, GCGraphicsExposures, &gcv); + + if (!xfc->primary) + xfc->primary = XCreatePixmap(xfc->display, xfc->drawable, + settings->DesktopWidth, + settings->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, + settings->DesktopWidth, + settings->DesktopHeight); + XFlush(xfc->display); + + if (!xfc->image) + { + rdpGdi* gdi = xfc->context.gdi; + xfc->image = XCreateImage(xfc->display, xfc->visual, + xfc->depth, + ZPixmap, 0, (char*) gdi->primary_buffer, + settings->DesktopWidth, settings->DesktopHeight, + xfc->scanline_pad, gdi->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 (xfc->hdc) + { + gdi_DeleteDC(xfc->hdc); + xfc->hdc = NULL; + } + + if (xfc->xv_context) + { + xf_tsmf_uninit(xfc, NULL); + xfc->xv_context = NULL; + } + + 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; + } + + if (xfc->modifierMap) + { + XFreeModifiermap(xfc->modifierMap); + xfc->modifierMap = NULL; + } +} + +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) + { + XUngrabKeyboard(xfc->display, CurrentTime); + } + + xfc->fullscreen = (xfc->fullscreen) ? FALSE : TRUE; + xfc->decorations = (xfc->fullscreen) ? FALSE : settings->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_toggle_control(xfContext* xfc) +{ + EncomspClientContext* encomsp; + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu; + encomsp = xfc->encomsp; + + if (!encomsp) + return; + + pdu.ParticipantId = 0; + pdu.Flags = ENCOMSP_REQUEST_VIEW; + + if (!xfc->controlToggle) + pdu.Flags |= ENCOMSP_REQUEST_INTERACT; + + encomsp->ChangeParticipantControlLevel(encomsp, &pdu); + xfc->controlToggle = !xfc->controlToggle; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_encomsp_participant_created(EncomspClientContext* context, + ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + return CHANNEL_RC_OK; +} + +void xf_encomsp_init(xfContext* xfc, EncomspClientContext* encomsp) +{ + xfc->encomsp = encomsp; + encomsp->custom = (void*) xfc; + encomsp->ParticipantCreated = xf_encomsp_participant_created; +} + +void xf_encomsp_uninit(xfContext* xfc, EncomspClientContext* encomsp) +{ + xfc->encomsp = NULL; +} + +void xf_lock_x11(xfContext* xfc, BOOL display) +{ + if (!xfc->UseXThreads) + { + WaitForSingleObject(xfc->mutex, INFINITE); + } + else + { + if (display) + XLockDisplay(xfc->display); + } +} + +void xf_unlock_x11(xfContext* xfc, BOOL display) +{ + if (!xfc->UseXThreads) + { + ReleaseMutex(xfc->mutex); + } + else + { + if (display) + XUnlockDisplay(xfc->display); + } +} + +static BOOL xf_get_pixmap_info(xfContext* xfc) +{ + int i; + int vi_count; + int pf_count; + XVisualInfo* vi; + XVisualInfo* vis; + XVisualInfo tpl; + XPixmapFormatValues* pf; + XPixmapFormatValues* pfs; + XWindowAttributes window_attributes; + assert(xfc->display); + pfs = XListPixmapFormats(xfc->display, &pf_count); + + if (!pfs) + { + WLog_ERR(TAG, "XListPixmapFormats failed"); + return 1; + } + + for (i = 0; i < pf_count; i++) + { + pf = pfs + i; + + if (pf->depth == xfc->depth) + { + xfc->scanline_pad = pf->scanline_pad; + break; + } + } + + XFree(pfs); + ZeroMemory(&tpl, sizeof(tpl)); + tpl.class = TrueColor; + tpl.screen = xfc->screen_number; + + if (XGetWindowAttributes(xfc->display, RootWindowOfScreen(xfc->screen), + &window_attributes) == 0) + { + WLog_ERR(TAG, "XGetWindowAttributes failed"); + return FALSE; + } + + vis = XGetVisualInfo(xfc->display, VisualClassMask | VisualScreenMask, + &tpl, &vi_count); + + if (!vis) + { + WLog_ERR(TAG, "XGetVisualInfo failed"); + return FALSE; + } + + vi = vis; + + for (i = 0; i < vi_count; i++) + { + vi = vis + i; + + if (vi->visual == window_attributes.visual) + { + xfc->visual = vi->visual; + break; + } + } + + if (xfc->visual) + { + /* + * Detect if the server visual has an inverted colormap + * (BGR vs RGB, or red being the least significant byte) + */ + if (vi->red_mask & 0xFF) + { + xfc->invert = FALSE; + } + } + + XFree(vis); + + if ((xfc->visual == NULL) || (xfc->scanline_pad == 0)) + { + return FALSE; + } + + return TRUE; +} + +static int xf_error_handler(Display* d, XErrorEvent* ev) +{ + char buf[256]; + int do_abort = TRUE; + XGetErrorText(d, ev->error_code, buf, sizeof(buf)); + WLog_ERR(TAG, "%s", buf); + + if (do_abort) + abort(); + + _def_error_handler(d, ev); + return FALSE; +} + +static int _xf_error_handler(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); + return xf_error_handler(d, ev); +} + +static BOOL xf_play_sound(rdpContext* context, + const PLAY_SOUND_UPDATE* play_sound) +{ + xfContext* xfc = (xfContext*) context; + XkbBell(xfc->display, None, 100, 0); + return TRUE; +} + +static void xf_check_extensions(xfContext* context) +{ + int xkb_opcode, xkb_event, xkb_error; + 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; + int xrender_error_base; + + 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); + +/* Invalid device identifier which indicate failure. */ +static const int INVALID_XID = -1; +#endif /* WITH_XI */ + +static void xf_get_x11_button_map(xfContext* xfc, unsigned char* x11_map) +{ +#ifdef WITH_XI + int opcode, event, error; + int xid; + XDevice* ptr_dev; + XExtensionVersion* version; + XDeviceInfo* devices1; + XIDeviceInfo* devices2; + int i, num_devices; + + if (XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error)) + { + WLog_DBG(TAG, "Searching for XInput pointer device"); + xid = INVALID_XID; + /* 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 (i = 0; i < num_devices; ++i) + { + if ((devices2[i].use == XISlavePointer) && + (strncmp(devices2[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + xid = devices2[i].deviceid; + break; + } + } + + XIFreeDeviceInfo(devices2); + } + } + else + { + /* XID of pointer device using XInput version 1 */ + devices1 = XListInputDevices(xfc->display, &num_devices); + + if (devices1) + { + for (i = 0; i < num_devices; ++i) + { + if ((devices1[i].use == IsXExtensionPointer) && + (strncmp(devices1[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + xid = devices1[i].id; + break; + } + } + + XFreeDeviceList(devices1); + } + } + + XFree(version); + + /* get button mapping from input extension if there is a pointer device; */ + /* otherwise leave unchanged. */ + if (xid != INVALID_XID) + { + WLog_DBG(TAG, "Pointer device: %d", xid); + ptr_dev = XOpenDevice(xfc->display, xid); + 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 int xf_button_flags[NUM_BUTTONS_MAPPED] = +{ + PTR_FLAGS_BUTTON1, + PTR_FLAGS_BUTTON3, + PTR_FLAGS_BUTTON2 +}; + +static void xf_button_map_init(xfContext* xfc) +{ + /* loop counter for array initialization */ + int physical; + int logical; + /* logical mouse button which is used for each physical mouse */ + /* button (indexed from zero). This is the default map. */ + unsigned char x11_map[NUM_BUTTONS_MAPPED] = + { + Button1, + Button2, + Button3 + }; + + /* query system for actual remapping */ + if (!xfc->context.settings->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 (physical = 0; physical < NUM_BUTTONS_MAPPED; ++physical) + { + logical = x11_map[physical]; + + if (Button1 <= logical && logical <= Button3) + { + xfc->button_map[logical - BUTTON_BASE] = xf_button_flags[physical]; + } + else + { + WLog_ERR(TAG, "Mouse physical button %d is mapped to logical button %d", + physical, logical); + } + } +} + +/** +* 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; + rdpSettings* settings; + rdpContext* context = instance->context; + xfContext* xfc = (xfContext*) instance->context; + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + settings = instance->settings; + channels = context->channels; + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_NATIVE_XSERVER; + ZeroMemory(settings->OrderSupport, 32); + settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_PATBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_SCRBLT_INDEX] = TRUE; + settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE; + settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE; + settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = TRUE; + settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE; + settings->OrderSupport[NEG_LINETO_INDEX] = TRUE; + settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE; + settings->OrderSupport[NEG_MEMBLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = settings->BitmapCacheEnabled; + settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE; + settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = settings->GlyphSupportLevel != GLYPH_SUPPORT_NONE; + settings->OrderSupport[NEG_FAST_INDEX_INDEX] = settings->GlyphSupportLevel != GLYPH_SUPPORT_NONE; + settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = settings->GlyphSupportLevel != GLYPH_SUPPORT_NONE; + settings->OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE; + settings->OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE; + settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE; + settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE; + PubSub_SubscribeChannelConnected(instance->context->pubSub, + xf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + xf_OnChannelDisconnectedEventHandler); + + if (!freerdp_client_load_addins(channels, instance->settings)) + return FALSE; + + if (!settings->Username && !settings->CredentialsFromStdin && !settings->SmartcardLogon) + { + char* login_name = getlogin(); + + if (login_name) + { + settings->Username = _strdup(login_name); + + if (!settings->Username) + return FALSE; + + WLog_INFO(TAG, "No user name set. - Using login name: %s", settings->Username); + } + } + + if (settings->AuthenticationOnly) + { + /* Check +auth-only has a username and password. */ + if (!settings->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 (!xf_keyboard_init(xfc)) + return FALSE; + + xf_detect_monitors(xfc, &maxWidth, &maxHeight); + + if (maxWidth && maxHeight) + { + settings->DesktopWidth = maxWidth; + settings->DesktopHeight = maxHeight; + } + +#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 (settings->Fullscreen && settings->SmartSizing && + settings->SmartSizingWidth && settings->SmartSizingHeight) + { + settings->DesktopWidth = settings->SmartSizingWidth; + settings->DesktopHeight = settings->SmartSizingHeight; + } + +#endif + xfc->fullscreen = settings->Fullscreen; + xfc->decorations = settings->Decorations; + xfc->grab_keyboard = settings->GrabKeyboard; + xfc->fullscreen_toggle = settings->ToggleFullscreen; + xfc->floatbar = settings->Floatbar; + xf_button_map_init(xfc); + return TRUE; +} + +/** +* 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) +{ + rdpUpdate* update; + rdpContext* context; + rdpSettings* settings; + ResizeWindowEventArgs e; + xfContext* xfc = (xfContext*) instance->context; + context = instance->context; + settings = instance->settings; + update = context->update; + + if (!gdi_init(instance, xf_get_local_color_format(xfc, TRUE))) + return FALSE; + + if (!xf_register_pointer(context->graphics)) + return FALSE; + + if (!settings->SoftwareGdi) + { + if (!xf_register_graphics(context->graphics)) + { + WLog_ERR(TAG, "failed to register graphics"); + return FALSE; + } + + xf_gdi_register_update_callbacks(update); + brush_cache_register_callbacks(instance->update); + glyph_cache_register_callbacks(instance->update); + bitmap_cache_register_callbacks(instance->update); + offscreen_cache_register_callbacks(instance->update); + palette_cache_register_callbacks(instance->update); + } + +#ifdef WITH_XRENDER + xfc->scaledWidth = settings->DesktopWidth; + xfc->scaledHeight = settings->DesktopHeight; + xfc->offset_x = 0; + xfc->offset_y = 0; +#endif + + if (!xfc->xrenderAvailable) + { + if (settings->SmartSizing) + { + WLog_ERR(TAG, "XRender not available: disabling smart-sizing"); + settings->SmartSizing = FALSE; + } + + if (settings->MultiTouchGestures) + { + WLog_ERR(TAG, "XRender not available: disabling local multi-touch gestures"); + settings->MultiTouchGestures = FALSE; + } + } + + if (settings->RemoteApplicationMode) + xfc->remote_app = TRUE; + + if (!xf_create_window(xfc)) + { + WLog_ERR(TAG, "xf_create_window failed"); + return FALSE; + } + + if (settings->SoftwareGdi) + { + update->EndPaint = xf_sw_end_paint; + update->DesktopResize = xf_sw_desktop_resize; + } + else + { + update->EndPaint = xf_hw_end_paint; + update->DesktopResize = xf_hw_desktop_resize; + } + + update->PlaySound = xf_play_sound; + update->SetKeyboardIndicators = xf_keyboard_set_indicators; + update->SetKeyboardImeStatus = xf_keyboard_set_ime_status; + + if (!(xfc->clipboard = xf_clipboard_new(xfc))) + return FALSE; + + if (!(xfc->xfDisp = xf_disp_new(xfc))) + { + xf_clipboard_free(xfc->clipboard); + return FALSE; + } + + EventArgsInit(&e, "xfreerdp"); + e.width = settings->DesktopWidth; + e.height = settings->DesktopHeight; + PubSub_OnResizeWindow(context->pubSub, xfc, &e); + return TRUE; +} + +static void xf_post_disconnect(freerdp* instance) +{ + xfContext* xfc; + rdpContext* context; + + 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->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); + xf_keyboard_free(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); + xf_rail_disable_remoteapp_mode(xfc); + return 1; +} + +static DWORD WINAPI xf_input_thread(LPVOID arg) +{ + BOOL running = TRUE; + DWORD status; + DWORD nCount; + HANDLE events[3]; + XEvent xevent; + wMessage msg; + wMessageQueue* queue; + int pending_status = 1; + int process_status = 1; + freerdp* instance = (freerdp*) arg; + xfContext* xfc = (xfContext*) instance->context; + queue = freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + nCount = 0; + events[nCount++] = MessageQueue_Event(queue); + events[nCount++] = xfc->x11event; + events[nCount++] = instance->context->abortEvent; + + while (running) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + switch (status) + { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + case WAIT_OBJECT_0 + 2: + if (WaitForSingleObject(events[0], 0) == WAIT_OBJECT_0) + { + if (MessageQueue_Peek(queue, &msg, FALSE)) + { + if (msg.id == WMQ_QUIT) + running = FALSE; + } + } + + if (WaitForSingleObject(events[1], 0) == WAIT_OBJECT_0) + { + do + { + xf_lock_x11(xfc, FALSE); + pending_status = XPending(xfc->display); + xf_unlock_x11(xfc, FALSE); + + if (pending_status) + { + xf_lock_x11(xfc, FALSE); + ZeroMemory(&xevent, sizeof(xevent)); + XNextEvent(xfc->display, &xevent); + process_status = xf_event_process(instance, &xevent); + xf_unlock_x11(xfc, FALSE); + + if (!process_status) + break; + } + } + while (pending_status); + + if (!process_status) + { + running = FALSE; + break; + } + } + + if (WaitForSingleObject(events[2], 0) == WAIT_OBJECT_0) + running = FALSE; + + break; + + default: + running = FALSE; + break; + } + } + + MessageQueue_PostQuit(queue, 0); + ExitThread(0); + return 0; +} + +static BOOL handle_window_events(freerdp* instance) +{ + rdpSettings* settings; + + if (!instance || !instance->settings) + return FALSE; + + settings = instance->settings; + + if (!settings->AsyncInput) + { + if (!xf_process_x_events(instance)) + { + WLog_INFO(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 instance - 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) +{ + BOOL status; + DWORD exit_code = 0; + DWORD nCount; + DWORD waitStatus; + HANDLE handles[64]; + xfContext* xfc; + freerdp* instance; + rdpContext* context; + HANDLE inputEvent = NULL; + HANDLE inputThread = NULL; + HANDLE timer = NULL; + LARGE_INTEGER due; + rdpSettings* settings; + TimerEventArgs timerEvent; + EventArgsInit(&timerEvent, "xfreerdp"); + instance = (freerdp*) param; + context = instance->context; + status = freerdp_connect(instance); + xfc = (xfContext*) instance->context; + + if (!status) + { + if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_AUTHENTICATION_FAILED) + exit_code = XF_EXIT_AUTH_FAILURE; + else if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED) + exit_code = XF_EXIT_NEGO_FAILURE; + else + exit_code = XF_EXIT_CONN_FAILED; + } + else + exit_code = XF_EXIT_SUCCESS; + + if (!status) + goto end; + + /* --authonly ? */ + if (instance->settings->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; + } + + settings = context->settings; + 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; + } + + if (!settings->AsyncInput) + { + inputEvent = xfc->x11event; + } + else + { + if (!(inputThread = CreateThread(NULL, 0, xf_input_thread, instance, 0, NULL))) + { + WLog_ERR(TAG, "async input: failed to create input thread"); + exit_code = XF_EXIT_UNKNOWN; + goto disconnect; + } + } + + while (!freerdp_shall_disconnect(instance)) + { + nCount = 0; + handles[nCount++] = timer; + + if (!settings->AsyncInput) + 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->floatbar && xfc->fullscreen && !xfc->remote_app) + xf_floatbar_hide_and_show(xfc); + + 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. + */ + if (freerdp_error_info(instance) == 0) + exit_code = XF_EXIT_CONN_FAILED; + } + + 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 ((status != WAIT_TIMEOUT) && (waitStatus == WAIT_OBJECT_0)) + { + timerEvent.now = GetTickCount64(); + PubSub_OnTimer(context->pubSub, context, &timerEvent); + } + } + + if (settings->AsyncInput) + { + WaitForSingleObject(inputThread, INFINITE); + CloseHandle(inputThread); + } + + 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_NEGO_FAILURE)) + 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, TerminateEventArgs* e) +{ + rdpContext* ctx = (rdpContext*)context; + freerdp_abort_connect(ctx->instance); +} + +#ifdef WITH_XRENDER +static void xf_ZoomingChangeEventHandler(void* context, + ZoomingChangeEventArgs* e) +{ + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = xfc->context.settings; + int w = xfc->scaledWidth + e->dx; + int 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, settings->DesktopWidth, settings->DesktopHeight); +} + +static void xf_PanningChangeEventHandler(void* context, + PanningChangeEventArgs* e) +{ + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = xfc->context.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, settings->DesktopWidth, settings->DesktopHeight); +} +#endif + +/** +* Client Interface +*/ + +static BOOL xfreerdp_client_global_init() +{ + setlocale(LC_ALL, ""); + + if (freerdp_handle_signals() != 0) + return FALSE; + + return TRUE; +} + +static void xfreerdp_client_global_uninit() +{ +} + +static int xfreerdp_client_start(rdpContext* context) +{ + xfContext* xfc = (xfContext*) context; + rdpSettings* settings = context->settings; + + if (!settings->ServerHostname) + { + WLog_ERR(TAG, + "error: server hostname was not specified with /v:[:port]"); + return -1; + } + + if (!(xfc->thread = CreateThread(NULL, 0, xf_client_thread, + context->instance, 0, NULL))) + { + WLog_ERR(TAG, "failed to create client thread"); + return -1; + } + + return 0; +} + +static int xfreerdp_client_stop(rdpContext* context) +{ + xfContext* xfc = (xfContext*) context; + freerdp_abort_connect(context->instance); + + if (xfc->thread) + { + WaitForSingleObject(xfc->thread, INFINITE); + CloseHandle(xfc->thread); + xfc->thread = NULL; + } + + return 0; +} + +static Atom get_supported_atom(xfContext* xfc, const char* atomName) +{ + unsigned long i; + const Atom atom = XInternAtom(xfc->display, atomName, False); + + for (i = 0; i < xfc->supportedAtomCount; i++) + { + if (xfc->supportedAtoms[i] == atom) + return atom; + } + + return None; +} +static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context) +{ + xfContext* xfc = (xfContext*) instance->context; + assert(context); + assert(xfc); + assert(!xfc->display); + assert(!xfc->mutex); + assert(!xfc->x11event); + instance->PreConnect = xf_pre_connect; + instance->PostConnect = xf_post_connect; + instance->PostDisconnect = xf_post_disconnect; + instance->Authenticate = client_cli_authenticate; + instance->GatewayAuthenticate = client_cli_gw_authenticate; + instance->VerifyCertificate = client_cli_verify_certificate; + instance->VerifyChangedCertificate = client_cli_verify_changed_certificate; + instance->LogonErrorInfo = xf_logon_error_info; + PubSub_SubscribeTerminate(context->pubSub, + xf_TerminateEventHandler); +#ifdef WITH_XRENDER + PubSub_SubscribeZoomingChange(context->pubSub, + xf_ZoomingChangeEventHandler); + PubSub_SubscribePanningChange(context->pubSub, + xf_PanningChangeEventHandler); +#endif + xfc->UseXThreads = TRUE; + /* uncomment below if debugging to prevent keyboard grap */ + /* xfc->debug = TRUE; */ + + 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_open_display; + } + + xfc->mutex = CreateMutex(NULL, FALSE, NULL); + + if (!xfc->mutex) + { + WLog_ERR(TAG, "Could not create mutex!"); + goto fail_create_mutex; + } + + xfc->xfds = ConnectionNumber(xfc->display); + xfc->screen_number = DefaultScreen(xfc->display); + xfc->screen = ScreenOfDisplay(xfc->display, xfc->screen_number); + xfc->depth = DefaultDepthOfScreen(xfc->screen); + 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; + int actual_format; + unsigned long nitems, after; + unsigned char* data = NULL; + int status = XGetWindowProperty(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(nitems, sizeof(Atom)); + memcpy(xfc->supportedAtoms, data, nitems * sizeof(Atom)); + } + + if (data) + XFree(data); + } + + 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_xfds_event; + } + + xfc->colormap = DefaultColormap(xfc->display, xfc->screen_number); + + if (xfc->debug) + { + WLog_INFO(TAG, "Enabling X11 debug mode."); + XSynchronize(xfc->display, TRUE); + _def_error_handler = XSetErrorHandler(_xf_error_handler); + } + + xf_check_extensions(xfc); + + if (!xf_get_pixmap_info(xfc)) + { + WLog_ERR(TAG, "Failed to get pixmap info"); + goto fail_pixmap_info; + } + + xfc->vscreen.monitors = calloc(16, sizeof(MONITOR_INFO)); + + if (!xfc->vscreen.monitors) + goto fail_vscreen_monitors; + + return TRUE; +fail_vscreen_monitors: +fail_pixmap_info: + CloseHandle(xfc->x11event); + xfc->x11event = NULL; +fail_xfds_event: + CloseHandle(xfc->mutex); + xfc->mutex = NULL; +fail_create_mutex: + XCloseDisplay(xfc->display); + xfc->display = NULL; +fail_open_display: + return FALSE; +} + +static void xfreerdp_client_free(freerdp* instance, rdpContext* context) +{ + xfContext* xfc = (xfContext*) instance->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 + + 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; + } + + free(xfc->supportedAtoms); +} + +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 = xfreerdp_client_stop; + return 0; +} diff --git a/client/X11/xf_client.h b/client/X11/xf_client.h new file mode 100644 index 0000000..34535a1 --- /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 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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..1c86031 --- /dev/null +++ b/client/X11/xf_cliprdr.c @@ -0,0 +1,1784 @@ +/** + * 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 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#ifdef WITH_XFIXES +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "xf_cliprdr.h" + +#define TAG CLIENT_TAG("x11") + +#define MAX_CLIPBOARD_FORMATS 255 + +struct xf_cliprdr_format +{ + Atom atom; + UINT32 formatId; + char* formatName; +}; +typedef struct xf_cliprdr_format xfCliprdrFormat; + +struct xf_clipboard +{ + xfContext* xfc; + rdpChannels* channels; + CliprdrClientContext* context; + + wClipboard* system; + wClipboardDelegate* delegate; + + Window root_window; + Atom clipboard_atom; + Atom property_atom; + + Atom raw_transfer_atom; + Atom raw_format_list_atom; + + int numClientFormats; + xfCliprdrFormat clientFormats[20]; + + int numServerFormats; + CLIPRDR_FORMAT* serverFormats; + + int numTargets; + Atom targets[20]; + + int requestedFormatId; + + BYTE* data; + BYTE* data_raw; + BOOL data_raw_format; + UINT32 data_format_id; + const char* data_format_name; + int data_length; + int data_raw_length; + XEvent* 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; + + /* File clipping */ + BOOL streams_supported; + BOOL file_formats_registered; +}; + +static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard); + +static void xf_cliprdr_check_owner(xfClipboard* clipboard) +{ + Window owner; + xfContext* xfc = clipboard->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); + } + } +} + +static BOOL xf_cliprdr_is_self_owned(xfClipboard* clipboard) +{ + xfContext* xfc = clipboard->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 = clipboard->xfc; + XChangeProperty(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; + int format; + int result = 0; + unsigned long length; + unsigned long bytes_left; + UINT32* data = NULL; + UINT32 is_enabled = 0; + Window owner = None; + xfContext* xfc = clipboard->xfc; + owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom); + + if (owner != None) + { + result = XGetWindowProperty(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) +{ + 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->formatId); + } + + return FALSE; +} + +static xfCliprdrFormat* xf_cliprdr_get_client_format_by_id( + xfClipboard* clipboard, UINT32 formatId) +{ + int index; + xfCliprdrFormat* format; + + for (index = 0; index < clipboard->numClientFormats; index++) + { + format = &(clipboard->clientFormats[index]); + + if (format->formatId == formatId) + return format; + } + + return NULL; +} + +static xfCliprdrFormat* xf_cliprdr_get_client_format_by_atom( + xfClipboard* clipboard, Atom atom) +{ + int i; + xfCliprdrFormat* format; + + for (i = 0; i < clipboard->numClientFormats; i++) + { + format = &(clipboard->clientFormats[i]); + + if (format->atom == atom) + return format; + } + + return NULL; +} + +static CLIPRDR_FORMAT* xf_cliprdr_get_server_format_by_atom( + xfClipboard* clipboard, Atom atom) +{ + int i, j; + xfCliprdrFormat* client_format; + CLIPRDR_FORMAT* server_format; + + for (i = 0; i < clipboard->numClientFormats; i++) + { + client_format = &(clipboard->clientFormats[i]); + + if (client_format->atom == atom) + { + for (j = 0; j < clipboard->numServerFormats; j++) + { + 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) +{ + CLIPRDR_FORMAT_DATA_REQUEST request; + ZeroMemory(&request, sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); + request.requestedFormatId = formatId; + 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, BYTE* data, + int size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response; + ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + response.dataLen = size; + response.requestedFormatData = data; + return clipboard->context->ClientFormatDataResponse(clipboard->context, + &response); +} + +static wStream* xf_cliprdr_serialize_server_format_list(xfClipboard* clipboard) +{ + UINT32 i; + UINT32 formatCount; + wStream* s = NULL; + + /* 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 (i = 0; i < formatCount; i++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[i]; + size_t name_length = format->formatName ? strlen(format->formatName) : 0; + + 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) +{ + UINT32 i; + wStream* s = NULL; + CLIPRDR_FORMAT* formats = NULL; + + if (!(s = Stream_New(data, length))) + { + WLog_ERR(TAG, "failed to allocate stream for parsing serialized format list"); + goto error; + } + + if (Stream_GetRemainingLength(s) < sizeof(UINT32)) + { + WLog_ERR(TAG, "too short serialized format list"); + 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 (i = 0; i < *numFormats; i++) + { + const char* formatName = NULL; + size_t formatNameLength = 0; + + if (Stream_GetRemainingLength(s) < sizeof(UINT32)) + { + WLog_ERR(TAG, "unexpected end of serialized format list"); + 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) +{ + UINT32 i; + + for (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; + BYTE* data = NULL; + CLIPRDR_FORMAT* formats = NULL; + xfContext* xfc = clipboard->xfc; + *numFormats = 0; + XGetWindowProperty(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) +{ + int i; + Atom atom; + BYTE* data = NULL; + int format_property; + unsigned long length; + unsigned long bytes_left; + xfCliprdrFormat* format = NULL; + CLIPRDR_FORMAT* formats = NULL; + xfContext* xfc = clipboard->xfc; + *numFormats = 0; + XGetWindowProperty(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 (i = 0; i < length; i++) + { + atom = ((Atom*) data)[i]; + format = xf_cliprdr_get_client_format_by_atom(clipboard, atom); + + if (format) + { + formats[*numFormats].formatId = format->formatId; + 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; + *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 = clipboard->xfc; + formats = xf_cliprdr_serialize_server_format_list(clipboard); + + if (formats) + { + XChangeProperty(xfc->display, xfc->drawable, clipboard->raw_format_list_atom, + clipboard->raw_format_list_atom, 8, PropModeReplace, + Stream_Buffer(formats), Stream_Length(formats)); + } + else + { + XDeleteProperty(xfc->display, xfc->drawable, clipboard->raw_format_list_atom); + } + + Stream_Free(formats, TRUE); +} + +static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard) +{ + UINT32 numFormats = 0; + CLIPRDR_FORMAT* formats = NULL; + CLIPRDR_FORMAT_LIST formatList; + formats = xf_cliprdr_get_client_formats(clipboard, &numFormats); + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = numFormats; + formatList.formats = formats; + clipboard->context->ClientFormatList(clipboard->context, &formatList); + xf_cliprdr_free_formats(formats, numFormats); +} + +static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, + BOOL hasData, BYTE* data, int size) +{ + BOOL bSuccess; + UINT32 SrcSize; + UINT32 DstSize; + UINT32 srcFormatId; + UINT32 dstFormatId; + BYTE* pDstData = NULL; + xfCliprdrFormat* format; + + 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, NULL, 0); + return; + } + + srcFormatId = 0; + + switch (format->formatId) + { + case CF_RAW: + srcFormatId = CF_RAW; + break; + + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + size = strlen((char*) data) + 1; + srcFormatId = ClipboardGetFormatId(clipboard->system, "UTF8_STRING"); + break; + + case CF_DIB: + srcFormatId = ClipboardGetFormatId(clipboard->system, "image/bmp"); + break; + + case CB_FORMAT_HTML: + srcFormatId = ClipboardGetFormatId(clipboard->system, "text/html"); + break; + + case CB_FORMAT_TEXTURILIST: + srcFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list"); + break; + } + + SrcSize = (UINT32) size; + bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize); + + if (format->formatName) + dstFormatId = ClipboardGetFormatId(clipboard->system, format->formatName); + else + dstFormatId = format->formatId; + + if (bSuccess) + { + DstSize = 0; + pDstData = (BYTE*) ClipboardGetData(clipboard->system, dstFormatId, &DstSize); + } + + if (!pDstData) + { + xf_cliprdr_send_data_response(clipboard, 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. + */ + if (dstFormatId && + (dstFormatId == ClipboardGetFormatId(clipboard->system, "FileGroupDescriptorW"))) + { + UINT error = NO_ERROR; + FILEDESCRIPTOR* file_array = (FILEDESCRIPTOR*) pDstData; + UINT32 file_count = DstSize / sizeof(FILEDESCRIPTOR); + pDstData = NULL; + DstSize = 0; + error = cliprdr_serialize_file_list(file_array, file_count, &pDstData, &DstSize); + + if (error) + WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error); + + free(file_array); + } + + xf_cliprdr_send_data_response(clipboard, pDstData, (int) DstSize); + free(pDstData); +} + +static BOOL xf_cliprdr_get_requested_data(xfClipboard* clipboard, Atom target) +{ + Atom type; + BYTE* data = NULL; + BOOL has_data = FALSE; + int format_property; + unsigned long dummy; + unsigned long length; + unsigned long bytes_left; + xfCliprdrFormat* format; + xfContext* xfc = clipboard->xfc; + format = xf_cliprdr_get_client_format_by_id(clipboard, + clipboard->requestedFormatId); + + if (!format || (format->atom != target)) + { + xf_cliprdr_send_data_response(clipboard, NULL, 0); + return FALSE; + } + + XGetWindowProperty(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 */ + } + 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 (XGetWindowProperty(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; + bytes_left = length * format_property / 8; + new_data = (BYTE*) realloc(clipboard->incr_data, + clipboard->incr_data_length + bytes_left); + + if (!new_data) + return FALSE; + + 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 + { + } + } + + XDeleteProperty(xfc->display, xfc->drawable, clipboard->property_atom); + xf_cliprdr_process_requested_data(clipboard, has_data, data, (int) bytes_left); + + if (data) + XFree(data); + + return TRUE; +} + +static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target) +{ + int i; + + if (clipboard->numTargets >= ARRAYSIZE(clipboard->targets)) + return; + + for (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, XEvent* respond) +{ + xfContext* xfc = clipboard->xfc; + + if (respond->xselection.property != None) + { + XChangeProperty(xfc->display, respond->xselection.requestor, + respond->xselection.property, XA_ATOM, 32, PropModeReplace, + (BYTE*) clipboard->targets, clipboard->numTargets); + } +} + +static void xf_cliprdr_provide_data(xfClipboard* clipboard, XEvent* respond, + BYTE* data, UINT32 size) +{ + xfContext* xfc = clipboard->xfc; + + if (respond->xselection.property != None) + { + XChangeProperty(xfc->display, respond->xselection.requestor, + respond->xselection.property, respond->xselection.target, + 8, PropModeReplace, data, size); + } +} + +static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard, + XEvent* xevent) +{ + if (xevent->xselection.target == clipboard->targets[1]) + { + if (xevent->xselection.property == None) + { + xf_cliprdr_send_client_format_list(clipboard); + } + else + { + xf_cliprdr_get_requested_targets(clipboard); + } + + return TRUE; + } + else + { + return xf_cliprdr_get_requested_data(clipboard, xevent->xselection.target); + } +} + +static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard) +{ + if (clipboard->data) + { + free(clipboard->data); + clipboard->data = NULL; + } + + clipboard->data_length = 0; + + if (clipboard->data_raw) + { + free(clipboard->data_raw); + clipboard->data_raw = NULL; + } + + clipboard->data_raw_length = 0; +} + +static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard, + XEvent* xevent) +{ + int fmt; + Atom type; + UINT32 formatId; + const char* formatName; + XEvent* respond; + BYTE* data = NULL; + BOOL delayRespond; + BOOL rawTransfer; + BOOL matchingFormat; + unsigned long length; + unsigned long bytes_left; + CLIPRDR_FORMAT* format; + xfContext* xfc = clipboard->xfc; + + if (xevent->xselectionrequest.owner != xfc->drawable) + return FALSE; + + delayRespond = FALSE; + + if (!(respond = (XEvent*) calloc(1, sizeof(XEvent)))) + { + WLog_ERR(TAG, "failed to allocate XEvent data"); + return FALSE; + } + + respond->xselection.property = None; + respond->xselection.type = SelectionNotify; + respond->xselection.display = xevent->xselectionrequest.display; + respond->xselection.requestor = xevent->xselectionrequest.requestor; + respond->xselection.selection = xevent->xselectionrequest.selection; + respond->xselection.target = xevent->xselectionrequest.target; + respond->xselection.time = xevent->xselectionrequest.time; + + if (xevent->xselectionrequest.target == clipboard->targets[0]) /* TIMESTAMP */ + { + /* TODO */ + } + else if (xevent->xselectionrequest.target == + clipboard->targets[1]) /* TARGETS */ + { + /* Someone else requests our available formats */ + respond->xselection.property = xevent->xselectionrequest.property; + xf_cliprdr_provide_targets(clipboard, respond); + } + else + { + format = xf_cliprdr_get_server_format_by_atom(clipboard, + xevent->xselectionrequest.target); + + if (format && (xevent->xselectionrequest.requestor != xfc->drawable)) + { + formatId = format->formatId; + formatName = format->formatName; + rawTransfer = FALSE; + + if (formatId == CF_RAW) + { + if (XGetWindowProperty(xfc->display, xevent->xselectionrequest.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); + } + } + + /* We can compare format names by pointer value here as they are both + * taken from the same clipboard->serverFormats array */ + matchingFormat = (formatId == clipboard->data_format_id) + && (formatName == clipboard->data_format_name); + + if (matchingFormat && (clipboard->data != 0) && !rawTransfer) + { + /* Cached converted clipboard data available. Send it now */ + respond->xselection.property = xevent->xselectionrequest.property; + xf_cliprdr_provide_data(clipboard, respond, clipboard->data, + clipboard->data_length); + } + else if (matchingFormat && (clipboard->data_raw != 0) && rawTransfer) + { + /* Cached raw clipboard data available. Send it now */ + respond->xselection.property = xevent->xselectionrequest.property; + xf_cliprdr_provide_data(clipboard, respond, clipboard->data_raw, clipboard->data_raw_length); + } + else if (clipboard->respond) + { + /* duplicate request */ + } + else + { + /** + * Send clipboard data request to the server. + * Response will be postponed after receiving the data + */ + xf_cliprdr_clear_cached_data(clipboard); + respond->xselection.property = xevent->xselectionrequest.property; + clipboard->respond = respond; + clipboard->data_format_id = formatId; + clipboard->data_format_name = formatName; + clipboard->data_raw_format = rawTransfer; + delayRespond = TRUE; + xf_cliprdr_send_data_request(clipboard, formatId); + } + } + } + + if (!delayRespond) + { + XSendEvent(xfc->display, xevent->xselectionrequest.requestor, 0, 0, respond); + XFlush(xfc->display); + free(respond); + } + + return TRUE; +} + +static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard, + XEvent* xevent) +{ + xfContext* xfc = clipboard->xfc; + + if (xf_cliprdr_is_self_owned(clipboard)) + return FALSE; + + XDeleteProperty(xfc->display, clipboard->root_window, clipboard->property_atom); + return TRUE; +} + +static BOOL xf_cliprdr_process_property_notify(xfClipboard* clipboard, + XEvent* xevent) +{ + xfCliprdrFormat* format; + xfContext* xfc = NULL; + + if (!clipboard) + return TRUE; + + xfc = clipboard->xfc; + + if (xevent->xproperty.atom != clipboard->property_atom) + return FALSE; /* Not cliprdr-related */ + + if (xevent->xproperty.window == clipboard->root_window) + { + xf_cliprdr_send_client_format_list(clipboard); + } + else if ((xevent->xproperty.window == xfc->drawable) && + (xevent->xproperty.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, XEvent* event) +{ + xfClipboard* clipboard; + + if (!xfc || !event) + return; + + clipboard = xfc->clipboard; + + if (!clipboard) + return; + +#ifdef WITH_XFIXES + + if (clipboard->xfixes_supported + && event->type == XFixesSelectionNotify + clipboard->xfixes_event_base) + { + XFixesSelectionNotifyEvent* se = (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: + xf_cliprdr_process_selection_notify(clipboard, event); + break; + + case SelectionRequest: + xf_cliprdr_process_selection_request(clipboard, event); + break; + + case SelectionClear: + xf_cliprdr_process_selection_clear(clipboard, event); + break; + + case PropertyNotify: + xf_cliprdr_process_property_notify(clipboard, event); + break; + + case FocusIn: + if (!clipboard->xfixes_supported) + { + xf_cliprdr_check_owner(clipboard); + } + + break; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + 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; + + if (clipboard->streams_supported && clipboard->file_formats_registered) + generalCapabilitySet.generalFlags |= + CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS; + + 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) +{ + UINT32 i, numFormats; + CLIPRDR_FORMAT* formats = NULL; + CLIPRDR_FORMAT_LIST formatList; + xfContext* xfc = clipboard->xfc; + UINT ret; + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + numFormats = clipboard->numClientFormats; + + if (numFormats) + { + if (!(formats = (CLIPRDR_FORMAT*) calloc(numFormats, sizeof(CLIPRDR_FORMAT)))) + { + WLog_ERR(TAG, "failed to allocate %"PRIu32" CLIPRDR_FORMAT structs", numFormats); + return CHANNEL_RC_NO_MEMORY; + } + } + + for (i = 0; i < numFormats; i++) + { + formats[i].formatId = clipboard->clientFormats[i].formatId; + formats[i].formatName = clipboard->clientFormats[i].formatName; + } + + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = numFormats; + formatList.formats = formats; + ret = clipboard->context->ClientFormatList(clipboard->context, &formatList); + free(formats); + + 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); + } + + 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; + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.dataLen = 0; + 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, + CLIPRDR_MONITOR_READY* monitorReady) +{ + xfClipboard* clipboard = (xfClipboard*) context->custom; + UINT ret; + + if ((ret = xf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK) + return ret; + + if ((ret = xf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK) + return ret; + + clipboard->sync = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context, + CLIPRDR_CAPABILITIES* capabilities) +{ + UINT32 i; + const CLIPRDR_CAPABILITY_SET* caps; + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps; + const BYTE* capsPtr = (const BYTE*) capabilities->capabilitySets; + xfClipboard* clipboard = (xfClipboard*) context->custom; + clipboard->streams_supported = FALSE; + + for (i = 0; i < capabilities->cCapabilitiesSets; i++) + { + caps = (const CLIPRDR_CAPABILITY_SET*) capsPtr; + + if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) + { + generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*) caps; + + if (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED) + { + clipboard->streams_supported = TRUE; + } + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context, + CLIPRDR_FORMAT_LIST* formatList) +{ + UINT32 i; + int j; + xfClipboard* clipboard = (xfClipboard*) context->custom; + xfContext* xfc = clipboard->xfc; + UINT ret; + xf_cliprdr_clear_cached_data(clipboard); + clipboard->data_format_id = -1; + clipboard->data_format_name = NULL; + + if (clipboard->serverFormats) + { + for (j = 0; j < clipboard->numServerFormats; j++) + free(clipboard->serverFormats[j].formatName); + + free(clipboard->serverFormats); + clipboard->serverFormats = NULL; + clipboard->numServerFormats = 0; + } + + 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 (i = 0; i < formatList->numFormats; i++) + { + CLIPRDR_FORMAT* format = &formatList->formats[i]; + clipboard->serverFormats[i].formatId = format->formatId; + + if (format->formatName) + { + clipboard->serverFormats[i].formatName = _strdup(format->formatName); + + if (!clipboard->serverFormats[i].formatName) + { + UINT32 k; + + for (k = 0; k < i; k++) + free(clipboard->serverFormats[k].formatName); + + clipboard->numServerFormats = 0; + free(clipboard->serverFormats); + clipboard->serverFormats = NULL; + return CHANNEL_RC_NO_MEMORY; + } + } + } + + /* 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 (i = 0; i < formatList->numFormats; i++) + { + CLIPRDR_FORMAT* format = &formatList->formats[i]; + + for (j = 0; j < clipboard->numClientFormats; j++) + { + if (xf_cliprdr_formats_equal(format, &clipboard->clientFormats[j])) + { + xf_cliprdr_append_target(clipboard, clipboard->clientFormats[j].atom); + } + } + } + + ret = xf_cliprdr_send_client_format_list_response(clipboard, TRUE); + XSetSelectionOwner(xfc->display, clipboard->clipboard_atom, xfc->drawable, + CurrentTime); + XFlush(xfc->display); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_format_list_response(CliprdrClientContext* + context, CLIPRDR_FORMAT_LIST_RESPONSE* 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, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + BOOL rawTransfer; + xfCliprdrFormat* format = NULL; + UINT32 formatId = formatDataRequest->requestedFormatId; + xfClipboard* clipboard = (xfClipboard*) context->custom; + xfContext* xfc = clipboard->xfc; + rawTransfer = xf_cliprdr_is_raw_transfer_available(clipboard); + + if (rawTransfer) + { + format = xf_cliprdr_get_client_format_by_id(clipboard, CF_RAW); + XChangeProperty(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); + + if (!format) + return xf_cliprdr_send_data_response(clipboard, NULL, 0); + + clipboard->requestedFormatId = rawTransfer ? CF_RAW : formatId; + 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, CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + BOOL bSuccess; + BYTE* pDstData; + UINT32 DstSize; + UINT32 SrcSize; + UINT32 srcFormatId; + UINT32 dstFormatId; + BOOL nullTerminated = FALSE; + UINT32 size = formatDataResponse->dataLen; + const BYTE* data = formatDataResponse->requestedFormatData; + xfClipboard* clipboard = (xfClipboard*) context->custom; + xfContext* xfc = clipboard->xfc; + + if (!clipboard->respond) + return CHANNEL_RC_OK; + + xf_cliprdr_clear_cached_data(clipboard); + pDstData = NULL; + DstSize = 0; + srcFormatId = 0; + dstFormatId = 0; + + if (clipboard->data_raw_format) + { + srcFormatId = CF_RAW; + dstFormatId = CF_RAW; + } + else if (clipboard->data_format_name) + { + if (strcmp(clipboard->data_format_name, "HTML Format") == 0) + { + srcFormatId = ClipboardGetFormatId(clipboard->system, "HTML Format"); + dstFormatId = ClipboardGetFormatId(clipboard->system, "text/html"); + nullTerminated = TRUE; + } + } + else + { + switch (clipboard->data_format_id) + { + case CF_TEXT: + srcFormatId = CF_TEXT; + dstFormatId = ClipboardGetFormatId(clipboard->system, "UTF8_STRING"); + nullTerminated = TRUE; + break; + + case CF_OEMTEXT: + srcFormatId = CF_OEMTEXT; + dstFormatId = ClipboardGetFormatId(clipboard->system, "UTF8_STRING"); + nullTerminated = TRUE; + break; + + case CF_UNICODETEXT: + srcFormatId = CF_UNICODETEXT; + dstFormatId = ClipboardGetFormatId(clipboard->system, "UTF8_STRING"); + nullTerminated = TRUE; + break; + + case CF_DIB: + srcFormatId = CF_DIB; + dstFormatId = ClipboardGetFormatId(clipboard->system, "image/bmp"); + break; + + default: + break; + } + } + + SrcSize = (UINT32) size; + bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize); + + if (bSuccess) + { + if (SrcSize == 0) + { + WLog_INFO(TAG, "skipping, empty data detected!!!"); + return CHANNEL_RC_OK; + } + + DstSize = 0; + pDstData = (BYTE*) ClipboardGetData(clipboard->system, dstFormatId, &DstSize); + + if (!pDstData) + { + WLog_ERR(TAG, "failed to get clipboard data in format %s [source format %s]", + ClipboardGetFormatName(clipboard->system, dstFormatId), + ClipboardGetFormatName(clipboard->system, srcFormatId)); + return ERROR_INTERNAL_ERROR; + } + + if (nullTerminated) + { + while (DstSize > 0 && pDstData[DstSize - 1] == '\0') + DstSize--; + } + } + + /* Cache converted and original data to avoid doing a possibly costly + * conversion again on subsequent requests */ + clipboard->data = pDstData; + clipboard->data_length = DstSize; + /* 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->data_raw = (BYTE*) malloc(size); + + if (clipboard->data_raw) + { + CopyMemory(clipboard->data_raw, data, size); + clipboard->data_raw_length = size; + } + else + { + WLog_WARN(TAG, "failed to allocate %"PRIu32" bytes for a copy of raw clipboard data", size); + } + + xf_cliprdr_provide_data(clipboard, clipboard->respond, pDstData, DstSize); + XSendEvent(xfc->display, clipboard->respond->xselection.requestor, 0, 0, + clipboard->respond); + XFlush(xfc->display); + free(clipboard->respond); + clipboard->respond = NULL; + return CHANNEL_RC_OK; +} + +static UINT xf_cliprdr_server_file_size_request(xfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileSizeRequest request; + ZeroMemory(&request, sizeof(request)); + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + + if (fileContentsRequest->cbRequested != sizeof(UINT64)) + { + WLog_WARN(TAG, "unexpected FILECONTENTS_SIZE request: %"PRIu32" bytes", + fileContentsRequest->cbRequested); + } + + return clipboard->delegate->ClientRequestFileSize(clipboard->delegate, &request); +} + +static UINT xf_cliprdr_server_file_range_request(xfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileRangeRequest request; + ZeroMemory(&request, sizeof(request)); + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + request.nPositionLow = fileContentsRequest->nPositionLow; + request.nPositionHigh = fileContentsRequest->nPositionHigh; + request.cbRequested = fileContentsRequest->cbRequested; + return clipboard->delegate->ClientRequestFileRange(clipboard->delegate, &request); +} + +static UINT xf_cliprdr_send_file_contents_failure(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + response.dwFlags = fileContentsRequest->dwFlags; + return context->ClientFileContentsResponse(context, &response); +} + +static UINT xf_cliprdr_server_file_contents_request(CliprdrClientContext* context, + CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + UINT error = NO_ERROR; + xfClipboard* clipboard = context->custom; + + /* + * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST): + * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time. + */ + if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) == + (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) + { + WLog_ERR(TAG, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); + return xf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) + error = xf_cliprdr_server_file_size_request(clipboard, fileContentsRequest); + + if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) + error = xf_cliprdr_server_file_range_request(clipboard, fileContentsRequest); + + if (error) + { + WLog_ERR(TAG, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", error); + return xf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + return CHANNEL_RC_OK; +} + +static UINT xf_cliprdr_clipboard_file_size_success(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, UINT64 fileSize) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + xfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_SIZE; + response.cbRequested = sizeof(UINT64); + response.requestedData = (BYTE*) &fileSize; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT xf_cliprdr_clipboard_file_size_failure(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + xfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_SIZE; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT xf_cliprdr_clipboard_file_range_success(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, const BYTE* data, UINT32 size) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + xfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_RANGE; + response.cbRequested = size; + response.requestedData = (BYTE*) data; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT xf_cliprdr_clipboard_file_range_failure(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response; + xfClipboard* clipboard = delegate->custom; + ZeroMemory(&response, sizeof(response)); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + response.dwFlags = FILECONTENTS_RANGE; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +xfClipboard* xf_clipboard_new(xfContext* xfc) +{ + int i, n = 0; + rdpChannels* channels; + xfClipboard* clipboard; + + if (!(clipboard = (xfClipboard*) calloc(1, sizeof(xfClipboard)))) + { + WLog_ERR(TAG, "failed to allocate xfClipboard data"); + return NULL; + } + + xfc->clipboard = clipboard; + clipboard->xfc = xfc; + channels = ((rdpContext*) xfc)->channels; + clipboard->channels = channels; + clipboard->system = ClipboardCreate(); + clipboard->requestedFormatId = -1; + clipboard->root_window = DefaultRootWindow(xfc->display); + clipboard->clipboard_atom = XInternAtom(xfc->display, "CLIPBOARD", FALSE); + + if (clipboard->clipboard_atom == None) + { + WLog_ERR(TAG, "unable to get CLIPBOARD atom"); + goto error; + } + + 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, xfminor; + + 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 + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "_FREERDP_RAW", False); + clipboard->clientFormats[n].formatId = CF_RAW; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "UTF8_STRING", False); + clipboard->clientFormats[n].formatId = CF_UNICODETEXT; + n++; + clipboard->clientFormats[n].atom = XA_STRING; + clipboard->clientFormats[n].formatId = CF_TEXT; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/png", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_PNG; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/jpeg", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_JPEG; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/gif", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_GIF; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/bmp", False); + clipboard->clientFormats[n].formatId = CF_DIB; + n++; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "text/html", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_HTML; + clipboard->clientFormats[n].formatName = _strdup("HTML Format"); + + if (!clipboard->clientFormats[n].formatName) + goto error; + + 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. + */ + if (ClipboardGetFormatId(clipboard->system, "text/uri-list")) + { + clipboard->file_formats_registered = TRUE; + clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "text/uri-list", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_TEXTURILIST; + clipboard->clientFormats[n].formatName = _strdup("FileGroupDescriptorW"); + + if (!clipboard->clientFormats[n].formatName) + goto error; + + n++; + } + + 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); + clipboard->delegate = ClipboardGetDelegate(clipboard->system); + clipboard->delegate->custom = clipboard; + clipboard->delegate->ClipboardFileSizeSuccess = xf_cliprdr_clipboard_file_size_success; + clipboard->delegate->ClipboardFileSizeFailure = xf_cliprdr_clipboard_file_size_failure; + clipboard->delegate->ClipboardFileRangeSuccess = xf_cliprdr_clipboard_file_range_success; + clipboard->delegate->ClipboardFileRangeFailure = xf_cliprdr_clipboard_file_range_failure; + return clipboard; +error: + + for (i = 0; i < n; i++) + free(clipboard->clientFormats[i].formatName); + + ClipboardDestroy(clipboard->system); + free(clipboard); + return NULL; +} + +void xf_clipboard_free(xfClipboard* clipboard) +{ + int i; + + if (!clipboard) + return; + + if (clipboard->serverFormats) + { + for (i = 0; i < clipboard->numServerFormats; i++) + free(clipboard->serverFormats[i].formatName); + + free(clipboard->serverFormats); + clipboard->serverFormats = NULL; + } + + if (clipboard->numClientFormats) + { + for (i = 0; i < clipboard->numClientFormats; i++) + free(clipboard->clientFormats[i].formatName); + } + + ClipboardDestroy(clipboard->system); + free(clipboard->data); + free(clipboard->data_raw); + free(clipboard->respond); + free(clipboard->incr_data); + free(clipboard); +} + +void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr) +{ + xfc->cliprdr = cliprdr; + xfc->clipboard->context = cliprdr; + cliprdr->custom = (void*) xfc->clipboard; + 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->ServerFileContentsRequest = xf_cliprdr_server_file_contents_request; +} + +void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr) +{ + xfc->cliprdr = NULL; + cliprdr->custom = NULL; + + if (xfc->clipboard) + xfc->clipboard->context = NULL; +} diff --git a/client/X11/xf_cliprdr.h b/client/X11/xf_cliprdr.h new file mode 100644 index 0000000..f9c3583 --- /dev/null +++ b/client/X11/xf_cliprdr.h @@ -0,0 +1,36 @@ +/** + * 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 + +xfClipboard* xf_clipboard_new(xfContext* xfc); +void xf_clipboard_free(xfClipboard* clipboard); + +void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr); +void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr); + +void xf_cliprdr_handle_xevent(xfContext* xfc, 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..1b30976 --- /dev/null +++ b/client/X11/xf_disp.c @@ -0,0 +1,456 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Display Control channel + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +#ifdef WITH_XRANDR +#include +#include + +#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105 +# define USABLE_XRANDR +#endif + +#endif + +#include "xf_disp.h" +#include "xf_monitor.h" + + +#define TAG CLIENT_TAG("x11disp") +#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ + +struct _xfDispContext +{ + xfContext* xfc; + DispClientContext* disp; + BOOL haveXRandr; + int eventBase, errorBase; + int lastSentWidth, lastSentHeight; + UINT64 lastSentDate; + int targetWidth, targetHeight; + BOOL activated; + BOOL waitingResize; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; +}; + +static UINT xf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, int nmonitors); + +static BOOL xf_disp_settings_changed(xfDispContext* xfDisp) +{ + rdpSettings* settings = xfDisp->xfc->context.settings; + + if (xfDisp->lastSentWidth != xfDisp->targetWidth) + return TRUE; + + if (xfDisp->lastSentHeight != xfDisp->targetHeight) + return TRUE; + + if (xfDisp->lastSentDesktopOrientation != settings->DesktopOrientation) + return TRUE; + + if (xfDisp->lastSentDesktopScaleFactor != settings->DesktopScaleFactor) + return TRUE; + + if (xfDisp->lastSentDeviceScaleFactor != settings->DeviceScaleFactor) + return TRUE; + + if (xfDisp->fullscreen != xfDisp->xfc->fullscreen) + return TRUE; + + return FALSE; +} + +static BOOL xf_update_last_sent(xfDispContext* xfDisp) +{ + rdpSettings* settings = xfDisp->xfc->context.settings; + xfDisp->lastSentWidth = xfDisp->targetWidth; + xfDisp->lastSentHeight = xfDisp->targetHeight; + xfDisp->lastSentDesktopOrientation = settings->DesktopOrientation; + xfDisp->lastSentDesktopScaleFactor = settings->DesktopScaleFactor; + xfDisp->lastSentDeviceScaleFactor = settings->DeviceScaleFactor; + xfDisp->fullscreen = xfDisp->xfc->fullscreen; + return TRUE; +} + +static BOOL xf_disp_sendResize(xfDispContext* xfDisp) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout; + xfContext* xfc; + rdpSettings* settings; + + if (!xfDisp || !xfDisp->xfc) + return FALSE; + + xfc = xfDisp->xfc; + settings = xfc->context.settings; + + if (!settings) + return FALSE; + + if (!xfDisp->activated || !xfDisp->disp) + return TRUE; + + if (GetTickCount64() - xfDisp->lastSentDate < RESIZE_MIN_DELAY) + return TRUE; + + xfDisp->lastSentDate = GetTickCount64(); + + if (!xf_disp_settings_changed(xfDisp)) + return TRUE; + + if (xfc->fullscreen && (settings->MonitorCount > 0)) + { + if (xf_disp_sendLayout(xfDisp->disp, settings->MonitorDefArray, + settings->MonitorCount) != CHANNEL_RC_OK) + return FALSE; + } + else + { + xfDisp->waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = xfDisp->targetWidth; + layout.Height = xfDisp->targetHeight; + layout.Orientation = settings->DesktopOrientation; + layout.DesktopScaleFactor = settings->DesktopScaleFactor; + layout.DeviceScaleFactor = settings->DeviceScaleFactor; + layout.PhysicalWidth = xfDisp->targetWidth; + layout.PhysicalHeight = xfDisp->targetHeight; + + 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_set_window_resizable(xfDispContext* xfDisp) +{ + XSizeHints* size_hints; + + 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; + + if (!context) + return FALSE; + + xfc = (xfContext*)context; + + if (!(xfc->xfDisp)) + return FALSE; + + if (!xfc->context.settings) + return FALSE; + + *ppXfc = xfc; + *ppXfDisp = xfc->xfDisp; + *ppSettings = xfc->context.settings; + return TRUE; +} + +static void xf_disp_OnActivated(void* context, ActivatedEventArgs* e) +{ + xfContext* xfc; + xfDispContext* xfDisp; + rdpSettings* settings; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + xfDisp->waitingResize = FALSE; + + if (xfDisp->activated && !settings->Fullscreen) + { + xf_disp_set_window_resizable(xfDisp); + + if (e->firstActivation) + return; + + xf_disp_sendResize(xfDisp); + } +} + +static void xf_disp_OnGraphicsReset(void* context, GraphicsResetEventArgs* e) +{ + xfContext* xfc; + xfDispContext* xfDisp; + rdpSettings* settings; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + xfDisp->waitingResize = FALSE; + + if (xfDisp->activated && !settings->Fullscreen) + { + xf_disp_set_window_resizable(xfDisp); + xf_disp_sendResize(xfDisp); + } +} + +static void xf_disp_OnTimer(void* context, TimerEventArgs* e) +{ + xfContext* xfc; + xfDispContext* xfDisp; + rdpSettings* settings; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (!xfDisp->activated || settings->Fullscreen) + return; + + xf_disp_sendResize(xfDisp); +} + +xfDispContext* xf_disp_new(xfContext* xfc) +{ + xfDispContext* ret; + + if (!xfc || !xfc->context.settings || !xfc->context.pubSub) + return NULL; + + 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 = xfc->context.settings->DesktopWidth; + ret->lastSentHeight = ret->targetHeight = xfc->context.settings->DesktopHeight; + PubSub_SubscribeActivated(xfc->context.pubSub, xf_disp_OnActivated); + PubSub_SubscribeGraphicsReset(xfc->context.pubSub, xf_disp_OnGraphicsReset); + PubSub_SubscribeTimer(xfc->context.pubSub, xf_disp_OnTimer); + return ret; +} + +void xf_disp_free(xfDispContext* disp) +{ + if (!disp) + return; + + if (disp->xfc) + { + PubSub_UnsubscribeActivated(disp->xfc->context.pubSub, xf_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(disp->xfc->context.pubSub, xf_disp_OnGraphicsReset); + PubSub_UnsubscribeTimer(disp->xfc->context.pubSub, xf_disp_OnTimer); + } + + free(disp); +} + +UINT xf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, int nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts; + int i; + xfDispContext* xfDisp = (xfDispContext*)disp->custom; + rdpSettings* settings = xfDisp->xfc->context.settings; + layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!layouts) + return CHANNEL_RC_NO_MEMORY; + + for (i = 0; i < nmonitors; i++) + { + layouts[i].Flags = (monitors[i].is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layouts[i].Left = monitors[i].x; + layouts[i].Top = monitors[i].y; + layouts[i].Width = monitors[i].width; + layouts[i].Height = monitors[i].height; + layouts[i].Orientation = ORIENTATION_LANDSCAPE; + layouts[i].PhysicalWidth = monitors[i].attributes.physicalWidth; + layouts[i].PhysicalHeight = monitors[i].attributes.physicalHeight; + + switch (monitors[i].attributes.orientation) + { + case 90: + layouts[i].Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layouts[i].Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layouts[i].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 + */ + layouts[i].Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layouts[i].DesktopScaleFactor = settings->DesktopScaleFactor; + layouts[i].DeviceScaleFactor = settings->DeviceScaleFactor; + } + + ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts); + free(layouts); + return ret; +} + +BOOL xf_disp_handle_xevent(xfContext* xfc, XEvent* event) +{ + xfDispContext* xfDisp; + rdpSettings* settings; + UINT32 maxWidth, maxHeight; + + if (!xfc || !event) + return FALSE; + + xfDisp = xfc->xfDisp; + + if (!xfDisp) + return FALSE; + + settings = xfc->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); + return xf_disp_sendLayout(xfDisp->disp, settings->MonitorDefArray, + settings->MonitorCount) == CHANNEL_RC_OK; +} + +BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height) +{ + xfDispContext* xfDisp; + + if (!xfc) + return FALSE; + + xfDisp = xfc->xfDisp; + + if (!xfDisp) + return FALSE; + + xfDisp->targetWidth = width; + xfDisp->targetHeight = height; + return xf_disp_sendResize(xfDisp); +} + +static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + xfDispContext* xfDisp = (xfDispContext*)disp->custom; + rdpSettings* settings = xfDisp->xfc->context.settings; + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %"PRIu32" MaxMonitorAreaFactorA: %"PRIu32" MaxMonitorAreaFactorB: %"PRIu32"", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + xfDisp->activated = TRUE; + + if (settings->Fullscreen) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizeable"); + return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp) +{ + rdpSettings* settings; + + if (!xfDisp || !xfDisp->xfc || !disp) + return FALSE; + + settings = xfDisp->xfc->context.settings; + + if (!settings) + return FALSE; + + xfDisp->disp = disp; + disp->custom = (void*) xfDisp; + + if (settings->DynamicResolutionUpdate) + { + disp->DisplayControlCaps = xf_DisplayControlCaps; +#ifdef USABLE_XRANDR + + if (settings->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..6e2db41 --- /dev/null +++ b/client/X11/xf_disp.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Display Control channel + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +#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); + +xfDispContext* xf_disp_new(xfContext* xfc); +void xf_disp_free(xfDispContext* disp); +BOOL xf_disp_handle_xevent(xfContext* xfc, 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..70c3aee --- /dev/null +++ b/client/X11/xf_event.c @@ -0,0 +1,1125 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Event Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#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_event.h" +#include "xf_input.h" + +#define TAG CLIENT_TAG("x11") + +#define CLAMP_COORDINATES(x, y) if (x < 0) x = 0; if (y < 0) y = 0 + +static 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) +{ + char* xevent; + FILE* actionScript; + char buffer[1024] = { 0 }; + char command[1024] = { 0 }; + xfc->xevents = ArrayList_New(TRUE); + + if (!xfc->xevents) + return FALSE; + + ArrayList_Object(xfc->xevents)->fnObjectFree = free; + sprintf_s(command, sizeof(command), "%s xevent", xfc->context.settings->ActionScript); + actionScript = popen(command, "r"); + + if (!actionScript) + return FALSE; + + while (fgets(buffer, sizeof(buffer), actionScript)) + { + strtok(buffer, "\n"); + xevent = _strdup(buffer); + + if (!xevent || ArrayList_Add(xfc->xevents, xevent) < 0) + { + 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, XEvent* event) +{ + int index; + int count; + char* name; + FILE* actionScript; + BOOL match = FALSE; + const char* xeventName; + char buffer[1024] = { 0 }; + char command[1024] = { 0 }; + + if (!xfc->actionScriptExists || !xfc->xevents) + return FALSE; + + if (event->type > LASTEvent) + return FALSE; + + xeventName = x11_event_string(event->type); + count = ArrayList_Count(xfc->xevents); + + for (index = 0; index < count; index++) + { + name = (char*) ArrayList_GetItem(xfc->xevents, index); + + if (_stricmp(name, xeventName) == 0) + { + match = TRUE; + break; + } + } + + if (!match) + return FALSE; + + sprintf_s(command, sizeof(command), "%s xevent %s %lu", + xfc->context.settings->ActionScript, xeventName, (unsigned long) xfc->window->handle); + actionScript = popen(command, "r"); + + if (!actionScript) + return FALSE; + + while (fgets(buffer, sizeof(buffer), actionScript)) + { + strtok(buffer, "\n"); + } + + pclose(actionScript); + return TRUE; +} +void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y) +{ + rdpSettings* settings; + + if (!xfc || !xfc->context.settings || !y || !x) + return; + + settings = xfc->context.settings; + + if (!xfc->remote_app) + { +#ifdef WITH_XRENDER + + if (xf_picture_transform_required(xfc)) + { + double xScalingFactor = settings->DesktopWidth / (double)xfc->scaledWidth; + double yScalingFactor = settings->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, XEvent* event, BOOL app) +{ + int x, y; + int w, h; + rdpSettings* settings = xfc->context.settings; + + if (!app && (settings->SmartSizing || settings->MultiTouchGestures)) + { + x = 0; + y = 0; + w = settings->DesktopWidth; + h = settings->DesktopHeight; + } + else + { + x = event->xexpose.x; + y = event->xexpose.y; + w = event->xexpose.width; + h = event->xexpose.height; + } + + if (xfc->context.gdi->gfx) + { + xf_OutputExpose(xfc, x, y, w, h); + return TRUE; + } + + if (!app) + { + xf_draw_screen(xfc, x, y, w, h); + } + else + { + xfAppWindow* appWindow; + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window); + + if (appWindow) + { + xf_UpdateWindowArea(xfc, appWindow, x, y, w, h); + } + } + + return TRUE; +} +static BOOL xf_event_VisibilityNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + xfc->unobscured = event->xvisibility.state == VisibilityUnobscured; + return TRUE; +} +BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, + Window window, BOOL app) +{ + rdpInput* input; + Window childWindow; + input = xfc->context.input; + + if (!xfc->context.settings->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_input_send_mouse_event(input, PTR_FLAGS_MOVE, x, y); + + if (xfc->fullscreen && !app) + { + XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, + CurrentTime); + } + + return TRUE; +} +static BOOL xf_event_MotionNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + if (xfc->use_xinput) + return TRUE; + + if(xfc->floatbar && !(app)) + xf_floatbar_set_root_y(xfc, event->xmotion.y); + + return xf_generic_MotionNotify(xfc, event->xmotion.x, event->xmotion.y, + event->xmotion.state, event->xmotion.window, app); +} +BOOL xf_generic_ButtonPress(xfContext* xfc, int x, int y, int button, + Window window, BOOL app) +{ + int flags; + BOOL wheel; + BOOL extended; + rdpInput* input; + Window childWindow; + wheel = FALSE; + extended = FALSE; + input = xfc->context.input; + + switch (button) + { + case Button1: + case Button2: + case Button3: + flags = PTR_FLAGS_DOWN | xfc->button_map[button - BUTTON_BASE]; + break; + + case 4: + wheel = TRUE; + flags = PTR_FLAGS_WHEEL | 0x0078; + break; + + case 5: + wheel = TRUE; + flags = PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0078; + break; + + case 8: /* back */ + case 97: /* Xming */ + extended = TRUE; + flags = PTR_XFLAGS_DOWN | PTR_XFLAGS_BUTTON1; + break; + + case 9: /* forward */ + case 112: /* Xming */ + extended = TRUE; + flags = PTR_XFLAGS_DOWN | PTR_XFLAGS_BUTTON2; + break; + + case 6: /* wheel left */ + wheel = TRUE; + flags = PTR_FLAGS_HWHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x0078; + break; + + case 7: /* wheel right */ + wheel = TRUE; + flags = PTR_FLAGS_HWHEEL | 0x0078; + break; + + default: + x = 0; + y = 0; + flags = 0; + break; + } + + if (flags != 0) + { + if (wheel) + { + freerdp_input_send_mouse_event(input, flags, 0, 0); + } + else + { + 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_input_send_extended_mouse_event(input, flags, x, y); + else + freerdp_input_send_mouse_event(input, flags, x, y); + } + } + + return TRUE; +} +static BOOL xf_event_ButtonPress(xfContext* xfc, XEvent* event, BOOL app) +{ + if (xfc->use_xinput) + return TRUE; + + return xf_generic_ButtonPress(xfc, event->xbutton.x, event->xbutton.y, + event->xbutton.button, event->xbutton.window, app); +} +BOOL xf_generic_ButtonRelease(xfContext* xfc, int x, int y, int button, + Window window, BOOL app) +{ + int flags = 0; + BOOL extended = FALSE; + rdpInput* input; + Window childWindow; + + if (!xfc || !xfc->context.input) + return FALSE; + + input = xfc->context.input; + + switch (button) + { + case Button1: + case Button2: + case Button3: + flags = xfc->button_map[button - BUTTON_BASE]; + break; + + case 6: + case 8: + case 97: + extended = TRUE; + flags = PTR_XFLAGS_BUTTON1; + break; + + case 7: + case 9: + case 112: + extended = TRUE; + flags = PTR_XFLAGS_BUTTON2; + break; + + default: + flags = 0; + break; + } + + if (flags != 0) + { + 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_input_send_extended_mouse_event(input, flags, x, y); + else + freerdp_input_send_mouse_event(input, flags, x, y); + } + + return TRUE; +} +static BOOL xf_event_ButtonRelease(xfContext* xfc, XEvent* event, BOOL app) +{ + if (xfc->use_xinput) + return TRUE; + + return xf_generic_ButtonRelease(xfc, event->xbutton.x, event->xbutton.y, + event->xbutton.button, event->xbutton.window, app); +} +static BOOL xf_event_KeyPress(xfContext* xfc, XEvent* event, BOOL app) +{ + KeySym keysym; + char str[256]; + XLookupString((XKeyEvent*) event, str, sizeof(str), &keysym, NULL); + xf_keyboard_key_press(xfc, event->xkey.keycode, keysym); + return TRUE; +} +static BOOL xf_event_KeyRelease(xfContext* xfc, XEvent* event, BOOL app) +{ + KeySym keysym; + char str[256]; + XLookupString((XKeyEvent*) event, str, sizeof(str), &keysym, NULL); + xf_keyboard_key_release(xfc, event->xkey.keycode, keysym); + return TRUE; +} +static BOOL xf_event_FocusIn(xfContext* xfc, XEvent* event, BOOL app) +{ + if (event->xfocus.mode == NotifyGrab) + return TRUE; + + xfc->focused = TRUE; + + if (xfc->mouse_active && !app) + XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, + GrabModeAsync, CurrentTime); + + if (app) + { + xfAppWindow* appWindow; + xf_rail_send_activate(xfc, event->xany.window, TRUE); + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.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, XEvent* event, BOOL app) +{ + if (event->xfocus.mode == NotifyUngrab) + return TRUE; + + xfc->focused = FALSE; + + if (event->xfocus.mode == NotifyWhileGrabbed) + XUngrabKeyboard(xfc->display, CurrentTime); + + xf_keyboard_release_all_keypress(xfc); + xf_keyboard_clear(xfc); + + if (app) + xf_rail_send_activate(xfc, event->xany.window, FALSE); + + return TRUE; +} +static BOOL xf_event_MappingNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + if (event->xmapping.request == MappingModifier) + { + if (xfc->modifierMap) + XFreeModifiermap(xfc->modifierMap); + + xfc->modifierMap = XGetModifierMapping(xfc->display); + } + + return TRUE; +} +static BOOL xf_event_ClientMessage(xfContext* xfc, XEvent* event, BOOL app) +{ + if ((event->xclient.message_type == xfc->WM_PROTOCOLS) + && ((Atom) event->xclient.data.l[0] == xfc->WM_DELETE_WINDOW)) + { + if (app) + { + xfAppWindow* appWindow; + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.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, XEvent* event, BOOL app) +{ + if (!app) + { + xfc->mouse_active = TRUE; + + if (xfc->fullscreen) + XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, + CurrentTime); + + if (xfc->focused) + XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, + GrabModeAsync, CurrentTime); + } + else + { + xfAppWindow* appWindow; + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window); + + /* keep track of which window has focus so that we can apply pointer updates */ + + if (appWindow) + { + xfc->appWindow = appWindow; + } + } + + return TRUE; +} +static BOOL xf_event_LeaveNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + if (!app) + { + xfc->mouse_active = FALSE; + XUngrabKeyboard(xfc->display, CurrentTime); + } + + return TRUE; +} +static BOOL xf_event_ConfigureNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + Window childWindow; + xfAppWindow* appWindow; + rdpSettings* settings = xfc->context.settings; + + if (!app) + { + if (xfc->window->left != event->xconfigure.x) + xfc->window->left = event->xconfigure.x; + + if (xfc->window->top != event->xconfigure.y) + xfc->window->top = event->xconfigure.y; + + if (xfc->window->width != event->xconfigure.width || + xfc->window->height != event->xconfigure.height) + { + xfc->window->width = event->xconfigure.width; + xfc->window->height = event->xconfigure.height; +#ifdef WITH_XRENDER + xfc->offset_x = 0; + xfc->offset_y = 0; + + if (xfc->context.settings->SmartSizing + || xfc->context.settings->MultiTouchGestures) + { + xfc->scaledWidth = xfc->window->width; + xfc->scaledHeight = xfc->window->height; + xf_draw_screen(xfc, 0, 0, settings->DesktopWidth, settings->DesktopHeight); + } + else + { + xfc->scaledWidth = settings->DesktopWidth; + xfc->scaledHeight = settings->DesktopHeight; + } + +#endif + } + + if (settings->DynamicResolutionUpdate) + { + int alignedWidth, alignedHeight; + 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); + } + + return TRUE; + } + + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.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->xconfigure.width; + appWindow->height = event->xconfigure.height; + + /* + * 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->xconfigure.send_event + || appWindow->local_move.state == LMS_NOT_ACTIVE) + && !appWindow->rail_ignore_configure && xfc->focused) + xf_rail_adjust_position(xfc, appWindow); + } + } + + return TRUE; +} +static BOOL xf_event_MapNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + xfAppWindow* appWindow; + + if (!app) + gdi_send_suppress_output(xfc->context.gdi, FALSE); + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.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, XEvent* event, BOOL app) +{ + xfAppWindow* appWindow; + xf_keyboard_release_all_keypress(xfc); + + if (!app) + gdi_send_suppress_output(xfc->context.gdi, TRUE); + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window); + + if (appWindow) + { + appWindow->is_mapped = FALSE; + } + } + + return TRUE; +} +static BOOL xf_event_PropertyNotify(xfContext* xfc, XEvent* event, BOOL app) +{ + /* + * 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->xproperty.atom == xfc->_NET_WM_STATE) + && (event->xproperty.state != PropertyDelete)) || + (((Atom) event->xproperty.atom == xfc->WM_STATE) + && (event->xproperty.state != PropertyDelete))) + { + int i; + BOOL status; + BOOL maxVert = FALSE; + BOOL maxHorz = FALSE; + BOOL minimized = FALSE; + BOOL minimizedChanged = FALSE; + unsigned long nitems; + unsigned long bytes; + unsigned char* prop; + xfAppWindow* appWindow = NULL; + + if (app) + { + appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window); + + if (!appWindow) + return TRUE; + } + + if ((Atom) event->xproperty.atom == xfc->_NET_WM_STATE) + { + status = xf_GetWindowProperty(xfc, event->xproperty.window, + xfc->_NET_WM_STATE, 12, &nitems, &bytes, &prop); + + if (status) + { + for (i = 0; i < nitems; i++) + { + if ((Atom)((UINT16**) prop)[i] == XInternAtom(xfc->display, + "_NET_WM_STATE_MAXIMIZED_VERT", False)) + { + maxVert = TRUE; + } + + if ((Atom)((UINT16**) prop)[i] == XInternAtom(xfc->display, + "_NET_WM_STATE_MAXIMIZED_HORZ", False)) + { + maxHorz = TRUE; + } + } + + XFree(prop); + } + } + + if ((Atom) event->xproperty.atom == xfc->WM_STATE) + { + status = xf_GetWindowProperty(xfc, event->xproperty.window, + xfc->WM_STATE, 1, &nitems, &bytes, &prop); + + if (status) + { + /* If the window is in the iconic state */ + if (((UINT32) *prop == 3)) + minimized = TRUE; + else + minimized = FALSE; + + minimizedChanged = TRUE; + XFree(prop); + } + } + + if (app) + { + if (maxVert && maxHorz && !minimized + && (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 (minimized && (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 (!minimized && !maxVert && !maxHorz + && (appWindow->rail_state != WINDOW_SHOW)) + { + appWindow->rail_state = WINDOW_SHOW; + xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE); + } + } + else if (minimizedChanged) + gdi_send_suppress_output(xfc->context.gdi, minimized); + } + + return TRUE; +} +static BOOL xf_event_suppress_events(xfContext* xfc, xfAppWindow* appWindow, + 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, XEvent* event) +{ + BOOL status = TRUE; + xfAppWindow* appWindow; + xfContext* xfc = (xfContext*) instance->context; + rdpSettings* settings = xfc->context.settings; + + if (xfc->remote_app) + { + 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->floatbar && xf_floatbar_check_event(xfc, event)) + { + xf_floatbar_event_process(xfc, event); + 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, xfc->remote_app); + break; + + case VisibilityNotify: + status = xf_event_VisibilityNotify(xfc, event, xfc->remote_app); + break; + + case MotionNotify: + status = xf_event_MotionNotify(xfc, event, xfc->remote_app); + break; + + case ButtonPress: + status = xf_event_ButtonPress(xfc, event, xfc->remote_app); + break; + + case ButtonRelease: + status = xf_event_ButtonRelease(xfc, event, xfc->remote_app); + break; + + case KeyPress: + status = xf_event_KeyPress(xfc, event, xfc->remote_app); + break; + + case KeyRelease: + status = xf_event_KeyRelease(xfc, event, xfc->remote_app); + break; + + case FocusIn: + status = xf_event_FocusIn(xfc, event, xfc->remote_app); + break; + + case FocusOut: + status = xf_event_FocusOut(xfc, event, xfc->remote_app); + break; + + case EnterNotify: + status = xf_event_EnterNotify(xfc, event, xfc->remote_app); + break; + + case LeaveNotify: + status = xf_event_LeaveNotify(xfc, event, xfc->remote_app); + break; + + case NoExpose: + break; + + case GraphicsExpose: + break; + + case ConfigureNotify: + status = xf_event_ConfigureNotify(xfc, event, xfc->remote_app); + break; + + case MapNotify: + status = xf_event_MapNotify(xfc, event, xfc->remote_app); + break; + + case UnmapNotify: + status = xf_event_UnmapNotify(xfc, event, xfc->remote_app); + break; + + case ReparentNotify: + break; + + case MappingNotify: + status = xf_event_MappingNotify(xfc, event, xfc->remote_app); + break; + + case ClientMessage: + status = xf_event_ClientMessage(xfc, event, xfc->remote_app); + break; + + case PropertyNotify: + status = xf_event_PropertyNotify(xfc, event, xfc->remote_app); + break; + + default: + if (settings->SupportDisplayControl) + xf_disp_handle_xevent(xfc, event); + + break; + } + + xf_cliprdr_handle_xevent(xfc, event); + xf_input_handle_event(xfc, event); + XSync(xfc->display, FALSE); + return status; +} diff --git a/client/X11/xf_event.h b/client/X11/xf_event.h new file mode 100644 index 0000000..4e880aa --- /dev/null +++ b/client/X11/xf_event.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Event Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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" + +BOOL xf_event_action_script_init(xfContext* xfc); +void xf_event_action_script_free(xfContext* xfc); + +BOOL xf_event_process(freerdp* instance, 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); + +BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app); +BOOL xf_generic_ButtonPress(xfContext* xfc, int x, int y, int button, Window window, BOOL app); +BOOL xf_generic_ButtonRelease(xfContext* xfc, int x, int y, int button, Window window, BOOL app); + +#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..1d60072 --- /dev/null +++ b/client/X11/xf_floatbar.c @@ -0,0 +1,722 @@ +/** + * 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 +#include +#include +#include +#include +#include + +#include "xf_floatbar.h" +#include "resource/close.xbm" +#include "resource/lock.xbm" +#include "resource/unlock.xbm" +#include "resource/minimize.xbm" +#include "resource/restore.xbm" + +#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 struct xf_floatbar_button 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; +}; + +struct xf_floatbar_button +{ + int x; + int y; + int type; + bool focus; + bool clicked; + OnClick onclick; + Window handle; +}; + +static void xf_floatbar_button_onclick_close(xfContext* xfc) +{ + ExitProcess(EXIT_SUCCESS); +} + +static void xf_floatbar_button_onclick_minimize(xfContext* xfc) +{ + xf_SetWindowMinimized(xfc, xfc->window); +} + +static void xf_floatbar_button_onclick_restore(xfContext* xfc) +{ + xf_toggle_fullscreen(xfc); +} + +static void xf_floatbar_button_onclick_locked(xfContext* xfc) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + floatbar->locked = (floatbar->locked) ? FALSE : TRUE; +} + +void xf_floatbar_set_root_y(xfContext* xfc, int y) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + floatbar->last_motion_y_root = y; +} + +void xf_floatbar_hide_and_show(xfContext* xfc) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + + if (!floatbar->locked) + { + if ((floatbar->mode == 0) && (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); + } + } +} + +void xf_floatbar_toggle_visibility(xfContext* xfc, bool visible) +{ + xfFloatbar* floatbar; + int i, size; + floatbar = xfc->window->floatbar; + + if (visible) + { + XMapWindow(xfc->display, floatbar->handle); + size = ARRAYSIZE(floatbar->buttons); + + for (i = 0; i < size; i++) + { + XMapWindow(xfc->display, floatbar->buttons[i]->handle); + } + } + else + { + XUnmapSubwindows(xfc->display, floatbar->handle); + XUnmapWindow(xfc->display, floatbar->handle); + } +} + +static xfFloatbarButton* xf_floatbar_new_button(xfContext* xfc, xfFloatbar* floatbar, int type) +{ + xfFloatbarButton* button; + 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(xfc->display, floatbar->handle, button->x, 0, FLOATBAR_BUTTON_WIDTH, + FLOATBAR_BUTTON_WIDTH, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + XSelectInput(xfc->display, button->handle, ExposureMask | ButtonPressMask | ButtonReleaseMask | + FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask); + return button; +} + +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window) +{ + xfFloatbar* floatbar; + XWindowAttributes attr; + int i, width; + if (!xfc) + return NULL; + + floatbar = (xfFloatbar*) calloc(1, sizeof(xfFloatbar)); + floatbar->locked = TRUE; + XGetWindowAttributes(xfc->display, window, &attr); + + for (i = 0; i < xfc->vscreen.nmonitors; i++) + { + if (attr.x >= xfc->vscreen.monitors[i].area.left && attr.x <= xfc->vscreen.monitors[i].area.right) + { + width = xfc->vscreen.monitors[i].area.right - xfc->vscreen.monitors[i].area.left; + floatbar->x = width / 2 + xfc->vscreen.monitors[i].area.left - FLOATBAR_DEFAULT_WIDTH / 2; + } + } + + floatbar->y = 0; + floatbar->handle = XCreateWindow(xfc->display, 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(xfc, floatbar, XF_FLOATBAR_BUTTON_CLOSE); + floatbar->buttons[1] = xf_floatbar_new_button(xfc, floatbar, XF_FLOATBAR_BUTTON_RESTORE); + floatbar->buttons[2] = xf_floatbar_new_button(xfc, floatbar, XF_FLOATBAR_BUTTON_MINIMIZE); + floatbar->buttons[3] = xf_floatbar_new_button(xfc, floatbar, XF_FLOATBAR_BUTTON_LOCKED); + XSelectInput(xfc->display, floatbar->handle, ExposureMask | ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask | + PropertyChangeMask); + return floatbar; +} + +static unsigned long xf_floatbar_get_color(xfContext* xfc, char* rgb_value) +{ + Colormap cmap; + XColor color; + cmap = DefaultColormap(xfc->display, XDefaultScreen(xfc->display)); + XParseColor(xfc->display, cmap, rgb_value, &color); + XAllocColor(xfc->display, cmap, &color); + XFreeColormap(xfc->display, cmap); + return color.pixel; +} + +static void xf_floatbar_event_expose(xfContext* xfc, XEvent* event) +{ + GC gc, shape_gc; + Pixmap pmap; + XPoint shape[5], border[5]; + xfFloatbar* floatbar; + int len; + floatbar = xfc->window->floatbar; + /* create the pixmap that we'll use for shaping the window */ + pmap = XCreatePixmap(xfc->display, floatbar->handle, floatbar->width, floatbar->height, 1); + gc = XCreateGC(xfc->display, floatbar->handle, 0, 0); + shape_gc = XCreateGC(xfc->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(xfc->display, shape_gc, 0); + XFillRectangle(xfc->display, pmap, shape_gc, 0, 0, floatbar->width, + floatbar->height); + /* Fill all pixels which should be shown with 1 */ + XSetForeground(xfc->display, shape_gc, 1); + XFillPolygon(xfc->display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin); + XShapeCombineMask(xfc->display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet); + /* draw the float bar */ + XSetForeground(xfc->display, gc, xf_floatbar_get_color(xfc, FLOATBAR_COLOR_BACKGROUND)); + XFillPolygon(xfc->display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin); + /* draw an border for the floatbar */ + XSetForeground(xfc->display, gc, xf_floatbar_get_color(xfc, FLOATBAR_COLOR_BORDER)); + XDrawLines(xfc->display, floatbar->handle, gc, border, 5, CoordModeOrigin); + /* draw the host name connected to */ + len = strlen(xfc->context.settings->ServerHostname); + XSetForeground(xfc->display, gc, xf_floatbar_get_color(xfc, FLOATBAR_COLOR_FOREGROUND)); + XDrawString(xfc->display, floatbar->handle, gc, floatbar->width / 2 - len * 2, 15, + xfc->context.settings->ServerHostname, len); + XFreeGC(xfc->display, gc); + XFreeGC(xfc->display, shape_gc); +} + +static xfFloatbarButton* xf_floatbar_get_button(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + int i, size; + size = ARRAYSIZE(floatbar->buttons); + floatbar = xfc->window->floatbar; + + for (i = 0; i < size; i++) + { + if (floatbar->buttons[i]->handle == event->xany.window) + { + return floatbar->buttons[i]; + } + } + + return NULL; +} + +static void xf_floatbar_button_update_positon(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + xfFloatbarButton* button; + int i, size; + floatbar = xfc->window->floatbar; + size = ARRAYSIZE(floatbar->buttons); + + for (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; + } + + XMoveWindow(xfc->display, button->handle, button->x, button->y); + xf_floatbar_event_expose(xfc, event); + } +} + +static void xf_floatbar_button_event_expose(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + xfFloatbarButton* button; + static unsigned char* bits; + GC gc; + Pixmap pattern; + button = xf_floatbar_get_button(xfc, event); + + if (!button) + return; + + gc = XCreateGC(xfc->display, button->handle, 0, 0); + floatbar = xfc->window->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(xfc, FLOATBAR_COLOR_BACKGROUND)); + else + XSetForeground(xfc->display, gc, xf_floatbar_get_color(xfc, FLOATBAR_COLOR_BORDER)); + + XSetBackground(xfc->display, gc, xf_floatbar_get_color(xfc, 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(xfContext* xfc, XEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(xfc, event); + + if (button) + button->clicked = TRUE; +} + +static void xf_floatbar_button_event_buttonrelease(xfContext* xfc, XEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(xfc, event); + + if (button) + { + if (button->clicked) + button->onclick(xfc); + } +} + +static void xf_floatbar_event_buttonpress(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + + switch (event->xbutton.button) + { + case Button1: + if (event->xmotion.x <= FLOATBAR_BORDER) + floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT; + else if (event->xmotion.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(xfContext* xfc, XEvent* event) +{ + switch (event->xbutton.button) + { + case Button1: + xfc->window->floatbar->mode = XF_FLOATBAR_MODE_NONE; + break; + + default: + break; + } +} + +static void xf_floatbar_resize(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + int x, width, movement; + /* calculate movement which happened on the root window */ + movement = event->xmotion.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(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + floatbar = xfc->window->floatbar; + int x, movement; + /* calculate movement and new x position */ + movement = event->xmotion.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(xfContext* xfc, XEvent* event) +{ + int mode; + xfFloatbar* floatbar; + Cursor cursor; + mode = xfc->window->floatbar->mode; + floatbar = xfc->window->floatbar; + cursor = XCreateFontCursor(xfc->display, XC_arrow); + + if ((event->xmotion.state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_resize(xfc, event); + } + else if ((event->xmotion.state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_dragging(xfc, event); + } + else + { + if (event->xmotion.x <= FLOATBAR_BORDER || + event->xmotion.x >= xfc->window->floatbar->width - FLOATBAR_BORDER) + cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow); + } + + XDefineCursor(xfc->display, xfc->window->handle, cursor); + XFreeCursor(xfc->display, cursor); + xfc->window->floatbar->last_motion_x_root = event->xmotion.x_root; +} + +static void xf_floatbar_button_event_focusin(xfContext* xfc, XEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(xfc, event); + + if (button) + { + button->focus = TRUE; + xf_floatbar_button_event_expose(xfc, event); + } +} + +static void xf_floatbar_button_event_focusout(xfContext* xfc, XEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(xfc, event); + + if (button) + { + button->focus = FALSE; + button->clicked = FALSE; + xf_floatbar_button_event_expose(xfc, event); + } +} + +static void xf_floatbar_event_focusout(xfContext* xfc, XEvent* event) +{ + Cursor cursor; + cursor = XCreateFontCursor(xfc->display, XC_arrow); + XDefineCursor(xfc->display, xfc->window->handle, cursor); + XFreeCursor(xfc->display, cursor); +} + +BOOL xf_floatbar_check_event(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + xfFloatbarButton* button; + size_t i, size; + + if (!xfc || !event || !xfc->window) + return FALSE; + + floatbar = xfc->window->floatbar; + + if (event->xany.window == floatbar->handle) + return TRUE; + + size = ARRAYSIZE(floatbar->buttons); + + for (i = 0; i < size; i++) + { + button = floatbar->buttons[i]; + + if (event->xany.window == button->handle) + return TRUE; + } + + return FALSE; +} + +BOOL xf_floatbar_event_process(xfContext* xfc, XEvent* event) +{ + xfFloatbar* floatbar; + + if (!xfc || !xfc->window || !event) + return FALSE; + + floatbar = xfc->window->floatbar; + + switch (event->type) + { + case Expose: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_expose(xfc, event); + else + xf_floatbar_button_event_expose(xfc, event); + + break; + + case MotionNotify: + xf_floatbar_event_motionnotify(xfc, event); + break; + + case ButtonPress: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonpress(xfc, event); + else + xf_floatbar_button_event_buttonpress(xfc, event); + + break; + + case ButtonRelease: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonrelease(xfc, event); + else + xf_floatbar_button_event_buttonrelease(xfc, event); + + break; + + case EnterNotify: + case FocusIn: + if (event->xany.window != floatbar->handle) + xf_floatbar_button_event_focusin(xfc, event); + + break; + + case LeaveNotify: + case FocusOut: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_focusout(xfc, event); + else + xf_floatbar_button_event_focusout(xfc, event); + + break; + + case ConfigureNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(xfc, event); + + break; + + case PropertyNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(xfc, event); + + 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) + { + XUnmapWindow(xfc->display, button->handle); + XDestroyWindow(xfc->display, button->handle); + } + + free(button); +} + +void xf_floatbar_free(xfContext* xfc, xfWindow* window, xfFloatbar* floatbar) +{ + size_t i, size; + size = ARRAYSIZE(floatbar->buttons); + + if (!floatbar) + return; + + if (window->floatbar == floatbar) + window->floatbar = NULL; + + for (i = 0; i < size; i++) + { + xf_floatbar_button_free(xfc, floatbar->buttons[i]); + floatbar->buttons[i] = NULL; + } + + if (floatbar->handle) + { + XUnmapWindow(xfc->display, floatbar->handle); + XDestroyWindow(xfc->display, floatbar->handle); + } + + free(floatbar); +} diff --git a/client/X11/xf_floatbar.h b/client/X11/xf_floatbar.h new file mode 100644 index 0000000..fecb5cb --- /dev/null +++ b/client/X11/xf_floatbar.h @@ -0,0 +1,34 @@ +/** + * 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" + +typedef void(*OnClick)(xfContext*); +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window); +BOOL xf_floatbar_event_process(xfContext* xfc, XEvent* event); +BOOL xf_floatbar_check_event(xfContext* xfc, XEvent* event); +void xf_floatbar_toggle_visibility(xfContext* xfc, bool visible); +void xf_floatbar_free(xfContext* xfc, xfWindow* window, xfFloatbar* floatbar); +void xf_floatbar_hide_and_show(xfContext* xfc); +void xf_floatbar_set_root_y(xfContext* xfc, int y); + +#endif /* FREERDP_CLIENT_X11_FLOATBAR_H */ diff --git a/client/X11/xf_gdi.c b/client/X11/xf_gdi.c new file mode 100644 index 0000000..8b31d74 --- /dev/null +++ b/client/X11/xf_gdi.c @@ -0,0 +1,1129 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 GDI + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2014 Thincast Technologies GmbH + * Copyright 2014 Norbert Federa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "xf_gdi.h" +#include "xf_graphics.h" + +#include +#define TAG CLIENT_TAG("x11") + +static const UINT8 GDI_BS_HATCHED_PATTERNS[] = +{ + 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, /* HS_HORIZONTAL */ + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, /* HS_VERTICAL */ + 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F, /* HS_FDIAGONAL */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE, /* HS_BDIAGONAL */ + 0xF7, 0xF7, 0xF7, 0x00, 0xF7, 0xF7, 0xF7, 0xF7, /* HS_CROSS */ + 0x7E, 0xBD, 0xDB, 0xE7, 0xE7, 0xDB, 0xBD, 0x7E /* HS_DIACROSS */ +}; + +static const BYTE xf_rop2_table[] = +{ + 0, + GXclear, /* 0 */ + GXnor, /* DPon */ + GXandInverted, /* DPna */ + GXcopyInverted, /* Pn */ + GXandReverse, /* PDna */ + GXinvert, /* Dn */ + GXxor, /* DPx */ + GXnand, /* DPan */ + GXand, /* DPa */ + GXequiv, /* DPxn */ + GXnoop, /* D */ + GXorInverted, /* DPno */ + GXcopy, /* P */ + GXorReverse, /* PDno */ + GXor, /* DPo */ + GXset /* 1 */ +}; + +static BOOL xf_set_rop2(xfContext* xfc, int rop2) +{ + if ((rop2 < 0x01) || (rop2 > 0x10)) + { + WLog_ERR(TAG, "Unsupported ROP2: %d", rop2); + return FALSE; + } + + XSetFunction(xfc->display, xfc->gc, xf_rop2_table[rop2]); + return TRUE; +} + +static BOOL xf_set_rop3(xfContext* xfc, UINT32 rop3) +{ + int function = -1; + + switch (rop3) + { + case GDI_BLACKNESS: + function = GXclear; + break; + + case GDI_DPon: + function = GXnor; + break; + + case GDI_DPna: + function = GXandInverted; + break; + + case GDI_Pn: + function = GXcopyInverted; + break; + + case GDI_NOTSRCERASE: + function = GXnor; + break; + + case GDI_DSna: + function = GXandInverted; + break; + + case GDI_NOTSRCCOPY: + function = GXcopyInverted; + break; + + case GDI_SRCERASE: + function = GXandReverse; + break; + + case GDI_PDna: + function = GXandReverse; + break; + + case GDI_DSTINVERT: + function = GXinvert; + break; + + case GDI_PATINVERT: + function = GXxor; + break; + + case GDI_DPan: + function = GXnand; + break; + + case GDI_SRCINVERT: + function = GXxor; + break; + + case GDI_DSan: + function = GXnand; + break; + + case GDI_SRCAND: + function = GXand; + break; + + case GDI_DSxn: + function = GXequiv; + break; + + case GDI_DPa: + function = GXand; + break; + + case GDI_PDxn: + function = GXequiv; + break; + + case GDI_DSTCOPY: + function = GXnoop; + break; + + case GDI_DPno: + function = GXorInverted; + break; + + case GDI_MERGEPAINT: + function = GXorInverted; + break; + + case GDI_SRCCOPY: + function = GXcopy; + break; + + case GDI_SDno: + function = GXorReverse; + break; + + case GDI_SRCPAINT: + function = GXor; + break; + + case GDI_PATCOPY: + function = GXcopy; + break; + + case GDI_PDno: + function = GXorReverse; + break; + + case GDI_DPo: + function = GXor; + break; + + case GDI_WHITENESS: + function = GXset; + break; + + case GDI_PSDPxax: + function = GXand; + break; + + default: + break; + } + + if (function < 0) + { + WLog_ERR(TAG, "Unsupported ROP3: 0x%08"PRIX32"", rop3); + XSetFunction(xfc->display, xfc->gc, GXclear); + return FALSE; + } + + XSetFunction(xfc->display, xfc->gc, function); + return TRUE; +} + +static Pixmap xf_brush_new(xfContext* xfc, UINT32 width, UINT32 height, + UINT32 bpp, + BYTE* data) +{ + GC gc; + Pixmap bitmap; + BYTE* cdata; + XImage* image; + rdpGdi* gdi; + UINT32 brushFormat; + gdi = xfc->context.gdi; + bitmap = XCreatePixmap(xfc->display, xfc->drawable, width, height, xfc->depth); + + if (data) + { + brushFormat = gdi_get_pixel_format(bpp); + cdata = (BYTE*) _aligned_malloc(width * height * 4, 16); + freerdp_image_copy(cdata, gdi->dstFormat, 0, 0, 0, + width, height, data, brushFormat, 0, 0, 0, + &xfc->context.gdi->palette, FREERDP_FLIP_NONE); + image = XCreateImage(xfc->display, xfc->visual, xfc->depth, + ZPixmap, 0, (char*) cdata, width, height, xfc->scanline_pad, 0); + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + gc = XCreateGC(xfc->display, xfc->drawable, 0, NULL); + XPutImage(xfc->display, bitmap, gc, image, 0, 0, 0, 0, width, height); + image->data = NULL; + XDestroyImage(image); + + if (cdata != data) + _aligned_free(cdata); + + XFreeGC(xfc->display, gc); + } + + return bitmap; +} + +static Pixmap xf_mono_bitmap_new(xfContext* xfc, int width, int height, + const BYTE* data) +{ + int scanline; + XImage* image; + Pixmap bitmap; + scanline = (width + 7) / 8; + bitmap = XCreatePixmap(xfc->display, xfc->drawable, width, height, 1); + image = XCreateImage(xfc->display, xfc->visual, 1, + ZPixmap, 0, (char*) data, width, height, 8, scanline); + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + XPutImage(xfc->display, bitmap, xfc->gc_mono, image, 0, 0, 0, 0, width, height); + image->data = NULL; + XDestroyImage(image); + return bitmap; +} + +static BOOL xf_gdi_set_bounds(rdpContext* context, + const rdpBounds* bounds) +{ + XRectangle clip; + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + + if (bounds) + { + clip.x = bounds->left; + clip.y = bounds->top; + clip.width = bounds->right - bounds->left + 1; + clip.height = bounds->bottom - bounds->top + 1; + XSetClipRectangles(xfc->display, xfc->gc, 0, 0, &clip, 1, YXBanded); + } + else + { + XSetClipMask(xfc->display, xfc->gc, None); + } + + xf_unlock_x11(xfc, FALSE); + return TRUE; +} + +static BOOL xf_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt) +{ + xfContext* xfc = (xfContext*) context; + BOOL ret = FALSE; + xf_lock_x11(xfc, FALSE); + + if (!xf_set_rop3(xfc, gdi_rop3_code(dstblt->bRop))) + goto fail; + + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + dstblt->nLeftRect, dstblt->nTopRect, + dstblt->nWidth, dstblt->nHeight); + ret = TRUE; + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, dstblt->nLeftRect, dstblt->nTopRect, + dstblt->nWidth, dstblt->nHeight); + +fail: + XSetFunction(xfc->display, xfc->gc, GXcopy); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) +{ + const rdpBrush* brush; + xfContext* xfc = (xfContext*) context; + BOOL ret = FALSE; + XColor xfg, xbg; + + if (!xf_decode_color(xfc, patblt->foreColor, &xfg)) + return FALSE; + + if (!xf_decode_color(xfc, patblt->backColor, &xbg)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + brush = &patblt->brush; + + if (!xf_set_rop3(xfc, gdi_rop3_code(patblt->bRop))) + goto fail; + + switch (brush->style) + { + case GDI_BS_SOLID: + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetBackground(xfc->display, xfc->gc, xbg.pixel); + XSetForeground(xfc->display, xfc->gc, xfg.pixel); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, patblt->nHeight); + break; + + case GDI_BS_HATCHED: + { + Pixmap pattern = xf_mono_bitmap_new(xfc, 8, 8, + &GDI_BS_HATCHED_PATTERNS[8 * brush->hatch]); + XSetBackground(xfc->display, xfc->gc, xbg.pixel); + XSetForeground(xfc->display, xfc->gc, xfg.pixel); + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + XSetStipple(xfc->display, xfc->gc, pattern); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, patblt->nHeight); + XFreePixmap(xfc->display, pattern); + } + break; + + case GDI_BS_PATTERN: + if (brush->bpp > 1) + { + UINT32 bpp = brush->bpp; + + if ((bpp == 16) && (context->settings->ColorDepth == 15)) + bpp = 15; + + Pixmap pattern = xf_brush_new(xfc, 8, 8, bpp, brush->data); + XSetFillStyle(xfc->display, xfc->gc, FillTiled); + XSetTile(xfc->display, xfc->gc, pattern); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, patblt->nHeight); + XSetTile(xfc->display, xfc->gc, xfc->primary); + XFreePixmap(xfc->display, pattern); + } + else + { + Pixmap pattern = xf_mono_bitmap_new(xfc, 8, 8, brush->data); + XSetBackground(xfc->display, xfc->gc, xfg.pixel); + XSetForeground(xfc->display, xfc->gc, xbg.pixel); + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + XSetStipple(xfc->display, xfc->gc, pattern); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, patblt->nHeight); + XFreePixmap(xfc->display, pattern); + } + + break; + + default: + WLog_ERR(TAG, "unimplemented brush style:%"PRIu32"", brush->style); + goto fail; + } + + ret = TRUE; + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, patblt->nLeftRect, patblt->nTopRect, + patblt->nWidth, patblt->nHeight); + +fail: + XSetFunction(xfc->display, xfc->gc, GXcopy); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt) +{ + xfContext* xfc = (xfContext*) context; + BOOL ret = FALSE; + + if (!xfc->display || !xfc->drawing) + return FALSE; + + xf_lock_x11(xfc, FALSE); + + if (!xf_set_rop3(xfc, gdi_rop3_code(scrblt->bRop))) + goto fail; + + XCopyArea(xfc->display, xfc->primary, xfc->drawing, xfc->gc, scrblt->nXSrc, + scrblt->nYSrc, + scrblt->nWidth, scrblt->nHeight, scrblt->nLeftRect, scrblt->nTopRect); + ret = TRUE; + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, scrblt->nLeftRect, scrblt->nTopRect, + scrblt->nWidth, scrblt->nHeight); + + XSetFunction(xfc->display, xfc->gc, GXcopy); +fail: + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_opaque_rect(rdpContext* context, + const OPAQUE_RECT_ORDER* opaque_rect) +{ + XColor color; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + + if (!xf_decode_color(xfc, opaque_rect->color, &color)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + XSetFunction(xfc->display, xfc->gc, GXcopy); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetForeground(xfc->display, xfc->gc, color.pixel); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + opaque_rect->nLeftRect, opaque_rect->nTopRect, + opaque_rect->nWidth, opaque_rect->nHeight); + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, opaque_rect->nLeftRect, + opaque_rect->nTopRect, + opaque_rect->nWidth, opaque_rect->nHeight); + + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_multi_opaque_rect(rdpContext* context, + const MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect) +{ + UINT32 i; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + XColor color; + + if (!xf_decode_color(xfc, multi_opaque_rect->color, &color)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + XSetFunction(xfc->display, xfc->gc, GXcopy); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetForeground(xfc->display, xfc->gc, color.pixel); + + for (i = 0; i < multi_opaque_rect->numRectangles; i++) + { + const DELTA_RECT* rectangle = &multi_opaque_rect->rectangles[i]; + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, + rectangle->left, rectangle->top, + rectangle->width, rectangle->height); + + if (xfc->drawing == xfc->primary) + { + if (!(ret = gdi_InvalidateRegion(xfc->hdc, rectangle->left, rectangle->top, + rectangle->width, rectangle->height))) + break; + } + } + + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_line_to(rdpContext* context, const LINE_TO_ORDER* line_to) +{ + XColor color; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + + if (!xf_decode_color(xfc, line_to->penColor, &color)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + xf_set_rop2(xfc, line_to->bRop2); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetForeground(xfc->display, xfc->gc, color.pixel); + XDrawLine(xfc->display, xfc->drawing, xfc->gc, + line_to->nXStart, line_to->nYStart, line_to->nXEnd, line_to->nYEnd); + + if (xfc->drawing == xfc->primary) + { + int x, y, w, h; + x = MIN(line_to->nXStart, line_to->nXEnd); + y = MIN(line_to->nYStart, line_to->nYEnd); + w = abs(line_to->nXEnd - line_to->nXStart) + 1; + h = abs(line_to->nYEnd - line_to->nYStart) + 1; + ret = gdi_InvalidateRegion(xfc->hdc, x, y, w, h); + } + + XSetFunction(xfc->display, xfc->gc, GXcopy); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_invalidate_poly_region(xfContext* xfc, XPoint* points, + int npoints) +{ + int x, y, x1, y1, x2, y2; + + if (npoints < 2) + return FALSE; + + x = x1 = x2 = points->x; + y = y1 = y2 = points->y; + + while (--npoints) + { + points++; + x += points->x; + y += points->y; + + if (x > x2) + x2 = x; + + if (x < x1) + x1 = x; + + if (y > y2) + y2 = y; + + if (y < y1) + y1 = y; + } + + x2++; + y2++; + return gdi_InvalidateRegion(xfc->hdc, x1, y1, x2 - x1, y2 - y1); +} + +static BOOL xf_gdi_polyline(rdpContext* context, + const POLYLINE_ORDER* polyline) +{ + UINT32 i; + int npoints; + XColor color; + XPoint* points; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + + if (!xf_decode_color(xfc, polyline->penColor, &color)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + xf_set_rop2(xfc, polyline->bRop2); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetForeground(xfc->display, xfc->gc, color.pixel); + npoints = polyline->numDeltaEntries + 1; + points = calloc(npoints, sizeof(XPoint)); + + if (!points) + { + xf_unlock_x11(xfc, FALSE); + return FALSE; + } + + points[0].x = polyline->xStart; + points[0].y = polyline->yStart; + + for (i = 0; i < polyline->numDeltaEntries; i++) + { + points[i + 1].x = polyline->points[i].x; + points[i + 1].y = polyline->points[i].y; + } + + XDrawLines(xfc->display, xfc->drawing, xfc->gc, points, npoints, + CoordModePrevious); + + if (xfc->drawing == xfc->primary) + { + if (!xf_gdi_invalidate_poly_region(xfc, points, npoints)) + ret = FALSE; + } + + XSetFunction(xfc->display, xfc->gc, GXcopy); + free(points); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) +{ + xfBitmap* bitmap; + xfContext* xfc; + BOOL ret = TRUE; + + if (!context || !memblt) + return FALSE; + + bitmap = (xfBitmap*) memblt->bitmap; + xfc = (xfContext*) context; + + if (!bitmap || !xfc || !xfc->display || !xfc->drawing) + return FALSE; + + xf_lock_x11(xfc, FALSE); + + if (xf_set_rop3(xfc, gdi_rop3_code(memblt->bRop))) + { + XCopyArea(xfc->display, bitmap->pixmap, xfc->drawing, xfc->gc, + memblt->nXSrc, memblt->nYSrc, memblt->nWidth, memblt->nHeight, + memblt->nLeftRect, memblt->nTopRect); + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, memblt->nLeftRect, + memblt->nTopRect, memblt->nWidth, + memblt->nHeight); + } + + XSetFunction(xfc->display, xfc->gc, GXcopy); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt) +{ + const rdpBrush* brush; + xfBitmap* bitmap; + XColor foreColor; + XColor backColor; + Pixmap pattern = 0; + xfContext* xfc = (xfContext*) context; + BOOL ret = FALSE; + + if (!xfc->display || !xfc->drawing) + return FALSE; + + if (!xf_decode_color(xfc, mem3blt->foreColor, &foreColor)) + return FALSE; + + if (!xf_decode_color(xfc, mem3blt->backColor, &backColor)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + brush = &mem3blt->brush; + bitmap = (xfBitmap*) mem3blt->bitmap; + + if (!xf_set_rop3(xfc, gdi_rop3_code(mem3blt->bRop))) + goto fail; + + switch (brush->style) + { + case GDI_BS_PATTERN: + if (brush->bpp > 1) + { + UINT32 bpp = brush->bpp; + + if ((bpp == 16) && (context->settings->ColorDepth == 15)) + bpp = 15; + + pattern = xf_brush_new(xfc, 8, 8, bpp, brush->data); + XSetFillStyle(xfc->display, xfc->gc, FillTiled); + XSetTile(xfc->display, xfc->gc, pattern); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + } + else + { + pattern = xf_mono_bitmap_new(xfc, 8, 8, brush->data); + XSetBackground(xfc->display, xfc->gc, backColor.pixel); + XSetForeground(xfc->display, xfc->gc, foreColor.pixel); + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + XSetStipple(xfc->display, xfc->gc, pattern); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + } + + break; + + case GDI_BS_SOLID: + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetBackground(xfc->display, xfc->gc, backColor.pixel); + XSetForeground(xfc->display, xfc->gc, foreColor.pixel); + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + break; + + default: + WLog_ERR(TAG, "Mem3Blt unimplemented brush style:%"PRIu32"", brush->style); + goto fail; + } + + XCopyArea(xfc->display, bitmap->pixmap, xfc->drawing, xfc->gc, + mem3blt->nXSrc, mem3blt->nYSrc, mem3blt->nWidth, mem3blt->nHeight, + mem3blt->nLeftRect, mem3blt->nTopRect); + ret = TRUE; + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, mem3blt->nLeftRect, mem3blt->nTopRect, + mem3blt->nWidth, mem3blt->nHeight); + + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetTSOrigin(xfc->display, xfc->gc, 0, 0); + + if (pattern != 0) + XFreePixmap(xfc->display, pattern); + +fail: + XSetFunction(xfc->display, xfc->gc, GXcopy); + xf_unlock_x11(xfc, FALSE); + return ret; +} + + +static BOOL xf_gdi_polygon_sc(rdpContext* context, + const POLYGON_SC_ORDER* polygon_sc) +{ + int i, npoints; + XPoint* points; + XColor brush_color; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + + if (!xf_decode_color(xfc, polygon_sc->brushColor, &brush_color)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + xf_set_rop2(xfc, polygon_sc->bRop2); + npoints = polygon_sc->numPoints + 1; + points = calloc(npoints, sizeof(XPoint)); + + if (!points) + { + xf_unlock_x11(xfc, FALSE); + return FALSE; + } + + points[0].x = polygon_sc->xStart; + points[0].y = polygon_sc->yStart; + + for (i = 0; i < polygon_sc->numPoints; i++) + { + points[i + 1].x = polygon_sc->points[i].x; + points[i + 1].y = polygon_sc->points[i].y; + } + + switch (polygon_sc->fillMode) + { + case 1: /* alternate */ + XSetFillRule(xfc->display, xfc->gc, EvenOddRule); + break; + + case 2: /* winding */ + XSetFillRule(xfc->display, xfc->gc, WindingRule); + break; + + default: + WLog_ERR(TAG, "PolygonSC unknown fillMode: %"PRIu32"", polygon_sc->fillMode); + break; + } + + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetForeground(xfc->display, xfc->gc, brush_color.pixel); + XFillPolygon(xfc->display, xfc->drawing, xfc->gc, + points, npoints, Complex, CoordModePrevious); + + if (xfc->drawing == xfc->primary) + { + if (!xf_gdi_invalidate_poly_region(xfc, points, npoints)) + ret = FALSE; + } + + XSetFunction(xfc->display, xfc->gc, GXcopy); + free(points); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_polygon_cb(rdpContext* context, + POLYGON_CB_ORDER* polygon_cb) +{ + int i, npoints; + XPoint* points; + Pixmap pattern; + const rdpBrush* brush; + XColor foreColor; + XColor backColor; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + + if (!xf_decode_color(xfc, polygon_cb->foreColor, &foreColor)) + return FALSE; + + if (!xf_decode_color(xfc, polygon_cb->backColor, &backColor)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + brush = &(polygon_cb->brush); + xf_set_rop2(xfc, polygon_cb->bRop2); + npoints = polygon_cb->numPoints + 1; + points = calloc(npoints, sizeof(XPoint)); + + if (!points) + { + xf_unlock_x11(xfc, FALSE); + return FALSE; + } + + points[0].x = polygon_cb->xStart; + points[0].y = polygon_cb->yStart; + + for (i = 0; i < polygon_cb->numPoints; i++) + { + points[i + 1].x = polygon_cb->points[i].x; + points[i + 1].y = polygon_cb->points[i].y; + } + + switch (polygon_cb->fillMode) + { + case GDI_FILL_ALTERNATE: /* alternate */ + XSetFillRule(xfc->display, xfc->gc, EvenOddRule); + break; + + case GDI_FILL_WINDING: /* winding */ + XSetFillRule(xfc->display, xfc->gc, WindingRule); + break; + + default: + WLog_ERR(TAG, "PolygonCB unknown fillMode: %"PRIu32"", polygon_cb->fillMode); + break; + } + + if (brush->style == GDI_BS_PATTERN) + { + if (brush->bpp > 1) + { + UINT32 bpp = brush->bpp; + + if ((bpp == 16) && (context->settings->ColorDepth == 15)) + bpp = 15; + + pattern = xf_brush_new(xfc, 8, 8, bpp, brush->data); + XSetFillStyle(xfc->display, xfc->gc, FillTiled); + XSetTile(xfc->display, xfc->gc, pattern); + } + else + { + pattern = xf_mono_bitmap_new(xfc, 8, 8, brush->data); + XSetForeground(xfc->display, xfc->gc, backColor.pixel); + XSetBackground(xfc->display, xfc->gc, foreColor.pixel); + + if (polygon_cb->backMode == BACKMODE_TRANSPARENT) + XSetFillStyle(xfc->display, xfc->gc, FillStippled); + else if (polygon_cb->backMode == BACKMODE_OPAQUE) + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + + XSetStipple(xfc->display, xfc->gc, pattern); + } + + XSetTSOrigin(xfc->display, xfc->gc, brush->x, brush->y); + XFillPolygon(xfc->display, xfc->drawing, xfc->gc, + points, npoints, Complex, CoordModePrevious); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + XSetTSOrigin(xfc->display, xfc->gc, 0, 0); + XFreePixmap(xfc->display, pattern); + + if (xfc->drawing == xfc->primary) + { + if (!xf_gdi_invalidate_poly_region(xfc, points, npoints)) + ret = FALSE; + } + } + else + { + WLog_ERR(TAG, "PolygonCB unimplemented brush style:%"PRIu32"", brush->style); + } + + XSetFunction(xfc->display, xfc->gc, GXcopy); + free(points); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_surface_frame_marker(rdpContext* context, + const SURFACE_FRAME_MARKER* surface_frame_marker) +{ + rdpSettings* settings; + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + settings = xfc->context.settings; + xf_lock_x11(xfc, FALSE); + + switch (surface_frame_marker->frameAction) + { + case SURFACECMD_FRAMEACTION_BEGIN: + xfc->frame_begin = TRUE; + xfc->frame_x1 = 0; + xfc->frame_y1 = 0; + xfc->frame_x2 = 0; + xfc->frame_y2 = 0; + break; + + case SURFACECMD_FRAMEACTION_END: + xfc->frame_begin = FALSE; + + if ((xfc->frame_x2 > xfc->frame_x1) && (xfc->frame_y2 > xfc->frame_y1)) + ret = gdi_InvalidateRegion(xfc->hdc, xfc->frame_x1, xfc->frame_y1, + xfc->frame_x2 - xfc->frame_x1, xfc->frame_y2 - xfc->frame_y1); + + if (settings->FrameAcknowledge > 0) + { + IFCALL(xfc->context.update->SurfaceFrameAcknowledge, context, + surface_frame_marker->frameId); + } + + break; + } + + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_gdi_surface_update_frame(xfContext* xfc, UINT16 tx, UINT16 ty, + UINT16 width, UINT16 height) +{ + BOOL ret = TRUE; + + if (!xfc->remote_app) + { + if (xfc->frame_begin) + { + if (xfc->frame_x2 > xfc->frame_x1 && xfc->frame_y2 > xfc->frame_y1) + { + xfc->frame_x1 = MIN(xfc->frame_x1, tx); + xfc->frame_y1 = MIN(xfc->frame_y1, ty); + xfc->frame_x2 = MAX(xfc->frame_x2, tx + width); + xfc->frame_y2 = MAX(xfc->frame_y2, ty + height); + } + else + { + xfc->frame_x1 = tx; + xfc->frame_y1 = ty; + xfc->frame_x2 = tx + width; + xfc->frame_y2 = ty + height; + } + } + else + { + ret = gdi_InvalidateRegion(xfc->hdc, tx, ty, width, height); + } + } + else + { + ret = gdi_InvalidateRegion(xfc->hdc, tx, ty, width, height); + } + + return ret; +} + +static BOOL xf_gdi_update_screen(xfContext* xfc, const BYTE* pSrcData, + UINT32 scanline, const REGION16* pRegion) +{ + BOOL ret = FALSE; + XImage* image; + UINT32 i, nbRects; + const RECTANGLE_16* rects; + UINT32 bpp; + + if (!xfc || !pSrcData) + return FALSE; + + if (!(rects = region16_rects(pRegion, &nbRects))) + return TRUE; + + if (xfc->depth > 16) + bpp = 4; + else if (xfc->depth > 8) + bpp = 2; + else + bpp = 1; + + XSetFunction(xfc->display, xfc->gc, GXcopy); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + + for (i = 0; i < nbRects; i++) + { + UINT32 left = rects[i].left; + UINT32 top = rects[i].top; + UINT32 width = rects[i].right - rects[i].left; + UINT32 height = rects[i].bottom - rects[i].top; + const BYTE* src = pSrcData + top * scanline + bpp * left; + image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0, + (char*) src, width, height, xfc->scanline_pad, scanline); + + if (!image) + break; + + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + XPutImage(xfc->display, xfc->primary, xfc->gc, image, 0, 0, left, top, width, height); + image->data = NULL; + XDestroyImage(image); + ret = xf_gdi_surface_update_frame(xfc, left, top, width, height); + } + + XSetClipMask(xfc->display, xfc->gc, None); + return ret; +} + +static BOOL xf_gdi_surface_bits(rdpContext* context, + const SURFACE_BITS_COMMAND* cmd) +{ + BYTE* pSrcData; + xfContext* xfc = (xfContext*) context; + BOOL ret = FALSE; + DWORD format; + rdpGdi* gdi; + REGION16 region; + RECTANGLE_16 cmdRect; + + if (!context || !cmd || !context->gdi) + return FALSE; + + region16_init(®ion); + cmdRect.left = cmd->destLeft; + cmdRect.top = cmd->destTop; + cmdRect.right = cmdRect.left + cmd->bmp.width; + cmdRect.bottom = cmdRect.top + cmd->bmp.height; + gdi = context->gdi; + xf_lock_x11(xfc, FALSE); + + switch (cmd->bmp.codecID) + { + case RDP_CODEC_ID_REMOTEFX: + if (!rfx_process_message(context->codecs->rfx, cmd->bmp.bitmapData, + cmd->bmp.bitmapDataLength, cmd->destLeft, cmd->destTop, + gdi->primary_buffer, gdi->dstFormat, gdi->stride, + gdi->height, ®ion)) + goto fail; + + break; + + case RDP_CODEC_ID_NSCODEC: + if (!nsc_process_message(context->codecs->nsc, cmd->bmp.bpp, cmd->bmp.width, + cmd->bmp.height, cmd->bmp.bitmapData, cmd->bmp.bitmapDataLength, + gdi->primary_buffer, gdi->dstFormat, gdi->stride, + 0, 0, cmd->bmp.width, cmd->bmp.height, FREERDP_FLIP_VERTICAL)) + goto fail; + + region16_union_rect(®ion, ®ion, &cmdRect); + break; + + case RDP_CODEC_ID_NONE: + pSrcData = cmd->bmp.bitmapData; + format = gdi_get_pixel_format(cmd->bmp.bpp); + + if (!freerdp_image_copy(gdi->primary_buffer, gdi->dstFormat, gdi->stride, + cmd->destLeft, cmd->destTop, cmd->bmp.width, cmd->bmp.height, + pSrcData, format, 0, 0, 0, + &xfc->context.gdi->palette, FREERDP_FLIP_VERTICAL)) + goto fail; + + region16_union_rect(®ion, ®ion, &cmdRect); + break; + + default: + WLog_ERR(TAG, "Unsupported codecID %"PRIu16"", cmd->bmp.codecID); + ret = TRUE; + goto fail; + } + + ret = xf_gdi_update_screen(xfc, gdi->primary_buffer, gdi->stride, ®ion); +fail: + region16_uninit(®ion); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +void xf_gdi_register_update_callbacks(rdpUpdate* update) +{ + rdpPrimaryUpdate* primary = update->primary; + update->SetBounds = xf_gdi_set_bounds; + primary->DstBlt = xf_gdi_dstblt; + primary->PatBlt = xf_gdi_patblt; + primary->ScrBlt = xf_gdi_scrblt; + primary->OpaqueRect = xf_gdi_opaque_rect; + primary->MultiOpaqueRect = xf_gdi_multi_opaque_rect; + primary->LineTo = xf_gdi_line_to; + primary->Polyline = xf_gdi_polyline; + primary->MemBlt = xf_gdi_memblt; + primary->Mem3Blt = xf_gdi_mem3blt; + primary->PolygonSC = xf_gdi_polygon_sc; + primary->PolygonCB = xf_gdi_polygon_cb; + update->SurfaceBits = xf_gdi_surface_bits; + update->SurfaceFrameMarker = xf_gdi_surface_frame_marker; +} + diff --git a/client/X11/xf_gdi.h b/client/X11/xf_gdi.h new file mode 100644 index 0000000..84dcfc4 --- /dev/null +++ b/client/X11/xf_gdi.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 GDI + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_GDI_H +#define FREERDP_CLIENT_X11_GDI_H + +#include + +#include "xf_client.h" +#include "xfreerdp.h" + +void xf_gdi_register_update_callbacks(rdpUpdate* update); + +#endif /* FREERDP_CLIENT_X11_GDI_H */ diff --git a/client/X11/xf_gfx.c b/client/X11/xf_gfx.c new file mode 100644 index 0000000..b68a297 --- /dev/null +++ b/client/X11/xf_gfx.c @@ -0,0 +1,385 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphics Pipeline + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2016 Armin Novak + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "xf_gfx.h" +#include "xf_rail.h" + +#include + +#define TAG CLIENT_TAG("x11") + +static UINT xf_OutputUpdate(xfContext* xfc, xfGfxSurface* surface) +{ + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 surfaceX, surfaceY; + RECTANGLE_16 surfaceRect; + rdpGdi* gdi; + UINT32 nbRects, x; + const RECTANGLE_16* rects; + gdi = xfc->context.gdi; + surfaceX = surface->gdi.outputOriginX; + surfaceY = surface->gdi.outputOriginY; + surfaceRect.left = 0; + surfaceRect.top = 0; + surfaceRect.right = surface->gdi.width; + surfaceRect.bottom = surface->gdi.height; + 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); + + if (!(rects = region16_rects(&surface->gdi.invalidRegion, &nbRects))) + return CHANNEL_RC_OK; + + for (x = 0; x < nbRects; x++) + { + const UINT32 nXSrc = rects[x].left; + const UINT32 nYSrc = rects[x].top; + const UINT32 width = rects[x].right - nXSrc; + const UINT32 height = rects[x].bottom - nYSrc; + const UINT32 nXDst = surfaceX + nXSrc; + const UINT32 nYDst = surfaceY + nYSrc; + + if (surface->stage) + { + if (!freerdp_image_copy(surface->stage, gdi->dstFormat, + surface->stageScanline, nXSrc, nYSrc, + width, height, + surface->gdi.data, surface->gdi.format, + surface->gdi.scanline, nXSrc, nYSrc, + NULL, FREERDP_FLIP_NONE)) + goto fail; + } + + if (xfc->remote_app) + { + XPutImage(xfc->display, xfc->primary, xfc->gc, + surface->image, nXSrc, nYSrc, + nXDst, nYDst, width, height); + xf_lock_x11(xfc, FALSE); + xf_rail_paint(xfc, nXDst, nYDst, nXDst + width, nYDst + height); + xf_unlock_x11(xfc, FALSE); + } + else +#ifdef WITH_XRENDER + if (xfc->context.settings->SmartSizing + || xfc->context.settings->MultiTouchGestures) + { + XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, + nXSrc, nYSrc, nXDst, nYDst, width, height); + xf_draw_screen(xfc, nXDst, nYDst, width, height); + } + else +#endif + { + XPutImage(xfc->display, xfc->drawable, xfc->gc, + surface->image, nXSrc, nYSrc, + nXDst, nYDst, width, height); + } + } + + 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_UpdateSurfaces(RdpgfxClientContext* context) +{ + UINT16 count; + UINT32 index; + UINT status = CHANNEL_RC_OK; + xfGfxSurface* surface; + UINT16* pSurfaceIds = NULL; + rdpGdi* gdi = (rdpGdi*)context->custom; + xfContext* xfc = (xfContext*) gdi->context; + + if (!gdi) + return status; + + if (!gdi->graphicsReset) + return status; + + if (gdi->suppressOutput) + return CHANNEL_RC_OK; + + context->GetSurfaceIds(context, &pSurfaceIds, &count); + + for (index = 0; index < count; index++) + { + surface = (xfGfxSurface*) context->GetSurfaceData(context, pSurfaceIds[index]); + + if (!surface || !surface->gdi.outputMapped) + continue; + + status = xf_OutputUpdate(xfc, surface); + + if (status != 0) + break; + } + + free(pSurfaceIds); + return status; +} + +UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, + UINT32 width, UINT32 height) +{ + UINT16 count; + UINT32 index; + UINT status = CHANNEL_RC_OK; + xfGfxSurface* surface; + RECTANGLE_16 invalidRect; + RECTANGLE_16 surfaceRect; + RECTANGLE_16 intersection; + UINT16* pSurfaceIds = NULL; + RdpgfxClientContext* context = xfc->context.gdi->gfx; + invalidRect.left = x; + invalidRect.top = y; + invalidRect.right = x + width; + invalidRect.bottom = y + height; + context->GetSurfaceIds(context, &pSurfaceIds, &count); + + for (index = 0; index < count; index++) + { + surface = (xfGfxSurface*) context->GetSurfaceData(context, pSurfaceIds[index]); + + if (!surface || !surface->gdi.outputMapped) + continue; + + surfaceRect.left = surface->gdi.outputOriginX; + surfaceRect.top = surface->gdi.outputOriginY; + surfaceRect.right = surface->gdi.outputOriginX + surface->gdi.width; + surfaceRect.bottom = surface->gdi.outputOriginY + surface->gdi.height; + + 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); + IFCALLRET(context->UpdateSurfaces, status, context); + return status; +} + +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; + xfGfxSurface* surface; + 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 = gdi->context->codecs; + + if (!surface->gdi.codecs) + { + WLog_ERR(TAG, "%s: global GDI codecs aren't set", __FUNCTION__); + goto out_free; + } + + surface->gdi.surfaceId = createSurface->surfaceId; + surface->gdi.width = (UINT32) createSurface->width; + surface->gdi.height = (UINT32) 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, "%s: unknown pixelFormat 0x%"PRIx32"", __FUNCTION__, createSurface->pixelFormat); + ret = ERROR_INTERNAL_ERROR; + goto out_free; + } + + surface->gdi.scanline = surface->gdi.width * GetBytesPerPixel(surface->gdi.format); + surface->gdi.scanline = x11_pad_scanline(surface->gdi.scanline, xfc->scanline_pad); + size = surface->gdi.scanline * surface->gdi.height; + surface->gdi.data = (BYTE*)_aligned_malloc(size, 16); + + if (!surface->gdi.data) + { + WLog_ERR(TAG, "%s: unable to allocate GDI data", __FUNCTION__); + goto out_free; + } + + ZeroMemory(surface->gdi.data, size); + + if (AreColorFormatsEqualNoAlpha(gdi->dstFormat, surface->gdi.format)) + { + surface->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0, + (char*) surface->gdi.data, surface->gdi.width, surface->gdi.height, + xfc->scanline_pad, surface->gdi.scanline); + } + else + { + UINT32 width = surface->gdi.width; + UINT32 bytes = GetBytesPerPixel(gdi->dstFormat); + surface->stageScanline = width * bytes; + surface->stageScanline = x11_pad_scanline(surface->stageScanline, xfc->scanline_pad); + size = surface->stageScanline * surface->gdi.height; + surface->stage = (BYTE*) _aligned_malloc(size, 16); + + if (!surface->stage) + { + WLog_ERR(TAG, "%s: unable to allocate stage buffer", __FUNCTION__); + goto out_free_gdidata; + } + + ZeroMemory(surface->stage, size); + surface->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, + ZPixmap, 0, (char*) surface->stage, + surface->gdi.width, surface->gdi.height, + xfc->scanline_pad, surface->stageScanline); + } + + if (!surface->image) + { + WLog_ERR(TAG, "%s: an error occurred when creating the XImage", __FUNCTION__); + goto error_surface_image; + } + + surface->image->byte_order = LSBFirst; + surface->image->bitmap_bit_order = LSBFirst; + surface->gdi.outputMapped = FALSE; + region16_init(&surface->gdi.invalidRegion); + + if (context->SetSurfaceData(context, surface->gdi.surfaceId, (void*) surface) != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "%s: an error occurred during SetSurfaceData", __FUNCTION__); + goto error_set_surface_data; + } + + return CHANNEL_RC_OK; +error_set_surface_data: + surface->image->data = NULL; + XDestroyImage(surface->image); +error_surface_image: + _aligned_free(surface->stage); +out_free_gdidata: + _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; + surface = (xfGfxSurface*) context->GetSurfaceData(context, + deleteSurface->surfaceId); + + if (surface) + { +#ifdef WITH_GFX_H264 + h264_context_free(surface->gdi.h264); +#endif + surface->image->data = NULL; + XDestroyImage(surface->image); + _aligned_free(surface->gdi.data); + _aligned_free(surface->stage); + region16_uninit(&surface->gdi.invalidRegion); + codecs = surface->gdi.codecs; + free(surface); + } + + context->SetSurfaceData(context, deleteSurface->surfaceId, NULL); + + if (codecs && codecs->progressive) + progressive_delete_surface_context(codecs->progressive, + deleteSurface->surfaceId); + + return CHANNEL_RC_OK; +} + +void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx) +{ + rdpGdi* gdi = xfc->context.gdi; + gdi_graphics_pipeline_init(gdi, gfx); + gfx->UpdateSurfaces = xf_UpdateSurfaces; + gfx->CreateSurface = xf_CreateSurface; + gfx->DeleteSurface = xf_DeleteSurface; +} + +void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx) +{ + rdpGdi* gdi = xfc->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..11c1720 --- /dev/null +++ b/client/X11/xf_gfx.h @@ -0,0 +1,56 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphics Pipeline + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +struct xf_gfx_surface +{ + gdiGfxSurface gdi; + BYTE* stage; + UINT32 stageScanline; + XImage* image; +}; +typedef struct xf_gfx_surface xfGfxSurface; + +struct xf_gfx_cache_entry +{ + UINT64 cacheKey; + UINT32 width; + UINT32 height; + BYTE* data; + UINT32 scanline; + UINT32 format; +}; +typedef struct xf_gfx_cache_entry xfGfxCacheEntry; + +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..5efc3f8 --- /dev/null +++ b/client/X11/xf_graphics.c @@ -0,0 +1,583 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphical Objects + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef WITH_XCURSOR +#include +#endif + +#include + +#include +#include + +#include "xf_graphics.h" +#include "xf_gdi.h" + +#include +#define TAG CLIENT_TAG("x11") + +BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color) +{ + rdpGdi* gdi; + rdpSettings* settings; + UINT32 SrcFormat; + BYTE r, g, b, a; + + if (!xfc || !color) + return FALSE; + + gdi = xfc->context.gdi; + + if (!gdi) + return FALSE; + + settings = xfc->context.settings; + + if (!settings) + return FALSE; + + switch (settings->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; + } + + SplitColor(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; +} + +/* Bitmap Class */ +static BOOL xf_Bitmap_New(rdpContext* context, rdpBitmap* bitmap) +{ + BOOL rc = FALSE; + UINT32 depth; + BYTE* data; + rdpGdi* gdi; + xfBitmap* xbitmap = (xfBitmap*)bitmap; + xfContext* xfc = (xfContext*) context; + + if (!context || !bitmap || !context->gdi) + return FALSE; + + gdi = context->gdi; + xf_lock_x11(xfc, FALSE); + depth = GetBitsPerPixel(bitmap->format); + xbitmap->pixmap = XCreatePixmap(xfc->display, xfc->drawable, bitmap->width, + bitmap->height, xfc->depth); + + if (!xbitmap->pixmap) + goto unlock; + + if (bitmap->data) + { + XSetFunction(xfc->display, xfc->gc, GXcopy); + + if (depth != xfc->depth) + { + if (!(data = _aligned_malloc(bitmap->width * bitmap->height * 4, 16))) + goto unlock; + + if (!freerdp_image_copy(data, gdi->dstFormat, 0, 0, 0, + bitmap->width, bitmap->height, + bitmap->data, bitmap->format, + 0, 0, 0, &context->gdi->palette, FREERDP_FLIP_NONE)) + { + _aligned_free(data); + goto unlock; + } + + _aligned_free(bitmap->data); + bitmap->data = data; + bitmap->format = gdi->dstFormat; + } + + xbitmap->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, + ZPixmap, 0, (char*) bitmap->data, bitmap->width, bitmap->height, + xfc->scanline_pad, 0); + + if (!xbitmap->image) + goto unlock; + + xbitmap->image->byte_order = LSBFirst; + xbitmap->image->bitmap_bit_order = LSBFirst; + XPutImage(xfc->display, xbitmap->pixmap, xfc->gc, xbitmap->image, 0, 0, 0, 0, bitmap->width, + bitmap->height); + } + + rc = TRUE; +unlock: + xf_unlock_x11(xfc, FALSE); + return rc; +} + +static void xf_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap) +{ + xfContext* xfc = (xfContext*) context; + xfBitmap* xbitmap = (xfBitmap*)bitmap; + + if (!xfc || !xbitmap) + return; + + xf_lock_x11(xfc, FALSE); + + if (xbitmap->pixmap != 0) + { + XFreePixmap(xfc->display, xbitmap->pixmap); + xbitmap->pixmap = 0; + } + + if (xbitmap->image) + { + xbitmap->image->data = NULL; + XDestroyImage(xbitmap->image); + xbitmap->image = NULL; + } + + xf_unlock_x11(xfc, FALSE); + _aligned_free(bitmap->data); + free(xbitmap); +} + +static BOOL xf_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap) +{ + int width, height; + xfContext* xfc = (xfContext*) context; + xfBitmap* xbitmap = (xfBitmap*)bitmap; + BOOL ret; + + if (!context || !xbitmap) + return FALSE; + + width = bitmap->right - bitmap->left + 1; + height = bitmap->bottom - bitmap->top + 1; + xf_lock_x11(xfc, FALSE); + XSetFunction(xfc->display, xfc->gc, GXcopy); + XPutImage(xfc->display, xfc->primary, xfc->gc, + xbitmap->image, 0, 0, bitmap->left, bitmap->top, width, height); + ret = gdi_InvalidateRegion(xfc->hdc, bitmap->left, bitmap->top, width, height); + xf_unlock_x11(xfc, FALSE); + return ret; +} + +static BOOL xf_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, + BOOL primary) +{ + xfContext* xfc = (xfContext*) context; + + if (!context || (!bitmap && !primary)) + return FALSE; + + xf_lock_x11(xfc, FALSE); + + if (primary) + xfc->drawing = xfc->primary; + else + xfc->drawing = ((xfBitmap*) bitmap)->pixmap; + + xf_unlock_x11(xfc, FALSE); + return TRUE; +} + +/* Pointer Class */ +static BOOL xf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ +#ifdef WITH_XCURSOR + UINT32 CursorFormat; + size_t size; + XcursorImage ci; + xfContext* xfc = (xfContext*) context; + xfPointer* xpointer = (xfPointer*)pointer; + + if (!context || !pointer || !context->gdi) + return FALSE; + + if (!xfc->invert) + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32; + else + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32; + + xf_lock_x11(xfc, FALSE); + ZeroMemory(&ci, sizeof(ci)); + ci.version = XCURSOR_IMAGE_VERSION; + ci.size = sizeof(ci); + ci.width = pointer->width; + ci.height = pointer->height; + ci.xhot = pointer->xPos; + ci.yhot = pointer->yPos; + size = ci.height * ci.width * GetBytesPerPixel(CursorFormat); + + if (!(ci.pixels = (XcursorPixel*) _aligned_malloc(size, 16))) + { + xf_unlock_x11(xfc, FALSE); + return FALSE; + } + + if (!freerdp_image_copy_from_pointer_data( + (BYTE*) ci.pixels, CursorFormat, + 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, + pointer->andMaskData, pointer->lengthAndMask, + pointer->xorBpp, &context->gdi->palette)) + { + _aligned_free(ci.pixels); + xf_unlock_x11(xfc, FALSE); + return FALSE; + } + + xpointer->cursor = XcursorImageLoadCursor(xfc->display, &ci); + _aligned_free(ci.pixels); + xf_unlock_x11(xfc, FALSE); +#endif + return TRUE; +} + +static void xf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + + if (((xfPointer*) pointer)->cursor) + XFreeCursor(xfc->display, ((xfPointer*) pointer)->cursor); + + xf_unlock_x11(xfc, FALSE); +#endif +} + +static BOOL xf_Pointer_Set(rdpContext* context, + const rdpPointer* pointer) +{ +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + xfc->pointer = (xfPointer*) pointer; + + /* in RemoteApp mode, window can be null if none has had focus */ + + if (xfc->window) + XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); + + xf_unlock_x11(xfc, FALSE); +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetNull(rdpContext* context) +{ +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*) context; + static Cursor nullcursor = None; + xf_lock_x11(xfc, FALSE); + + if (nullcursor == None) + { + XcursorImage ci; + XcursorPixel xp = 0; + ZeroMemory(&ci, sizeof(ci)); + 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 ((xfc->window) && (nullcursor != None)) + XDefineCursor(xfc->display, xfc->window->handle, nullcursor); + + xf_unlock_x11(xfc, FALSE); +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetDefault(rdpContext* context) +{ +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + xfc->pointer = NULL; + + if (xfc->window) + XUndefineCursor(xfc->display, xfc->window->handle); + + xf_unlock_x11(xfc, FALSE); +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + xfContext* xfc = (xfContext*) context; + XWindowAttributes current; + XSetWindowAttributes tmp; + BOOL ret = FALSE; + + if (!xfc->focused || !xfc->window) + return TRUE; + + xf_lock_x11(xfc, FALSE); + + if (XGetWindowAttributes(xfc->display, xfc->window->handle, ¤t) == 0) + goto out; + + tmp.event_mask = (current.your_event_mask & ~(PointerMotionMask)); + + if (XChangeWindowAttributes(xfc->display, xfc->window->handle, CWEventMask, + &tmp) == 0) + goto out; + + XWarpPointer(xfc->display, None, xfc->window->handle, 0, 0, 0, 0, x, y); + tmp.event_mask = current.your_event_mask; + XChangeWindowAttributes(xfc->display, xfc->window->handle, CWEventMask, &tmp); + ret = TRUE; +out: + xf_unlock_x11(xfc, FALSE); + return ret; +} + +/* Glyph Class */ +static BOOL xf_Glyph_New(rdpContext* context, const rdpGlyph* glyph) +{ + int scanline; + XImage* image; + xfGlyph* xf_glyph; + xf_glyph = (xfGlyph*) glyph; + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + scanline = (glyph->cx + 7) / 8; + xf_glyph->pixmap = XCreatePixmap(xfc->display, xfc->drawing, glyph->cx, + glyph->cy, 1); + image = XCreateImage(xfc->display, xfc->visual, 1, + ZPixmap, 0, (char*) glyph->aj, glyph->cx, glyph->cy, 8, scanline); + image->byte_order = MSBFirst; + image->bitmap_bit_order = MSBFirst; + XInitImage(image); + XPutImage(xfc->display, xf_glyph->pixmap, xfc->gc_mono, image, 0, 0, 0, 0, + glyph->cx, glyph->cy); + image->data = NULL; + XDestroyImage(image); + xf_unlock_x11(xfc, FALSE); + return TRUE; +} + +static void xf_Glyph_Free(rdpContext* context, rdpGlyph* glyph) +{ + xfContext* xfc = (xfContext*) context; + xf_lock_x11(xfc, FALSE); + + if (((xfGlyph*) glyph)->pixmap != 0) + XFreePixmap(xfc->display, ((xfGlyph*) glyph)->pixmap); + + xf_unlock_x11(xfc, FALSE); + free(glyph->aj); + free(glyph); +} + +static BOOL xf_Glyph_Draw(rdpContext* context, const rdpGlyph* glyph, INT32 x, + INT32 y, INT32 w, INT32 h, INT32 sx, INT32 sy, + BOOL fOpRedundant) +{ + xfGlyph* xf_glyph; + xfContext* xfc = (xfContext*) context; + xf_glyph = (xfGlyph*) glyph; + xf_lock_x11(xfc, FALSE); + + if (!fOpRedundant) + { + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + XFillRectangle(xfc->display, xfc->drawable, xfc->gc, x, y, w, h); + } + + XSetFillStyle(xfc->display, xfc->gc, FillStippled); + XSetStipple(xfc->display, xfc->gc, xf_glyph->pixmap); + + if (sx || sy) + WLog_ERR(TAG, ""); + + //XSetClipOrigin(xfc->display, xfc->gc, sx, sy); + XSetTSOrigin(xfc->display, xfc->gc, x, y); + XFillRectangle(xfc->display, xfc->drawing, xfc->gc, x, y, w, h); + xf_unlock_x11(xfc, FALSE); + return TRUE; +} + +static BOOL xf_Glyph_BeginDraw(rdpContext* context, INT32 x, INT32 y, + INT32 width, INT32 height, UINT32 bgcolor, + UINT32 fgcolor, BOOL fOpRedundant) +{ + xfContext* xfc = (xfContext*) context; + XRectangle rect; + XColor xbgcolor, xfgcolor; + + if (!xf_decode_color(xfc, bgcolor, &xbgcolor)) + return FALSE; + + if (!xf_decode_color(xfc, fgcolor, &xfgcolor)) + return FALSE; + + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + xf_lock_x11(xfc, FALSE); + + if (!fOpRedundant) + { + XSetForeground(xfc->display, xfc->gc, xfgcolor.pixel); + XSetBackground(xfc->display, xfc->gc, xfgcolor.pixel); + XSetFillStyle(xfc->display, xfc->gc, FillOpaqueStippled); + XFillRectangle(xfc->display, xfc->drawable, xfc->gc, x, y, width, height); + } + + XSetForeground(xfc->display, xfc->gc, xbgcolor.pixel); + XSetBackground(xfc->display, xfc->gc, xfgcolor.pixel); + xf_unlock_x11(xfc, FALSE); + return TRUE; +} + +static BOOL xf_Glyph_EndDraw(rdpContext* context, INT32 x, INT32 y, + INT32 width, INT32 height, + UINT32 bgcolor, UINT32 fgcolor) +{ + xfContext* xfc = (xfContext*) context; + BOOL ret = TRUE; + XColor xfgcolor, xbgcolor; + + if (!xf_decode_color(xfc, bgcolor, &xbgcolor)) + return FALSE; + + if (!xf_decode_color(xfc, fgcolor, &xfgcolor)) + return FALSE; + + if (xfc->drawing == xfc->primary) + ret = gdi_InvalidateRegion(xfc->hdc, x, y, width, height); + + return ret; +} + +/* Graphics Module */ +BOOL xf_register_pointer(rdpGraphics* graphics) +{ + rdpPointer* pointer = NULL; + + if (!(pointer = (rdpPointer*) calloc(1, sizeof(rdpPointer)))) + return FALSE; + + 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); + free(pointer); + return TRUE; +} + +BOOL xf_register_graphics(rdpGraphics* graphics) +{ + rdpBitmap bitmap; + rdpGlyph glyph; + + if (!graphics || !graphics->Bitmap_Prototype || !graphics->Glyph_Prototype) + return FALSE; + + bitmap = *graphics->Bitmap_Prototype; + glyph = *graphics->Glyph_Prototype; + bitmap.size = sizeof(xfBitmap); + bitmap.New = xf_Bitmap_New; + bitmap.Free = xf_Bitmap_Free; + bitmap.Paint = xf_Bitmap_Paint; + bitmap.SetSurface = xf_Bitmap_SetSurface; + graphics_register_bitmap(graphics, &bitmap); + glyph.size = sizeof(xfGlyph); + glyph.New = xf_Glyph_New; + glyph.Free = xf_Glyph_Free; + glyph.Draw = xf_Glyph_Draw; + glyph.BeginDraw = xf_Glyph_BeginDraw; + glyph.EndDraw = xf_Glyph_EndDraw; + graphics_register_glyph(graphics, &glyph); + return TRUE; +} + +UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned) +{ + UINT32 DstFormat; + BOOL invert = FALSE; + + if (!xfc) + return 0; + + invert = xfc->invert; + + if (xfc->depth == 32) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32; + 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..303e116 --- /dev/null +++ b/client/X11/xf_graphics.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphical Objects + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_register_graphics(rdpGraphics* graphics); + +BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color); +UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned); + +#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..0bed66c --- /dev/null +++ b/client/X11/xf_input.c @@ -0,0 +1,658 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Input + * + * Copyright 2013 Corey Clayton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef WITH_XCURSOR +#include +#endif + +#ifdef WITH_XI +#include +#endif + +#include + +#include "xf_event.h" +#include "xf_input.h" + +#include +#define TAG CLIENT_TAG("x11") + +#ifdef WITH_XI + +#define MAX_CONTACTS 2 + +#define PAN_THRESHOLD 50 +#define ZOOM_THRESHOLD 10 + +#define MIN_FINGER_DIST 5 + +typedef struct touch_contact +{ + int id; + int count; + double pos_x; + double pos_y; + double last_x; + double last_y; + +} touchContact; + +static touchContact contacts[MAX_CONTACTS]; + +static int active_contacts; +static int lastEvType; +static XIDeviceEvent lastEvent; +static double firstDist = -1.0; +static double lastDist; + +static double z_vector; +static double px_vector; +static double py_vector; + +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"; +} + + +int xf_input_init(xfContext* xfc, Window window) +{ + int i, j; + int nmasks; + int ndevices; + int major = 2; + int minor = 2; + Status xstatus; + XIDeviceInfo* info; + XIEventMask evmasks[64]; + int opcode, event, error; + BYTE masks[8][XIMaskLen(XI_LASTEVENT)]; + z_vector = 0; + px_vector = 0; + py_vector = 0; + nmasks = 0; + ndevices = 0; + active_contacts = 0; + ZeroMemory(contacts, sizeof(touchContact) * MAX_CONTACTS); + + 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 * 1000 + minor < 2002) + { + WLog_WARN(TAG, "Server does not support XI 2.2"); + return -1; + } + + if (xfc->context.settings->MultiTouchInput) + xfc->use_xinput = TRUE; + + info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices); + + for (i = 0; i < ndevices; i++) + { + BOOL touch = FALSE; + XIDeviceInfo* dev = &info[i]; + + for (j = 0; j < dev->num_classes; j++) + { + XIAnyClassInfo* class = dev->classes[j]; + XITouchClassInfo* t = (XITouchClassInfo*) class; + + if ((class->type == XITouchClass) && (t->mode == XIDirectTouch) && + (strcmp(dev->name, "Virtual core pointer") != 0)) + { + touch = TRUE; + } + } + + for (j = 0; j < dev->num_classes; j++) + { + XIAnyClassInfo* class = dev->classes[j]; + XITouchClassInfo* t = (XITouchClassInfo*) class; + + if (xfc->context.settings->MultiTouchInput) + { + WLog_INFO(TAG, "%s (%d) \"%s\" id: %d", + xf_input_get_class_string(class->type), + class->type, dev->name, dev->deviceid); + } + + evmasks[nmasks].mask = masks[nmasks]; + evmasks[nmasks].mask_len = sizeof(masks[0]); + ZeroMemory(masks[nmasks], sizeof(masks[0])); + evmasks[nmasks].deviceid = dev->deviceid; + + if ((class->type == XITouchClass) && (t->mode == XIDirectTouch) && + (strcmp(dev->name, "Virtual core pointer") != 0)) + { + if (xfc->context.settings->MultiTouchInput) + { + WLog_INFO(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); + nmasks++; + } + + if (xfc->use_xinput) + { + if (!touch && (class->type == XIButtonClass) + && strcmp(dev->name, "Virtual core pointer")) + { + WLog_INFO(TAG, "%s button device (id: %d, mode: %d)", + dev->name, + dev->deviceid, t->mode); + XISetMask(masks[nmasks], XI_ButtonPress); + XISetMask(masks[nmasks], XI_ButtonRelease); + XISetMask(masks[nmasks], XI_Motion); + nmasks++; + } + } + } + } + + XIFreeDeviceInfo(info); + + if (nmasks > 0) + xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks); + + return 0; +} + +static BOOL xf_input_is_duplicate(XGenericEventCookie* cookie) +{ + XIDeviceEvent* event; + event = cookie->data; + + if ((lastEvent.time == event->time) && + (lastEvType == cookie->evtype) && + (lastEvent.detail == event->detail) && + (lastEvent.event_x == event->event_x) && + (lastEvent.event_y == event->event_y)) + { + return TRUE; + } + + return FALSE; +} + +static void xf_input_save_last_event(XGenericEventCookie* cookie) +{ + XIDeviceEvent* event; + event = cookie->data; + lastEvType = cookie->evtype; + lastEvent.time = event->time; + lastEvent.detail = event->detail; + lastEvent.event_x = event->event_x; + lastEvent.event_y = event->event_y; +} + +static void xf_input_detect_pan(xfContext* xfc) +{ + double dx[2]; + double dy[2]; + double px; + double py; + double dist_x; + double dist_y; + rdpContext* ctx = &xfc->context; + + if (active_contacts != 2) + { + return; + } + + dx[0] = contacts[0].pos_x - contacts[0].last_x; + dx[1] = contacts[1].pos_x - contacts[1].last_x; + dy[0] = contacts[0].pos_y - contacts[0].last_y; + dy[1] = contacts[1].pos_y - 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]; + px_vector += px; + py_vector += py; + dist_x = fabs(contacts[0].pos_x - contacts[1].pos_x); + dist_y = fabs(contacts[0].pos_y - contacts[1].pos_y); + + if (dist_y > MIN_FINGER_DIST) + { + if (px_vector > PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 5; + e.dy = 0; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + px_vector = 0; + py_vector = 0; + z_vector = 0; + } + else if (px_vector < -PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = -5; + e.dy = 0; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + px_vector = 0; + py_vector = 0; + z_vector = 0; + } + } + + if (dist_x > MIN_FINGER_DIST) + { + if (py_vector > PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 0; + e.dy = 5; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + py_vector = 0; + px_vector = 0; + z_vector = 0; + } + else if (py_vector < -PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 0; + e.dy = -5; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + py_vector = 0; + px_vector = 0; + z_vector = 0; + } + } +} + +static void xf_input_detect_pinch(xfContext* xfc) +{ + double dist; + double delta; + ZoomingChangeEventArgs e; + rdpContext* ctx = &xfc->context; + + if (active_contacts != 2) + { + firstDist = -1.0; + return; + } + + /* first calculate the distance */ + dist = sqrt(pow(contacts[1].pos_x - contacts[0].last_x, 2.0) + + pow(contacts[1].pos_y - contacts[0].last_y, 2.0)); + + /* if this is the first 2pt touch */ + if (firstDist <= 0) + { + firstDist = dist; + lastDist = firstDist; + z_vector = 0; + px_vector = 0; + py_vector = 0; + } + else + { + delta = lastDist - dist; + + if (delta > 1.0) + delta = 1.0; + + if (delta < -1.0) + delta = -1.0; + + /* compare the current distance to the first one */ + z_vector += delta; + lastDist = dist; + + if (z_vector > ZOOM_THRESHOLD) + { + EventArgsInit(&e, "xfreerdp"); + e.dx = e.dy = -10; + PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); + z_vector = 0; + px_vector = 0; + py_vector = 0; + } + + if (z_vector < -ZOOM_THRESHOLD) + { + EventArgsInit(&e, "xfreerdp"); + e.dx = e.dy = 10; + PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); + z_vector = 0; + px_vector = 0; + py_vector = 0; + } + } +} + +static void xf_input_touch_begin(xfContext* xfc, XIDeviceEvent* event) +{ + int i; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == 0) + { + contacts[i].id = event->detail; + contacts[i].count = 1; + contacts[i].pos_x = event->event_x; + contacts[i].pos_y = event->event_y; + active_contacts++; + break; + } + } +} + +static void xf_input_touch_update(xfContext* xfc, XIDeviceEvent* event) +{ + int i; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == event->detail) + { + contacts[i].count++; + contacts[i].last_x = contacts[i].pos_x; + contacts[i].last_y = contacts[i].pos_y; + contacts[i].pos_x = event->event_x; + 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, XIDeviceEvent* event) +{ + int i; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == event->detail) + { + contacts[i].id = 0; + contacts[i].count = 0; + active_contacts--; + break; + } + } +} + +static int xf_input_handle_event_local(xfContext* xfc, XEvent* event) +{ + XGenericEventCookie* cookie = &event->xcookie; + XGetEventData(xfc->display, cookie); + + if ((cookie->type == GenericEvent) && (cookie->extension == xfc->XInputOpcode)) + { + switch (cookie->evtype) + { + case XI_TouchBegin: + if (xf_input_is_duplicate(cookie) == FALSE) + xf_input_touch_begin(xfc, cookie->data); + + xf_input_save_last_event(cookie); + break; + + case XI_TouchUpdate: + if (xf_input_is_duplicate(cookie) == FALSE) + xf_input_touch_update(xfc, cookie->data); + + xf_input_save_last_event(cookie); + break; + + case XI_TouchEnd: + if (xf_input_is_duplicate(cookie) == FALSE) + xf_input_touch_end(xfc, cookie->data); + + xf_input_save_last_event(cookie); + break; + + default: + WLog_ERR(TAG, "unhandled xi type= %d", cookie->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie); + return 0; +} + +#ifdef WITH_DEBUG_X11 +static char* xf_input_touch_state_string(DWORD flags) +{ + if (flags & CONTACT_FLAG_DOWN) + return "TouchBegin"; + else if (flags & CONTACT_FLAG_UPDATE) + return "TouchUpdate"; + else if (flags & CONTACT_FLAG_UP) + return "TouchEnd"; + else + return "TouchUnknown"; +} +#endif + +static void xf_input_hide_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + + if (!xfc->cursorHidden) + { + XcursorImage ci; + XcursorPixel xp = 0; + static Cursor nullcursor = None; + xf_lock_x11(xfc, FALSE); + ZeroMemory(&ci, sizeof(ci)); + 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, FALSE); + } + +#endif +} + +static void xf_input_show_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + xf_lock_x11(xfc, FALSE); + + 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, FALSE); +#endif +} + +static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype) +{ + int x, y; + int touchId; + int contactId; + RdpeiClientContext* rdpei = xfc->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); + + if (evtype == XI_TouchBegin) + { + WLog_DBG(TAG, "TouchBegin: %d", touchId); + rdpei->TouchBegin(rdpei, touchId, x, y, &contactId); + } + else if (evtype == XI_TouchUpdate) + { + WLog_DBG(TAG, "TouchUpdate: %d", touchId); + rdpei->TouchUpdate(rdpei, touchId, x, y, &contactId); + } + else if (evtype == XI_TouchEnd) + { + WLog_DBG(TAG, "TouchEnd: %d", touchId); + rdpei->TouchEnd(rdpei, touchId, x, y, &contactId); + } + + return 0; +} + +static int xf_input_event(xfContext* xfc, XIDeviceEvent* event, int evtype) +{ + xf_input_show_cursor(xfc); + + switch (evtype) + { + case XI_ButtonPress: + xf_generic_ButtonPress(xfc, (int) event->event_x, (int) event->event_y, + event->detail, event->event, xfc->remote_app); + break; + + case XI_ButtonRelease: + xf_generic_ButtonRelease(xfc, (int) event->event_x, (int) event->event_y, + event->detail, event->event, xfc->remote_app); + break; + + case XI_Motion: + xf_generic_MotionNotify(xfc, (int) event->event_x, (int) event->event_y, + event->detail, event->event, xfc->remote_app); + break; + } + + return 0; +} + +static int xf_input_handle_event_remote(xfContext* xfc, XEvent* event) +{ + XGenericEventCookie* cookie = &event->xcookie; + XGetEventData(xfc->display, cookie); + + if ((cookie->type == GenericEvent) && (cookie->extension == xfc->XInputOpcode)) + { + switch (cookie->evtype) + { + case XI_TouchBegin: + xf_input_touch_remote(xfc, cookie->data, XI_TouchBegin); + break; + + case XI_TouchUpdate: + xf_input_touch_remote(xfc, cookie->data, XI_TouchUpdate); + break; + + case XI_TouchEnd: + xf_input_touch_remote(xfc, cookie->data, XI_TouchEnd); + break; + + default: + xf_input_event(xfc, cookie->data, cookie->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie); + return 0; +} + +#else + +int xf_input_init(xfContext* xfc, Window window) +{ + return 0; +} + +#endif + +int xf_input_handle_event(xfContext* xfc, XEvent* event) +{ +#ifdef WITH_XI + + if (xfc->context.settings->MultiTouchInput) + { + return xf_input_handle_event_remote(xfc, event); + } + + if (xfc->context.settings->MultiTouchGestures) + { + return xf_input_handle_event_local(xfc, event); + } + +#endif + return 0; +} diff --git a/client/X11/xf_input.h b/client/X11/xf_input.h new file mode 100644 index 0000000..c08fea0 --- /dev/null +++ b/client/X11/xf_input.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Input + * + * Copyright 2013 Corey Clayton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#endif + +int xf_input_init(xfContext* xfc, Window window); +int xf_input_handle_event(xfContext* xfc, 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..61bf89f --- /dev/null +++ b/client/X11/xf_keyboard.c @@ -0,0 +1,654 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "xf_event.h" + +#include "xf_keyboard.h" + +#include +#define TAG CLIENT_TAG("x11") + +static BOOL firstPressRightCtrl = TRUE; +static BOOL ungrabKeyboardWithRightCtrl = TRUE; + +static BOOL xf_keyboard_action_script_init(xfContext* xfc) +{ + FILE* keyScript; + char* keyCombination; + char buffer[1024] = { 0 }; + char command[1024] = { 0 }; + xfc->actionScriptExists = PathFileExistsA(xfc->context.settings->ActionScript); + + if (!xfc->actionScriptExists) + return FALSE; + + xfc->keyCombinations = ArrayList_New(TRUE); + + if (!xfc->keyCombinations) + return FALSE; + + ArrayList_Object(xfc->keyCombinations)->fnObjectFree = free; + sprintf_s(command, sizeof(command), "%s key", xfc->context.settings->ActionScript); + keyScript = popen(command, "r"); + + if (!keyScript) + { + xfc->actionScriptExists = FALSE; + return FALSE; + } + + while (fgets(buffer, sizeof(buffer), keyScript) != NULL) + { + strtok(buffer, "\n"); + keyCombination = _strdup(buffer); + + if (!keyCombination || ArrayList_Add(xfc->keyCombinations, keyCombination) < 0) + { + 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) +{ + xf_keyboard_clear(xfc); + xfc->KeyboardLayout = xfc->context.settings->KeyboardLayout; + xfc->KeyboardLayout = freerdp_keyboard_init(xfc->KeyboardLayout); + xfc->context.settings->KeyboardLayout = xfc->KeyboardLayout; + + if (xfc->modifierMap) + XFreeModifiermap(xfc->modifierMap); + + if (!(xfc->modifierMap = XGetModifierMapping(xfc->display))) + return FALSE; + + xf_keyboard_action_script_init(xfc); + return TRUE; +} + +void xf_keyboard_free(xfContext* xfc) +{ + if (xfc->modifierMap) + { + XFreeModifiermap(xfc->modifierMap); + xfc->modifierMap = NULL; + } + + xf_keyboard_action_script_free(xfc); +} + +void xf_keyboard_clear(xfContext* xfc) +{ + ZeroMemory(xfc->KeyboardState, 256 * sizeof(BOOL)); +} + +void xf_keyboard_key_press(xfContext* xfc, BYTE keycode, KeySym keysym) +{ + if (keycode < 8) + return; + + xfc->KeyboardState[keycode] = TRUE; + + if (xf_keyboard_handle_special_keys(xfc, keysym)) + return; + + xf_keyboard_send_key(xfc, TRUE, keycode); +} + +void xf_keyboard_key_release(xfContext* xfc, BYTE keycode, KeySym keysym) +{ + if (keycode < 8) + return; + + xfc->KeyboardState[keycode] = FALSE; + xf_keyboard_handle_special_keys_release(xfc, keysym); + xf_keyboard_send_key(xfc, FALSE, keycode); +} + +void xf_keyboard_release_all_keypress(xfContext* xfc) +{ + size_t keycode; + DWORD rdp_scancode; + + for (keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++) + { + if (xfc->KeyboardState[keycode]) + { + 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->context.input, FALSE, + RDP_SCANCODE_TAB); + + freerdp_input_send_keyboard_event_ex(xfc->context.input, FALSE, rdp_scancode); + xfc->KeyboardState[keycode] = FALSE; + } + } +} + +BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym) +{ + KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); + return xfc->KeyboardState[keycode]; +} + +void xf_keyboard_send_key(xfContext* xfc, BOOL down, BYTE keycode) +{ + DWORD rdp_scancode; + rdpInput* input; + input = xfc->context.input; + rdp_scancode = freerdp_keyboard_get_rdp_scancode_from_x11_keycode(keycode); + + if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + { + WLog_ERR(TAG, "Unknown key with X keycode 0x%02"PRIx8"", keycode); + } + else 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 + { + freerdp_input_send_keyboard_event_ex(input, down, rdp_scancode); + + if ((rdp_scancode == RDP_SCANCODE_CAPSLOCK) && (down == FALSE)) + { + UINT32 syncFlags; + syncFlags = xf_keyboard_get_toggle_keys_state(xfc); + input->SynchronizeEvent(input, syncFlags); + } + } +} + +int xf_keyboard_read_keyboard_state(xfContext* xfc) +{ + int dummy; + Window wdummy; + UINT32 state = 0; + + if (!xfc->remote_app) + { + 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 modifierpos, key, keysymMask = 0; + KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); + + if (keycode == NoSymbol) + return 0; + + for (modifierpos = 0; modifierpos < 8; modifierpos++) + { + int offset = xfc->modifierMap->max_keypermod * modifierpos; + + for (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) +{ + int keysymMask; + + if (!xfc->xkbAvailable) + return FALSE; + + 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) +{ + int state; + UINT32 toggleKeysState = 0; + 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) +{ + int state; + size_t i; + KeyCode keycode; + 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 + }; + state = xf_keyboard_read_keyboard_state(xfc); + + for (i = 0; i < ARRAYSIZE(keysyms); i++) + { + if (xf_keyboard_get_key_state(xfc, state, keysyms[i])) + { + keycode = XKeysymToKeycode(xfc->display, keysyms[i]); + xfc->KeyboardState[keycode] = TRUE; + } + } +} + +void xf_keyboard_focus_in(xfContext* xfc) +{ + rdpInput* input; + UINT32 syncFlags, state; + Window w; + int d, x, y; + + if (!xfc->display || !xfc->window) + return; + + input = xfc->context.input; + syncFlags = xf_keyboard_get_toggle_keys_state(xfc); + input->FocusInEvent(input, syncFlags); + xk_keyboard_update_modifier_keys(xfc); + + /* finish with a mouse pointer position like mstsc.exe if required */ + + if (xfc->remote_app) + return; + + if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, + &state)) + { + if (x >= 0 && x < xfc->window->width && y >= 0 && y < xfc->window->height) + { + xf_event_adjust_coordinates(xfc, &x, &y); + input->MouseEvent(input, PTR_FLAGS_MOVE, x, y); + } + } +} + +static int xf_keyboard_execute_action_script(xfContext* xfc, + XF_MODIFIER_KEYS* mod, + KeySym keysym) +{ + int index; + int count; + int status = 1; + FILE* keyScript; + const char* keyStr; + BOOL match = FALSE; + char* keyCombination; + 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; + } + + keyStr = XKeysymToString(keysym); + + if (keyStr == 0) + { + return 1; + } + + if (mod->Shift) + strcat(combination, "Shift+"); + + if (mod->Ctrl) + strcat(combination, "Ctrl+"); + + if (mod->Alt) + strcat(combination, "Alt+"); + + if (mod->Super) + strcat(combination, "Super+"); + + strcat(combination, keyStr); + count = ArrayList_Count(xfc->keyCombinations); + + for (index = 0; index < count; index++) + { + keyCombination = (char*) ArrayList_GetItem(xfc->keyCombinations, index); + + if (_stricmp(keyCombination, combination) == 0) + { + match = TRUE; + break; + } + } + + if (!match) + return 1; + + sprintf_s(command, sizeof(command), "%s key %s", + xfc->context.settings->ActionScript, combination); + keyScript = popen(command, "r"); + + if (!keyScript) + return -1; + + while (fgets(buffer, sizeof(buffer), keyScript) != NULL) + { + strtok(buffer, "\n"); + + 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 && firstPressRightCtrl) + { + // Right Ctrl is pressed, getting ready to ungrab + ungrabKeyboardWithRightCtrl = TRUE; + firstPressRightCtrl = FALSE; + } + } + else + { + // some other key has been pressed, abort ungrabbing + if (ungrabKeyboardWithRightCtrl) + 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 */ + xf_toggle_control(xfc); + return TRUE; + } + } + +#if 0 /* set to 1 to enable multi touch gesture simulation via keyboard */ +#ifdef WITH_XRENDER + + if (!xfc->remote_app && xfc->settings->MultiTouchGestures) + { + rdpContext* ctx = &xfc->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 */ + xfc->scaledWidth = xfc->sessionWidth; + xfc->scaledHeight = xfc->sessionHeight; + xfc->offset_x = 0; + xfc->offset_y = 0; + + if (!xfc->fullscreen && (xfc->sessionWidth != xfc->window->width || + xfc->sessionHeight != xfc->window->height)) + { + xf_ResizeDesktopWindow(xfc, xfc->window, xfc->sessionWidth, xfc->sessionHeight); + } + + xf_draw_screen(xfc, 0, 0, xfc->sessionWidth, xfc->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; + + firstPressRightCtrl = TRUE; + + if (!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) + { + xf_toggle_control(xfc); + } + + xfc->mouse_active = FALSE; + XUngrabKeyboard(xfc->display, CurrentTime); + } + + // ungrabbed + 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; +} diff --git a/client/X11/xf_keyboard.h b/client/X11/xf_keyboard.h new file mode 100644 index 0000000..c7e601b --- /dev/null +++ b/client/X11/xf_keyboard.h @@ -0,0 +1,62 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#include "xf_client.h" +#include "xfreerdp.h" + +struct _XF_MODIFIER_KEYS +{ + BOOL Shift; + BOOL LeftShift; + BOOL RightShift; + BOOL Alt; + BOOL LeftAlt; + BOOL RightAlt; + BOOL Ctrl; + BOOL LeftCtrl; + BOOL RightCtrl; + BOOL Super; + BOOL LeftSuper; + BOOL RightSuper; +}; +typedef struct _XF_MODIFIER_KEYS XF_MODIFIER_KEYS; + +BOOL xf_keyboard_init(xfContext* xfc); +void xf_keyboard_free(xfContext* xfc); +void xf_keyboard_clear(xfContext* xfc); +void xf_keyboard_key_press(xfContext* xfc, BYTE keycode, KeySym keysym); +void xf_keyboard_key_release(xfContext* xfc, BYTE keycode, KeySym keysym); +void xf_keyboard_release_all_keypress(xfContext* xfc); +BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym); +void xf_keyboard_send_key(xfContext* xfc, BOOL down, BYTE keycode); +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); + +#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..9eb854e --- /dev/null +++ b/client/X11/xf_monitor.c @@ -0,0 +1,529 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2017 David Fort + * Copyright 2018 Kai Harms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include + +#include + +#define TAG CLIENT_TAG("x11") + +#ifdef WITH_XINERAMA +#include +#endif + +#ifdef WITH_XRANDR +#include +#include + +#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; + int major, minor; + int i, nmonitors = 0; + display = XOpenDisplay(NULL); + + if (!display) + { + WLog_ERR(TAG, "failed to open X display"); + return -1; + } + +#if defined(USABLE_XRANDR) + + if (XRRQueryExtension(xfc->display, &major, &minor) && + (XRRQueryVersion(xfc->display, &major, &minor) == True) && + (major * 100 + minor >= 105)) + { + XRRMonitorInfo* monitors = XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, + &nmonitors); + + for (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 (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) +{ + int index; + rdpSettings* settings = xfc->context.settings; + + if (!settings->NumMonitorIds) + return TRUE; + + for (index = 0; index < settings->NumMonitorIds; index++) + { + if (settings->MonitorIds[index] == id) + return TRUE; + } + + return FALSE; +} + +BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + UINT32 i; + int nmonitors = 0; + int monitor_index = 0; + BOOL primaryMonitorFound = FALSE; + VIRTUAL_SCREEN* vscreen; + rdpSettings* settings = xfc->context.settings; + int mouse_x, mouse_y, _dummy_i; + Window _dummy_w; + int current_monitor = 0; + Screen* screen; + MONITOR_INFO* monitor; +#if defined WITH_XINERAMA || defined WITH_XRANDR + int major, minor; +#endif +#if defined(USABLE_XRANDR) + XRRMonitorInfo* rrmonitors = NULL; + BOOL useXRandr = FALSE; +#endif + vscreen = &xfc->vscreen; + *pMaxWidth = settings->DesktopWidth; + *pMaxHeight = settings->DesktopHeight; + + /* 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)) + { + XRRMonitorInfo* rrmonitors = XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, + &vscreen->nmonitors); + + if (vscreen->nmonitors > 16) + vscreen->nmonitors = 0; + + if (vscreen->nmonitors) + { + for (i = 0; i < vscreen->nmonitors; i++) + { + vscreen->monitors[i].area.left = rrmonitors[i].x; + vscreen->monitors[i].area.top = rrmonitors[i].y; + vscreen->monitors[i].area.right = rrmonitors[i].x + rrmonitors[i].width - 1; + vscreen->monitors[i].area.bottom = rrmonitors[i].y + rrmonitors[i].height - 1; + vscreen->monitors[i].primary = rrmonitors[i].primary > 0; + } + } + + XRRFreeMonitors(rrmonitors); + 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 (i = 0; i < vscreen->nmonitors; i++) + { + vscreen->monitors[i].area.left = screenInfo[i].x_org; + vscreen->monitors[i].area.top = screenInfo[i].y_org; + vscreen->monitors[i].area.right = screenInfo[i].x_org + screenInfo[i].width - 1; + vscreen->monitors[i].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 (i = 0; i < vscreen->nmonitors; i++) + { + if ((mouse_x >= vscreen->monitors[i].area.left) && + (mouse_x <= vscreen->monitors[i].area.right) && + (mouse_y >= vscreen->monitors[i].area.top) && + (mouse_y <= vscreen->monitors[i].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 ((!settings->UseMultimon && !settings->SpanMonitors) || + (settings->Workarea && !settings->RemoteApplicationMode)) + { + /* If no monitors were specified on the command-line then set the current monitor as active */ + if (!settings->NumMonitorIds) + { + settings->MonitorIds[0] = current_monitor; + } + + /* 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. + */ + settings->NumMonitorIds = 1; + } + + /* 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 (settings->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 (settings->NumMonitorIds == 1) + { + monitor = vscreen->monitors + current_monitor; + 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 (settings->Fullscreen) + { + *pMaxWidth = WidthOfScreen(xfc->screen); + *pMaxHeight = HeightOfScreen(xfc->screen); + } + else if (settings->Workarea) + { + *pMaxWidth = xfc->workArea.width; + *pMaxHeight = xfc->workArea.height; + } + else if (settings->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) + { + *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 (settings->PercentScreenUseWidth) + *pMaxWidth = ((vscreen->monitors[current_monitor].area.right - + vscreen->monitors[current_monitor].area.left + 1) * settings->PercentScreen) / + 100; + + if (settings->PercentScreenUseHeight) + *pMaxHeight = ((vscreen->monitors[current_monitor].area.bottom - + vscreen->monitors[current_monitor].area.top + 1) * settings->PercentScreen) / + 100; + } + else + { + *pMaxWidth = xfc->workArea.width; + *pMaxHeight = xfc->workArea.height; + + if (settings->PercentScreenUseWidth) + *pMaxWidth = (xfc->workArea.width * settings->PercentScreen) / 100; + + if (settings->PercentScreenUseHeight) + *pMaxHeight = (xfc->workArea.height * settings->PercentScreen) / 100; + } + } + else if (settings->DesktopWidth && settings->DesktopHeight) + { + *pMaxWidth = settings->DesktopWidth; + *pMaxHeight = settings->DesktopHeight; + } + + /* Create array of all active monitors by taking into account monitors requested on the command-line */ + for (i = 0; i < vscreen->nmonitors; i++) + { + MONITOR_ATTRIBUTES* attrs; + + if (!xf_is_monitor_id_active(xfc, i)) + continue; + + settings->MonitorDefArray[nmonitors].x = vscreen->monitors[i].area.left; + settings->MonitorDefArray[nmonitors].y = vscreen->monitors[i].area.top; + settings->MonitorDefArray[nmonitors].width = + MIN(vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1, *pMaxWidth); + settings->MonitorDefArray[nmonitors].height = + MIN(vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1, *pMaxHeight); + settings->MonitorDefArray[nmonitors].orig_screen = i; +#ifdef USABLE_XRANDR + + if (useXRandr && rrmonitors) + { + Rotation rot, ret; + attrs = &settings->MonitorDefArray[nmonitors].attributes; + attrs->physicalWidth = rrmonitors[i].mwidth; + attrs->physicalHeight = rrmonitors[i].mheight; + ret = XRRRotations(xfc->display, i, &rot); + attrs->orientation = rot; + } + +#endif + + if (i == settings->MonitorIds[0]) + { + settings->MonitorDefArray[nmonitors].is_primary = TRUE; + settings->MonitorLocalShiftX = settings->MonitorDefArray[nmonitors].x; + settings->MonitorLocalShiftY = settings->MonitorDefArray[nmonitors].y; + 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) + { + settings->MonitorDefArray[0].x = vscreen->monitors[current_monitor].area.left; + settings->MonitorDefArray[0].y = vscreen->monitors[current_monitor].area.top; + settings->MonitorDefArray[0].width = MIN( + vscreen->monitors[current_monitor].area.right - + vscreen->monitors[current_monitor].area.left + 1, *pMaxWidth); + settings->MonitorDefArray[0].height = MIN( + vscreen->monitors[current_monitor].area.bottom - + vscreen->monitors[current_monitor].area.top + 1, *pMaxHeight); + settings->MonitorDefArray[0].orig_screen = current_monitor; + nmonitors = 1; + } + + settings->MonitorCount = nmonitors; + + /* If we have specific monitor information */ + if (settings->MonitorCount) + { + /* Initialize bounding rectangle for all monitors */ + int vX = settings->MonitorDefArray[0].x; + int vY = settings->MonitorDefArray[0].y; + int vR = vX + settings->MonitorDefArray[0].width; + int vB = vY + settings->MonitorDefArray[0].height; + xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = + xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right = + settings->MonitorDefArray[0].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 (i = 1; i < settings->MonitorCount; i++) + { + /* does the same as gdk_rectangle_union */ + int destX = MIN(vX, settings->MonitorDefArray[i].x); + int destY = MIN(vY, settings->MonitorDefArray[i].y); + int destR = MAX(vR, settings->MonitorDefArray[i].x + + settings->MonitorDefArray[i].width); + int destB = MAX(vB, settings->MonitorDefArray[i].y + + settings->MonitorDefArray[i].height); + + if (vX != destX) + xfc->fullscreenMonitors.left = settings->MonitorDefArray[i].orig_screen; + + if (vY != destY) + xfc->fullscreenMonitors.top = settings->MonitorDefArray[i].orig_screen; + + if (vR != destR) + xfc->fullscreenMonitors.right = settings->MonitorDefArray[i].orig_screen; + + if (vB != destB) + xfc->fullscreenMonitors.bottom = settings->MonitorDefArray[i].orig_screen; + + vX = destX; + vY = destY; + vR = destR; + vB = destB; + } + + vscreen->area.left = 0; + vscreen->area.right = vR - vX - 1; + vscreen->area.top = 0; + vscreen->area.bottom = vB - vY - 1; + + if (settings->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 (settings->NumMonitorIds) + { + /* The first monitor is the first in the setting which should be used */ + monitor_index = settings->MonitorIds[0]; + } + else + { + /* This is the same as when we would trust the Xinerama results.. + and set the monitor index to zero. + The monitor listed with /monitor-list on index zero is always the primary + */ + screen = DefaultScreenOfDisplay(xfc->display); + monitor_index = XScreenNumberOfScreen(screen); + } + + int j = monitor_index; + + /* If the "default" monitor is not 0,0 use it */ + if (settings->MonitorDefArray[j].x != 0 || settings->MonitorDefArray[j].y != 0) + { + settings->MonitorDefArray[j].is_primary = TRUE; + settings->MonitorLocalShiftX = settings->MonitorDefArray[j].x; + settings->MonitorLocalShiftY = settings->MonitorDefArray[j].y; + primaryMonitorFound = TRUE; + } + else + { + /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a fallback*/ + for (i = 0; i < settings->MonitorCount; i++) + { + if (!primaryMonitorFound && settings->MonitorDefArray[i].x == 0 + && settings->MonitorDefArray[i].y == 0) + { + settings->MonitorDefArray[i].is_primary = TRUE; + settings->MonitorLocalShiftX = settings->MonitorDefArray[i].x; + settings->MonitorLocalShiftY = settings->MonitorDefArray[i].y; + 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 (i = 0; i < settings->MonitorCount; i++) + { + settings->MonitorDefArray[i].x = settings->MonitorDefArray[i].x - + settings->MonitorLocalShiftX; + settings->MonitorDefArray[i].y = settings->MonitorDefArray[i].y - + settings->MonitorLocalShiftY; + } + + /* Set the desktop width and height according to the bounding rectangle around the active monitors */ + *pMaxWidth = vscreen->area.right - vscreen->area.left + 1; + *pMaxHeight = 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 (settings->MonitorCount) + settings->SupportMonitorLayoutPdu = TRUE; + +#ifdef USABLE_XRANDR + + if (rrmonitors) + XRRFreeMonitors(rrmonitors); + +#endif + return TRUE; +} diff --git a/client/X11/xf_monitor.h b/client/X11/xf_monitor.h new file mode 100644 index 0000000..2e3cd2f --- /dev/null +++ b/client/X11/xf_monitor.h @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +struct _MONITOR_INFO +{ + RECTANGLE_16 area; + RECTANGLE_16 workarea; + BOOL primary; +}; +typedef struct _MONITOR_INFO MONITOR_INFO; + +struct _VIRTUAL_SCREEN +{ + int nmonitors; + RECTANGLE_16 area; + RECTANGLE_16 workarea; + MONITOR_INFO* monitors; +}; +typedef struct _VIRTUAL_SCREEN 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..0b4e992 --- /dev/null +++ b/client/X11/xf_rail.c @@ -0,0 +1,1273 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 RAIL + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "xf_window.h" +#include "xf_rail.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 xf_rail_icon_cache xfRailIconCache; + +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); + } +} + +void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled) +{ + xfAppWindow* appWindow; + RAIL_ACTIVATE_ORDER activate; + 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 != appWindow->windowWidth || + appWindow->height != 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; + windowMove.top = appWindow->y; + windowMove.right = windowMove.left + appWindow->width; + windowMove.bottom = windowMove.top + appWindow->height; + xfc->rail->ClientWindowMove(xfc->rail, &windowMove); + } +} + +void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow) +{ + int x, y; + int child_x; + int child_y; + unsigned int mask; + Window root_window; + Window child_window; + RAIL_WINDOW_MOVE_ORDER windowMove; + rdpInput* input = xfc->context.input; + /* + * 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; + windowMove.top = appWindow->y; + windowMove.right = windowMove.left + + appWindow->width; /* In the update to RDP the position is one past the window */ + windowMove.bottom = windowMove.top + appWindow->height; + 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)) + { + input->MouseEvent(input, 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; +} + +static void xf_rail_invalidate_region(xfContext* xfc, REGION16* invalidRegion) +{ + int index; + int count; + RECTANGLE_16 updateRect; + RECTANGLE_16 windowRect; + ULONG_PTR* pKeys = NULL; + xfAppWindow* appWindow; + const RECTANGLE_16* extents; + REGION16 windowInvalidRegion; + region16_init(&windowInvalidRegion); + count = HashTable_GetKeys(xfc->railWindows, &pKeys); + + for (index = 0; index < count; index++) + { + appWindow = (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*) pKeys[index]); + + if (appWindow) + { + windowRect.left = MAX(appWindow->x, 0); + windowRect.top = MAX(appWindow->y, 0); + windowRect.right = MAX(appWindow->x + appWindow->width, 0); + windowRect.bottom = MAX(appWindow->y + appWindow->height, 0); + region16_clear(&windowInvalidRegion); + region16_intersect_rect(&windowInvalidRegion, invalidRegion, &windowRect); + + if (!region16_is_empty(&windowInvalidRegion)) + { + extents = region16_extents(&windowInvalidRegion); + updateRect.left = extents->left - appWindow->x; + updateRect.top = extents->top - appWindow->y; + updateRect.right = extents->right - appWindow->x; + updateRect.bottom = extents->bottom - appWindow->y; + xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top, + updateRect.right - updateRect.left, + updateRect.bottom - updateRect.top); + } + } + } + + free(pKeys); + region16_uninit(&windowInvalidRegion); +} + +void xf_rail_paint(xfContext* xfc, INT32 uleft, INT32 utop, UINT32 uright, + UINT32 ubottom) +{ + REGION16 invalidRegion; + RECTANGLE_16 invalidRect; + invalidRect.left = uleft; + invalidRect.top = utop; + invalidRect.right = uright; + invalidRect.bottom = ubottom; + region16_init(&invalidRegion); + region16_union_rect(&invalidRegion, &invalidRegion, &invalidRect); + xf_rail_invalidate_region(xfc, &invalidRegion); + region16_uninit(&invalidRegion); +} + +/* RemoteApp Core Protocol Extension */ + +static BOOL xf_rail_window_common(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, WINDOW_STATE_ORDER* windowState) +{ + xfAppWindow* appWindow = NULL; + xfContext* xfc = (xfContext*) context; + UINT32 fieldFlags = orderInfo->fieldFlags; + BOOL position_or_size_updated = FALSE; + + if (fieldFlags & WINDOW_ORDER_STATE_NEW) + { + appWindow = (xfAppWindow*) calloc(1, sizeof(xfAppWindow)); + + if (!appWindow) + return FALSE; + + appWindow->xfc = xfc; + appWindow->windowId = orderInfo->windowId; + appWindow->dwStyle = windowState->style; + appWindow->dwExStyle = windowState->extendedStyle; + appWindow->x = appWindow->windowOffsetX = windowState->windowOffsetX; + appWindow->y = appWindow->windowOffsetY = windowState->windowOffsetY; + appWindow->width = appWindow->windowWidth = windowState->windowWidth; + appWindow->height = appWindow->windowHeight = windowState->windowHeight; + + /* Ensure window always gets a window title */ + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = NULL; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + /* error handled below */ + } + } + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) windowState->titleInfo.string, + windowState->titleInfo.length / 2, &title, 0, NULL, NULL) < 1) + { + 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; + } + + HashTable_Add(xfc->railWindows, (void*)(UINT_PTR) orderInfo->windowId, + (void*) appWindow); + xf_AppWindowInit(xfc, appWindow); + } + else + { + appWindow = (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*)(UINT_PTR) orderInfo->windowId); + } + + 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_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; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + return FALSE; + } + } + else if (ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) windowState->titleInfo.string, + windowState->titleInfo.length / 2, &title, 0, NULL, NULL) < 1) + { + 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 == appWindow->windowOffsetX && + appWindow->y == appWindow->windowOffsetY && + appWindow->width == appWindow->windowWidth && + appWindow->height == 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, + WINDOW_ORDER_INFO* orderInfo) +{ + xfContext* xfc = (xfContext*) context; + + if (!xfc) + return FALSE; + + HashTable_Remove(xfc->railWindows, (void*)(UINT_PTR) orderInfo->windowId); + return TRUE; +} + +static xfRailIconCache* RailIconCache_New(rdpSettings* settings) +{ + xfRailIconCache* cache; + cache = calloc(1, sizeof(xfRailIconCache)); + + if (!cache) + return NULL; + + cache->numCaches = settings->RemoteAppNumIconCaches; + cache->numCacheEntries = settings->RemoteAppNumIconCacheEntries; + cache->entries = calloc(cache->numCaches * cache->numCacheEntries, + sizeof(xfRailIcon)); + + if (!cache->entries) + { + WLog_ERR(TAG, "failed to allocate icon cache %d x %d entries", + cache->numCaches, cache->numCacheEntries); + free(cache); + return NULL; + } + + return cache; +} + +static void RailIconCache_Free(xfRailIconCache* cache) +{ + UINT32 i; + + if (cache) + { + for (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]; +} + +/* + * DIB color palettes are arrays of RGBQUAD structs with colors in BGRX format. + * They are used only by 1, 2, 4, and 8-bit bitmaps. + */ +static void fill_gdi_palette_for_icon(ICON_INFO* iconInfo, gdiPalette* palette) +{ + UINT32 i; + palette->format = PIXEL_FORMAT_BGRX32; + ZeroMemory(palette->palette, sizeof(palette->palette)); + + if (!iconInfo->cbColorTable) + return; + + if ((iconInfo->cbColorTable % 4 != 0) || (iconInfo->cbColorTable / 4 > 256)) + { + WLog_WARN(TAG, "weird palette size: %u", iconInfo->cbColorTable); + return; + } + + for (i = 0; i < iconInfo->cbColorTable / 4; i++) + { + palette->palette[i] = ReadColor(&iconInfo->colorTable[4 * i], palette->format); + } +} + +static BOOL convert_icon_color_to_argb(ICON_INFO* iconInfo, BYTE* argbPixels) +{ + DWORD format; + gdiPalette palette; + + /* + * Color formats used by icons are DIB bitmap formats (2-bit format + * is not used by MS-RDPERP). Note that 16-bit is RGB555, not RGB565, + * and that 32-bit format uses BGRA order. + */ + switch (iconInfo->bpp) + { + case 1: + case 4: + /* + * These formats are not supported by freerdp_image_copy(). + * PIXEL_FORMAT_MONO and PIXEL_FORMAT_A4 are *not* correct + * color formats for this. Please fix freerdp_image_copy() + * if you came here to fix a broken icon of some weird app + * that still uses 1 or 4bpp format in the 21st century. + */ + WLog_WARN(TAG, "1bpp and 4bpp icons are not supported"); + return FALSE; + + case 8: + format = PIXEL_FORMAT_RGB8; + break; + + case 16: + format = PIXEL_FORMAT_RGB15; + break; + + case 24: + format = PIXEL_FORMAT_RGB24; + break; + + case 32: + format = PIXEL_FORMAT_BGRA32; + break; + + default: + WLog_WARN(TAG, "invalid icon bpp: %d", iconInfo->bpp); + return FALSE; + } + + fill_gdi_palette_for_icon(iconInfo, &palette); + return freerdp_image_copy( + argbPixels, + PIXEL_FORMAT_ARGB32, + 0, 0, 0, + iconInfo->width, + iconInfo->height, + iconInfo->bitsColor, + format, + 0, 0, 0, + &palette, + FREERDP_FLIP_VERTICAL + ); +} + +static inline UINT32 div_ceil(UINT32 a, UINT32 b) +{ + return (a + (b - 1)) / b; +} + +static inline UINT32 round_up(UINT32 a, UINT32 b) +{ + return b * div_ceil(a, b); +} + +static void apply_icon_alpha_mask(ICON_INFO* iconInfo, BYTE* argbPixels) +{ + BYTE nextBit; + BYTE* maskByte; + UINT32 x, y; + UINT32 stride; + + if (!iconInfo->cbBitsMask) + return; + + /* + * Each byte encodes 8 adjacent pixels (with LSB padding as needed). + * And due to hysterical raisins, stride of DIB bitmaps must be + * a multiple of 4 bytes. + */ + stride = round_up(div_ceil(iconInfo->width, 8), 4); + + for (y = 0; y < iconInfo->height; y++) + { + /* ɐᴉlɐɹʇsn∀ uᴉ ǝɹ,ǝʍ ʇɐɥʇ ʇǝƃɹoɟ ʇ,uop */ + maskByte = &iconInfo->bitsMask[stride * (iconInfo->height - 1 - y)]; + nextBit = 0x80; + + for (x = 0; x < iconInfo->width; x++) + { + BYTE alpha = (*maskByte & nextBit) ? 0x00 : 0xFF; + argbPixels[4 * (x + y * iconInfo->width)] &= alpha; + nextBit >>= 1; + + if (!nextBit) + { + nextBit = 0x80; + maskByte++; + } + } + } +} + +/* + * _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(ICON_INFO* iconInfo, xfRailIcon* railIcon) +{ + BYTE* argbPixels; + BYTE* nextPixel; + long* pixels; + int i; + int nelements; + argbPixels = calloc(iconInfo->width * iconInfo->height, 4); + + if (!argbPixels) + goto error; + + if (!convert_icon_color_to_argb(iconInfo, argbPixels)) + goto error; + + apply_icon_alpha_mask(iconInfo, argbPixels); + 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 (i = 2; i < nelements; i++) + { + pixels[i] = ReadColor(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) +{ + XChangeProperty(xfc->display, railWindow->handle, xfc->_NET_WM_ICON, + XA_CARDINAL, 32, replace ? PropModeReplace : PropModeAppend, + (unsigned char*) icon->data, icon->length); + XFlush(xfc->display); +} + +static xfAppWindow* xf_rail_get_window_by_id(xfContext* xfc, UINT32 windowId) +{ + return (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*)(UINT_PTR) windowId); +} + +static BOOL xf_rail_window_icon(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, WINDOW_ICON_ORDER* windowIcon) +{ + xfContext* xfc = (xfContext*) context; + xfAppWindow* railWindow; + xfRailIcon* icon; + BOOL replaceIcon; + railWindow = xf_rail_get_window_by_id(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, + WINDOW_ORDER_INFO* orderInfo, WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + xfContext* xfc = (xfContext*) context; + xfAppWindow* railWindow; + xfRailIcon* icon; + BOOL replaceIcon; + railWindow = xf_rail_get_window_by_id(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, + WINDOW_ORDER_INFO* orderInfo, 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, + WINDOW_ORDER_INFO* orderInfo, NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_update(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_delete(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo) +{ + return TRUE; +} + +static BOOL xf_rail_monitored_desktop(rdpContext* context, + WINDOW_ORDER_INFO* orderInfo, MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + return TRUE; +} + +static BOOL xf_rail_non_monitored_desktop(rdpContext* context, + 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 = (xfContext*) context->custom; + + 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(xfc->context.instance); + } + 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) +{ + 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) +{ + RAIL_EXEC_ORDER exec; + RAIL_SYSPARAM_ORDER sysparam; + RAIL_HANDSHAKE_ORDER clientHandshake; + RAIL_CLIENT_STATUS_ORDER clientStatus; + xfContext* xfc = (xfContext*) context->custom; + rdpSettings* settings = xfc->context.settings; + clientHandshake.buildNumber = 0x00001DB0; + context->ClientHandshake(context, &clientHandshake); + ZeroMemory(&clientStatus, sizeof(RAIL_CLIENT_STATUS_ORDER)); + clientStatus.flags = RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; + context->ClientInformation(context, &clientStatus); + + if (settings->RemoteAppLanguageBarSupported) + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ + context->ClientLanguageBarInfo(context, &langBarInfo); + } + + ZeroMemory(&sysparam, sizeof(RAIL_SYSPARAM_ORDER)); + sysparam.params = 0; + sysparam.params |= SPI_MASK_SET_HIGH_CONTRAST; + sysparam.highContrast.colorScheme.string = NULL; + sysparam.highContrast.colorScheme.length = 0; + sysparam.highContrast.flags = 0x7E; + sysparam.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; + sysparam.mouseButtonSwap = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_PREF; + sysparam.keyboardPref = FALSE; + sysparam.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; + sysparam.dragFullWindows = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_CUES; + sysparam.keyboardCues = FALSE; + sysparam.params |= SPI_MASK_SET_WORK_AREA; + sysparam.workArea.left = 0; + sysparam.workArea.top = 0; + sysparam.workArea.right = settings->DesktopWidth; + sysparam.workArea.bottom = settings->DesktopHeight; + sysparam.dragFullWindows = FALSE; + context->ClientSystemParam(context, &sysparam); + ZeroMemory(&exec, sizeof(RAIL_EXEC_ORDER)); + exec.RemoteApplicationProgram = settings->RemoteApplicationProgram; + exec.RemoteApplicationWorkingDir = settings->ShellWorkingDirectory; + exec.RemoteApplicationArguments = settings->RemoteApplicationCmdLine; + context->ClientExecute(context, &exec); + return CHANNEL_RC_OK; +} + +/** + * 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 CHANNEL_RC_OK; +} + +/** + * 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, y = 0; + int direction = 0; + Window child_window; + xfAppWindow* appWindow = NULL; + xfContext* xfc = (xfContext*) context->custom; + appWindow = (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*)(UINT_PTR) 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) +{ + xfAppWindow* appWindow = NULL; + xfContext* xfc = (xfContext*) context->custom; + appWindow = (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*)(UINT_PTR) 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 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; + + xfc->railWindows->valueFree = rail_window_free; + xfc->railIconCache = RailIconCache_New(xfc->context.settings); + + if (!xfc->railIconCache) + { + HashTable_Free(xfc->railWindows); + return 0; + } + + return 1; +} + +int xf_rail_uninit(xfContext* xfc, RailClientContext* 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; +} diff --git a/client/X11/xf_rail.h b/client/X11/xf_rail.h new file mode 100644 index 0000000..2e1ea53 --- /dev/null +++ b/client/X11/xf_rail.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 RAIL + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +void xf_rail_paint(xfContext* xfc, INT32 uleft, INT32 utop, UINT32 uright, UINT32 ubottom); +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); + +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..58fa794 --- /dev/null +++ b/client/X11/xf_tsmf.c @@ -0,0 +1,469 @@ +/** + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "xf_tsmf.h" + +#ifdef WITH_XV + +#include +#include + +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) +{ + int i; + + if (!xv->xv_pixfmts) + return FALSE; + + for (i = 0; xv->xv_pixfmts[i]; i++) + { + if (xv->xv_pixfmts[i] == pixfmt) + return TRUE; + } + + return FALSE; +} + +int xf_tsmf_xv_video_frame_event(TsmfClientContext* tsmf, TSMF_VIDEO_FRAME_EVENT* event) +{ + int i; + int x, y; + UINT32 width; + UINT32 height; + BYTE* data1; + BYTE* data2; + UINT32 pixfmt; + UINT32 xvpixfmt; + XvImage* image; + int colorkey = 0; + int numRects = 0; + xfContext* xfc; + xfXvContext* xv; + XRectangle* xrects; + 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 (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 (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 (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: + CopyMemory(image->data, event->frameData, image->data_size <= event->frameSize ? + image->data_size : event->frameSize); + 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; +} + +int xf_tsmf_xv_init(xfContext* xfc, TsmfClientContext* tsmf) +{ + int ret; + unsigned int i; + unsigned int version; + unsigned int release; + unsigned int event_base; + unsigned int error_base; + unsigned int request_base; + unsigned int num_adaptors; + xfXvContext* xv; + XvAdaptorInfo* ai; + XvAttribute* attr; + XvImageFormatValues* fo; + + 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 (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); + + for (i = 0; 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 (i = 0; i < 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; +} + +int xf_tsmf_xv_uninit(xfContext* xfc, TsmfClientContext* tsmf) +{ + xfXvContext* xv = (xfXvContext*) xfc->xv_context; + + 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); +#endif + + return 1; +} + +int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf) +{ +#ifdef WITH_XV + return xf_tsmf_xv_uninit(xfc, tsmf); +#endif + + return 1; +} + 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_video.c b/client/X11/xf_video.c new file mode 100644 index 0000000..5fc8f2f --- /dev/null +++ b/client/X11/xf_video.c @@ -0,0 +1,110 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension for X11 + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include +#include + +#include "xf_video.h" + +#define TAG CLIENT_TAG("video") + +typedef struct +{ + VideoSurface base; + XImage* image; +} xfVideoSurface; + +static VideoSurface* xfVideoCreateSurface(VideoClientContext* video, BYTE* data, UINT32 x, UINT32 y, + UINT32 width, UINT32 height) +{ + xfContext* xfc = (xfContext*)video->custom; + xfVideoSurface* ret = calloc(1, sizeof(*ret)); + + if (!ret) + return NULL; + + ret->base.data = data; + ret->base.x = x; + ret->base.y = y; + ret->base.w = width; + ret->base.h = height; + ret->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0, + (char*)data, width, height, 8, width * 4); + + if (!ret->image) + { + WLog_ERR(TAG, "unable to create surface image"); + free(ret); + return NULL; + } + + return &ret->base; +} + + +static BOOL xfVideoShowSurface(VideoClientContext* video, VideoSurface* surface) +{ + xfVideoSurface* xfSurface = (xfVideoSurface*)surface; + xfContext* xfc = video->custom; +#ifdef WITH_XRENDER + + if (xfc->context.settings->SmartSizing + || xfc->context.settings->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; + + if (xfSurface) + XFree(xfSurface->image); + + free(surface); + return TRUE; +} +void xf_video_control_init(xfContext* xfc, VideoClientContext* video) +{ + gdi_video_control_init(xfc->context.gdi, video); + video->custom = xfc; + video->createSurface = xfVideoCreateSurface; + video->showSurface = xfVideoShowSurface; + video->deleteSurface = xfVideoDeleteSurface; +} + + +void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video) +{ + gdi_video_control_uninit(xfc->context.gdi, video); +} diff --git a/client/X11/xf_video.h b/client/X11/xf_video.h new file mode 100644 index 0000000..47af49c --- /dev/null +++ b/client/X11/xf_video.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension for X11 + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 +#include + +void xf_video_control_init(xfContext* xfc, VideoClientContext* video); +void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video); + +xfVideoContext* xf_video_new(xfContext* xfc); +void xf_video_free(xfVideoContext* context); + + +#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..a86ad4c --- /dev/null +++ b/client/X11/xf_window.c @@ -0,0 +1,1133 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2012 HP Development Company, LLC + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef WITH_XEXT +#include +#endif + +#ifdef WITH_XI +#include +#include "xf_input.h" +#endif + +#include "xf_rail.h" +#include "xf_input.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 + +struct _PropMotifWmHints +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +}; +typedef struct _PropMotifWmHints PropMotifWmHints; + +static void xf_SetWindowTitleText(xfContext* xfc, Window window, const char* name) +{ + const size_t i = strlen(name); + XStoreName(xfc->display, window, name); + Atom wm_Name = xfc->_NET_WM_NAME; + Atom utf8Str = xfc->UTF8_STRING; + XChangeProperty(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; + unsigned int i; + va_list argp; + va_start(argp, numArgs); + ZeroMemory(&xevent, sizeof(XEvent)); + 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 (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) +{ + UINT32 i; + rdpSettings* settings = xfc->context.settings; + int startX, startY; + UINT32 width = window->width; + UINT32 height = window->height; + /* 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); + + if (xfc->floatbar) + xf_floatbar_toggle_visibility(xfc, fullscreen); + + if (fullscreen) + { + xfc->savedWidth = xfc->window->width; + xfc->savedHeight = xfc->window->height; + xfc->savedPosX = xfc->window->left; + xfc->savedPosY = xfc->window->top; + startX = (settings->DesktopPosX != UINT32_MAX) ? settings->DesktopPosX : 0; + startY = (settings->DesktopPosY != UINT32_MAX) ? settings->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) + { + /* Initialize startX and startY with reasonable values */ + startX = xfc->context.settings->MonitorDefArray[0].x; + startY = xfc->context.settings->MonitorDefArray[0].y; + + /* Search all monitors to find the lowest startX and startY values */ + for (i = 0; i < xfc->context.settings->MonitorCount; i++) + { + startX = MIN(startX, xfc->context.settings->MonitorDefArray[i].x); + startY = MIN(startY, xfc->context.settings->MonitorDefArray[i].y); + } + + /* Lastly apply any monitor shift(translation from remote to local coordinate system) + * to startX and startY values + */ + startX += xfc->context.settings->MonitorLocalShiftX; + startY += xfc->context.settings->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 || settings->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 */ + XMoveWindow(xfc->display, window->handle, startX, startY); + } + + /* Set monitor bounds */ + if (settings->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; + unsigned long nitems; + unsigned long bytes; + BYTE* prop; + + 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; + Atom actual_type; + int actual_format; + + if (property == None) + return FALSE; + + status = XGetWindowProperty(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_INFO(TAG, "Property %lu does not exist", (unsigned long) property); + return FALSE; + } + + return TRUE; +} + +BOOL xf_GetCurrentDesktop(xfContext* xfc) +{ + BOOL status; + unsigned long nitems; + unsigned long bytes; + unsigned char* prop; + 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; + BOOL status; + unsigned long nitems; + unsigned long bytes; + unsigned char* prop; + 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) >= 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; + hints.decorations = (show) ? MWM_DECOR_ALL : 0; + hints.functions = MWM_FUNC_ALL ; + hints.flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS; + hints.inputMode = 0; + hints.status = 0; + XChangeProperty(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) +{ + Atom window_state[2]; + window_state[0] = xfc->_NET_WM_STATE_SKIP_PAGER; + window_state[1] = xfc->_NET_WM_STATE_SKIP_TASKBAR; + XChangeProperty(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; + + if (!pid) + pid = getpid(); + + am_wm_pid = xfc->_NET_WM_PID; + XChangeProperty(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 XCreateSimpleWindow(xfc->display, DefaultRootWindow(xfc->display), + 0, 0, 1, 1, 0, 0, 0); +} + +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; + int input_mask; + xfWindow* window; + Window parentWindow; + XClassHint* classHints; + rdpSettings* settings; + window = (xfWindow*) calloc(1, sizeof(xfWindow)); + + if (!window) + return NULL; + + settings = xfc->context.settings; + parentWindow = (Window) xfc->context.settings->ParentWindowId; + window->width = width; + window->height = height; + window->decorations = xfc->decorations; + window->is_mapped = FALSE; + window->is_transient = FALSE; + 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, + CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap | + CWBorderPixel | CWWinGravity | CWBitGravity, &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 + { + void* mem; + ftruncate(window->shmid, sizeof(window->handle)); + 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"; + + if (xfc->context.settings->WmClass) + classHints->res_class = xfc->context.settings->WmClass; + else + classHints->res_class = "xfreerdp"; + + XSetClassHint(xfc->display, window->handle, classHints); + XFree(classHints); + } + + 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; + + XChangeProperty(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 (xfc->context.settings->RemoteApplicationMode) + { + XMoveWindow(xfc->display, window->handle, 0, 0); + } + else if (settings->DesktopPosX != UINT32_MAX && settings->DesktopPosY != UINT32_MAX) + { + XMoveWindow(xfc->display, window->handle, settings->DesktopPosX, + settings->DesktopPosY); + } + + window->floatbar = xf_floatbar_new(xfc, window->handle); + return window; +} + +void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, + int height) +{ + XSizeHints* size_hints; + rdpSettings* settings = NULL; + + if (!xfc || !window) + return; + + settings = xfc->context.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 (!settings->SmartSizing && !settings->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; + + if (window->floatbar) + xf_floatbar_free(xfc, window, 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; + 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); + } + + XChangeProperty(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; + int vscreen_height; + 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) +{ + XGCValues gcv; + int input_mask; + XWMHints* InputModeHint; + XClassHint* class_hints; + xf_FixWindowCoordinates(xfc, &appWindow->x, &appWindow->y, &appWindow->width, + &appWindow->height); + 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->rail_ignore_configure = FALSE; + appWindow->handle = XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), + appWindow->x, appWindow->y, appWindow->width, appWindow->height, + 0, xfc->depth, InputOutput, xfc->visual, 0, &xfc->attribs); + + if (!appWindow->handle) + return -1; + + ZeroMemory(&gcv, sizeof(gcv)); + appWindow->gc = XCreateGC(xfc->display, appWindow->handle, GCGraphicsExposures, + &gcv); + class_hints = XAllocClassHint(); + + if (class_hints) + { + char* class = NULL; + + if (xfc->context.settings->WmClass) + { + class_hints->res_class = xfc->context.settings->WmClass; + } + else + { + class = malloc(sizeof("RAIL:00000000")); + sprintf_s(class, sizeof("RAIL:00000000"), "RAIL:%08"PRIX32"", appWindow->windowId); + class_hints->res_class = class; + } + + class_hints->res_name = "RAIL"; + XSetClassHint(xfc->display, appWindow->handle, class_hints); + XFree(class_hints); + free(class); + } + + /* 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); + 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; +} + +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; + 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; + XUngrabPointer(xfc->display, CurrentTime); + 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) +{ + switch (state) + { + case WINDOW_HIDE: + XWithdrawWindow(xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MINIMIZED: + XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MAXIMIZED: + /* Set the window as maximized */ + 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); + + 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) +{ + int i; + XRectangle* xrects; + + if (nrects < 1) + return; + +#ifdef WITH_XEXT + xrects = (XRectangle*) calloc(nrects, sizeof(XRectangle)); + + for (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) +{ + int i; + XRectangle* xrects; + + if (nrects < 1) + return; + +#ifdef WITH_XEXT + xrects = (XRectangle*) calloc(nrects, sizeof(XRectangle)); + + for (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, ay; + + if (appWindow == NULL) + 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, TRUE); + + if (xfc->context.settings->SoftwareGdi) + { + XPutImage(xfc->display, xfc->primary, appWindow->gc, xfc->image, + ax, ay, ax, ay, width, height); + } + + XCopyArea(xfc->display, xfc->primary, appWindow->handle, appWindow->gc, + ax, ay, width, height, x, y); + XFlush(xfc->display); + xf_unlock_x11(xfc, TRUE); +} + +void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow) +{ + if (!appWindow) + return; + + if (appWindow->gc) + XFreeGC(xfc->display, appWindow->gc); + + 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) +{ + int index; + int count; + ULONG_PTR* pKeys = NULL; + xfAppWindow* appWindow; + count = HashTable_GetKeys(xfc->railWindows, &pKeys); + + for (index = 0; index < count; index++) + { + appWindow = (xfAppWindow*) HashTable_GetItemValue(xfc->railWindows, + (void*) pKeys[index]); + + if (appWindow->handle == wnd) + { + free(pKeys); + return appWindow; + } + } + + free(pKeys); + return NULL; +} diff --git a/client/X11/xf_window.h b/client/X11/xf_window.h new file mode 100644 index 0000000..b0ecb02 --- /dev/null +++ b/client/X11/xf_window.h @@ -0,0 +1,182 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#include + +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 +#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 */ + +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 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; + + 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 rail_ignore_configure; +}; + +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, ...); + +int xf_AppWindowInit(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); +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-channels.1.xml b/client/X11/xfreerdp-channels.1.xml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/client/X11/xfreerdp-channels.1.xml diff --git a/client/X11/xfreerdp-envvar.1.xml b/client/X11/xfreerdp-envvar.1.xml new file mode 100644 index 0000000..955adf5 --- /dev/null +++ b/client/X11/xfreerdp-envvar.1.xml @@ -0,0 +1,15 @@ + + Environment variables + + + + wlog environment variable + + xfreerdp uses wLog as its log facility, you can refer to the + corresponding man page (wlog(7)) for more informations. Arguments passed + via the /log-level or /log-filters + have precedence over the environment variables. + + + + diff --git a/client/X11/xfreerdp-examples.1.xml b/client/X11/xfreerdp-examples.1.xml new file mode 100644 index 0000000..3418143 --- /dev/null +++ b/client/X11/xfreerdp-examples.1.xml @@ -0,0 +1,95 @@ + + Examples + + + xfreerdp connection.rdp /p:Pwd123! /f + + Connect in fullscreen mode using a stored configuration connection.rdp and the password Pwd123! + + + + xfreerdp /u:USER /size:50%h /v:rdp.contoso.com + + Connect to host rdp.contoso.com with user USER and a size of 50 percent of the height. If width (w) is set instead of height (h) like /size:50%w. 50 percent of the width is used. + + + + xfreerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com + + Connect to host rdp.contoso.com with user CONTOSO\\JohnDoe and password Pwd123! + + + + xfreerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489 + + Connect to host 192.168.1.100 on port 4489 with user JohnDoe, password Pwd123!. The screen width is set to 1366 and the height to 768 + + + + xfreerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100 + + Establish a connection to host 192.168.1.100 with user JohnDoe, password Pwd123! and connect to Hyper-V console (use port 2179, disable negotiation) with VMID C824F53E-95D2-46C6-9A18-23A5BB403532 + + + + +clipboard + + Activate clipboard redirection + + + + /drive:home,/home/user + + Activate drive redirection of /home/user as home drive + + + + /smartcard:<device> + + Activate smartcard redirection for device device + + + + /printer:<device>,<driver> + + Activate printer redirection for printer device using driver driver + + + + /serial:<device> + + Activate serial port redirection for port device + + + + /parallel:<device> + + Activate parallel port redirection for port device + + + + /sound:sys:alsa + + Activate audio output redirection using device sys:alsa + + + + /microphone:sys:alsa + + Activate audio input redirection using device sys:alsa + + + + /multimedia:sys:alsa + + Activate multimedia redirection using device sys:alsa + + + + /usb:id,dev:054c:0268 + + Activate USB device redirection for the device identified by 054c:0268 + + + + diff --git a/client/X11/xfreerdp.1.xml.in b/client/X11/xfreerdp.1.xml.in new file mode 100644 index 0000000..119f7f3 --- /dev/null +++ b/client/X11/xfreerdp.1.xml.in @@ -0,0 +1,63 @@ + + + + + + ] +> + + + + @MAN_TODAY@ + + The FreeRDP Team + + + + xfreerdp + 1 + freerdp + xfreerdp + + + xfreerdp + FreeRDP X11 client + + + + @MAN_TODAY@ + + + xfreerdp [file] [options] [/v:server[:port]] + + + + + @MAN_TODAY@ + + DESCRIPTION + + xfreerdp 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 xrdp and VRDP (VirtualBox). + + + + &syntax; + + &channels; + + &envvar; + + &examples; + + + LINKS + + http://www.freerdp.com/ + + + diff --git a/client/X11/xfreerdp.h b/client/X11/xfreerdp.h new file mode 100644 index 0000000..746ea33 --- /dev/null +++ b/client/X11/xfreerdp.h @@ -0,0 +1,298 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +typedef struct xf_context xfContext; + +#include + +#include "xf_window.h" +#include "xf_monitor.h" +#include "xf_channels.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + 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 _xfDispContext xfDispContext; +typedef struct _xfVideoContext xfVideoContext; +typedef struct xf_rail_icon_cache xfRailIconCache; + +/* Value of the first logical button number in X11 which must be */ +/* subtracted to go from a button number in X11 to an index into */ +/* a per-button array. */ +#define BUTTON_BASE Button1 + +/* Number of buttons that are mapped from X11 to RDP button events. */ +#define NUM_BUTTONS_MAPPED 3 + +struct xf_context +{ + rdpContext context; + DEFINE_RDP_CLIENT_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; + + HGDI_DC hdc; + UINT32 bitmap_size; + BYTE* bitmap_buffer; + + BOOL frame_begin; + UINT16 frame_x1; + UINT16 frame_y1; + UINT16 frame_x2; + UINT16 frame_y2; + + 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 use_xinput; + BOOL mouse_active; + BOOL fullscreen_toggle; + BOOL floatbar; + BOOL controlToggle; + UINT32 KeyboardLayout; + BOOL KeyboardState[256]; + XModifierKeymap* modifierMap; + wArrayList* keyCombinations; + wArrayList* xevents; + BOOL actionScriptExists; + + XSetWindowAttributes attribs; + BOOL complex_regions; + VIRTUAL_SCREEN vscreen; + void* xv_context; + + Atom* supportedAtoms; + unsigned long supportedAtomCount; + + Atom UTF8_STRING; + + 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 */ + TsmfClientContext* tsmf; + xfClipboard* clipboard; + CliprdrClientContext* cliprdr; + xfVideoContext* xfVideo; + RdpeiClientContext* rdpei; + EncomspClientContext* encomsp; + xfDispContext* xfDisp; + + RailClientContext* rail; + wHashTable* railWindows; + xfRailIconCache* railIconCache; + + BOOL xkbAvailable; + BOOL xrenderAvailable; + + /* value to be sent over wire for each logical client mouse button */ + int button_map[NUM_BUTTONS_MAPPED]; + BYTE savedMaximizedState; +}; + +BOOL xf_create_window(xfContext* xfc); +void xf_toggle_fullscreen(xfContext* xfc); +void xf_toggle_control(xfContext* xfc); + +void xf_encomsp_init(xfContext* xfc, EncomspClientContext* encomsp); +void xf_encomsp_uninit(xfContext* xfc, EncomspClientContext* encomsp); + +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_UNKNOWN = 255, +}; + +void xf_lock_x11(xfContext* xfc, BOOL display); +void xf_unlock_x11(xfContext* xfc, BOOL display); + +BOOL xf_picture_transform_required(xfContext* xfc); +void xf_draw_screen(xfContext* xfc, int x, int y, int w, int h); + +FREERDP_API DWORD xf_exit_code_from_disconnect_reason(DWORD reason); + +#endif /* FREERDP_CLIENT_X11_FREERDP_H */ + diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt new file mode 100644 index 0000000..b6805e5 --- /dev/null +++ b/client/common/CMakeLists.txt @@ -0,0 +1,89 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Client Common +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "freerdp-client") +set(MODULE_PREFIX "FREERDP_CLIENT") + +# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link +# interface. Run "cmake --help-policy CMP0022" for policy details. Use the +# cmake_policy command to set the policy and suppress this warning. +if(POLICY CMP0022) + cmake_policy(SET CMP0022 NEW) +endif() + +set(${MODULE_PREFIX}_SRCS + client.c + cmdline.c + compatibility.c + compatibility.h + file.c) + +foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS}) + get_filename_component(NINC ${FREERDP_CHANNELS_CLIENT_SRC} PATH) + include_directories(${NINC}) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} "${FREERDP_CHANNELS_CLIENT_SRC}") +endforeach() + + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32 AND BUILD_SHARED_LIBS) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${CMAKE_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) +include_directories(${OPENSSL_INCLUDE_DIR}) +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr) + +target_link_libraries(${MODULE_NAME} ${PRIVATE_KEYWORD} ${FREERDP_CHANNELS_CLIENT_LIBS}) +if(OPENBSD) + target_link_libraries(${MODULE_NAME} ${PUBLIC_KEYWORD} ${${MODULE_PREFIX}_LIBS} ossaudio) +else() + target_link_libraries(${MODULE_NAME} ${PUBLIC_KEYWORD} ${${MODULE_PREFIX}_LIBS}) +endif() + + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries EXPORT FreeRDP-ClientTargets) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Common") + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + +export_complex_library(LIBNAME ${MODULE_NAME}) diff --git a/client/common/client.c b/client/common/client.c new file mode 100644 index 0000000..06146a4 --- /dev/null +++ b/client/common/client.c @@ -0,0 +1,616 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Client Common + * + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#define TAG CLIENT_TAG("common") + +static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints; + return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context); +} + +static void freerdp_client_common_free(freerdp* instance, rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints; + IFCALL(pEntryPoints->ClientFree, instance, context); +} + +/* Common API */ + +rdpContext* freerdp_client_context_new(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + freerdp* instance; + rdpContext* context; + + if (!pEntryPoints) + return NULL; + + IFCALL(pEntryPoints->GlobalInit); + instance = freerdp_new(); + + if (!instance) + return NULL; + + instance->settings = pEntryPoints->settings; + instance->ContextSize = pEntryPoints->ContextSize; + instance->ContextNew = freerdp_client_common_new; + instance->ContextFree = freerdp_client_common_free; + instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*) malloc( + pEntryPoints->Size); + + if (!instance->pClientEntryPoints) + goto out_fail; + + CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size); + + if (!freerdp_context_new(instance)) + goto out_fail2; + + context = instance->context; + context->instance = instance; + context->settings = instance->settings; + + if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, + 0) != CHANNEL_RC_OK) + goto out_fail2; + + return context; +out_fail2: + free(instance->pClientEntryPoints); +out_fail: + freerdp_free(instance); + return NULL; +} + +void freerdp_client_context_free(rdpContext* context) +{ + freerdp* instance; + + if (!context) + return; + + instance = context->instance; + + if (instance) + { + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints; + freerdp_context_free(instance); + + if (pEntryPoints) + IFCALL(pEntryPoints->GlobalUninit); + + free(instance->pClientEntryPoints); + freerdp_free(instance); + } +} + +int freerdp_client_start(rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints; + + if (!context || !context->instance || !context->instance->pClientEntryPoints) + return ERROR_BAD_ARGUMENTS; + + pEntryPoints = context->instance->pClientEntryPoints; + return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context); +} + +int freerdp_client_stop(rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints; + + if (!context || !context->instance || !context->instance->pClientEntryPoints) + return ERROR_BAD_ARGUMENTS; + + pEntryPoints = context->instance->pClientEntryPoints; + return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context); +} + +freerdp* freerdp_client_get_instance(rdpContext* context) +{ + if (!context || !context->instance) + return NULL; + + return context->instance; +} + +HANDLE freerdp_client_get_thread(rdpContext* context) +{ + if (!context) + return NULL; + + return ((rdpClientContext*) context)->thread; +} + +static BOOL freerdp_client_settings_post_process(rdpSettings* settings) +{ + /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so + * that the rdp file also triggers this functionality */ + if (settings->GatewayEnabled) + { + if (settings->GatewayUseSameCredentials) + { + if (settings->Username) + { + free(settings->GatewayUsername); + settings->GatewayUsername = _strdup(settings->Username); + + if (!settings->GatewayUsername) + goto out_error; + } + + if (settings->Domain) + { + free(settings->GatewayDomain); + settings->GatewayDomain = _strdup(settings->Domain); + + if (!settings->GatewayDomain) + goto out_error; + } + + if (settings->Password) + { + free(settings->GatewayPassword); + settings->GatewayPassword = _strdup(settings->Password); + + if (!settings->GatewayPassword) + goto out_error; + } + } + } + + /* Moved logic for Multimon and Span monitors to force fullscreen, so + * that the rdp file also triggers this functionality */ + if (settings->SpanMonitors) + { + settings->UseMultimon = TRUE; + settings->Fullscreen = TRUE; + } + else if (settings->UseMultimon) + { + settings->Fullscreen = TRUE; + } + + return TRUE; +out_error: + free(settings->GatewayUsername); + free(settings->GatewayDomain); + free(settings->GatewayPassword); + return FALSE; +} + + +int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, + char** argv, BOOL allowUnknown) +{ + int status; + + if (argc < 1) + return 0; + + if (!argv) + return -1; + + status = freerdp_client_settings_parse_command_line_arguments(settings, argc, + argv, allowUnknown); + + if (status < 0) + return status; + + /* This function will call logic that is applicable to the settings + * from command line parsing AND the rdp file parsing */ + if (!freerdp_client_settings_post_process(settings)) + status = -1; + + return status; +} + +int freerdp_client_settings_parse_connection_file(rdpSettings* settings, + const char* filename) +{ + rdpFile* file; + int ret = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (!freerdp_client_parse_rdp_file(file, filename)) + goto out; + + if (!freerdp_client_populate_settings_from_rdp_file(file, settings)) + goto out; + + ret = 0; +out: + freerdp_client_rdp_file_free(file); + return ret; +} + +int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, + const BYTE* buffer, size_t size) +{ + rdpFile* file; + int status = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) + && freerdp_client_populate_settings_from_rdp_file(file, settings)) + { + status = 0; + } + + freerdp_client_rdp_file_free(file); + return status; +} + +int freerdp_client_settings_write_connection_file(const rdpSettings* settings, + const char* filename, BOOL unicode) +{ + rdpFile* file; + int ret = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (!freerdp_client_populate_rdp_file_from_settings(file, settings)) + goto out; + + if (!freerdp_client_write_rdp_file(file, filename, unicode)) + goto out; + + ret = 0; +out: + freerdp_client_rdp_file_free(file); + return ret; +} + +int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, + const char* filename) +{ + int status; + int ret = -1; + rdpAssistanceFile* file; + file = freerdp_assistance_file_new(); + + if (!file) + return -1; + + status = freerdp_assistance_parse_file(file, filename); + + if (status < 0) + goto out; + + status = freerdp_client_populate_settings_from_assistance_file(file, settings); + + if (status < 0) + goto out; + + ret = 0; +out: + freerdp_assistance_file_free(file); + return ret; +} + +/** Callback set in the rdp_freerdp structure, and used to get the user's password, + * if required to establish the connection. + * This function is actually called in credssp_ntlmssp_client_init() + * @see rdp_server_accept_nego() and rdp_check_fds() + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param username - unused + * @param password - on return: pointer to a character string that will be filled by the password entered by the user. + * Note that this character string will be allocated inside the function, and needs to be deallocated by the caller + * using free(), even in case this function fails. + * @param domain - unused + * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more details. + */ +static BOOL client_cli_authenticate_raw(freerdp* instance, BOOL gateway, + char** username, + char** password, char** domain) +{ + static const size_t password_size = 512; + const char* auth[] = + { + "Username: ", + "Domain: ", + "Password: " + }; + const char* gw[] = + { + "GatewayUsername: ", + "GatewayDomain: ", + "GatewayPassword: " + }; + const char** prompt = (gateway) ? gw : auth; + + if (!username || !password || !domain) + return FALSE; + + if (!*username) + { + size_t username_size = 0; + printf("%s", prompt[0]); + + if (GetLine(username, &username_size, stdin) < 0) + { + WLog_ERR(TAG, "GetLine returned %s [%d]", strerror(errno), errno); + goto fail; + } + + if (*username) + { + *username = StrSep(username, "\r"); + *username = StrSep(username, "\n"); + } + } + + if (!*domain) + { + size_t domain_size = 0; + printf("%s", prompt[1]); + + if (GetLine(domain, &domain_size, stdin) < 0) + { + WLog_ERR(TAG, "GetLine returned %s [%d]", strerror(errno), errno); + goto fail; + } + + if (*domain) + { + *domain = StrSep(domain, "\r"); + *domain = StrSep(domain, "\n"); + } + } + + if (!*password) + { + *password = calloc(password_size, sizeof(char)); + + if (!*password) + goto fail; + + if (freerdp_passphrase_read(prompt[2], *password, password_size, + instance->settings->CredentialsFromStdin) == NULL) + goto fail; + } + + return TRUE; +fail: + free(*username); + free(*domain); + free(*password); + *username = NULL; + *domain = NULL; + *password = NULL; + return FALSE; +} + +BOOL client_cli_authenticate(freerdp* instance, char** username, + char** password, char** domain) +{ + if (instance->settings->SmartcardLogon) + { + WLog_INFO(TAG, "Authentication via smartcard"); + return TRUE; + } + + return client_cli_authenticate_raw(instance, FALSE, username, password, domain); +} + +BOOL client_cli_gw_authenticate(freerdp* instance, char** username, + char** password, char** domain) +{ + return client_cli_authenticate_raw(instance, TRUE, username, password, domain); +} + +static DWORD client_cli_accept_certificate(rdpSettings* settings) +{ + char answer; + + if (settings->CredentialsFromStdin) + return 0; + + while (1) + { + printf("Do you trust the above certificate? (Y/T/N) "); + fflush(stdout); + answer = fgetc(stdin); + + if (feof(stdin)) + { + printf("\nError: Could not read answer from stdin."); + + if (settings->CredentialsFromStdin) + printf(" - Run without parameter \"--from-stdin\" to set trust."); + + printf("\n"); + return 0; + } + + switch (answer) + { + case 'y': + case 'Y': + return 1; + + case 't': + case 'T': + return 2; + + case 'n': + case 'N': + return 0; + + default: + break; + } + + printf("\n"); + } + + return 0; +} + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when the connection requires it. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and tls_connect() + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param host_mismatch Indicates the certificate host does not match. + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, + const char* subject, const char* issuer, + const char* fingerprint, BOOL host_mismatch) +{ + printf("Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("The above X.509 certificate could not be verified, possibly because you do not have\n" + "the CA certificate in your certificate store, or the certificate has expired.\n" + "Please look at the OpenSSL documentation on how to add a private CA to the store.\n"); + return client_cli_accept_certificate(instance->settings); +} + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when a stored certificate does not match the remote counterpart. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and tls_connect() + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param old_subject + * @param old_issuer + * @param old_fingerprint + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_changed_certificate(freerdp* instance, + const char* common_name, + const char* subject, const char* issuer, + const char* fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint) +{ + printf("!!! Certificate has changed !!!\n"); + printf("\n"); + printf("New Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("\n"); + printf("Old Certificate details:\n"); + printf("\tSubject: %s\n", old_subject); + printf("\tIssuer: %s\n", old_issuer); + printf("\tThumbprint: %s\n", old_fingerprint); + printf("\n"); + printf("The above X.509 certificate does not match the certificate used for previous connections.\n" + "This may indicate that the certificate has been tampered with.\n" + "Please contact the administrator of the RDP server and clarify.\n"); + return client_cli_accept_certificate(instance->settings); +} + +BOOL client_auto_reconnect(freerdp* instance) +{ + return client_auto_reconnect_ex(instance, NULL); +} + +BOOL client_auto_reconnect_ex(freerdp* instance, BOOL(*window_events)(freerdp* instance)) +{ + UINT32 maxRetries; + UINT32 numRetries = 0; + rdpSettings* settings; + + if (!instance || !instance->settings) + return FALSE; + + settings = instance->settings; + maxRetries = settings->AutoReconnectMaxRetries; + + /* Only auto reconnect on network disconnects. */ + if (freerdp_error_info(instance) != 0) + return FALSE; + + /* A network disconnect was detected */ + WLog_INFO(TAG, "Network disconnect!"); + + if (!settings->AutoReconnectionEnabled) + { + /* No auto-reconnect - just quit */ + return FALSE; + } + + /* Perform an auto-reconnect. */ + while (TRUE) + { + UINT32 x; + + /* Quit retrying if max retries has been exceeded */ + if ((maxRetries > 0) && (numRetries++ >= maxRetries)) + { + return FALSE; + } + + /* Attempt the next reconnect */ + WLog_INFO(TAG, "Attempting reconnect (%"PRIu32" of %"PRIu32")", numRetries, maxRetries); + + if (freerdp_reconnect(instance)) + return TRUE; + + for (x = 0; x < 50; x++) + { + if (!IFCALLRESULT(TRUE, window_events, instance)) + return FALSE; + + Sleep(100); + } + } + + WLog_ERR(TAG, "Maximum reconnect retries exceeded"); + return FALSE; +} + + diff --git a/client/common/cmdline.c b/client/common/cmdline.c new file mode 100644 index 0000000..103b4d2 --- /dev/null +++ b/client/common/cmdline.c @@ -0,0 +1,3180 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Client Command-Line Interface + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2014 Norbert Federa + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "compatibility.h" +#include "cmdline.h" + +#include +#define TAG CLIENT_TAG("common.cmdline") + +static BOOL copy_value(const char* value, char** dst) +{ + if (!dst || !value) + return FALSE; + + free(*dst); + (*dst) = _strdup(value); + return (*dst) != NULL; +} + +BOOL freerdp_client_print_version(void) +{ + printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, + GIT_REVISION); + return TRUE; +} + +BOOL freerdp_client_print_buildconfig(void) +{ + printf("%s", freerdp_get_build_config()); + return TRUE; +} + +static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* arg) +{ + if (!arg) + return; + + do + { + if (arg->Flags & COMMAND_LINE_VALUE_FLAG) + { + printf(" %s", "/"); + printf("%-20s", arg->Name); + printf("\t%s\n", arg->Text); + } + else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) + || (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)) + { + BOOL overlong = FALSE; + printf(" %s", "/"); + + if (arg->Format) + { + size_t length = (strlen(arg->Name) + strlen(arg->Format) + 2); + + if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) + length += 2; + + if (length >= 20 + 8 + 8) + overlong = TRUE; + + if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) + printf("%s[:%s]", arg->Name, overlong ? "..." : arg->Format); + else + printf("%s:%s", arg->Name, overlong ? "..." : arg->Format); + } + else + { + printf("%-20s", arg->Name); + } + + printf("\t%s\n", arg->Text); + } + else if (arg->Flags & COMMAND_LINE_VALUE_BOOL) + { + printf(" %s", arg->Default ? "-" : "+"); + printf("%-20s", arg->Name); + printf("\t%s %s\n", arg->Default ? "Disable" : "Enable", arg->Text); + } + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); +} + +BOOL freerdp_client_print_command_line_help(int argc, char** argv) +{ + return freerdp_client_print_command_line_help_ex(argc, argv, NULL); +} + +BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv, + COMMAND_LINE_ARGUMENT_A* custom) +{ + printf("\n"); + printf("FreeRDP - A Free Remote Desktop Protocol Implementation\n"); + printf("See www.freerdp.com for more information\n"); + printf("\n"); + printf("Usage: %s [file] [options] [/v:[:port]]\n", argv[0]); + printf("\n"); + printf("Syntax:\n"); + printf(" /flag (enables flag)\n"); + printf(" /option: (specifies option with value)\n"); + printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n"); + printf("\n"); + freerdp_client_print_command_line_args(custom); + freerdp_client_print_command_line_args(args); + printf("\n"); + printf("Examples:\n"); + printf(" xfreerdp connection.rdp /p:Pwd123! /f\n"); + printf(" xfreerdp /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n"); + printf(" xfreerdp /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n"); + printf(" xfreerdp /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 /v:192.168.1.100\n"); + printf("\n"); + printf("Clipboard Redirection: +clipboard\n"); + printf("\n"); + printf("Drive Redirection: /drive:home,/home/user\n"); + printf("Smartcard Redirection: /smartcard:\n"); + printf("Serial Port Redirection: /serial:,,[SerCx2|SerCx|Serial],[permissive]\n"); + printf("Serial Port Redirection: /serial:COM1,/dev/ttyS0\n"); + printf("Parallel Port Redirection: /parallel:,\n"); + printf("Printer Redirection: /printer:,\n"); + printf("\n"); + printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n"); + printf("Audio Output Redirection: /sound:sys:alsa\n"); + printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n"); + printf("Audio Input Redirection: /microphone:sys:alsa\n"); + printf("\n"); + printf("Multimedia Redirection: /multimedia:sys:oss,dev:/dev/dsp1,decoder:ffmpeg\n"); + printf("Multimedia Redirection: /multimedia:sys:alsa\n"); + printf("USB Device Redirection: /usb:id,dev:054c:0268\n"); + printf("\n"); + printf("For Gateways, the https_proxy environment variable is respected:\n"); +#ifdef _WIN32 + printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n"); +#else + printf(" export https_proxy=http://proxy.contoso.com:3128/\n"); +#endif + printf(" xfreerdp /g:rdp.contoso.com ...\n"); + printf("\n"); + printf("More documentation is coming, in the meantime consult source files\n"); + printf("\n"); + return TRUE; +} + +static int freerdp_client_command_line_pre_filter(void* context, int index, + int argc, LPSTR* argv) +{ + if (index == 1) + { + size_t length; + rdpSettings* settings; + length = strlen(argv[index]); + + if (length > 4) + { + if (_stricmp(&(argv[index])[length - 4], ".rdp") == 0) + { + settings = (rdpSettings*) context; + + if (!copy_value(argv[index], &settings->ConnectionFile)) + return COMMAND_LINE_ERROR_MEMORY; + + return 1; + } + } + + if (length > 13) + { + if (_stricmp(&(argv[index])[length - 13], ".msrcIncident") == 0) + { + settings = (rdpSettings*) context; + + if (!copy_value(argv[index], &settings->AssistanceFile)) + return COMMAND_LINE_ERROR_MEMORY; + + return 1; + } + } + } + + return 0; +} + + +BOOL freerdp_client_add_device_channel(rdpSettings* settings, int count, + char** params) +{ + if (strcmp(params[0], "drive") == 0) + { + RDPDR_DRIVE* drive; + + if (count < 3) + return FALSE; + + settings->DeviceRedirection = TRUE; + drive = (RDPDR_DRIVE*) calloc(1, sizeof(RDPDR_DRIVE)); + + if (!drive) + return FALSE; + + drive->Type = RDPDR_DTYP_FILESYSTEM; + + if (count > 1) + { + if (!(drive->Name = _strdup(params[1]))) + { + free(drive); + return FALSE; + } + } + + if (count > 2) + { + const BOOL isPath = PathFileExistsA(params[2]); + const BOOL isSpecial = (strncmp(params[2], "*", 2) == 0) || + (strncmp(params[2], "%", 2) == 0) ? TRUE : FALSE; + + if ((!isPath && !isSpecial) || !(drive->Path = _strdup(params[2]))) + { + free(drive->Name); + free(drive); + return FALSE; + } + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) drive)) + { + free(drive->Path); + free(drive->Name); + free(drive); + return FALSE; + } + + return TRUE; + } + else if (strcmp(params[0], "printer") == 0) + { + RDPDR_PRINTER* printer; + + if (count < 1) + return FALSE; + + settings->RedirectPrinters = TRUE; + settings->DeviceRedirection = TRUE; + + if (count > 1) + { + printer = (RDPDR_PRINTER*) calloc(1, sizeof(RDPDR_PRINTER)); + + if (!printer) + return FALSE; + + printer->Type = RDPDR_DTYP_PRINT; + + if (count > 1) + { + if (!(printer->Name = _strdup(params[1]))) + { + free(printer); + return FALSE; + } + } + + if (count > 2) + { + if (!(printer->DriverName = _strdup(params[2]))) + { + free(printer->Name); + free(printer); + return FALSE; + } + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) printer)) + { + free(printer->DriverName); + free(printer->Name); + free(printer); + return FALSE; + } + } + + return TRUE; + } + else if (strcmp(params[0], "smartcard") == 0) + { + RDPDR_SMARTCARD* smartcard; + + if (count < 1) + return FALSE; + + settings->RedirectSmartCards = TRUE; + settings->DeviceRedirection = TRUE; + smartcard = (RDPDR_SMARTCARD*) calloc(1, sizeof(RDPDR_SMARTCARD)); + + if (!smartcard) + return FALSE; + + smartcard->Type = RDPDR_DTYP_SMARTCARD; + + if (count > 1 && strlen(params[1])) + { + if (!(smartcard->Name = _strdup(params[1]))) + { + free(smartcard); + return FALSE; + } + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) smartcard)) + { + free(smartcard->Name); + free(smartcard); + return FALSE; + } + + return TRUE; + } + else if (strcmp(params[0], "serial") == 0) + { + RDPDR_SERIAL* serial; + + if (count < 1) + return FALSE; + + settings->RedirectSerialPorts = TRUE; + settings->DeviceRedirection = TRUE; + serial = (RDPDR_SERIAL*) calloc(1, sizeof(RDPDR_SERIAL)); + + if (!serial) + return FALSE; + + serial->Type = RDPDR_DTYP_SERIAL; + + if (count > 1) + { + if (!(serial->Name = _strdup(params[1]))) + { + free(serial); + return FALSE; + } + } + + if (count > 2) + { + if (!(serial->Path = _strdup(params[2]))) + { + free(serial->Name); + free(serial); + return FALSE; + } + } + + if (count > 3) + { + if (!(serial->Driver = _strdup(params[3]))) + { + free(serial->Path); + free(serial->Name); + free(serial); + return FALSE; + } + } + + if (count > 4) + { + if (!(serial->Permissive = _strdup(params[4]))) + { + free(serial->Driver); + free(serial->Path); + free(serial->Name); + free(serial); + return FALSE; + } + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) serial)) + { + free(serial->Permissive); + free(serial->Driver); + free(serial->Path); + free(serial->Name); + free(serial); + return FALSE; + } + + return TRUE; + } + else if (strcmp(params[0], "parallel") == 0) + { + RDPDR_PARALLEL* parallel; + + if (count < 1) + return FALSE; + + settings->RedirectParallelPorts = TRUE; + settings->DeviceRedirection = TRUE; + parallel = (RDPDR_PARALLEL*) calloc(1, sizeof(RDPDR_PARALLEL)); + + if (!parallel) + return FALSE; + + parallel->Type = RDPDR_DTYP_PARALLEL; + + if (count > 1) + { + if (!(parallel->Name = _strdup(params[1]))) + { + free(parallel); + return FALSE; + } + } + + if (count > 2) + { + if (!(parallel->Path = _strdup(params[2]))) + { + free(parallel->Name); + free(parallel); + return FALSE; + } + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) parallel)) + { + free(parallel->Path); + free(parallel->Name); + free(parallel); + return FALSE; + } + + return TRUE; + } + + return FALSE; +} + +BOOL freerdp_client_add_static_channel(rdpSettings* settings, int count, + char** params) +{ + int index; + ADDIN_ARGV* args; + + if (!settings || !params || !params[0]) + return FALSE; + + if (freerdp_static_channel_collection_find(settings, params[0])) + return TRUE; + + args = (ADDIN_ARGV*) calloc(1, sizeof(ADDIN_ARGV)); + + if (!args) + return FALSE; + + args->argc = count; + args->argv = (char**) calloc(args->argc, sizeof(char*)); + + if (!args->argv) + goto error_argv; + + for (index = 0; index < args->argc; index++) + { + args->argv[index] = _strdup(params[index]); + + if (!args->argv[index]) + { + for (--index; index >= 0; --index) + free(args->argv[index]); + + goto error_argv_strdup; + } + } + + if (!freerdp_static_channel_collection_add(settings, args)) + goto error_argv_index; + + return TRUE; +error_argv_index: + + for (index = 0; index < args->argc; index++) + free(args->argv[index]); + +error_argv_strdup: + free(args->argv); +error_argv: + free(args); + return FALSE; +} + +BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, int count, + char** params) +{ + int index; + ADDIN_ARGV* args; + + if (!settings || !params || !params[0]) + return FALSE; + + if (freerdp_dynamic_channel_collection_find(settings, params[0])) + return TRUE; + + args = (ADDIN_ARGV*) malloc(sizeof(ADDIN_ARGV)); + + if (!args) + return FALSE; + + args->argc = count; + args->argv = (char**) calloc(args->argc, sizeof(char*)); + + if (!args->argv) + goto error_argv; + + for (index = 0; index < args->argc; index++) + { + args->argv[index] = _strdup(params[index]); + + if (!args->argv[index]) + { + for (--index; index >= 0; --index) + free(args->argv[index]); + + goto error_argv_strdup; + } + } + + if (!freerdp_dynamic_channel_collection_add(settings, args)) + goto error_argv_index; + + return TRUE; +error_argv_index: + + for (index = 0; index < args->argc; index++) + free(args->argv[index]); + +error_argv_strdup: + free(args->argv); +error_argv: + free(args); + return FALSE; +} + +static char** freerdp_command_line_parse_comma_separated_values_ex(const char* name, + const char* list, + size_t* count) +{ + char** p; + char* str; + size_t nArgs; + size_t index; + size_t nCommas; + size_t prefix, len; + nCommas = 0; + assert(NULL != count); + *count = 0; + + if (!list) + { + if (name) + { + size_t len = strlen(name); + p = (char**) calloc(2UL + len, sizeof(char*)); + + if (p) + { + char* dst = (char*)&p[1]; + p[0] = dst; + sprintf_s(dst, len + 1, "%s", name); + *count = 1; + return p; + } + } + + return NULL; + } + + { + const char* it = list; + + while ((it = strchr(it, ',')) != NULL) + { + it++; + nCommas++; + } + } + + nArgs = nCommas + 1; + + if (name) + nArgs++; + + prefix = (nArgs + 1UL) * sizeof(char*); + len = strlen(list); + p = (char**) calloc(len + prefix + 1, sizeof(char*)); + + if (!p) + return NULL; + + str = &((char*)p)[prefix]; + memcpy(str, list, len); + + if (name) + p[0] = (char*)name; + + for (index = name ? 1 : 0; index < nArgs; index++) + { + char* comma = strchr(str, ','); + p[index] = str; + + if (comma) + { + str = comma + 1; + *comma = '\0'; + } + } + + *count = nArgs; + return p; +} + +static char** freerdp_command_line_parse_comma_separated_values(char* list, + size_t* count) +{ + return freerdp_command_line_parse_comma_separated_values_ex(NULL, list, count); +} + +static char** freerdp_command_line_parse_comma_separated_values_offset( + const char* name, char* list, size_t* count) +{ + return freerdp_command_line_parse_comma_separated_values_ex(name, list, count); +} + +static int freerdp_client_command_line_post_filter(void* context, + COMMAND_LINE_ARGUMENT_A* arg) +{ + rdpSettings* settings = (rdpSettings*) context; + BOOL status = TRUE; + BOOL enable = arg->Value ? TRUE : FALSE; + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "a") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values(arg->Value, &count); + + if ((status = freerdp_client_add_device_channel(settings, count, p))) + { + settings->DeviceRedirection = TRUE; + } + + free(p); + } + CommandLineSwitchCase(arg, "vc") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values(arg->Value, &count); + status = freerdp_client_add_static_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "dvc") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values(arg->Value, &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "drive") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset(arg->Name, arg->Value, + &count); + status = freerdp_client_add_device_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "serial") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset(arg->Name, arg->Value, + &count); + status = freerdp_client_add_device_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "parallel") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset(arg->Name, arg->Value, + &count); + status = freerdp_client_add_device_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "smartcard") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset(arg->Name, arg->Value, + &count); + status = freerdp_client_add_device_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "printer") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset(arg->Name, arg->Value, + &count); + status = freerdp_client_add_device_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "usb") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset("urbdrc", arg->Value, + &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "multitouch") + { + settings->MultiTouchInput = enable; + } + CommandLineSwitchCase(arg, "gestures") + { + settings->MultiTouchGestures = enable; + } + CommandLineSwitchCase(arg, "echo") + { + settings->SupportEchoChannel = enable; + } + CommandLineSwitchCase(arg, "ssh-agent") + { + settings->SupportSSHAgentChannel = enable; + } + CommandLineSwitchCase(arg, "disp") + { + settings->SupportDisplayControl = enable; + } + CommandLineSwitchCase(arg, "geometry") + { + settings->SupportGeometryTracking = enable; + } + CommandLineSwitchCase(arg, "video") + { + settings->SupportGeometryTracking = enable; /* this requires geometry tracking */ + settings->SupportVideoOptimized = enable; + } + CommandLineSwitchCase(arg, "sound") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset("rdpsnd", arg->Value, + &count); + status = freerdp_client_add_static_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "microphone") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset("audin", arg->Value, + &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "multimedia") + { + char** p; + size_t count; + p = freerdp_command_line_parse_comma_separated_values_offset("tsmf", arg->Value, + &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "heartbeat") + { + settings->SupportHeartbeatPdu = enable; + } + CommandLineSwitchCase(arg, "multitransport") + { + settings->SupportMultitransport = enable; + + if (settings->SupportMultitransport) + settings->MultitransportFlags = (TRANSPORT_TYPE_UDP_FECR | + TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED); + else + settings->MultitransportFlags = 0; + } + CommandLineSwitchCase(arg, "password-is-pin") + { + settings->PasswordIsSmartcardPin = enable; + } + CommandLineSwitchEnd(arg) + return status ? 1 : -1; +} + +BOOL freerdp_parse_username(const char* username, char** user, char** domain) +{ + char* p; + int length = 0; + p = strchr(username, '\\'); + *user = NULL; + *domain = NULL; + + if (p) + { + length = (int)(p - username); + *user = _strdup(&p[1]); + + if (!*user) + return FALSE; + + *domain = (char*) calloc(length + 1UL, sizeof(char)); + + if (!*domain) + { + free(*user); + *user = NULL; + return FALSE; + } + + strncpy(*domain, username, length); + (*domain)[length] = '\0'; + } + else if (username) + { + /* Do not break up the name for '@'; both credSSP and the + * ClientInfo PDU expect 'user@corp.net' to be transmitted + * as username 'user@corp.net', domain empty (not NULL!). + */ + *user = _strdup(username); + + if (!*user) + return FALSE; + + *domain = _strdup("\0"); + + if (!*domain) + { + free(*user); + *user = NULL; + return FALSE; + } + } + else + return FALSE; + + return TRUE; +} + +BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port) +{ + char* p; + p = strrchr(hostname, ':'); + + if (p) + { + unsigned long val; + SSIZE_T length = (p - hostname); + errno = 0; + val = strtoul(p + 1, NULL, 0); + + if ((errno != 0) || (val <= 0) || (val > UINT16_MAX)) + return FALSE; + + *host = (char*) calloc(length + 1UL, sizeof(char)); + + if (!(*host)) + return FALSE; + + CopyMemory(*host, hostname, length); + (*host)[length] = '\0'; + *port = val; + } + else + { + *host = _strdup(hostname); + + if (!(*host)) + return FALSE; + + *port = -1; + } + + return TRUE; +} + +BOOL freerdp_set_connection_type(rdpSettings* settings, int type) +{ + settings->ConnectionType = type; + + if (type == CONNECTION_TYPE_MODEM) + { + settings->DisableWallpaper = TRUE; + settings->AllowFontSmoothing = FALSE; + settings->AllowDesktopComposition = FALSE; + settings->DisableFullWindowDrag = TRUE; + settings->DisableMenuAnims = TRUE; + settings->DisableThemes = TRUE; + } + else if (type == CONNECTION_TYPE_BROADBAND_LOW) + { + settings->DisableWallpaper = TRUE; + settings->AllowFontSmoothing = FALSE; + settings->AllowDesktopComposition = FALSE; + settings->DisableFullWindowDrag = TRUE; + settings->DisableMenuAnims = TRUE; + settings->DisableThemes = FALSE; + } + else if (type == CONNECTION_TYPE_SATELLITE) + { + settings->DisableWallpaper = TRUE; + settings->AllowFontSmoothing = FALSE; + settings->AllowDesktopComposition = TRUE; + settings->DisableFullWindowDrag = TRUE; + settings->DisableMenuAnims = TRUE; + settings->DisableThemes = FALSE; + } + else if (type == CONNECTION_TYPE_BROADBAND_HIGH) + { + settings->DisableWallpaper = TRUE; + settings->AllowFontSmoothing = FALSE; + settings->AllowDesktopComposition = TRUE; + settings->DisableFullWindowDrag = TRUE; + settings->DisableMenuAnims = TRUE; + settings->DisableThemes = FALSE; + } + else if (type == CONNECTION_TYPE_WAN) + { + settings->DisableWallpaper = FALSE; + settings->AllowFontSmoothing = TRUE; + settings->AllowDesktopComposition = TRUE; + settings->DisableFullWindowDrag = FALSE; + settings->DisableMenuAnims = FALSE; + settings->DisableThemes = FALSE; + } + else if (type == CONNECTION_TYPE_LAN) + { + settings->DisableWallpaper = FALSE; + settings->AllowFontSmoothing = TRUE; + settings->AllowDesktopComposition = TRUE; + settings->DisableFullWindowDrag = FALSE; + settings->DisableMenuAnims = FALSE; + settings->DisableThemes = FALSE; + } + else if (type == CONNECTION_TYPE_AUTODETECT) + { + settings->DisableWallpaper = FALSE; + settings->AllowFontSmoothing = TRUE; + settings->AllowDesktopComposition = TRUE; + settings->DisableFullWindowDrag = FALSE; + settings->DisableMenuAnims = FALSE; + settings->DisableThemes = FALSE; + settings->NetworkAutoDetect = TRUE; + } + else + { + return FALSE; + } + + return TRUE; +} + +int freerdp_map_keyboard_layout_name_to_id(char* name) +{ + int i; + int id = 0; + RDP_KEYBOARD_LAYOUT* layouts; + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD); + + if (!layouts) + return -1; + + for (i = 0; layouts[i].code; i++) + { + if (_stricmp(layouts[i].name, name) == 0) + id = layouts[i].code; + } + + freerdp_keyboard_layouts_free(layouts); + + if (id) + return id; + + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_VARIANT); + + if (!layouts) + return -1; + + for (i = 0; layouts[i].code; i++) + { + if (_stricmp(layouts[i].name, name) == 0) + id = layouts[i].code; + } + + freerdp_keyboard_layouts_free(layouts); + + if (id) + return id; + + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_IME); + + if (!layouts) + return -1; + + for (i = 0; layouts[i].code; i++) + { + if (_stricmp(layouts[i].name, name) == 0) + id = layouts[i].code; + } + + freerdp_keyboard_layouts_free(layouts); + + if (id) + return id; + + return 0; +} + +static int freerdp_detect_command_line_pre_filter(void* context, int index, + int argc, LPSTR* argv) +{ + int length; + + if (index == 1) + { + length = (int) strlen(argv[index]); + + if (length > 4) + { + if (_stricmp(&(argv[index])[length - 4], ".rdp") == 0) + { + return 1; + } + } + + if (length > 13) + { + if (_stricmp(&(argv[index])[length - 13], ".msrcIncident") == 0) + { + return 1; + } + } + } + + return 0; +} + +static int freerdp_detect_windows_style_command_line_syntax(int argc, char** argv, + size_t* count, BOOL ignoreUnknown) +{ + int status; + DWORD flags; + int detect_status; + COMMAND_LINE_ARGUMENT_A* arg; + flags = COMMAND_LINE_SEPARATOR_COLON; + flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(args); + status = CommandLineParseArgumentsA(argc, argv, args, flags, + NULL, freerdp_detect_command_line_pre_filter, NULL); + + if (status < 0) + return status; + + arg = args; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST)) + detect_status = -1; + + return detect_status; +} + +int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, + size_t* count, BOOL ignoreUnknown) +{ + int status; + DWORD flags; + int detect_status; + COMMAND_LINE_ARGUMENT_A* arg; + flags = COMMAND_LINE_SEPARATOR_SPACE; + flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(args); + status = CommandLineParseArgumentsA(argc, argv, args, flags, + NULL, freerdp_detect_command_line_pre_filter, NULL); + + if (status < 0) + return status; + + arg = args; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST)) + detect_status = -1; + + return detect_status; +} + +static BOOL freerdp_client_detect_command_line(int argc, char** argv, + DWORD* flags, BOOL ignoreUnknown) +{ + int old_cli_status; + int old_cli_count; + int posix_cli_status; + size_t posix_cli_count; + int windows_cli_status; + size_t windows_cli_count; + BOOL compatibility = FALSE; + windows_cli_status = freerdp_detect_windows_style_command_line_syntax(argc, + argv, &windows_cli_count, ignoreUnknown); + posix_cli_status = freerdp_detect_posix_style_command_line_syntax(argc, argv, + &posix_cli_count, ignoreUnknown); + old_cli_status = freerdp_detect_old_command_line_syntax(argc, argv, + &old_cli_count); + /* Default is POSIX syntax */ + *flags = COMMAND_LINE_SEPARATOR_SPACE; + *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + *flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE; + + if (posix_cli_status <= COMMAND_LINE_STATUS_PRINT) + return compatibility; + + /* Check, if this may be windows style syntax... */ + if ((windows_cli_count && (windows_cli_count >= posix_cli_count)) + || (windows_cli_status <= COMMAND_LINE_STATUS_PRINT)) + { + windows_cli_count = 1; + *flags = COMMAND_LINE_SEPARATOR_COLON; + *flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + } + else if (old_cli_status >= 0) + { + /* Ignore legacy parsing in case there is an error in the command line. */ + if ((old_cli_status == 1) || ((old_cli_count > posix_cli_count) + && (old_cli_status != -1))) + { + *flags = COMMAND_LINE_SEPARATOR_SPACE; + *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + compatibility = TRUE; + } + } + + WLog_DBG(TAG, "windows: %d/%d posix: %d/%d compat: %d/%d", windows_cli_status, + windows_cli_count, + posix_cli_status, posix_cli_count, old_cli_status, old_cli_count); + return compatibility; +} + +int freerdp_client_settings_command_line_status_print(rdpSettings* settings, + int status, int argc, char** argv) +{ + return freerdp_client_settings_command_line_status_print_ex( + settings, status, argc, argv, NULL); +} + +int freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, + int status, int argc, char** argv, COMMAND_LINE_ARGUMENT_A* custom) +{ + COMMAND_LINE_ARGUMENT_A* arg; + + if (status == COMMAND_LINE_STATUS_PRINT_VERSION) + { + freerdp_client_print_version(); + return COMMAND_LINE_STATUS_PRINT_VERSION; + } + + if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG) + { + freerdp_client_print_version(); + freerdp_client_print_buildconfig(); + return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG; + } + else if (status == COMMAND_LINE_STATUS_PRINT) + { + arg = CommandLineFindArgumentA(args, "kbd-list"); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + DWORD i; + RDP_KEYBOARD_LAYOUT* layouts; + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD); + //if (!layouts) /* FIXME*/ + printf("\nKeyboard Layouts\n"); + + for (i = 0; layouts[i].code; i++) + printf("0x%08"PRIX32"\t%s\n", layouts[i].code, layouts[i].name); + + freerdp_keyboard_layouts_free(layouts); + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_VARIANT); + //if (!layouts) /* FIXME*/ + printf("\nKeyboard Layout Variants\n"); + + for (i = 0; layouts[i].code; i++) + printf("0x%08"PRIX32"\t%s\n", layouts[i].code, layouts[i].name); + + freerdp_keyboard_layouts_free(layouts); + layouts = freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_IME); + //if (!layouts) /* FIXME*/ + printf("\nKeyboard Input Method Editors (IMEs)\n"); + + for (i = 0; layouts[i].code; i++) + printf("0x%08"PRIX32"\t%s\n", layouts[i].code, layouts[i].name); + + freerdp_keyboard_layouts_free(layouts); + printf("\n"); + } + + arg = CommandLineFindArgumentA(args, "monitor-list"); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + settings->ListMonitors = TRUE; + } + + return COMMAND_LINE_STATUS_PRINT; + } + else if (status < 0) + { + freerdp_client_print_command_line_help_ex(argc, argv, custom); + return COMMAND_LINE_STATUS_PRINT_HELP; + } + + return 0; +} + +static BOOL ends_with(const char* str, const char* ext) +{ + const size_t strLen = strlen(str); + const size_t extLen = strlen(ext); + + if (strLen < extLen) + return FALSE; + + return strncmp(&str[strLen - extLen], ext, extLen) == 0; +} + +static void activate_smartcard_logon_rdp(rdpSettings* settings) +{ + settings->SmartcardLogon = TRUE; + /* TODO: why not? settings->UseRdpSecurityLayer = TRUE; */ + freerdp_set_param_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE); +} + +/** + * parses a string value with the format x + * @param input: input string + * @param v1: pointer to output v1 + * @param v2: pointer to output v2 + * @return if the parsing was successful + */ +static BOOL parseSizeValue(const char *input, unsigned long *v1, unsigned long *v2) +{ + const char *xcharpos; + char *endPtr; + unsigned long v; + + errno = 0; + v = strtoul(input, &endPtr, 10); + + if ((v == 0 || v == ULONG_MAX) && (errno != 0)) + return FALSE; + if (v1) + *v1 = v; + + xcharpos = strchr(input, 'x'); + if (!xcharpos || xcharpos != endPtr) + return FALSE; + + errno = 0; + v = strtoul(xcharpos + 1, &endPtr, 10); + + if ((v == 0 || v == ULONG_MAX) && (errno != 0)) + return FALSE; + + if (*endPtr != '\0') + return FALSE; + if (v2) + *v2 = v; + + return TRUE; +} + +int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, + int argc, char** argv, BOOL allowUnknown) +{ + char* p; + char* user = NULL; + char* gwUser = NULL; + char* str; + size_t length; + int status; + BOOL ext = FALSE; + BOOL assist = FALSE; + DWORD flags = 0; + BOOL promptForPassword = FALSE; + BOOL compatibility = FALSE; + COMMAND_LINE_ARGUMENT_A* arg; + + /* Command line detection fails if only a .rdp or .msrcIncident file + * is supplied. Check this case first, only then try to detect + * legacy command line syntax. */ + if (argc > 1) + { + ext = ends_with(argv[1], ".rdp"); + assist = ends_with(argv[1], ".msrcIncident"); + } + + if (!ext && !assist) + compatibility = freerdp_client_detect_command_line(argc, argv, &flags, + allowUnknown); + else + compatibility = freerdp_client_detect_command_line(argc - 1, &argv[1], &flags, + allowUnknown); + + settings->ProxyHostname = NULL; + settings->ProxyUsername = NULL; + settings->ProxyPassword = NULL; + + if (compatibility) + { + WLog_WARN(TAG, "Using deprecated command-line interface!"); + return freerdp_client_parse_old_command_line_arguments(argc, argv, settings); + } + else + { + if (allowUnknown) + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + + if (ext) + { + if (freerdp_client_settings_parse_connection_file(settings, argv[1])) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (assist) + { + if (freerdp_client_settings_parse_assistance_file(settings, + argv[1]) < 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + CommandLineClearArgumentsA(args); + status = CommandLineParseArgumentsA(argc, argv, args, flags, + settings, + freerdp_client_command_line_pre_filter, + freerdp_client_command_line_post_filter); + + if (status < 0) + return status; + } + + CommandLineFindArgumentA(args, "v"); + arg = args; + errno = 0; + + do + { + BOOL enable = arg->Value ? TRUE : FALSE; + + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + CommandLineSwitchStart(arg) + CommandLineSwitchCase(arg, "v") + { + free(settings->ServerHostname); + settings->ServerHostname = NULL; + p = strchr(arg->Value, '['); + + /* ipv4 */ + if (!p) + { + p = strchr(arg->Value, ':'); + + if (p) + { + unsigned long val = strtoul(&p[1], NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (int)(p - arg->Value); + settings->ServerPort = val; + + if (!(settings->ServerHostname = (char*) calloc(length + 1UL, sizeof(char)))) + return COMMAND_LINE_ERROR_MEMORY; + + strncpy(settings->ServerHostname, arg->Value, length); + settings->ServerHostname[length] = '\0'; + } + else + { + if (!(settings->ServerHostname = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + } + } + else /* ipv6 */ + { + char* p2 = strchr(arg->Value, ']'); + + /* not a valid [] ipv6 addr found */ + if (!p2) + continue; + + length = p2 - p; + + if (!(settings->ServerHostname = (char*) calloc(length, sizeof(char)))) + return COMMAND_LINE_ERROR_MEMORY; + + strncpy(settings->ServerHostname, p + 1, length - 1); + + if (*(p2 + 1) == ':') + { + unsigned long val = strtoul(&p2[2], NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ServerPort = val; + } + + printf("hostname %s port %"PRIu32"\n", settings->ServerHostname, settings->ServerPort); + } + } + CommandLineSwitchCase(arg, "spn-class") + { + if (!copy_value(arg->Value, &settings->AuthenticationServiceClass)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "redirect-prefer") + { + size_t count = 0; + char* cur = arg->Value; + settings->RedirectionPreferType = 0; + + do + { + UINT32 mask; + char* next = strchr(cur, ','); + + if (next) + { + *next = '\0'; + next++; + } + + if (_strnicmp(cur, "fqdn", 5) == 0) + mask = 0x06U; + else if (_strnicmp(cur, "ip", 3) == 0) + mask = 0x05U; + else if (_strnicmp(cur, "netbios", 8) == 0) + mask = 0x03U; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + cur = next; + mask = (mask & 0x07); + settings->RedirectionPreferType |= mask << (count * 3); + count++; + } + while (cur != NULL); + + if (count > 3) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + CommandLineSwitchCase(arg, "credentials-delegation") + { + settings->DisableCredentialsDelegation = !enable; + } + CommandLineSwitchCase(arg, "vmconnect") + { + settings->VmConnectMode = TRUE; + settings->ServerPort = 2179; + settings->NegotiateSecurityLayer = FALSE; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + settings->SendPreconnectionPdu = TRUE; + + if (!copy_value(arg->Value, &settings->PreconnectionBlob)) + return COMMAND_LINE_ERROR_MEMORY; + } + } + CommandLineSwitchCase(arg, "w") + { + long val = strtol(arg->Value, NULL, 0); + + if ((errno != 0) || (val <= 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopWidth = val; + } + CommandLineSwitchCase(arg, "h") + { + long val = strtol(arg->Value, NULL, 0); + + if ((errno != 0) || (val <= 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopHeight = val; + } + CommandLineSwitchCase(arg, "size") + { + + p = strchr(arg->Value, 'x'); + + if (p) + { + unsigned long w, h; + if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopWidth = w; + settings->DesktopHeight = h; + } + else + { + if (!(str = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + + p = strchr(str, '%'); + + if (p) + { + BOOL partial = FALSE; + + if (strchr(p, 'w')) + { + settings->PercentScreenUseWidth = 1; + partial = TRUE; + } + + if (strchr(p, 'h')) + { + settings->PercentScreenUseHeight = 1; + partial = TRUE; + } + + if (!partial) + { + settings->PercentScreenUseWidth = 1; + settings->PercentScreenUseHeight = 1; + } + + *p = '\0'; + { + long val = strtol(str, NULL, 0); + + if ((errno != 0) || (val < 0) || (val > 100)) + { + free(str); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + settings->PercentScreen = val; + } + } + + free(str); + } + } + CommandLineSwitchCase(arg, "f") + { + settings->Fullscreen = enable; + } + CommandLineSwitchCase(arg, "multimon") + { + settings->UseMultimon = TRUE; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + if (_stricmp(arg->Value, "force") == 0) + { + settings->ForceMultimon = TRUE; + } + } + } + CommandLineSwitchCase(arg, "span") + { + settings->SpanMonitors = enable; + } + CommandLineSwitchCase(arg, "workarea") + { + settings->Workarea = enable; + } + CommandLineSwitchCase(arg, "monitors") + { + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + UINT32 i; + char** p; + size_t count = 0; + p = freerdp_command_line_parse_comma_separated_values(arg->Value, &count); + + if (!p) + return COMMAND_LINE_ERROR_MEMORY; + + if (count > 16) + count = 16; + + settings->NumMonitorIds = (UINT32) count; + + for (i = 0; i < settings->NumMonitorIds; i++) + { + unsigned long val = strtoul(p[i], NULL, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->MonitorIds[i] = val; + } + + free(p); + } + } + CommandLineSwitchCase(arg, "monitor-list") + { + settings->ListMonitors = enable; + } + CommandLineSwitchCase(arg, "t") + { + if (!copy_value(arg->Value, &settings->WindowTitle)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "decorations") + { + settings->Decorations = enable; + } + CommandLineSwitchCase(arg, "dynamic-resolution") + { + if (settings->SmartSizing) + { + WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + settings->SupportDisplayControl = TRUE; + settings->DynamicResolutionUpdate = TRUE; + } + CommandLineSwitchCase(arg, "smart-sizing") + { + if (settings->DynamicResolutionUpdate) + { + WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + settings->SmartSizing = TRUE; + + if (arg->Value) + { + unsigned long w, h; + + if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->SmartSizingWidth = w; + settings->SmartSizingHeight = h; + } + } + CommandLineSwitchCase(arg, "bpp") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ColorDepth = val; + + switch (settings->ColorDepth) + { + case 32: + case 24: + case 16: + case 15: + case 8: + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineSwitchCase(arg, "admin") + { + settings->ConsoleSession = enable; + } + CommandLineSwitchCase(arg, "relax-order-checks") + { + settings->AllowUnanouncedOrdersFromServer = enable; + } + CommandLineSwitchCase(arg, "restricted-admin") + { + settings->ConsoleSession = enable; + settings->RestrictedAdminModeRequired = enable; + } + CommandLineSwitchCase(arg, "pth") + { + settings->ConsoleSession = TRUE; + settings->RestrictedAdminModeRequired = TRUE; + + if (!copy_value(arg->Value, &settings->PasswordHash)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "client-hostname") + { + if (!copy_value(arg->Value, &settings->ClientHostname)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "kbd") + { + unsigned long id = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (id > UINT32_MAX) || (id == 0)) + { + const int rc = freerdp_map_keyboard_layout_name_to_id(arg->Value); + + if (rc <= 0) + { + WLog_ERR(TAG, "Could not identify keyboard layout: %s", arg->Value); + WLog_ERR(TAG, "Use /kbd-list to list available layouts"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + /* Found a valid mapping, reset errno */ + id = (unsigned long)rc; + errno = 0; + } + + settings->KeyboardLayout = (UINT32) id; + } + CommandLineSwitchCase(arg, "kbd-type") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardType = val; + } + CommandLineSwitchCase(arg, "kbd-subtype") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardSubType = val; + } + CommandLineSwitchCase(arg, "kbd-fn-key") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardFunctionKey = val; + } + CommandLineSwitchCase(arg, "u") + { + user = _strdup(arg->Value); + } + CommandLineSwitchCase(arg, "d") + { + if (!copy_value(arg->Value, &settings->Domain)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "p") + { + if (!copy_value(arg->Value, &settings->Password)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "g") + { + free(settings->GatewayHostname); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + p = strchr(arg->Value, ':'); + + if (p) + { + unsigned long val = strtoul(&p[1], NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (int)(p - arg->Value); + settings->GatewayPort = val; + + if (!(settings->GatewayHostname = (char*) calloc(length + 1UL, sizeof(char)))) + return COMMAND_LINE_ERROR_MEMORY; + + strncpy(settings->GatewayHostname, arg->Value, length); + settings->GatewayHostname[length] = '\0'; + } + else + { + if (!(settings->GatewayHostname = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + } + } + else + { + if (!(settings->GatewayHostname = _strdup(settings->ServerHostname))) + return COMMAND_LINE_ERROR_MEMORY; + } + + settings->GatewayEnabled = TRUE; + settings->GatewayUseSameCredentials = TRUE; + freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT); + } + CommandLineSwitchCase(arg, "proxy") + { + /* initial value */ + settings->ProxyType = PROXY_TYPE_HTTP; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + char* atPtr; + /* value is [scheme://][user:password@]hostname:port */ + p = strstr(arg->Value, "://"); + + if (p) + { + *p = '\0'; + + if (_stricmp("no_proxy", arg->Value) == 0) + settings->ProxyType = PROXY_TYPE_IGNORE; + + if (_stricmp("http", arg->Value) == 0) + settings->ProxyType = PROXY_TYPE_HTTP; + else if (_stricmp("socks5", arg->Value) == 0) + settings->ProxyType = PROXY_TYPE_SOCKS; + else + { + WLog_ERR(TAG, "Only HTTP and SOCKS5 proxies supported by now"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + arg->Value = p + 3; + } + + /* arg->Value is now [user:password@]hostname:port */ + atPtr = strrchr(arg->Value, '@'); + + if (atPtr) + { + /* got a login / password, + * atPtr + * v + * [user:password@]hostname:port + * ^ + * colonPtr + */ + char* colonPtr = strchr(arg->Value, ':'); + + if (!colonPtr || (colonPtr > atPtr)) + { + WLog_ERR(TAG, "invalid syntax for proxy, expected syntax is user:password@host:port"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + *colonPtr = '\0'; + settings->ProxyUsername = _strdup(arg->Value); + + if (!settings->ProxyUsername) + { + WLog_ERR(TAG, "unable to allocate proxy username"); + return COMMAND_LINE_ERROR_MEMORY; + } + + *atPtr = '\0'; + settings->ProxyPassword = _strdup(colonPtr + 1); + + if (!settings->ProxyPassword) + { + WLog_ERR(TAG, "unable to allocate proxy password"); + return COMMAND_LINE_ERROR_MEMORY; + } + + arg->Value = atPtr + 1; + } + + p = strchr(arg->Value, ':'); + + if (p) + { + unsigned long val = strtoul(&p[1], NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (p - arg->Value); + settings->ProxyPort = val; + settings->ProxyHostname = (char*) malloc(length + 1); + strncpy(settings->ProxyHostname, arg->Value, length); + settings->ProxyHostname[length] = '\0'; + } + } + else + { + WLog_ERR(TAG, "Option http-proxy needs argument."); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineSwitchCase(arg, "gu") + { + if (!(gwUser = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + + settings->GatewayUseSameCredentials = FALSE; + } + CommandLineSwitchCase(arg, "gd") + { + if (!copy_value(arg->Value, &settings->GatewayDomain)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->GatewayUseSameCredentials = FALSE; + } + CommandLineSwitchCase(arg, "gp") + { + if (!copy_value(arg->Value, &settings->GatewayPassword)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->GatewayUseSameCredentials = FALSE; + } + CommandLineSwitchCase(arg, "gt") + { + if (_stricmp(arg->Value, "rpc") == 0) + { + settings->GatewayRpcTransport = TRUE; + settings->GatewayHttpTransport = FALSE; + } + else if (_stricmp(arg->Value, "http") == 0) + { + settings->GatewayRpcTransport = FALSE; + settings->GatewayHttpTransport = TRUE; + } + else if (_stricmp(arg->Value, "auto") == 0) + { + settings->GatewayRpcTransport = TRUE; + settings->GatewayHttpTransport = TRUE; + } + } + CommandLineSwitchCase(arg, "gat") + { + if (!copy_value(arg->Value, &settings->GatewayAccessToken)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "gateway-usage-method") + { + long type = 0; + char* pEnd; + + if (_stricmp(arg->Value, "none") == 0) + type = TSC_PROXY_MODE_NONE_DIRECT; + else if (_stricmp(arg->Value, "direct") == 0) + type = TSC_PROXY_MODE_DIRECT; + else if (_stricmp(arg->Value, "detect") == 0) + type = TSC_PROXY_MODE_DETECT; + else if (_stricmp(arg->Value, "default") == 0) + type = TSC_PROXY_MODE_DEFAULT; + else + { + type = strtol(arg->Value, &pEnd, 10); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + freerdp_set_gateway_usage_method(settings, (UINT32) type); + } + CommandLineSwitchCase(arg, "app") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationProgram)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->RemoteApplicationMode = TRUE; + settings->RemoteAppLanguageBarSupported = TRUE; + settings->Workarea = TRUE; + settings->DisableWallpaper = TRUE; + settings->DisableFullWindowDrag = TRUE; + } + CommandLineSwitchCase(arg, "load-balance-info") + { + if (!copy_value(arg->Value, (char**)&settings->LoadBalanceInfo)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->LoadBalanceInfoLength = (UINT32) strlen((char*) + settings->LoadBalanceInfo); + } + CommandLineSwitchCase(arg, "app-name") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationName)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "app-icon") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationIcon)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "app-cmd") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationCmdLine)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "app-file") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationFile)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "app-guid") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationGuid)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "compression") + { + settings->CompressionEnabled = enable; + } + CommandLineSwitchCase(arg, "compression-level") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->CompressionLevel = val; + } + CommandLineSwitchCase(arg, "drives") + { + settings->RedirectDrives = enable; + } + CommandLineSwitchCase(arg, "home-drive") + { + settings->RedirectHomeDrive = enable; + } + CommandLineSwitchCase(arg, "ipv6") + { + settings->PreferIPv6OverIPv4 = enable; + } + CommandLineSwitchCase(arg, "clipboard") + { + settings->RedirectClipboard = enable; + } + CommandLineSwitchCase(arg, "shell") + { + if (!copy_value(arg->Value, &settings->AlternateShell)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "shell-dir") + { + if (!copy_value(arg->Value, &settings->ShellWorkingDirectory)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "audio-mode") + { + long mode = strtol(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (mode == AUDIO_MODE_REDIRECT) + { + settings->AudioPlayback = TRUE; + } + else if (mode == AUDIO_MODE_PLAY_ON_SERVER) + { + settings->RemoteConsoleAudio = TRUE; + } + else if (mode == AUDIO_MODE_NONE) + { + settings->AudioPlayback = FALSE; + settings->RemoteConsoleAudio = FALSE; + } + } + CommandLineSwitchCase(arg, "network") + { + long type = 0; + char* pEnd; + + if (_stricmp(arg->Value, "modem") == 0) + type = CONNECTION_TYPE_MODEM; + else if (_stricmp(arg->Value, "broadband") == 0) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (_stricmp(arg->Value, "broadband-low") == 0) + type = CONNECTION_TYPE_BROADBAND_LOW; + else if (_stricmp(arg->Value, "broadband-high") == 0) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (_stricmp(arg->Value, "wan") == 0) + type = CONNECTION_TYPE_WAN; + else if (_stricmp(arg->Value, "lan") == 0) + type = CONNECTION_TYPE_LAN; + else if ((_stricmp(arg->Value, "autodetect") == 0) || + (_stricmp(arg->Value, "auto") == 0) || + (_stricmp(arg->Value, "detect") == 0)) + { + type = CONNECTION_TYPE_AUTODETECT; + } + else + { + type = strtol(arg->Value, &pEnd, 10); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (!freerdp_set_connection_type(settings, type)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "fonts") + { + settings->AllowFontSmoothing = enable; + } + CommandLineSwitchCase(arg, "wallpaper") + { + settings->DisableWallpaper = !enable; + } + CommandLineSwitchCase(arg, "window-drag") + { + settings->DisableFullWindowDrag = !enable; + } + CommandLineSwitchCase(arg, "window-position") + { + unsigned long x, y; + + if (!arg->Value) + return COMMAND_LINE_ERROR_MISSING_ARGUMENT; + + if (!parseSizeValue(arg->Value, &x, &y) || x > UINT16_MAX || y > UINT16_MAX) + { + WLog_ERR(TAG, "invalid window-position argument"); + return COMMAND_LINE_ERROR_MISSING_ARGUMENT; + } + + settings->DesktopPosX = x; + settings->DesktopPosY = y; + } + CommandLineSwitchCase(arg, "menu-anims") + { + settings->DisableMenuAnims = !enable; + } + CommandLineSwitchCase(arg, "themes") + { + settings->DisableThemes = !enable; + } + CommandLineSwitchCase(arg, "aero") + { + settings->AllowDesktopComposition = enable; + } + CommandLineSwitchCase(arg, "gdi") + { + if (_stricmp(arg->Value, "sw") == 0) + settings->SoftwareGdi = TRUE; + else if (_stricmp(arg->Value, "hw") == 0) + settings->SoftwareGdi = FALSE; + } + CommandLineSwitchCase(arg, "gfx") + { + settings->SupportGraphicsPipeline = TRUE; + + if (arg->Value) + { +#ifdef WITH_GFX_H264 + + if (_strnicmp("AVC444", arg->Value, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = TRUE; + } + else if (_strnicmp("AVC420", arg->Value, 7) == 0) + { + settings->GfxH264 = TRUE; + } + else +#endif + if (_strnicmp("RFX", arg->Value, 4) != 0) + return COMMAND_LINE_ERROR; + } + } + CommandLineSwitchCase(arg, "gfx-thin-client") + { + settings->GfxThinClient = enable; + + if (settings->GfxThinClient) + settings->GfxSmallCache = TRUE; + + settings->SupportGraphicsPipeline = TRUE; + } + CommandLineSwitchCase(arg, "gfx-small-cache") + { + settings->GfxSmallCache = enable; + + if (enable) + settings->SupportGraphicsPipeline = TRUE; + } + CommandLineSwitchCase(arg, "gfx-progressive") + { + settings->GfxProgressive = enable; + settings->GfxThinClient = !enable; + + if (enable) + settings->SupportGraphicsPipeline = TRUE; + } +#ifdef WITH_GFX_H264 + CommandLineSwitchCase(arg, "gfx-h264") + { + settings->SupportGraphicsPipeline = TRUE; + settings->GfxH264 = TRUE; + + if (arg->Value) + { + if (_strnicmp("AVC444", arg->Value, 7) == 0) + { + settings->GfxAVC444 = TRUE; + } + else if (_strnicmp("AVC420", arg->Value, 7) != 0) + return COMMAND_LINE_ERROR; + } + } +#endif + CommandLineSwitchCase(arg, "rfx") + { + settings->RemoteFxCodec = enable; + } + CommandLineSwitchCase(arg, "rfx-mode") + { + if (strcmp(arg->Value, "video") == 0) + settings->RemoteFxCodecMode = 0x00; + else if (strcmp(arg->Value, "image") == 0) + settings->RemoteFxCodecMode = 0x02; + } + CommandLineSwitchCase(arg, "frame-ack") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->FrameAcknowledge = val; + } + CommandLineSwitchCase(arg, "nsc") + { + settings->NSCodec = enable; + } +#if defined(WITH_JPEG) + CommandLineSwitchCase(arg, "jpeg") + { + settings->JpegCodec = enable; + settings->JpegQuality = 75; + } + CommandLineSwitchCase(arg, "jpeg-quality") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > 100)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->JpegQuality = val; + } +#endif + CommandLineSwitchCase(arg, "nego") + { + settings->NegotiateSecurityLayer = enable; + } + CommandLineSwitchCase(arg, "pcb") + { + settings->SendPreconnectionPdu = TRUE; + + if (!copy_value(arg->Value, &settings->PreconnectionBlob)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "pcid") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->SendPreconnectionPdu = TRUE; + settings->PreconnectionId = val; + } + CommandLineSwitchCase(arg, "sec") + { + if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */ + { + settings->RdpSecurity = TRUE; + settings->TlsSecurity = FALSE; + settings->NlaSecurity = FALSE; + settings->ExtSecurity = FALSE; + settings->UseRdpSecurityLayer = TRUE; + } + else if (strcmp("tls", arg->Value) == 0) /* TLS */ + { + settings->RdpSecurity = FALSE; + settings->TlsSecurity = TRUE; + settings->NlaSecurity = FALSE; + settings->ExtSecurity = FALSE; + } + else if (strcmp("nla", arg->Value) == 0) /* NLA */ + { + settings->RdpSecurity = FALSE; + settings->TlsSecurity = FALSE; + settings->NlaSecurity = TRUE; + settings->ExtSecurity = FALSE; + } + else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */ + { + settings->RdpSecurity = FALSE; + settings->TlsSecurity = FALSE; + settings->NlaSecurity = FALSE; + settings->ExtSecurity = TRUE; + } + else + { + WLog_ERR(TAG, "unknown protocol security: %s", arg->Value); + } + } + CommandLineSwitchCase(arg, "encryption-methods") + { + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + UINT32 i; + char** p; + size_t count = 0; + p = freerdp_command_line_parse_comma_separated_values(arg->Value, &count); + + for (i = 0; i < count; i++) + { + if (!strcmp(p[i], "40")) + settings->EncryptionMethods |= ENCRYPTION_METHOD_40BIT; + else if (!strcmp(p[i], "56")) + settings->EncryptionMethods |= ENCRYPTION_METHOD_56BIT; + else if (!strcmp(p[i], "128")) + settings->EncryptionMethods |= ENCRYPTION_METHOD_128BIT; + else if (!strcmp(p[i], "FIPS")) + settings->EncryptionMethods |= ENCRYPTION_METHOD_FIPS; + else + WLog_ERR(TAG, "unknown encryption method '%s'", p[i]); + } + + free(p); + } + } + CommandLineSwitchCase(arg, "from-stdin") + { + settings->CredentialsFromStdin = TRUE; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + promptForPassword = (strncmp(arg->Value, "force", 6) == 0); + + if (!promptForPassword) + return COMMAND_LINE_ERROR; + } + } + CommandLineSwitchCase(arg, "log-level") + { + wLog* root = WLog_GetRoot(); + + if (!WLog_SetStringLogLevel(root, arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "log-filters") + { + if (!WLog_AddStringLogFilters(arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sec-rdp") + { + settings->RdpSecurity = enable; + } + CommandLineSwitchCase(arg, "sec-tls") + { + settings->TlsSecurity = enable; + } + CommandLineSwitchCase(arg, "sec-nla") + { + settings->NlaSecurity = enable; + } + CommandLineSwitchCase(arg, "sec-ext") + { + settings->ExtSecurity = enable; + } + CommandLineSwitchCase(arg, "tls-ciphers") + { + free(settings->AllowedTlsCiphers); + + if (strcmp(arg->Value, "netmon") == 0) + { + if (!(settings->AllowedTlsCiphers = _strdup("ALL:!ECDH"))) + return COMMAND_LINE_ERROR_MEMORY; + } + else if (strcmp(arg->Value, "ma") == 0) + { + if (!(settings->AllowedTlsCiphers = _strdup("AES128-SHA"))) + return COMMAND_LINE_ERROR_MEMORY; + } + else + { + if (!(settings->AllowedTlsCiphers = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + } + } + CommandLineSwitchCase(arg, "tls-seclevel") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > 5)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->TlsSecLevel = val; + } + CommandLineSwitchCase(arg, "cert-name") + { + if (!copy_value(arg->Value, &settings->CertificateName)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "cert-ignore") + { + settings->IgnoreCertificate = enable; + } + CommandLineSwitchCase(arg, "cert-tofu") + { + settings->AutoAcceptCertificate = enable; + } + CommandLineSwitchCase(arg, "authentication") + { + settings->Authentication = enable; + } + CommandLineSwitchCase(arg, "encryption") + { + settings->UseRdpSecurityLayer = !enable; + } + CommandLineSwitchCase(arg, "grab-keyboard") + { + settings->GrabKeyboard = enable; + } + CommandLineSwitchCase(arg, "unmap-buttons") + { + settings->UnmapButtons = enable; + } + CommandLineSwitchCase(arg, "toggle-fullscreen") + { + settings->ToggleFullscreen = enable; + } + CommandLineSwitchCase(arg, "floatbar") + { + settings->Floatbar = enable; + } + CommandLineSwitchCase(arg, "mouse-motion") + { + settings->MouseMotion = enable; + } + CommandLineSwitchCase(arg, "parent-window") + { + UINT64 val = _strtoui64(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ParentWindowId = val; + } + CommandLineSwitchCase(arg, "bitmap-cache") + { + settings->BitmapCacheEnabled = enable; + } + CommandLineSwitchCase(arg, "offscreen-cache") + { + settings->OffscreenSupportLevel = enable; + } + CommandLineSwitchCase(arg, "glyph-cache") + { + settings->GlyphSupportLevel = arg->Value ? GLYPH_SUPPORT_FULL : + GLYPH_SUPPORT_NONE; + } + CommandLineSwitchCase(arg, "codec-cache") + { + settings->BitmapCacheV3Enabled = TRUE; + + if (strcmp(arg->Value, "rfx") == 0) + { + settings->RemoteFxCodec = TRUE; + } + else if (strcmp(arg->Value, "nsc") == 0) + { + settings->NSCodec = TRUE; + } + +#if defined(WITH_JPEG) + else if (strcmp(arg->Value, "jpeg") == 0) + { + settings->JpegCodec = TRUE; + + if (settings->JpegQuality == 0) + settings->JpegQuality = 75; + } + +#endif + } + CommandLineSwitchCase(arg, "fast-path") + { + settings->FastPathInput = enable; + settings->FastPathOutput = enable; + } + CommandLineSwitchCase(arg, "max-fast-path-size") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->MultifragMaxRequestSize = val; + } + CommandLineSwitchCase(arg, "max-loop-time") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->MaxTimeInCheckLoop = val; + + if ((long) settings->MaxTimeInCheckLoop < 0) + { + WLog_ERR(TAG, "invalid max loop time: %s", arg->Value); + return COMMAND_LINE_ERROR; + } + + if ((long) settings->MaxTimeInCheckLoop <= 0) + { + settings->MaxTimeInCheckLoop = 10 * 60 * 60 * 1000; /* 10 hours can be considered as infinite */ + } + } + CommandLineSwitchCase(arg, "async-input") + { + settings->AsyncInput = enable; + } + CommandLineSwitchCase(arg, "async-update") + { + settings->AsyncUpdate = enable; + } + CommandLineSwitchCase(arg, "async-channels") + { + settings->AsyncChannels = enable; + } + CommandLineSwitchCase(arg, "wm-class") + { + if (!copy_value(arg->Value, &settings->WmClass)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "play-rfx") + { + if (!copy_value(arg->Value, &settings->PlayRemoteFxFile)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->PlayRemoteFx = TRUE; + } + CommandLineSwitchCase(arg, "auth-only") + { + settings->AuthenticationOnly = enable; + } + CommandLineSwitchCase(arg, "auto-reconnect") + { + settings->AutoReconnectionEnabled = enable; + } + CommandLineSwitchCase(arg, "auto-reconnect-max-retries") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->AutoReconnectMaxRetries = val; + + if (settings->AutoReconnectMaxRetries > 1000) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "reconnect-cookie") + { + BYTE* base64 = NULL; + int length; + crypto_base64_decode((const char*)(arg->Value), (int) strlen(arg->Value), + &base64, &length); + + if ((base64 != NULL) && (length == sizeof(ARC_SC_PRIVATE_PACKET))) + { + memcpy(settings->ServerAutoReconnectCookie, base64, length); + } + else + { + WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value); + } + + free(base64); + } + CommandLineSwitchCase(arg, "print-reconnect-cookie") + { + settings->PrintReconnectCookie = enable; + } + CommandLineSwitchCase(arg, "assistance") + { + settings->RemoteAssistanceMode = TRUE; + + if (!copy_value(arg->Value, &settings->RemoteAssistancePassword)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "pwidth") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopPhysicalWidth = val; + } + CommandLineSwitchCase(arg, "pheight") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopPhysicalHeight = val; + } + CommandLineSwitchCase(arg, "orientation") + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > INT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopOrientation = val; + } + CommandLineSwitchCase(arg, "old-license") + { + settings->OldLicenseBehaviour = TRUE; + } + CommandLineSwitchCase(arg, "scale") + { + unsigned long scaleFactor = strtoul(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (scaleFactor == 100 || scaleFactor == 140 || scaleFactor == 180) + { + settings->DesktopScaleFactor = scaleFactor; + settings->DeviceScaleFactor = scaleFactor; + } + else + { + WLog_ERR(TAG, "scale: invalid scale factor (%d)", scaleFactor); + return COMMAND_LINE_ERROR; + } + } + CommandLineSwitchCase(arg, "scale-desktop") + { + unsigned long desktopScaleFactor = strtoul(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (desktopScaleFactor >= 100 && desktopScaleFactor <= 500) + { + settings->DesktopScaleFactor = desktopScaleFactor; + } + else + { + WLog_ERR(TAG, "scale: invalid desktop scale factor (%d)", desktopScaleFactor); + return COMMAND_LINE_ERROR; + } + } + CommandLineSwitchCase(arg, "scale-device") + { + unsigned long deviceScaleFactor = strtoul(arg->Value, NULL, 0); + + if (errno != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (deviceScaleFactor == 100 || deviceScaleFactor == 140 + || deviceScaleFactor == 180) + { + settings->DeviceScaleFactor = deviceScaleFactor; + } + else + { + WLog_ERR(TAG, "scale: invalid device scale factor (%d)", deviceScaleFactor); + return COMMAND_LINE_ERROR; + } + } + CommandLineSwitchCase(arg, "action-script") + { + if (!copy_value(arg->Value, &settings->ActionScript)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "fipsmode") + { + settings->FIPSMode = enable; + } + CommandLineSwitchCase(arg, "smartcard-logon") + { + if (!settings->SmartcardLogon) + activate_smartcard_logon_rdp(settings); + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } + while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + if (user) + { + free(settings->Username); + + if (!settings->Domain && user) + { + BOOL ret; + free(settings->Domain); + ret = freerdp_parse_username(user, &settings->Username, &settings->Domain); + free(user); + + if (!ret) + return COMMAND_LINE_ERROR; + } + else + settings->Username = user; + } + + if (gwUser) + { + free(settings->GatewayUsername); + + if (!settings->GatewayDomain && gwUser) + { + BOOL ret; + free(settings->GatewayDomain); + ret = freerdp_parse_username(gwUser, &settings->GatewayUsername, + &settings->GatewayDomain); + free(gwUser); + + if (!ret) + return COMMAND_LINE_ERROR; + } + else + settings->GatewayUsername = gwUser; + } + + if (promptForPassword) + { + const size_t size = 512; + + if (!settings->Password) + { + settings->Password = calloc(size, sizeof(char)); + + if (!settings->Password) + return COMMAND_LINE_ERROR; + + if (!freerdp_passphrase_read("Password: ", settings->Password, size, 1)) + return COMMAND_LINE_ERROR; + } + + if (settings->GatewayEnabled && !settings->GatewayUseSameCredentials) + { + if (!settings->GatewayPassword) + { + settings->GatewayPassword = calloc(size, sizeof(char)); + + if (!settings->GatewayPassword) + return COMMAND_LINE_ERROR; + + if (!freerdp_passphrase_read("Gateway Password: ", settings->GatewayPassword, size, 1)) + return COMMAND_LINE_ERROR; + } + } + } + + freerdp_performance_flags_make(settings); + + if (settings->RemoteFxCodec || settings->NSCodec + || settings->SupportGraphicsPipeline) + { + settings->FastPathOutput = TRUE; + settings->LargePointerFlag = TRUE; + settings->FrameMarkerCommandEnabled = TRUE; + settings->ColorDepth = 32; + } + + arg = CommandLineFindArgumentA(args, "port"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val == 0) || (val > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ServerPort = val; + } + + arg = CommandLineFindArgumentA(args, "p"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + FillMemory(arg->Value, strlen(arg->Value), '*'); + } + + arg = CommandLineFindArgumentA(args, "gp"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + FillMemory(arg->Value, strlen(arg->Value), '*'); + } + + return status; +} + +static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, + rdpSettings* settings, char* name, void* data) +{ + PVIRTUALCHANNELENTRY entry = NULL; + PVIRTUALCHANNELENTRYEX entryEx = NULL; + entryEx = (PVIRTUALCHANNELENTRYEX) freerdp_load_channel_addin_entry(name, NULL, NULL, + FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + + if (!entryEx) + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (entryEx) + { + if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) + { + WLog_INFO(TAG, "loading channelEx %s", name); + return TRUE; + } + } + else if (entry) + { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) + { + WLog_INFO(TAG, "loading channel %s", name); + return TRUE; + } + } + + return FALSE; +} + +BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) +{ + UINT32 index; + ADDIN_ARGV* args; + + if ((freerdp_static_channel_collection_find(settings, "rdpsnd")) || + (freerdp_dynamic_channel_collection_find(settings, "tsmf"))) + { + settings->DeviceRedirection = TRUE; /* rdpsnd requires rdpdr to be registered */ + settings->AudioPlayback = + TRUE; /* Both rdpsnd and tsmf require this flag to be set */ + } + + if (freerdp_dynamic_channel_collection_find(settings, "audin")) + { + settings->AudioCapture = TRUE; + } + + if (settings->NetworkAutoDetect || + settings->SupportHeartbeatPdu || + settings->SupportMultitransport) + { + settings->DeviceRedirection = + TRUE; /* these RDP8 features require rdpdr to be registered */ + } + + if (settings->RedirectDrives || settings->RedirectHomeDrive + || settings->RedirectSerialPorts + || settings->RedirectSmartCards || settings->RedirectPrinters) + { + settings->DeviceRedirection = TRUE; /* All of these features require rdpdr */ + } + + if (settings->RedirectDrives) + { + if (!freerdp_device_collection_find(settings, "drive")) + { + char* params[3]; + params[0] = "drive"; + params[1] = "media"; + params[2] = "*"; + + if (!freerdp_client_add_device_channel(settings, 3, (char**) params)) + return FALSE; + } + } + + if (settings->RedirectHomeDrive) + { + if (!freerdp_device_collection_find(settings, "drive")) + { + char* params[3]; + params[0] = "drive"; + params[1] = "home"; + params[2] = "%"; + + if (!freerdp_client_add_device_channel(settings, 3, (char**) params)) + return FALSE; + } + } + + if (settings->DeviceRedirection) + { + if (!freerdp_client_load_static_channel_addin(channels, settings, "rdpdr", + settings)) + return FALSE; + + if (!freerdp_static_channel_collection_find(settings, "rdpsnd")) + { + char* params[2]; + params[0] = "rdpsnd"; + params[1] = "sys:fake"; + + if (!freerdp_client_add_static_channel(settings, 2, (char**) params)) + return FALSE; + } + } + + if (settings->RedirectSmartCards) + { + RDPDR_SMARTCARD* smartcard; + + if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD)) + { + smartcard = (RDPDR_SMARTCARD*) calloc(1, sizeof(RDPDR_SMARTCARD)); + + if (!smartcard) + return FALSE; + + smartcard->Type = RDPDR_DTYP_SMARTCARD; + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) smartcard)) + return FALSE; + } + } + + if (settings->RedirectPrinters) + { + RDPDR_PRINTER* printer; + + if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_PRINT)) + { + printer = (RDPDR_PRINTER*) calloc(1, sizeof(RDPDR_PRINTER)); + + if (!printer) + return FALSE; + + printer->Type = RDPDR_DTYP_PRINT; + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*) printer)) + return FALSE; + } + } + + if (settings->RedirectClipboard) + { + char* params[1]; + params[0] = "cliprdr"; + + if (!freerdp_client_add_static_channel(settings, 1, (char**) params)) + return FALSE; + } + + if (settings->LyncRdpMode) + { + settings->EncomspVirtualChannel = TRUE; + settings->RemdeskVirtualChannel = TRUE; + settings->CompressionEnabled = FALSE; + } + + if (settings->RemoteAssistanceMode) + { + settings->EncomspVirtualChannel = TRUE; + settings->RemdeskVirtualChannel = TRUE; + } + + if (settings->EncomspVirtualChannel) + { + if (!freerdp_client_load_static_channel_addin(channels, settings, "encomsp", + settings)) + return FALSE; + } + + if (settings->RemdeskVirtualChannel) + { + if (!freerdp_client_load_static_channel_addin(channels, settings, "remdesk", + settings)) + return FALSE; + } + + for (index = 0; index < settings->StaticChannelCount; index++) + { + args = settings->StaticChannelArray[index]; + + if (!freerdp_client_load_static_channel_addin(channels, settings, args->argv[0], + args)) + return FALSE; + } + + if (settings->RemoteApplicationMode) + { + if (!freerdp_client_load_static_channel_addin(channels, settings, "rail", + settings)) + return FALSE; + } + + if (settings->MultiTouchInput) + { + char* p[1]; + int count; + count = 1; + p[0] = "rdpei"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportGraphicsPipeline) + { + char* p[1]; + int count; + count = 1; + p[0] = "rdpgfx"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportEchoChannel) + { + char* p[1]; + int count; + count = 1; + p[0] = "echo"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportSSHAgentChannel) + { + char* p[1]; + int count; + count = 1; + p[0] = "sshagent"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportDisplayControl) + { + char* p[1]; + int count; + count = 1; + p[0] = "disp"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportGeometryTracking) + { + char* p[1]; + int count; + count = 1; + p[0] = "geometry"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->SupportVideoOptimized) + { + char* p[1]; + int count; + count = 1; + p[0] = "video"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + + if (settings->DynamicChannelCount) + settings->SupportDynamicChannels = TRUE; + + if (settings->SupportDynamicChannels) + { + if (!freerdp_client_load_static_channel_addin(channels, settings, "drdynvc", + settings)) + return FALSE; + } + + return TRUE; +} diff --git a/client/common/cmdline.h b/client/common/cmdline.h new file mode 100644 index 0000000..7f1d9e0 --- /dev/null +++ b/client/common/cmdline.h @@ -0,0 +1,190 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Client Command-Line Interface + * + * Copyright 2018 Bernhard Miklautz + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CLIENT_COMMON_CMDLINE_H +#define CLIENT_COMMON_CMDLINE_H + +#include + +static COMMAND_LINE_ARGUMENT_A args[] = +{ + { "a", COMMAND_LINE_VALUE_REQUIRED, "[,]", NULL, NULL, -1, "addin", "Addin" }, + { "action-script", COMMAND_LINE_VALUE_REQUIRED, "", "~/.config/freerdp/action.sh", NULL, -1, NULL, "Action script" }, + { "admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "console", "Admin (or console) session" }, + { "aero", COMMAND_LINE_VALUE_BOOL, NULL, NULL, BoolValueFalse, -1, NULL, "desktop composition" }, + { "app", COMMAND_LINE_VALUE_REQUIRED, " or ||", NULL, NULL, -1, NULL, "Remote application program" }, + { "app-cmd", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Remote application command-line parameters" }, + { "app-file", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "File to open with remote application" }, + { "app-guid", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Remote application GUID" }, + { "app-icon", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Remote application icon for user interface" }, + { "app-name", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Remote application name for user interface" }, + { "assistance", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Remote assistance password" }, + { "async-channels", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Asynchronous channels (experimental)" }, + { "async-input", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Asynchronous input" }, + { "async-update", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Asynchronous update" }, + { "audio-mode", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Audio output mode" }, + { "auth-only", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Authenticate only" }, + { "authentication", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Authentication (expermiental)" }, + { "auto-reconnect", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Automatic reconnection" }, + { "auto-reconnect-max-retries", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Automatic reconnection maximum retries, 0 for unlimited [0,1000]" }, + { "bitmap-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "bitmap cache" }, + { "bpp", COMMAND_LINE_VALUE_REQUIRED, "", "16", NULL, -1, NULL, "Session bpp (color depth)" }, + { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1, NULL, "Print the build configuration" }, + { "cert-ignore", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Ignore certificate" }, + { "cert-name", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Certificate name" }, + { "cert-tofu", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Automatically accept certificate on first connect" }, + { "client-hostname", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Client Hostname to send to server" }, + { "clipboard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Redirect clipboard" }, + { "codec-cache", COMMAND_LINE_VALUE_REQUIRED, "rfx|nsc|jpeg", NULL, NULL, -1, NULL, "Bitmap codec cache" }, + { "compression", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, "z", "compression" }, + { "compression-level", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Compression level (0,1,2)" }, + { "credentials-delegation", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "credentials delegation" }, + { "d", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Domain" }, + { "decorations", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Window decorations" }, + { "disp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Display control" }, + { "drive", COMMAND_LINE_VALUE_REQUIRED, ",", NULL, NULL, -1, NULL, "Redirect directory as named share " }, + { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Redirect all mount points as shares" }, + { "dvc", COMMAND_LINE_VALUE_REQUIRED, "[,]", NULL, NULL, -1, NULL, "Dynamic virtual channel" }, + { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Send resolution updates when the window is resized" }, + { "echo", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "echo", "Echo channel" }, + { "encryption", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Encryption (experimental)" }, + { "encryption-methods", COMMAND_LINE_VALUE_REQUIRED, "[40,][56,][128,][FIPS]", NULL, NULL, -1, NULL, "RDP standard security encryption methods" }, + { "f", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Fullscreen mode (++ toggles fullscreen)" }, + { "fast-path", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "fast-path input/output" }, + { "fipsmode", COMMAND_LINE_VALUE_BOOL, NULL, NULL, NULL, -1, NULL, "FIPS mode" }, + { "floatbar", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "floatbar in fullscreen mode" }, + { "fonts", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "smooth fonts (ClearType)" }, + { "frame-ack", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Number of frame acknowledgement" }, + { "from-stdin", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL, "Read credentials from stdin. With the prompt is done before connection, otherwise on server request." }, + { "g", COMMAND_LINE_VALUE_REQUIRED, "[:]", NULL, NULL, -1, NULL, "Gateway Hostname" }, + { "gateway-usage-method", COMMAND_LINE_VALUE_REQUIRED, "direct|detect", NULL, NULL, -1, "gum", "Gateway usage method" }, + { "gd", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Gateway domain" }, + { "gdi", COMMAND_LINE_VALUE_REQUIRED, "sw|hw", NULL, NULL, -1, NULL, "GDI rendering" }, + { "geometry", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Geometry tracking channel" }, + { "gestures", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Consume multitouch input locally" }, +#ifdef WITH_GFX_H264 + { "gfx", COMMAND_LINE_VALUE_OPTIONAL, "RFX|AVC420|AVC444", NULL, NULL, -1, NULL, "RDP8 graphics pipeline (experimental)" }, + { "gfx-h264", COMMAND_LINE_VALUE_OPTIONAL, "AVC420|AVC444", NULL, NULL, -1, NULL, "RDP8.1 graphics pipeline using H264 codec" }, +#else + { "gfx", COMMAND_LINE_VALUE_OPTIONAL, "RFX", NULL, NULL, -1, NULL, "RDP8 graphics pipeline (experimental)" }, +#endif + { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "RDP8 graphics pipeline using progressive codec" }, + { "gfx-small-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "RDP8 graphics pipeline using small cache mode" }, + { "gfx-thin-client", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "RDP8 graphics pipeline using thin client mode" }, + { "glyph-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Glyph cache (experimental)" }, + { "gp", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Gateway password" }, + { "grab-keyboard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Grab keyboard" }, + { "gt", COMMAND_LINE_VALUE_REQUIRED, "rpc|http|auto", NULL, NULL, -1, NULL, "Gateway transport type" }, + { "gu", COMMAND_LINE_VALUE_REQUIRED, "[\\] or [@]", NULL, NULL, -1, NULL, "Gateway username" }, + { "gat", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Gateway Access Token" }, + { "h", COMMAND_LINE_VALUE_REQUIRED, "", "768", NULL, -1, NULL, "Height" }, + { "heartbeat", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Support heartbeat PDUs" }, + { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?", "Print help" }, + { "home-drive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "Redirect user home as share" }, + { "ipv6", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "6", "Prefer IPv6 AAA record over IPv4 A record"}, +#if defined(WITH_JPEG) + { "jpeg", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "JPEG codec support" }, + { "jpeg-quality", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "JPEG quality" }, +#endif + { "kbd", COMMAND_LINE_VALUE_REQUIRED, "0x or ", NULL, NULL, -1, NULL, "Keyboard layout" }, + { "kbd-fn-key", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Function key value" }, + { "kbd-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL, "List keyboard layouts" }, + { "kbd-subtype", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Keyboard subtype" }, + { "kbd-type", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Keyboard type" }, + { "load-balance-info", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Load balance info" }, + { "log-filters", COMMAND_LINE_VALUE_REQUIRED, ":[,:[,...]]", NULL, NULL, -1, NULL, "Set logger filters, see wLog(7) for details" }, + { "log-level", COMMAND_LINE_VALUE_REQUIRED, "OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE", NULL, NULL, -1, NULL, "Set the default log level, see wLog(7) for details" }, + { "max-fast-path-size", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "Specify maximum fast-path update size" }, + { "max-loop-time", COMMAND_LINE_VALUE_REQUIRED, "