commit de0fdeebc12d1830cfcdd50119fcba6ad3c3a9fe Author: zhouganqing Date: Mon Nov 21 15:16:05 2022 +0800 Import Upstream version 2.8.1 diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..33a6584 --- /dev/null +++ b/.clang-format @@ -0,0 +1,125 @@ +--- +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: ForIndentation +... +Language: Cpp +Standard: Auto +NamespaceIndentation: All +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +... +Language: ObjC +PointerBindsToType: false +ObjCSpaceAfterProperty: true +SortIncludes: false +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +... +Language: Java +BreakAfterJavaFieldAnnotations: false +... +Language: JavaScript +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +... +Language: Proto +... +Language: TableGen +... +Language: TextProto +... 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..8ee9f39 --- /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..91a1cef --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +## 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 _clang-format_ script can be found at ```.clang-format``` + * The cmake target ```clangformat``` reformats the whole codebase +* 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/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..aa6df82 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master, stable* ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master, stable* ] + schedule: + - cron: '30 8 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - run: | + sudo apt update + sudo apt install libxrandr-dev libxinerama-dev libusb-1.0-0-dev xserver-xorg-dev libswscale-dev libswresample-dev libavutil-dev libavcodec-dev libcups2-dev libpulse-dev libasound2-dev libpcsclite-dev xsltproc libxcb-cursor-dev libxcursor-dev libcairo2-dev libfaac-dev libfaad-dev libjpeg-dev libgsm1-dev ninja-build libxfixes-dev libxkbcommon-dev libwayland-dev libpam0g-dev libxdamage-dev libxcb-damage0-dev ccache libxtst-dev libfuse-dev libsystemd-dev libcairo2-dev libsoxr-dev + mkdir ci-build + cd ci-build + cmake -GNinja ../ci/cmake-preloads/config-linux-all.txt .. + cmake --build . + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c844922 --- /dev/null +++ b/.gitignore @@ -0,0 +1,154 @@ +#ninja +.ninja_deps +.ninja_log +build.ninja +rules.ninja + +# 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 +*.VC.db +*.VC.opendb + +# Binaries +*.a +*.o +*.so +*.so.* +*.dylib +bin +libs +cunit/test_freerdp +client/X11/xfreerdp +client/Mac/xcode +client/Sample/sfreerdp +client/Wayland/wlfreerdp +server/Sample/sfreerdp-server +server/X11/xfreerdp-server +server/proxy/freerdp-proxy +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/**.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 +cache/ diff --git a/.source_version b/.source_version new file mode 100644 index 0000000..dbe5900 --- /dev/null +++ b/.source_version @@ -0,0 +1 @@ +2.8.1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b5dc259 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,56 @@ +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 + - libxext-dev + - ninja-build + - libsystemd-dev + - libwayland-dev + +before_script: + - ulimit -c unlimited -S + +script: + - sudo hostname travis-ci.local + - cmake -G Ninja -C ci/cmake-preloads/config-linux-all.txt -D CMAKE_BUILD_TYPE=Debug . + - 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..aa1131d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,1117 @@ +# 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 +if(WITH_CLANG_FORMAT) + include(ClangFormat) +endif() + +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) + +if (DEFINE_NO_DEPRECATED) + add_definitions(-DDEFINE_NO_DEPRECATED) +endif() + +# Soname versioning +set(BUILD_NUMBER 0) +if ($ENV{BUILD_NUMBER}) + set(BUILD_NUMBER $ENV{BUILD_NUMBER}) +endif() +set(WITH_LIBRARY_VERSIONING "ON") + +set(RAW_VERSION_STRING "2.8.1") +if(EXISTS "${CMAKE_SOURCE_DIR}/.source_tag") + file(READ ${CMAKE_SOURCE_DIR}/.source_tag RAW_VERSION_STRING) +elseif(USE_VERSION_FROM_GIT_TAG) + git_get_exact_tag(_GIT_TAG --tags --always) + if (NOT ${_GIT_TAG} STREQUAL "n/a") + set(RAW_VERSION_STRING ${_GIT_TAG}) + endif() +endif() +string(STRIP ${RAW_VERSION_STRING} RAW_VERSION_STRING) + +set(VERSION_REGEX "^.?([0-9]+)\\.([0-9]+)\\.([0-9]+)-?(.*)") +string(REGEX REPLACE "${VERSION_REGEX}" "\\1" FREERDP_VERSION_MAJOR "${RAW_VERSION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\2" FREERDP_VERSION_MINOR "${RAW_VERSION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\3" FREERDP_VERSION_REVISION "${RAW_VERSION_STRING}") +string(REGEX REPLACE "${VERSION_REGEX}" "\\4" FREERDP_VERSION_SUFFIX "${RAW_VERSION_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}") + +if(EXISTS "${PROJECT_SOURCE_DIR}/.source_version" ) + file(READ ${PROJECT_SOURCE_DIR}/.source_version GIT_REVISION) + + string(STRIP ${GIT_REVISION} GIT_REVISION) +elseif(USE_VERSION_FROM_GIT_TAG) + git_get_exact_tag(GIT_REVISION --tags --always) + + if (${GIT_REVISION} STREQUAL "n/a") + git_rev_parse (GIT_REVISION --short) + endif() +endif() + +if (NOT GIT_REVISION) + set(GIT_REVISION ${FREERDP_VERSION}) +endif() + +message(STATUS "Git Revision ${GIT_REVISION}") + +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(CMAKE_CROSSCOMPILING) + SET (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +endif(CMAKE_CROSSCOMPILING) +# /Allow to search the host machine for git/ccache + +# 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() + +set(THREAD_PREFER_PTHREAD_FLAG TRUE) + +if(NOT IOS) + find_package(Threads REQUIRED) +endif() + +if(NOT WIN32 AND NOT IOS) + 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() + +# Enable address sanitizer, where supported and when required +if(${CMAKE_C_COMPILER_ID} STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCC) + CHECK_C_COMPILER_FLAG ("-fno-omit-frame-pointer" fno-omit-frame-pointer) + + if (fno-omit-frame-pointer) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer") + endif() + + set(CMAKE_REQUIRED_LINK_OPTIONS_SAVED ${CMAKE_REQUIRED_LINK_OPTIONS}) + file(WRITE ${CMAKE_BINARY_DIR}/foo.txt "") + if(WITH_SANITIZE_ADDRESS) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-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) + + 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) + elseif(WITH_SANITIZE_MEMORY) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-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) + + 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) + elseif(WITH_SANITIZE_THREAD) + list(APPEND CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=thread") + CHECK_C_COMPILER_FLAG ("-fsanitize=thread" fsanitize-thread) + CHECK_C_COMPILER_FLAG ("-fsanitize-blacklist=${CMAKE_BINARY_DIR}/foo.txt" fsanitize-blacklist) + + 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") + endif() + + file(REMOVE ${CMAKE_BINARY_DIR}/foo.txt) + set(CMAKE_REQUIRED_LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS_SAVED}) + + if (WITH_NO_UNDEFINED) + CHECK_C_COMPILER_FLAG (-Wl,--no-undefined no-undefined) + + if(no-undefined) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined" ) + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() + endif() +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*" OR ${CMAKE_GENERATOR} MATCHES "Unix Makefiles") + 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() + + string(TIMESTAMP RC_VERSION_YEAR "%Y") + + if(NOT DEFINED CMAKE_WINDOWS_VERSION) + set(CMAKE_WINDOWS_VERSION "WIN7") + 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() + + # 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 "${FREERDP_VERSION_FULL} ${GIT_REVISION} ${CMAKE_WINDOWS_VERSION} ${CMAKE_SYSTEM_PROCESSOR}") + + 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(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) + check_include_files(execinfo.h HAVE_EXECINFO_HEADER) + if (HAVE_EXECINFO_HEADER) + check_symbol_exists(backtrace execinfo.h HAVE_EXECINFO_BACKTRACE) + check_symbol_exists(backtrace_symbols execinfo.h HAVE_EXECINFO_BACKTRACE_SYMBOLS) + check_symbol_exists(backtrace_symbols_fd execinfo.h HAVE_EXECINFO_BACKTRACE_SYMBOLS_FD) + + # Some implementations (e.g. Android NDK API < 33) provide execinfo.h but do not define + # the backtrace functions. Disable detection for these cases + if (HAVE_EXECINFO_BACKTRACE AND HAVE_EXECINFO_BACKTRACE_SYMBOLS AND HAVE_EXECINFO_BACKTRACE_SYMBOLS_FD) + set(HAVE_EXECINFO_H ON) + endif() + endif() +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(ANDROID_ABI STREQUAL arm64-v8a) + # https://github.com/android/ndk/issues/910 + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=softfp") + 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}/${ANDROID_ABI}/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() + +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") + + include(CheckFunctionExists) + + check_function_exists(getlogin_r HAVE_GETLOGIN_R) + check_function_exists(getpwuid_r HAVE_GETPWUID_R) +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(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 "RECOMMENDED") +set(PULSE_FEATURE_PURPOSE "sound") +set(PULSE_FEATURE_DESCRIPTION "audio input, audio output and multimedia redirection") + +set(CUPS_FEATURE_TYPE "RECOMMENDED") +set(CUPS_FEATURE_PURPOSE "printing") +set(CUPS_FEATURE_DESCRIPTION "printer device redirection") + +set(PCSC_FEATURE_TYPE "RECOMMENDED") +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 "[experimental] VA-API hardware acceleration for 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(OPENH264_FEATURE_TYPE "OPTIONAL") +set(OPENH264_FEATURE_PURPOSE "codec") +set(OPENH264_FEATURE_DESCRIPTION "use OpenH264 library") + +set(OPENCL_FEATURE_TYPE "OPTIONAL") +set(OPENCL_FEATURE_PURPOSE "codec") +set(OPENCL_FEATURE_DESCRIPTION "[experimental] use OpenCL 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 "[experimental] 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 "[experimental] add kerberos support") + +if(WIN32) + set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(ZLIB_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(OPENSLES_FEATURE_TYPE "DISABLED") +endif() + +if(APPLE) + set(FFMPEG_FEATURE_TYPE "OPTIONAL") + set(VAAPI_FEATURE_TYPE "DISABLED") + 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") + 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(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(VAAPI_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(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(JPEG ${JPEG_FEATURE_TYPE} ${JPEG_FEATURE_PURPOSE} ${JPEG_FEATURE_DESCRIPTION}) +find_feature(OpenH264 ${OPENH264_FEATURE_TYPE} ${OPENH264_FEATURE_PURPOSE} ${OPENH264_FEATURE_DESCRIPTION}) +find_feature(OpenCL ${OPENCL_FEATURE_TYPE} ${OPENCL_FEATURE_PURPOSE} ${OPENCL_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_OPENH264 AND NOT WITH_OPENH264_LOADING) + option(WITH_OPENH264_LOADING "Use LoadLibrary to load openh264 at runtime" OFF) +endif (WITH_OPENH264 AND NOT WITH_OPENH264_LOADING) + +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]+") + if (EXISTS "${AVCODEC_INCLUDE_DIR}/libavcodec/version_major.h") + file(STRINGS "${AVCODEC_INCLUDE_DIR}/libavcodec/version_major.h" AV_VERSION_FILE2 REGEX "LIBAVCODEC_VERSION_M[A-Z]+[\t ]*[0-9]+") + list(APPEND AV_VERSION_FILE ${AV_VERSION_FILE2}) + endif() + + 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}") + include_directories(${OPENSSL_INCLUDE_DIR}) +endif() + +if(MBEDTLS_FOUND) + add_definitions("-DWITH_MBEDTLS") +endif() + +if (WITH_OPENH264 OR WITH_MEDIA_FOUNDATION OR WITH_FFMPEG OR WITH_MEDIACODEC) + set(WITH_GFX_H264 ON) +else() + set(WITH_GFX_H264 OFF) +endif() + +# Android expects all libraries to be loadable +# without paths. +if (ANDROID OR WIN32 OR MAC_BUNDLE) + set(FREERDP_DATA_PATH "share") + if (NOT FREERDP_INSTALL_PREFIX) + set(FREERDP_INSTALL_PREFIX ".") + endif() + set(FREERDP_LIBRARY_PATH ".") + set(FREERDP_PLUGIN_PATH ".") +else() + set(FREERDP_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/freerdp${FREERDP_VERSION_MAJOR}") + if (NOT FREERDP_INSTALL_PREFIX) + set(FREERDP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + endif() + set(FREERDP_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}") + set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/freerdp${FREERDP_VERSION_MAJOR}") +endif() +set(FREERDP_ADDIN_PATH "${FREERDP_PLUGIN_PATH}") + +# Path to put extensions +set(FREERDP_EXTENSION_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/freerdp${FREERDP_VERSION_MAJOR}/extensions") + +# Proxy plugins path +if(NOT DEFINED PROXY_PLUGINDIR) + message("using default plugins location") + set(FREERDP_PROXY_PLUGINDIR "${CMAKE_BINARY_DIR}/server/proxy/plugins") +else() + set(FREERDP_PROXY_PLUGINDIR "${PROXY_PLUGINDIR}") +endif() + +# Declare we have config.h, generated later on. +add_definitions("-DHAVE_CONFIG_H") + +# Include directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# 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) + +# 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() + +# Configure files - Add last so all symbols are defined +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# 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..b878eac --- /dev/null +++ b/ChangeLog @@ -0,0 +1,638 @@ +# 2022-10-12 Version 2.8.1 + +Notewhorth changes: +* Fixed CVE-2022-39282 +* Fixed CVE-2022-39283 +* Added missing commit for backported #8041: Remove ALAW/ULAW codecs from linux backends (unreliable) +* Added hash checks for android build script dependencies + +Fixed issues: +* Backported #8190: Fix build break with newer FFMPEG versions +* Backported #8234: Updated flatpak with build script +* Backported #8210: Better execinfo support check for android +* Backported #7708: Header now defines DumpThreadHandles +* Backported #8176: Check fullscreen state and not setting +* Backported #8236: Send resize on window state change +* Backported #7611: Audin macOS monterey fix +* Backported #8291: Android build script update + +# 2022-07-28 Version 2.8.0 + +Noteworthy changes: + +* Backported API to get peer accepted channel option flags +* Backported API to get peer accepted channel names +* Backported Stream_CheckAndLogRequiredLength +* Backported #7954: Add server side handling for [MS-RDPET] +* Backported #8010: Add server side handling for [MS-RDPECAM] +* Backported #8041: Remove ALAW/ULAW codecs from linux backends (unreliable) +* Backported #8051: Relieve CLIPRDR filename restriction when connecting to non-MS Windows servers +* Backported #8048: TLS version control +* Backported #7987: Add a new command line arg to enforce tls1.2 + +Fixed issues: + +* Fixed #7837: Prevent out of bound reads for FFMPEG +* Backported #7859 and #7861: Unwind support for backtrace generation +* Backported #7440: wlfreerdp appid +* Backported #7832: RAIL window restore +* Backported #7833: Refactored WinPR thread locking +* Backported #7893: Mac rdpsnd memory leak fixes +* Backported #7895: Mac audin memory leak fixes +* Backported #7898: Automatic android versioning +* Backported #7916: GFX 10.7 capability support +* Backported #7949: Server RDPSND API improvements +* Backported #7957: Server DVC API improvements +* Backported #7760: Fixed osMinorType values +* Backported #8013: Add missing osMajorType values +* Backported #8076: Fix wrong usage of subband diffing flag (tile artifact fix) + +For a complete and detailed change log since the last release run: +git log 2.7.0..2.8.0 + + +# 2022-04-25 Version 2.7.0 + +Noteworthy changes: +* Backported OpenSSL3 gateway support (#7822) +* Backported various NTLM fixes +* Backported WINPR_ASSERT to ease future backports + +Fixed issues: +* Backported #6786: Use /network:auto by default +* Backported #7714: Workaround for broken surface frame marker +* Backported #7733: Support 10bit X11 color (BGRX32 only) +* Backported #7745: GFX progressive double free +* Backported #7808: Disable websockets with /gt:rpc +* Backported #7815: RAIL expect LOGON_MSG_SESSION_CONTINUE + +Important notes: + +For a complete and detailed change log since the last release run: +git log 2.6.1..2.7.0 + +# 2022-03-07 Version 2.6.1 + +Noteworthy changes: + +Fixed issues: +* Backported freerdp_abort_connect during freerdp_connect fix (#7700) +* Backported improved version dection see docs/version_detection.md for details +* Backported various rdpsnd fixes (#7695) + +Important notes: + +For a complete and detailed change log since the last release run: +git log 2.0.0..2.6.1 + +# 2022-02-22 Version 2.6.0 + +Noteworthy changes: +* Backported android FFMPEG build scripts +* Updated android build dependencies + +Fixed issues: +* Backported #7303: Fix PDU length for RDPINPUT_PROTOCOL_V300 +* Backported #7658: Sanitize optional physical monitor size values +* Backported #7426: Wayland memory corruption +* Backported #7293: Remove unused codec x264 +* Backported #7541: Allow resolutions larger 2048x2048 +* Backported #7574: FFMPEG 5.0 support +* Backported #7578: FFMPEG 5.0 support +* Backported #7580: Fixed device hotplugging +* Backported #7583: GetUserNameExA: Prefer getpwuid_r over getlogin_r over getlogin +* Backported #7585: Android Mediacodec support + +Important notes: + +For a complete and detailed change log since the last release run: +git log 2.5.0..2.6.0 + +# 2022-01-12 Version 2.5.0 + +Noteworthy changes: +* Fixed smartcard login in case a redirection occurs the pin was lost +* Backported windows client drawing fixes +* Backported improved macOS keyboard layout detection +* Backported TcpConnectTimeout +* Backported LibreSSL compatibility patches +* Backported signal handler backtrace +* Backported OpenSSL 3.0 support + +Fixed issues: +* Backport #7539: Wayland client clipboard issues +* Backport #7509: Various fixes regarding registry emulation, addin loader + and updated locale detection +* Backport #7466: Android android_register_pointer missing initialization + +Important notes: + +For a complete and detailed change log since the last release run: +git log 2.4.1..2.5.0 + +# 2021-10-20 Version 2.4.1 + +Noteworthy changes: +* Refactored RPC gateway parsing code +* OpenSSL 3.0 compatibility fixes +* USB redirection: fixed transfer lengths + +Fixed issues: +* #7363: Length checks in ConvertUTF8toUTF16 +* #7349: Added checks for bitmap width and heigth values + +Important notes: +* CVE-2021-41159: Improper client input validation for gateway connections allows to overwrite memory +* CVE-2021-41160: Improper region checks in all clients allow out of bound write to memory + +For a complete and detailed change log since the last release run: +git log 2.4.0..2.4.1 + +# 2021-07-27 Version 2.4.0 + +Noteworthy changes: +* Backported multithreadded progressive decoder (#7036) +* Backported clipboard fixes (#6924) +* Fixed remote file read (#7185) + +Fixed issues: +* #6938: RAILS clipboard remote -> local +* #6985: Support newer FFMPEG builds +* #6989: Use OpenSSL default certificate store settings +* #7073: Planar alignment fixes + +# 2021-03-15 Version 2.3.2 + +For a complete and detailed change log since the last release run: +git log 2.3.2..2.4.0 + +Noteworthy changes: +* Fixed autoreconnect printer backend loading +* Fixed compilation on older mac os versions < 10.14 +* Fixed mouse pointer move with smart-sizing +* Added command line option to disable websocket gateway support +* Fixed drive hotplugging issues with windows +* Fixed smartcard issues on mac + +Fixed issues: +* #6900: Transparency issues with aFreeRDP +* #6848: Invalid format string in smartcard trace +* #6846: Fixed static builds +* #6888: Crash due to missing bounds checks +* #6882: Use default sound devoce on mac + +For a complete and detailed change log since the last release run: +git log 2.3.1..2.3.2 + +# 2021-03-01 Version 2.3.1 + +Noteworthy changes: +* This is a compatibility bugfix release readding some (deprecated) + symbols/defines +* Also add some more EXPERIMENTAL warnings to CMake flags as some were not + clear enough. +* Fixed a memory leak in xfreerdp (mouse pointer updates) +* No longer activating some compile time debug options with -DWITH_DEBUG_ALL=ON + which might leak sensitive information. +* Added -DDEFINE_NO_DEPRECATED for developers to detect use of deprecated + symbols + +For a complete and detailed change log since the last release run: +git log 2.3.0..2.3.1 + + +# 2021-02-24 Version 2.3.0 + +Important notes: +* CMake option WITH_PROXY_MODULES is currently experimental, do not use in +production. +* The clipboard struct FILEDESCRIPTOR was replaced by FILEDESCRIPTORW with + proper data types. They are binary compatible and the former is kept for + compatibility but compilers will emit warnings. + +Noteworthy changes: +* Websocket support for proxy connections +* Progressive codec improvements. Reduces graphical glitches against windows +and ogon servers +* Fixed +glyph-cache, now working properly without disconnects +* Huge file support in clipboard +* XWayland support for xfreerdp (keyboard grabbing) +* Improved wlfreerdp (wayland client) +* Option to allow keyboard scancodes to be remapped manually +* Improved mouse wheel behaviour when scrolling +* Improved dynamic channel behaviour, more stable event detection +* New connection state PubSub notification: Clients can now monitor current + connection state + +Fixed issues: +* #6626: Fixed parsing of FastGlyph order. +* #6624: Added support for xwayland keyboard grab +* #6492: Added clipboard CB_HUGE_FILE_SUPPORT_ENABLED flag +* #6428: Improve NLA error code logging. +* #6416: Http gateway message support +* #6753: List of pull requests to backport for stable-next + +For a complete and detailed change log since the last release run: +git log 2.2.0..2.3.0 + + +# 2020-07-20 Version 2.2.0 + +Important notes: +* CVE-2020-15103 - Integer overflow due to missing input sanitation in rdpegfx channel + +Noteworty changes: +* fix: memory leak in nsc +* urbdrc + * some fixes and improvements +* build + * use cmake to detect getlogin_r + * improve asan checks/detection +* server/proxy + * new: support for heartbeats + * new: support for rail handshake ex flags + * fix: possible race condition with redirects + +Fixed issues: +* #6263 Sound & mic - filter GSM codec for microphone redirection +* #6335: windows client title length +* #6370 - "Alternate Secondary Drawing Order UNKNOWN" +* #6298 - remoteapp with dialog is disconnecting when it loses focus +* #6299 - v2.1.2: Can't connect to Windows7 + +For a complete and detailed change log since the last release run: +git log 2.1.2..2.2.0 + + +# 2020-06-22 Version 2.1.2 + +Important notes: +* CVE-2020-4033 Out of bound read in RLEDECOMPRESS +* CVE-2020-4031 Use-After-Free in gdi_SelectObject +* CVE-2020-4032 Integer casting vulnerability in `update_recv_secondary_order` +* CVE-2020-4030 OOB read in `TrioParse` +* CVE-2020-11099 OOB Read in license_read_new_or_upgrade_license_packet +* CVE-2020-11098 Out-of-bound read in glyph_cache_put +* CVE-2020-11097 OOB read in ntlm_av_pair_get +* CVE-2020-11095 Global OOB read in update_recv_primary_order +* CVE-2020-11096 Global OOB read in update_read_cache_bitmap_v3_order +* Gateway RPC fixes for windows +* Fixed resource fee race resulting in double free in USB redirection +* Fixed wayland client crashes +* Fixed X11 client mouse mapping issues (X11 mapping on/off) +* Some proxy related improvements (capture module) +* Code cleanup (use getlogin_r, ...) + +For a complete and detailed change log since the last release candidate run: +git log 2.1.1..2.1.2 + + +# 2020-05-20 Version 2.1.1 + +Important notes: +* CVE: GHSL-2020-100 OOB Read in ntlm_read_ChallengeMessage +* CVE: GHSL-2020-101 OOB Read in security_fips_decrypt due to uninitialized value +* CVE: GHSL-2020-102 OOB Write in crypto_rsa_common +* Enforce synchronous legacy RDP encryption count (#6156) +* Fixed some leaks and crashes missed in 2.1.0 +* Removed dynamic channel listener limits +* Lots of resource cleanup fixes (clang sanitizers) +* A couple of performance improvements +* Various small annoyances eliminated (typos, prefilled username for windows client, ...) + + +For a complete and detailed change log since the last release candidate run: +git log 2.1.0..2.1.1 + + +# 2020-05-05 Version 2.1.0 + +Important notes: + +* fix multiple CVEs: CVE-2020-11039, CVE-2020-11038, CVE-2020-11043, CVE-2020-11040, CVE-2020-11041, + CVE-2020-11019, CVE-2020-11017, CVE-2020-11018 +* fix multiple leak and crash issues (#6129, #6128, #6127, #6110, #6081, #6077) + +Noteworthy features and improvements: +* Fixed sound issues (#6043) +* New expert command line options /tune and /tune-list to modify all client + settings in a generic way. +* Fixes for smartcard cache, this improves compatibility of smartcard devices + with newer smartcard channel. +* Shadow server can now be instructed to listen to multiple interfaces. +* Improved server certificate support (#6052) +* Various fixes for wayland client (fullscreen, mouse wheel, ...) +* Fixed large mouse pointer support, now mouse pointers > 96x96 pixel are visible. +* USB redirection command line improvements (filter options) +* Various translation improvements for android and ios clients + +For a complete and detailed change log since the last release candidate run: +git log 2.0.0..2.1.0 + + +# 2020-04-09 Version 2.0.0 + +Important notes: + +* fix multiple CVEs: CVE-2020-11521 CVE-2020-11522 CVE-2020-11523 CVE-2020-11524 CVE-2020-11525 CVE-2020-11526 +* fix multiple other security related issues (#6005, #6006, #6007, #6008, #6009, #6010, #6011, #6012, #6013) +* sha256 is now used instead of sha1 to fingerprint certificates. This will + invalidate all hosts in FreeRDP known_hosts2 file and causes a prompt if a + new connection is established after the update + +Noteworthy features and improvements: + +* First version of the RDP proxy was added (#5372) - thanks to @kubistika +* Smartcard received some refactoring. Missing functions were added and input + validation was improved (#5884) +* A new option /cert that unifies all certificate related options (#5880) + The old options (cert-ignore, cert-deny, cert-name, cert-tofu) are still + available but marked as deprecated +* Support for Remote Assistance Protocol Version 2 [MS-RA] +* The DirectFB client was removed because it was unmaintained +* Unified initialization of OrderSupport +* Fix for licensing against Windows Server 2003 +* Font smoothing is now enabled per default +* Flatpack support was added +* Smart scaling for Wayland using libcairo was added (#5215) +* Unified update->BeginPaint and update->EndPaint +* An image scaling API for software drawing was added +* Rail was updated to the latest spec version 28.0 +* Support for H.264 in the shadow server is now detected at runtime +* Add mask= option for /gfx and /gfx-h264 (#5771) +* Code reformatting (#5667) +* A new option /timeout was added to adjust the TCP ACK timeout (#5987) + +For a complete and detailed change log since the last release candidate run: +git log 2.0.0-rc4..2.0.0 + + +# 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.md b/README.md new file mode 100644 index 0000000..70cf40d --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# 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..882fef7 --- /dev/null +++ b/channels/CMakeLists.txt @@ -0,0 +1,346 @@ +# 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) + string(TOUPPER "${CHANNEL_TYPE}" CHANNEL_TYPE) + + 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") + + if ("${CHANNEL_TYPE}" STREQUAL "DYNAMIC") + CMAKE_DEPENDENT_OPTION(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT} "CHANNEL_DRDYNVC" OFF) + else() + option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT}) + endif() + +endmacro(define_channel_options) + +macro(define_channel_client_options _channel_client_default) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION) + set(CHANNEL_CLIENT_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel client") + + 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) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION) + set(CHANNEL_SERVER_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel server") + + 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) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if (NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + 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}) + target_link_libraries(${_module_name} ${${_module_prefix}_LIBS}) + 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}) + target_link_libraries(${_module_name} ${${_module_prefix}_LIBS}) + + 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) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if (NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + 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}) + target_link_libraries(${_module_name} ${${_module_prefix}_LIBS}) + 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}) + target_link_libraries(${_module_name} ${${_module_prefix}_LIBS}) + 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) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if (NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + 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}") + +# We need special treatement for drdynvc: +# It needs to be the first entry so that every +# dynamic channel has the dependent options available. +set(DRDYNVC_MATCH "") + +foreach(FILEPATH ${FILEPATHS}) + if(${FILEPATH} MATCHES "^([^/]*)drdynvc/+${FILENAME}") + set(DRDYNVC_MATCH ${FILEPATH}) + endif() +endforeach() + +if (NOT "${DRDYNVC_MATCH}" STREQUAL "") + list(REMOVE_ITEM FILEPATHS ${DRDYNVC_MATCH}) + list(APPEND FILEPATHS ${DRDYNVC_MATCH}) + list(REVERSE FILEPATHS) # list PREPEND is not available on old CMake3 +endif() + +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/ainput/CMakeLists.txt b/channels/ainput/CMakeLists.txt new file mode 100644 index 0000000..b1d1a6a --- /dev/null +++ b/channels/ainput/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("ainput") + +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/ainput/ChannelOptions.cmake b/channels/ainput/ChannelOptions.cmake new file mode 100644 index 0000000..eafc6c0 --- /dev/null +++ b/channels/ainput/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "ainput" TYPE "dynamic" + DESCRIPTION "Advanced Input Virtual Channel Extension" + SPECIFICATIONS "[XXXXX]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/ainput/client/CMakeLists.txt b/channels/ainput/client/CMakeLists.txt new file mode 100644 index 0000000..4cf2d22 --- /dev/null +++ b/channels/ainput/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("ainput") + +set(${MODULE_PREFIX}_SRCS + ainput_main.c + ainput_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 ${PROJECT_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/ainput/client/ainput_main.c b/channels/ainput/client/ainput_main.c new file mode 100644 index 0000000..d8eb8ec --- /dev/null +++ b/channels/ainput/client/ainput_main.c @@ -0,0 +1,315 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include + +#include "ainput_main.h" +#include +#include +#include + +#include "../common/ainput_common.h" + +#define TAG CHANNELS_TAG("ainput.client") + +typedef struct AINPUT_CHANNEL_CALLBACK_ AINPUT_CHANNEL_CALLBACK; +struct AINPUT_CHANNEL_CALLBACK_ +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; + +typedef struct AINPUT_LISTENER_CALLBACK_ AINPUT_LISTENER_CALLBACK; +struct AINPUT_LISTENER_CALLBACK_ +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + AINPUT_CHANNEL_CALLBACK* channel_callback; +}; + +typedef struct AINPUT_PLUGIN_ AINPUT_PLUGIN; +struct AINPUT_PLUGIN_ +{ + IWTSPlugin iface; + + AINPUT_LISTENER_CALLBACK* listener_callback; + IWTSListener* listener; + UINT32 MajorVersion; + UINT32 MinorVersion; + BOOL initialized; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT16 type; + AINPUT_PLUGIN* ainput; + AINPUT_CHANNEL_CALLBACK* callback = (AINPUT_CHANNEL_CALLBACK*)pChannelCallback; + + WINPR_ASSERT(callback); + WINPR_ASSERT(data); + + ainput = (AINPUT_PLUGIN*)callback->plugin; + WINPR_ASSERT(ainput); + + if (Stream_GetRemainingLength(data) < 2) + return ERROR_NO_DATA; + Stream_Read_UINT16(data, type); + switch (type) + { + case MSG_AINPUT_VERSION: + if (Stream_GetRemainingLength(data) < 8) + return ERROR_NO_DATA; + Stream_Read_UINT32(data, ainput->MajorVersion); + Stream_Read_UINT32(data, ainput->MinorVersion); + break; + default: + WLog_WARN(TAG, "Received unsupported message type 0x%04" PRIx16, type); + break; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_send_input_event(AInputClientContext* context, UINT64 flags, INT32 x, INT32 y) +{ + AINPUT_PLUGIN* ainput; + AINPUT_CHANNEL_CALLBACK* callback; + BYTE buffer[32] = { 0 }; + UINT64 time; + wStream sbuffer = { 0 }; + wStream* s = &sbuffer; + + Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + + WINPR_ASSERT(s); + WINPR_ASSERT(context); + + time = GetTickCount64(); + ainput = (AINPUT_PLUGIN*)context->handle; + WINPR_ASSERT(ainput); + WINPR_ASSERT(ainput->listener_callback); + + if (ainput->MajorVersion != AINPUT_VERSION_MAJOR) + { + WLog_WARN(TAG, "Unsupported channel version %" PRIu32 ".%" PRIu32 ", aborting.", + ainput->MajorVersion, ainput->MinorVersion); + return CHANNEL_RC_UNSUPPORTED_VERSION; + } + callback = ainput->listener_callback->channel_callback; + WINPR_ASSERT(callback); + + { + char buffer[128] = { 0 }; + WLog_VRB(TAG, "[%s] sending timestamp=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, + __FUNCTION__, time, ainput_flags_to_string(flags, buffer, sizeof(buffer)), x, y); + } + + /* Message type */ + Stream_Write_UINT16(s, MSG_AINPUT_MOUSE); + + /* Event data */ + Stream_Write_UINT64(s, time); + Stream_Write_UINT64(s, flags); + Stream_Write_INT32(s, x); + Stream_Write_INT32(s, y); + Stream_SealLength(s); + + /* ainput back what we have received. AINPUT does not have any message IDs. */ + WINPR_ASSERT(callback->channel); + WINPR_ASSERT(callback->channel->Write); + return callback->channel->Write(callback->channel, (ULONG)Stream_Length(s), Stream_Buffer(s), + NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + AINPUT_CHANNEL_CALLBACK* callback = (AINPUT_CHANNEL_CALLBACK*)pChannelCallback; + + free(callback); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + AINPUT_CHANNEL_CALLBACK* callback; + AINPUT_LISTENER_CALLBACK* listener_callback = (AINPUT_LISTENER_CALLBACK*)pListenerCallback; + + WINPR_ASSERT(listener_callback); + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + callback = (AINPUT_CHANNEL_CALLBACK*)calloc(1, sizeof(AINPUT_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = ainput_on_data_received; + callback->iface.OnClose = ainput_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + + *ppCallback = &callback->iface; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pPlugin; + + WINPR_ASSERT(ainput); + + if (ainput->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AINPUT_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + ainput->listener_callback = + (AINPUT_LISTENER_CALLBACK*)calloc(1, sizeof(AINPUT_LISTENER_CALLBACK)); + + if (!ainput->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ainput->listener_callback->iface.OnNewChannelConnection = ainput_on_new_channel_connection; + ainput->listener_callback->plugin = pPlugin; + ainput->listener_callback->channel_mgr = pChannelMgr; + + status = pChannelMgr->CreateListener(pChannelMgr, AINPUT_DVC_CHANNEL_NAME, 0, + &ainput->listener_callback->iface, &ainput->listener); + + ainput->listener->pInterface = ainput->iface.pInterface; + ainput->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_plugin_terminated(IWTSPlugin* pPlugin) +{ + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pPlugin; + if (ainput && ainput->listener_callback) + { + IWTSVirtualChannelManager* mgr = ainput->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, ainput->listener); + } + if (ainput) + { + free(ainput->listener_callback); + free(ainput->iface.pInterface); + } + free(ainput); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry ainput_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; + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "ainput"); + + if (!ainput) + { + AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext)); + ainput = (AINPUT_PLUGIN*)calloc(1, sizeof(AINPUT_PLUGIN)); + + if (!ainput || !context) + { + free(context); + free(ainput); + + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ainput->iface.Initialize = ainput_plugin_initialize; + ainput->iface.Terminated = ainput_plugin_terminated; + + context->handle = (void*)ainput; + context->AInputSendInputEvent = ainput_send_input_event; + ainput->iface.pInterface = (void*)context; + + status = pEntryPoints->RegisterPlugin(pEntryPoints, AINPUT_CHANNEL_NAME, &ainput->iface); + } + + return status; +} diff --git a/channels/ainput/client/ainput_main.h b/channels/ainput/client/ainput_main.h new file mode 100644 index 0000000..8a19ad9 --- /dev/null +++ b/channels/ainput/client/ainput_main.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H +#define FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("ainput.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_AINPUT_CLIENT_MAIN_H */ diff --git a/channels/ainput/common/ainput_common.h b/channels/ainput/common/ainput_common.h new file mode 100644 index 0000000..34442f7 --- /dev/null +++ b/channels/ainput/common/ainput_common.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_INT_AINPUT_COMMON_H +#define FREERDP_INT_AINPUT_COMMON_H + +#include + +#include + +static INLINE const char* ainput_flags_to_string(UINT64 flags, char* buffer, size_t size) +{ + char number[32] = { 0 }; + + if (flags & AINPUT_FLAGS_HAVE_REL) + winpr_str_append("AINPUT_FLAGS_HAVE_REL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_WHEEL) + winpr_str_append("AINPUT_FLAGS_WHEEL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_MOVE) + winpr_str_append("AINPUT_FLAGS_MOVE", buffer, size, "|"); + if (flags & AINPUT_FLAGS_DOWN) + winpr_str_append("AINPUT_FLAGS_DOWN", buffer, size, "|"); + if (flags & AINPUT_FLAGS_REL) + winpr_str_append("AINPUT_FLAGS_REL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON1) + winpr_str_append("AINPUT_FLAGS_BUTTON1", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON2) + winpr_str_append("AINPUT_FLAGS_BUTTON2", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON3) + winpr_str_append("AINPUT_FLAGS_BUTTON3", buffer, size, "|"); + if (flags & AINPUT_XFLAGS_BUTTON1) + winpr_str_append("AINPUT_XFLAGS_BUTTON1", buffer, size, "|"); + if (flags & AINPUT_XFLAGS_BUTTON2) + winpr_str_append("AINPUT_XFLAGS_BUTTON2", buffer, size, "|"); + + _snprintf(number, sizeof(number), "[0x%08" PRIx64 "]", flags); + winpr_str_append(number, buffer, size, " "); + + return buffer; +} + +#endif /* FREERDP_INT_AINPUT_COMMON_H */ diff --git a/channels/ainput/server/CMakeLists.txt b/channels/ainput/server/CMakeLists.txt new file mode 100644 index 0000000..59be263 --- /dev/null +++ b/channels/ainput/server/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("ainput") + +set(${MODULE_PREFIX}_SRCS + ainput_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/ainput/server/ainput_main.c b/channels/ainput/server/ainput_main.c new file mode 100644 index 0000000..943d0fa --- /dev/null +++ b/channels/ainput/server/ainput_main.c @@ -0,0 +1,587 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/ainput_common.h" + +#define TAG CHANNELS_TAG("ainput.server") + +typedef enum +{ + AINPUT_INITIAL, + AINPUT_OPENED, + AINPUT_VERSION_SENT, +} eAInputChannelState; + +typedef struct +{ + ainput_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* ainput_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eAInputChannelState state; + + wStream* buffer; +} ainput_server; + +static UINT ainput_server_context_poll(ainput_server_context* context); +static BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle); +static UINT ainput_server_context_poll_int(ainput_server_context* context); + +static BOOL ainput_server_is_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + return ainput->isOpened; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open_channel(ainput_server* ainput) +{ + DWORD Error; + HANDLE hEvent; + DWORD StartTick; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + + WINPR_ASSERT(ainput); + + if (WTSQuerySessionInformationA(ainput->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(ainput->context.vcm); + StartTick = GetTickCount(); + + while (ainput->ainput_channel == NULL) + { + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + ainput->ainput_channel = WTSVirtualChannelOpenEx(ainput->SessionId, AINPUT_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + Error = GetLastError(); + + if (Error == ERROR_NOT_FOUND) + { + WLog_DBG(TAG, "Channel %s not found", AINPUT_DVC_CHANNEL_NAME); + break; + } + + if (ainput->ainput_channel) + { + UINT32 channelId; + BOOL status = TRUE; + + channelId = WTSChannelGetIdByHandle(ainput->ainput_channel); + + IFCALLRET(ainput->context.ChannelIdAssigned, status, &ainput->context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + break; + } + + if (GetTickCount() - StartTick > 5000) + { + WLog_WARN(TAG, "Timeout opening channel %s", AINPUT_DVC_CHANNEL_NAME); + break; + } + } + + return ainput->ainput_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static UINT ainput_server_send_version(ainput_server* ainput) +{ + ULONG written; + wStream* s; + + WINPR_ASSERT(ainput); + + s = ainput->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + if (!Stream_EnsureCapacity(s, 10)) + { + WLog_WARN(TAG, "[%s] out of memory", AINPUT_DVC_CHANNEL_NAME); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT16(s, MSG_AINPUT_VERSION); + Stream_Write_UINT32(s, AINPUT_VERSION_MAJOR); /* Version (4 bytes) */ + Stream_Write_UINT32(s, AINPUT_VERSION_MINOR); /* Version (4 bytes) */ + + WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX); + if (!WTSVirtualChannelWrite(ainput->ainput_channel, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_server_recv_mouse_event(ainput_server* ainput, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + UINT64 flags, time; + INT32 x, y; + char buffer[128] = { 0 }; + + WINPR_ASSERT(ainput); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_NO_DATA; + + Stream_Read_UINT64(s, time); + Stream_Read_UINT64(s, flags); + Stream_Read_INT32(s, x); + Stream_Read_INT32(s, y); + + WLog_VRB(TAG, "[%s] received: time=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, + __FUNCTION__, time, ainput_flags_to_string(flags, buffer, sizeof(buffer)), x, y); + IFCALLRET(ainput->context.MouseEvent, error, &ainput->context, time, flags, x, y); + + return error; +} + +static HANDLE ainput_server_get_channel_handle(ainput_server* ainput) +{ + BYTE* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(ainput); + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI ainput_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + ainput_server* ainput = (ainput_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(ainput); + + nCount = 0; + events[nCount++] = ainput->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (ainput->state) + { + case AINPUT_OPENED: + events[1] = ainput_server_get_channel_handle(ainput); + nCount = 2; + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + switch (status) + { + case WAIT_TIMEOUT: + case WAIT_OBJECT_0 + 1: + case WAIT_OBJECT_0: + error = ainput_server_context_poll_int(&ainput->context); + break; + + case WAIT_FAILED: + default: + WLog_WARN(TAG, "[%s] Wait for open failed", AINPUT_DVC_CHANNEL_NAME); + error = ERROR_INTERNAL_ERROR; + break; + } + break; + case AINPUT_VERSION_SENT: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_TIMEOUT: + case WAIT_OBJECT_0 + 1: + case WAIT_OBJECT_0: + error = ainput_server_context_poll_int(&ainput->context); + break; + case WAIT_FAILED: + default: + WLog_WARN(TAG, "[%s] Wait for version failed", AINPUT_DVC_CHANNEL_NAME); + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + error = ainput_server_context_poll_int(&ainput->context); + break; + } + } + + WTSVirtualChannelClose(ainput->ainput_channel); + ainput->ainput_channel = NULL; + + if (error && ainput->context.rdpcontext) + setChannelError(ainput->context.rdpcontext, error, + "ainput_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (!ainput->externalThread && (ainput->thread == NULL)) + { + ainput->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ainput->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->thread = CreateThread(NULL, 0, ainput_server_thread_func, ainput, 0, NULL); + if (!ainput->thread) + { + WLog_ERR(TAG, "CreateEvent failed!"); + CloseHandle(ainput->stopEvent); + ainput->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + ainput->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_close(ainput_server_context* context) +{ + UINT error = CHANNEL_RC_OK; + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (!ainput->externalThread && ainput->thread) + { + SetEvent(ainput->stopEvent); + + if (WaitForSingleObject(ainput->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(ainput->thread); + CloseHandle(ainput->stopEvent); + ainput->thread = NULL; + ainput->stopEvent = NULL; + } + if (ainput->externalThread) + { + if (ainput->state != AINPUT_INITIAL) + { + WTSVirtualChannelClose(ainput->ainput_channel); + ainput->ainput_channel = NULL; + ainput->state = AINPUT_INITIAL; + } + } + ainput->isOpened = FALSE; + + return error; +} + +static UINT ainput_server_initialize(ainput_server_context* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (ainput->isOpened) + { + WLog_WARN(TAG, "Application error: AINPUT channel already initialized, calling in this " + "state is not possible!"); + return ERROR_INVALID_STATE; + } + ainput->externalThread = externalThread; + return error; +} + +ainput_server_context* ainput_server_context_new(HANDLE vcm) +{ + ainput_server* ainput = (ainput_server*)calloc(1, sizeof(ainput_server)); + + if (!ainput) + return NULL; + + ainput->context.vcm = vcm; + ainput->context.Open = ainput_server_open; + ainput->context.IsOpen = ainput_server_is_open; + ainput->context.Close = ainput_server_close; + ainput->context.Initialize = ainput_server_initialize; + ainput->context.Poll = ainput_server_context_poll; + ainput->context.ChannelHandle = ainput_server_context_handle; + + ainput->buffer = Stream_New(NULL, 4096); + if (!ainput->buffer) + goto fail; + return &ainput->context; +fail: + ainput_server_context_free(ainput); + return NULL; +} + +void ainput_server_context_free(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + if (ainput) + { + ainput_server_close(context); + Stream_Free(ainput->buffer, TRUE); + } + free(ainput); +} + +static UINT ainput_process_message(ainput_server* ainput) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned, ActualBytesReturned; + UINT16 MessageId; + wStream* s; + + WINPR_ASSERT(ainput); + WINPR_ASSERT(ainput->ainput_channel); + + s = ainput->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(ainput->ainput_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 2) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(ainput->ainput_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &ActualBytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + if (BytesReturned != ActualBytesReturned) + { + WLog_ERR(TAG, "WTSVirtualChannelRead size mismatch %" PRId32 ", expected %" PRId32, + ActualBytesReturned, BytesReturned); + goto out; + } + + Stream_SetLength(s, ActualBytesReturned); + Stream_Read_UINT16(s, MessageId); + + switch (MessageId) + { + case MSG_AINPUT_MOUSE: + error = ainput_server_recv_mouse_event(ainput, s); + break; + + default: + WLog_ERR(TAG, "audin_server_thread_func: unknown MessageId %" PRIu8 "", MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle) +{ + ainput_server* ainput = (ainput_server*)context; + WINPR_ASSERT(ainput); + WINPR_ASSERT(handle); + + if (!ainput->externalThread) + { + WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME); + return FALSE; + } + if (ainput->state == AINPUT_INITIAL) + { + WLog_WARN(TAG, "[%s] state fail!", AINPUT_DVC_CHANNEL_NAME); + return FALSE; + } + *handle = ainput_server_get_channel_handle(ainput); + return TRUE; +} + +UINT ainput_server_context_poll_int(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(ainput); + + switch (ainput->state) + { + case AINPUT_INITIAL: + error = ainput_server_open_channel(ainput); + if (error) + WLog_ERR(TAG, "ainput_server_open_channel failed with error %" PRIu32 "!", error); + else + ainput->state = AINPUT_OPENED; + break; + case AINPUT_OPENED: + { + BYTE* buffer = NULL; + DWORD BytesReturned = 0; + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) != TRUE) + { + WLog_ERR(TAG, "WTSVirtualChannelReady failed,"); + } + else + { + if (*buffer != 0) + { + error = ainput_server_send_version(ainput); + if (error) + WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!", + error); + else + ainput->state = AINPUT_VERSION_SENT; + } + else + error = CHANNEL_RC_OK; + } + WTSFreeMemory(buffer); + } + break; + case AINPUT_VERSION_SENT: + error = ainput_process_message(ainput); + break; + + default: + WLog_ERR(TAG, "AINPUT chanel is in invalid state %d", ainput->state); + break; + } + + return error; +} + +UINT ainput_server_context_poll(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + if (!ainput->externalThread) + { + WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME); + return ERROR_INTERNAL_ERROR; + } + return ainput_server_context_poll_int(context); +} 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..53d4e9b --- /dev/null +++ b/channels/audin/client/alsa/audin_alsa.c @@ -0,0 +1,459 @@ +/** + * 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; + SSIZE_T s; + 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); + if (channels > UINT16_MAX) + return FALSE; + s = snd_pcm_format_size(format, 1); + if ((s < 0) || (s > UINT16_MAX)) + return FALSE; + alsa->aformat.nChannels = (UINT16)channels; + alsa->bytes_per_frame = (size_t)s * 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; + + 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; +} + +/** + * 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; + 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 } }; + 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..81a6e8f --- /dev/null +++ b/channels/audin/client/audin_main.c @@ -0,0 +1,1099 @@ +/** + * 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 + +#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; + + IWTSListener* listener; + + BOOL initialized; +}; + +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; x < ARRAYSIZE(samplerates); x++) + { + format.nSamplesPerSec = samplerates[x]; + test = IFCALLRESULT(FALSE, audin->device->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 (!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) +{ + UINT rc; + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!pChannelMgr) + return ERROR_INVALID_PARAMETER; + + if (audin->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + + 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; + rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0, + &audin->listener_callback->iface, &audin->listener); + + audin->initialized = rc == CHANNEL_RC_OK; + return rc; +} + +/** + * 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, "..."); + + if (audin->listener_callback) + { + IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, audin->listener); + } + audio_formats_free(audin->fixed_format, 1); + + 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; +} + +BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + UINT error; + 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 } + }; + + 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, + "Unable to load microphone redirection subsystem %s because of 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) + { + /* If we have no audin device do not register plugin but still return OK or the client will + * just disconnect due to a missing microphone. */ + WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found."); + error = CHANNEL_RC_OK; + goto out; + } + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "audin", (IWTSPlugin*)audin); + if (error == CHANNEL_RC_OK) + return error; + +out: + 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..760d3c8 --- /dev/null +++ b/channels/audin/client/audin_main.h @@ -0,0 +1,35 @@ +/** + * 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..f07e9f0 --- /dev/null +++ b/channels/audin/client/mac/CMakeLists.txt @@ -0,0 +1,35 @@ +# 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(AVFOUNDATION AVFoundation) +FIND_LIBRARY(AUDIO_TOOL AudioToolbox) +FIND_LIBRARY(APP_SERVICES ApplicationServices) + +set(${MODULE_PREFIX}_SRCS + audin_mac.m) + +include_directories(..) +include_directories(${MAC_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +set(${MODULE_PREFIX}_LIBS freerdp ${AVFOUNDATION} ${CORE_AUDIO} ${AUDIO_TOOL} ${APP_SERVICES} winpr) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/audin/client/mac/audin_mac.m b/channels/audin/client/mac/audin_mac.m new file mode 100644 index 0000000..b81e551 --- /dev/null +++ b/channels/audin/client/mac/audin_mac.m @@ -0,0 +1,466 @@ +/** + * 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 + +#import + +#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 isAuthorized; + 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) +{ + AudinMacDevice *mac = (AudinMacDevice *)device; + AudioFormatID req_fmt = 0; + + if (!mac->isAuthorized) + return FALSE; + + 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 (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + 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.mChannelsPerFrame = mac->format.nChannels; + mac->audioFormat.mFramesPerPacket = 1; + + mac->audioFormat.mBytesPerFrame = + mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8); + mac->audioFormat.mBytesPerPacket = + mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket; + + mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format); + mac->audioFormat.mFormatID = audin_mac_get_format(format); + 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 (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + 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; + + if (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + 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 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; + 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 } }; + + 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; + } + +#if defined(MAC_OS_X_VERSION_10_14) + if (@available(macOS 10.14, *)) + { + @autoreleasepool { + AVAuthorizationStatus status = + [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + switch (status) + { + case AVAuthorizationStatusAuthorized: + mac->isAuthorized = TRUE; + break; + case AVAuthorizationStatusNotDetermined: + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio + completionHandler:^(BOOL granted) { + if (granted == YES) + { + mac->isAuthorized = TRUE; + } + else + WLog_WARN(TAG, "Microphone access denied by user"); + }]; + break; + case AVAuthorizationStatusRestricted: + WLog_WARN(TAG, "Microphone access restricted by policy"); + break; + case AVAuthorizationStatusDenied: + WLog_WARN(TAG, "Microphone access denied by policy"); + break; + default: + break; + } + } + } +#endif + + 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..a3b7c3c --- /dev/null +++ b/channels/audin/client/opensles/audin_opensl_es.c @@ -0,0 +1,342 @@ +/** + * 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); + + 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); + + 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(device); + 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; +} + +/** + * 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; + const COMMAND_LINE_ARGUMENT_A* arg; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + 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 } + }; + + 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..be3e0b4 --- /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..e99522c --- /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..305686c --- /dev/null +++ b/channels/audin/client/oss/audin_oss.c @@ -0,0 +1,488 @@ +/** + * 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 UINT32 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; + + 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; + unsigned long 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) + { + SSIZE_T stmp; + 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; + + stmp = read(pcm_handle, buffer, buffer_size); + + /* Error happen. */ + if (stmp < 0) + { + OSS_LOG_ERR("read() error", errno); + continue; + } + + if ((size_t)stmp < 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 && 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; + UINT 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; +} + +/** + * 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; + 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 } }; + + 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 = (INT32)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..fb8fdb7 --- /dev/null +++ b/channels/audin/client/pulse/audin_pulse.c @@ -0,0 +1,572 @@ +/** + * 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 const char* pulse_context_state_string(pa_context_state_t state) +{ + switch (state) + { + case PA_CONTEXT_UNCONNECTED: + return "PA_CONTEXT_UNCONNECTED"; + case PA_CONTEXT_CONNECTING: + return "PA_CONTEXT_CONNECTING"; + case PA_CONTEXT_AUTHORIZING: + return "PA_CONTEXT_AUTHORIZING"; + case PA_CONTEXT_SETTING_NAME: + return "PA_CONTEXT_SETTING_NAME"; + case PA_CONTEXT_READY: + return "PA_CONTEXT_READY"; + case PA_CONTEXT_FAILED: + return "PA_CONTEXT_FAILED"; + case PA_CONTEXT_TERMINATED: + return "PA_CONTEXT_TERMINATED"; + default: + return "UNKNOWN"; + } +} + +static const char* pulse_stream_state_string(pa_stream_state_t state) +{ + switch (state) + { + case PA_STREAM_UNCONNECTED: + return "PA_STREAM_UNCONNECTED"; + case PA_STREAM_CREATING: + return "PA_STREAM_CREATING"; + case PA_STREAM_READY: + return "PA_STREAM_READY"; + case PA_STREAM_FAILED: + return "PA_STREAM_FAILED"; + case PA_STREAM_TERMINATED: + return "PA_STREAM_TERMINATED"; + default: + return "UNKNOWN"; + } +} + +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); + + WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state)); + 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; + } +} + +/** + * 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 (%s: %d)", + pulse_context_state_string(state), 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; + + 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); + + WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state)); + 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 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 (%s: %d)", + pulse_stream_state_string(state), 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; +} + +/** + * 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; + 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 } }; + + 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(&pulse->iface))) + { + WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed"); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface))) + { + WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + audin_pulse_free(&pulse->iface); + 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..7538672 --- /dev/null +++ b/channels/audin/client/winmm/audin_winmm.c @@ -0,0 +1,568 @@ +/** + * 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 BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result) +{ + if (result != MMSYSERR_NOERROR) + { + CHAR buffer[8192] = { 0 }; + CHAR msg[8192] = { 0 }; + CHAR cmsg[8192] = { 0 }; + waveInGetErrorTextA(result, buffer, sizeof(buffer)); + + _snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer); + _snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg); + WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg); + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg); + return FALSE; + } + return TRUE; +} + +static BOOL test_format_supported(const PWAVEFORMATEX pwfx) +{ + MMRESULT rc; + WAVEINCAPSA caps = { 0 }; + + rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps)); + if (rc != MMSYSERR_NOERROR) + return FALSE; + + switch (pwfx->nChannels) + { + case 1: + if ((caps.dwFormats & + (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 | + WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0) + return FALSE; + break; + case 2: + if ((caps.dwFormats & + (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 | + WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0) + return FALSE; + break; + default: + return FALSE; + } + + rc = waveInOpen(NULL, WAVE_MAPPER, pwfx, 0, 0, + WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + return (rc == MMSYSERR_NOERROR); +} + +static DWORD WINAPI audin_winmm_thread_func(LPVOID arg) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg; + char* buffer; + int size, i; + WAVEHDR waveHdr[4] = { 0 }; + DWORD status; + MMRESULT rc; + + if (!winmm->hWaveIn) + { + MMRESULT rc; + rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc, + (DWORD_PTR)winmm, + CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + if (!log_mmresult(winmm, "waveInOpen", rc)) + 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 (!log_mmresult(winmm, "waveInPrepareHeader", rc)) + { + + } + + rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (!log_mmresult(winmm, "waveInAddBuffer", rc)) + { + } + } + + rc = waveInStart(winmm->hWaveIn); + + if (!log_mmresult(winmm, "waveInStart", rc)) + { + } + + 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 (!log_mmresult(winmm, "waveInReset", rc)) + { + } + + for (i = 0; i < 4; i++) + { + rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (!log_mmresult(winmm, "waveInUnprepareHeader", rc)) + { + + } + + free(waveHdr[i].lpData); + } + + rc = waveInClose(winmm->hWaveIn); + + if (!log_mmresult(winmm, "waveInClose", rc)) + { + } + + 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++) + { + const PWAVEFORMATEX ppwfx = winmm->ppwfx[i]; + if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) && + (ppwfx->wBitsPerSample == format->wBitsPerSample) && + (ppwfx->nSamplesPerSec == format->nSamplesPerSec)) + { + /* BUG: Many devices report to support stereo recording but fail here. + * Ensure we always use mono. */ + if (ppwfx->nChannels > 1) + { + ppwfx->nChannels = 1; + } + + if (ppwfx->nBlockAlign != 2) + { + ppwfx->nBlockAlign = 2; + ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign; + } + + if (!test_format_supported(ppwfx)) + return ERROR_INVALID_PARAMETER; + winmm->pwfx_cur = ppwfx; + return CHANNEL_RC_OK; + } + } + + return ERROR_INVALID_PARAMETER; +} + +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; + + if (format->wFormatTag != WAVE_FORMAT_PCM) + 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); + + pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; + + if (!test_format_supported(pwfx)) + goto fail; + + if (winmm->cFormats >= winmm->ppwfx_size) + { + PWAVEFORMATEX* tmp_ppwfx; + tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2); + + if (!tmp_ppwfx) + goto fail; + + winmm->ppwfx_size *= 2; + winmm->ppwfx = tmp_ppwfx; + } + + winmm->ppwfx[winmm->cFormats++] = pwfx; + return TRUE; + +fail: + 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; +} + +/** + * 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; + 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 } }; + + 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; + + if (waveInGetNumDevs() == 0) + { + WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!"); + return ERROR_DEVICE_NOT_AVAILABLE; + } + + 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 = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX)); + + 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, &winmm->iface))) + { + 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..8252236 --- /dev/null +++ b/channels/audin/server/audin.c @@ -0,0 +1,692 @@ +/** + * 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 +#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]; + + 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; + UINT32 channelId; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned)) + { + audin->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + } + + audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!audin->audin_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + return FALSE; + } + + channelId = WTSChannelGetIdByHandle(audin->audin_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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..eb0c80f --- /dev/null +++ b/channels/client/CMakeLists.txt @@ -0,0 +1,117 @@ +# 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}) + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + if(${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}_${ENTRY}") + if(${ENTRY} STREQUAL "VirtualChannelEntry") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS);") + elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS,PVOID);") + elseif(${ENTRY} MATCHES "DVCPluginEntry$") + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(IDRDYNVC_ENTRY_POINTS* pEntryPoints);") + elseif(${ENTRY} MATCHES "DeviceServiceEntry$") + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);") + 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() +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 UINT ${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}") + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + set (ENTRY_POINT_NAME ${STATIC_MODULE_CHANNEL}_${ENTRY}) + set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", \"${ENTRY}\", (static_addin_fkt)${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },") + endforeach() +endforeach() +set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, 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..db32ff8 --- /dev/null +++ b/channels/client/addin.c @@ -0,0 +1,470 @@ +/** + * 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[]; + +static void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table, + const char* identifier) +{ + size_t 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) +{ + size_t 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[]; + +static FREERDP_ADDIN** freerdp_channels_list_client_static_addins(LPCSTR pszName, + LPCSTR pszSubsystem, + LPCSTR 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; +} + +static FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR 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 = strnlen(pszAddinPath, sizeof(FREERDP_ADDIN_PATH)); + cchInstallPrefix = strnlen(pszInstallPrefix, sizeof(FREERDP_INSTALL_PREFIX)); + pszExtension = PathGetSharedLibraryExtensionA(0); + cchPattern = 128 + strnlen(pszExtension, MAX_PATH) + 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 = strnlen(pszPattern, cchPattern); + 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 + { + BOOL used = FALSE; + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + nDashes = 0; + for (index = 0; FindData.cFileName[index]; index++) + nDashes += (FindData.cFileName[index] == '-') ? 1 : 0; + + if (nDashes == 1) + { + size_t len; + char* p[2] = { 0 }; + /* -client. */ + p[0] = FindData.cFileName; + p[1] = strchr(p[0], '-') + 1; + + len = p[1] - p[0]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 2) + { + size_t len; + char* p[4] = { 0 }; + /* -client-. */ + p[0] = FindData.cFileName; + p[1] = strchr(p[0], '-') + 1; + p[2] = strchr(p[1], '-') + 1; + p[3] = strchr(p[2], '.') + 1; + + len = p[1] - p[0]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = p[3] - p[2]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 3) + { + size_t len; + char* p[5] = { 0 }; + /* -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; + + len = p[1] - p[0]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = p[3] - p[2]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + len = p[4] - p[3]; + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", FindData.cFileName); + goto skip; + } + strncpy(pAddin->cType, p[3], MIN(ARRAYSIZE(pAddin->cType), len - 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; + + used = TRUE; + } + + skip: + if (!used) + 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(LPCSTR pszName, LPCSTR pszSubsystem, LPCSTR 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) +{ + size_t index; + + if (!ppAddins) + return; + + for (index = 0; ppAddins[index] != NULL; index++) + free(ppAddins[index]); + + free(ppAddins); +} + +extern const STATIC_ENTRY CLIENT_VirtualChannelEntryEx_TABLE[]; + +static BOOL freerdp_channels_is_virtual_channel_entry_ex(LPCSTR pszName) +{ + size_t i; + + for (i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != NULL; i++) + { + const STATIC_ENTRY* entry = &CLIENT_VirtualChannelEntryEx_TABLE[i]; + + if (!strncmp(entry->name, pszName, MAX_PATH)) + return TRUE; + } + + return FALSE; +} + +PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR pszType, DWORD dwFlags) +{ + const STATIC_ADDIN_TABLE* table = CLIENT_STATIC_ADDIN_TABLE; + const char* type = NULL; + + if (!pszName) + return NULL; + + if (dwFlags & FREERDP_ADDIN_CHANNEL_DYNAMIC) + type = "DVCPluginEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_DEVICE) + type = "DeviceServiceEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_STATIC) + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + type = "VirtualChannelEntryEx"; + else + type = "VirtualChannelEntry"; + } + + for (; table->name != NULL; table++) + { + if (strncmp(table->name, pszName, MAX_PATH) == 0) + { + if (type && strncmp(table->type, type, MAX_PATH)) + continue; + + if (pszSubsystem != NULL) + { + const STATIC_SUBSYSTEM_ENTRY* subsystems = table->table; + + for (; subsystems->name != NULL; subsystems++) + { + /* If the pszSubsystem is an empty string use the default backend. */ + if ((strnlen(pszSubsystem, 1) == + 0) || /* we only want to know if strnlen is > 0 */ + (strncmp(subsystems->name, pszSubsystem, MAX_PATH) == 0)) + { + if (pszType) + { + if (strncmp(subsystems->type, pszType, MAX_PATH) == 0) + return (PVIRTUALCHANNELENTRY)subsystems->entry; + } + else + { + return (PVIRTUALCHANNELENTRY)subsystems->entry; + } + } + } + } + else + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + { + if (!freerdp_channels_is_virtual_channel_entry_ex(pszName)) + return NULL; + } + + return (PVIRTUALCHANNELENTRY)table->entry; + } + } + } + + return NULL; +} diff --git a/channels/client/addin.h b/channels/client/addin.h new file mode 100644 index 0000000..849b11f --- /dev/null +++ b/channels/client/addin.h @@ -0,0 +1,18 @@ +/** + * 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..aafc71d --- /dev/null +++ b/channels/client/tables.c.in @@ -0,0 +1,32 @@ +/** + * 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 +#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..b6b3f9c --- /dev/null +++ b/channels/client/tables.h @@ -0,0 +1,51 @@ +/** + * 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; + UINT (*entry)(); +}; +typedef struct _STATIC_SUBSYSTEM_ENTRY STATIC_SUBSYSTEM_ENTRY; + +struct _STATIC_ADDIN_TABLE +{ + const char* name; + const char* type; + 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..4511e08 --- /dev/null +++ b/channels/cliprdr/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("cliprdr") + +set(${MODULE_PREFIX}_SRCS + cliprdr_format.c + cliprdr_format.h + cliprdr_main.c + cliprdr_main.h + ../cliprdr_common.h + ../cliprdr_common.c + ) + +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..0b6111b --- /dev/null +++ b/channels/cliprdr/client/cliprdr_format.c @@ -0,0 +1,175 @@ +/** + * 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" +#include "../cliprdr_common.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) +{ + CLIPRDR_FORMAT_LIST formatList = { 0 }; + 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; + } + + formatList.msgType = CB_FORMAT_LIST; + formatList.msgFlags = msgFlags; + formatList.dataLen = dataLen; + + if ((error = cliprdr_read_format_list(s, &formatList, cliprdr->useLongFormatNames))) + goto error_out; + + 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: + cliprdr_free_format_list(&formatList); + 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 = { 0 }; + 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; + + if ((error = cliprdr_read_format_data_request(s, &formatDataRequest))) + return error; + + context->lastRequestedFormatId = formatDataRequest.requestedFormatId; + 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; + + if ((error = cliprdr_read_format_data_response(s, &formatDataResponse))) + return error; + + IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse); + if (error) + WLog_ERR(TAG, "ServerFormatDataResponse failed with error %" PRIu32 "!", error); + + return error; +} diff --git a/channels/cliprdr/client/cliprdr_format.h b/channels/cliprdr/client/cliprdr_format.h new file mode 100644 index 0000000..a068d6f --- /dev/null +++ b/channels/cliprdr/client/cliprdr_format.h @@ -0,0 +1,35 @@ +/** + * 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..5aed224 --- /dev/null +++ b/channels/cliprdr/client/cliprdr_main.c @@ -0,0 +1,1222 @@ +/** + * 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" +#include "../cliprdr_common.h" + +#ifdef WITH_DEBUG_CLIPRDR +static const char* CB_MSG_TYPE_STRINGS(UINT32 type) +{ + switch (type) + { + case CB_MONITOR_READY: + return "CB_MONITOR_READY"; + case CB_FORMAT_LIST: + return "CB_FORMAT_LIST"; + case CB_FORMAT_LIST_RESPONSE: + return "CB_FORMAT_LIST_RESPONSE"; + case CB_FORMAT_DATA_REQUEST: + return "CB_FORMAT_DATA_REQUEST"; + case CB_FORMAT_DATA_RESPONSE: + return "CB_FORMAT_DATA_RESPONSE"; + case CB_TEMP_DIRECTORY: + return "CB_TEMP_DIRECTORY"; + case CB_CLIP_CAPS: + return "CB_CLIP_CAPS"; + case CB_FILECONTENTS_REQUEST: + return "CB_FILECONTENTS_REQUEST"; + case CB_FILECONTENTS_RESPONSE: + return "CB_FILECONTENTS_RESPONSE"; + case CB_LOCK_CLIPDATA: + return "CB_LOCK_CLIPDATA"; + case CB_UNLOCK_CLIPDATA: + return "CB_UNLOCK_CLIPDATA"; + default: + return "UNKNOWN"; + } +} +#endif + +CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr) +{ + CliprdrClientContext* pInterface; + + if (!cliprdr) + return NULL; + + pInterface = (CliprdrClientContext*)cliprdr->channelEntryPoints.pInterface; + return pInterface; +} + +/** + * 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) + { + Stream_Free(s, TRUE); + 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"); + + if (flags & CB_HUGE_FILE_SUPPORT_ENABLED) + WLog_INFO(TAG, "\tCB_HUGE_FILE_SUPPORT_ENABLED"); + + 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 + + cliprdr->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES); + cliprdr->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED); + cliprdr->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS); + cliprdr->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA); + cliprdr->hasHugeFileSupport = (generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED); + cliprdr->capabilitiesReceived = TRUE; + + if (!context->custom) + { + WLog_ERR(TAG, "context->custom not set!"); + return ERROR_INTERNAL_ERROR; + } + + capabilities.msgType = CB_CLIP_CAPS; + 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, UINT32 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 - 4U))) + 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; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr, wStream* s, UINT32 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; + } + + request.msgType = CB_FILECONTENTS_REQUEST; + request.msgFlags = flags; + request.dataLen = length; + + if ((error = cliprdr_read_file_contents_request(s, &request))) + return error; + + 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; + } + + response.msgType = CB_FILECONTENTS_RESPONSE; + response.msgFlags = flags; + response.dataLen = length; + + if ((error = cliprdr_read_file_contents_response(s, &response))) + return error; + + 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 ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData))) + return error; + + unlockClipboardData.msgType = CB_UNLOCK_CLIPDATA; + unlockClipboardData.msgFlags = flags; + unlockClipboardData.dataLen = length; + + 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, + const CLIPRDR_CAPABILITIES* capabilities) +{ + wStream* s; + UINT32 flags; + const 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 = (const 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 */ + flags = generalCapabilitySet->generalFlags; + + /* Client capabilities are sent in response to server capabilities. + * -> Do not request features the server does not support. + * -> Update clipboard context feature state to what was agreed upon. + */ + if (!cliprdr->useLongFormatNames) + flags &= ~CB_USE_LONG_FORMAT_NAMES; + if (!cliprdr->streamFileClipEnabled) + flags &= ~CB_STREAM_FILECLIP_ENABLED; + if (!cliprdr->fileClipNoFilePaths) + flags &= ~CB_FILECLIP_NO_FILE_PATHS; + if (!cliprdr->canLockClipData) + flags &= ~CB_CAN_LOCK_CLIPDATA; + if (!cliprdr->hasHugeFileSupport) + flags &= ~CB_HUGE_FILE_SUPPORT_ENABLED; + + cliprdr->useLongFormatNames = flags & CB_USE_LONG_FORMAT_NAMES; + cliprdr->streamFileClipEnabled = flags & CB_STREAM_FILECLIP_ENABLED; + cliprdr->fileClipNoFilePaths = flags & CB_FILECLIP_NO_FILE_PATHS; + cliprdr->canLockClipData = flags & CB_CAN_LOCK_CLIPDATA; + cliprdr->hasHugeFileSupport = flags & CB_HUGE_FILE_SUPPORT_ENABLED; + + Stream_Write_UINT32(s, flags); /* 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, + const CLIPRDR_TEMP_DIRECTORY* tempDirectory) +{ + int length; + wStream* s; + WCHAR* wszTempDir = NULL; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + s = cliprdr_packet_new(CB_TEMP_DIRECTORY, 0, 260 * sizeof(WCHAR)); + + 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; + + /* Path must be 260 UTF16 characters with '\0' termination. + * ensure this here */ + if (length >= 260) + length = 259; + + Stream_Write_UTF16_String(s, wszTempDir, length); + Stream_Zero(s, 520 - (length * sizeof(WCHAR))); + 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, + const CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + + s = cliprdr_packet_format_list_new(formatList, cliprdr->useLongFormatNames); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->msgFlags, 0); + + 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, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + s = cliprdr_packet_lock_clipdata_new(lockClipboardData); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + + s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4); + + 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, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + + s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, 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, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + + if (!cliprdr) + return ERROR_INTERNAL_ERROR; + + if (!cliprdr->hasHugeFileSupport) + { + if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) > + UINT32_MAX) + return ERROR_INVALID_PARAMETER; + if (fileContentsRequest->nPositionHigh != 0) + return ERROR_INVALID_PARAMETER; + } + + s = cliprdr_packet_file_contents_request_new(fileContentsRequest); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + s = cliprdr_packet_file_contents_response_new(fileContentsResponse); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!cliprdr || (cliprdr->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && cliprdr && 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; +} + +static void cliprdr_free_msg(void* obj) +{ + wMessage* msg = (wMessage*)obj; + + if (msg) + { + wStream* s = (wStream*)msg->wParam; + Stream_Free(s, TRUE); + } +} + +/** + * 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; + wObject obj = { 0 }; + 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; + } + + obj.fnObjectFree = cliprdr_free_msg; + cliprdr->queue = MessageQueue_New(&obj); + + 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"); + 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..b6cd7db --- /dev/null +++ b/channels/cliprdr/client/cliprdr_main.h @@ -0,0 +1,67 @@ +/** + * 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; + BOOL hasHugeFileSupport; +}; +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/cliprdr_common.c b/channels/cliprdr/cliprdr_common.c new file mode 100644 index 0000000..69157ad --- /dev/null +++ b/channels/cliprdr/cliprdr_common.c @@ -0,0 +1,588 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Cliprdr common + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#define TAG CHANNELS_TAG("cliprdr.common") + +#include "cliprdr_common.h" + +static BOOL cliprdr_validate_file_contents_request(const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + /* + * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST). + * + * A request for the size of the file identified by the lindex field. The size MUST be + * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to + * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be + * set to 0x00000000. + */ + + if (request->dwFlags & FILECONTENTS_SIZE) + { + if (request->cbRequested != sizeof(UINT64)) + { + WLog_ERR(TAG, "[%s]: cbRequested must be %" PRIu32 ", got %" PRIu32 "", __FUNCTION__, + sizeof(UINT64), request->cbRequested); + return FALSE; + } + + if (request->nPositionHigh != 0 || request->nPositionLow != 0) + { + WLog_ERR(TAG, "[%s]: nPositionHigh and nPositionLow must be set to 0", __FUNCTION__); + return FALSE; + } + } + + return TRUE; +} + +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; +} + +static void cliprdr_write_file_contents_request(wStream* s, + const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + Stream_Write_UINT32(s, request->streamId); /* streamId (4 bytes) */ + Stream_Write_UINT32(s, request->listIndex); /* listIndex (4 bytes) */ + Stream_Write_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */ + Stream_Write_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Write_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Write_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */ + + if (request->haveClipDataId) + Stream_Write_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */ +} + +static INLINE void cliprdr_write_lock_unlock_clipdata(wStream* s, UINT32 clipDataId) +{ + Stream_Write_UINT32(s, clipDataId); +} + +static void cliprdr_write_lock_clipdata(wStream* s, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + cliprdr_write_lock_unlock_clipdata(s, lockClipboardData->clipDataId); +} + +static void cliprdr_write_unlock_clipdata(wStream* s, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + cliprdr_write_lock_unlock_clipdata(s, unlockClipboardData->clipDataId); +} + +static void cliprdr_write_file_contents_response(wStream* s, + const CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + Stream_Write_UINT32(s, response->streamId); /* streamId (4 bytes) */ + Stream_Write(s, response->requestedData, response->cbRequested); +} + +wStream* cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s; + + if (!lockClipboardData) + return NULL; + + s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4); + + if (!s) + return NULL; + + cliprdr_write_lock_clipdata(s, lockClipboardData); + return s; +} + +wStream* +cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s; + + if (!unlockClipboardData) + return NULL; + + s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4); + + if (!s) + return NULL; + + cliprdr_write_unlock_clipdata(s, unlockClipboardData); + return s; +} + +wStream* cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + wStream* s; + + if (!request) + return NULL; + + s = cliprdr_packet_new(CB_FILECONTENTS_REQUEST, 0, 28); + + if (!s) + return NULL; + + cliprdr_write_file_contents_request(s, request); + return s; +} + +wStream* cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + wStream* s; + + if (!response) + return NULL; + + s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, response->msgFlags, 4 + response->cbRequested); + + if (!s) + return NULL; + + cliprdr_write_file_contents_response(s, response); + return s; +} + +wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames) +{ + wStream* s; + UINT32 index; + int cchWideChar; + LPWSTR lpWideCharStr; + int formatNameSize; + char* szFormatName; + WCHAR* wszFormatName; + BOOL asciiNames = FALSE; + CLIPRDR_FORMAT* format; + UINT32 length; + + if (formatList->msgType != CB_FORMAT_LIST) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + formatList->msgType); + + if (!useLongFormatNames) + { + length = formatList->numFormats * 36; + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return NULL; + } + + for (index = 0; index < formatList->numFormats; index++) + { + size_t formatNameLength = 0; + format = (CLIPRDR_FORMAT*)&(formatList->formats[index]); + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + formatNameSize = 0; + + szFormatName = format->formatName; + + if (asciiNames) + { + if (szFormatName) + formatNameLength = strnlen(szFormatName, 32); + + 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 < 0) + { + Stream_Free(s, TRUE); + return NULL; + } + + if (formatNameSize > 15) + formatNameSize = 15; + + /* size in bytes instead of wchar */ + formatNameSize *= 2; + + if (wszFormatName) + Stream_Write(s, wszFormatName, (size_t)formatNameSize); + + Stream_Zero(s, (size_t)(32 - formatNameSize)); + free(wszFormatName); + } + } + } + else + { + length = 0; + 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; + + if (formatNameSize < 0) + return NULL; + + length += (UINT32)formatNameSize; + } + + s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return NULL; + } + + 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) + { + const size_t cap = Stream_Capacity(s); + const size_t pos = Stream_GetPosition(s); + const size_t rem = cap - pos; + if ((cap < pos) || ((rem / 2) > INT_MAX)) + { + Stream_Free(s, TRUE); + return NULL; + } + + lpWideCharStr = (LPWSTR)Stream_Pointer(s); + cchWideChar = (int)(rem / 2); + formatNameSize = MultiByteToWideChar(CP_UTF8, 0, format->formatName, -1, + lpWideCharStr, cchWideChar) * + 2; + if (formatNameSize < 0) + { + Stream_Free(s, TRUE); + return NULL; + } + Stream_Seek(s, (size_t)formatNameSize); + } + else + { + Stream_Write_UINT16(s, 0); + } + } + } + + return s; +} +UINT cliprdr_read_unlock_clipdata(wStream* s, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_data_request(wStream* s, CLIPRDR_FORMAT_DATA_REQUEST* request) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, request->requestedFormatId); /* requestedFormatId (4 bytes) */ + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_data_response(wStream* s, CLIPRDR_FORMAT_DATA_RESPONSE* response) +{ + response->requestedFormatData = NULL; + + if (Stream_GetRemainingLength(s) < response->dataLen) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + if (response->dataLen) + response->requestedFormatData = Stream_Pointer(s); + + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + if (Stream_GetRemainingLength(s) < 24) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + 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; + } + + if (!cliprdr_validate_file_contents_request(request)) + return ERROR_BAD_ARGUMENTS; + + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_file_contents_response(wStream* s, CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, response->streamId); /* streamId (4 bytes) */ + response->requestedData = Stream_Pointer(s); /* requestedFileContentsData */ + response->cbRequested = response->dataLen - 4; + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList, BOOL useLongFormatNames) +{ + UINT32 index; + BOOL asciiNames; + int formatNameLength; + char* szFormatName; + WCHAR* wszFormatName; + wStream sub1, sub2; + CLIPRDR_FORMAT* formats = NULL; + UINT error = ERROR_INTERNAL_ERROR; + + asciiNames = (formatList->msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE; + + index = 0; + /* empty format list */ + formatList->formats = NULL; + formatList->numFormats = 0; + + Stream_StaticInit(&sub1, Stream_Pointer(s), formatList->dataLen); + if (!Stream_SafeSeek(s, formatList->dataLen)) + return ERROR_INVALID_DATA; + + if (!formatList->dataLen) + { + } + else if (!useLongFormatNames) + { + const size_t cap = Stream_Capacity(&sub1); + formatList->numFormats = (cap / 36); + + if ((formatList->numFormats * 36) != cap) + { + WLog_ERR(TAG, "Invalid short format list length: %" PRIuz "", cap); + 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 (Stream_GetRemainingLength(&sub1) >= 4) + { + Stream_Read_UINT32(&sub1, formats[index].formatId); /* formatId (4 bytes) */ + + 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 ! + */ + + szFormatName = (char*)Stream_Pointer(&sub1); + wszFormatName = (WCHAR*)Stream_Pointer(&sub1); + if (!Stream_SafeSeek(&sub1, 32)) + goto error_out; + if (asciiNames) + { + 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 + { + 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; + } + } + } + + index++; + } + } + else + { + sub2 = sub1; + while (Stream_GetRemainingLength(&sub1) > 0) + { + size_t rest; + if (!Stream_SafeSeek(&sub1, 4)) /* formatId (4 bytes) */ + goto error_out; + + wszFormatName = (WCHAR*)Stream_Pointer(&sub1); + rest = Stream_GetRemainingLength(&sub1); + formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR)); + + if (!Stream_SafeSeek(&sub1, (formatNameLength + 1) * sizeof(WCHAR))) + goto error_out; + formatList->numFormats++; + } + + 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 (Stream_GetRemainingLength(&sub2) >= 4) + { + size_t rest; + Stream_Read_UINT32(&sub2, formats[index].formatId); /* formatId (4 bytes) */ + + formats[index].formatName = NULL; + + wszFormatName = (WCHAR*)Stream_Pointer(&sub2); + rest = Stream_GetRemainingLength(&sub2); + formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR)); + if (!Stream_SafeSeek(&sub2, (formatNameLength + 1) * sizeof(WCHAR))) + goto error_out; + + if (formatNameLength) + { + if (ConvertFromUnicode(CP_UTF8, 0, wszFormatName, formatNameLength, + &(formats[index].formatName), 0, NULL, NULL) < 1) + { + WLog_ERR(TAG, "failed to convert long clipboard format name"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + index++; + } + } + + return CHANNEL_RC_OK; + +error_out: + cliprdr_free_format_list(formatList); + return error; +} + +void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList) +{ + UINT index = 0; + + if (formatList == NULL) + return; + + if (formatList->formats) + { + for (index = 0; index < formatList->numFormats; index++) + { + free(formatList->formats[index].formatName); + } + + free(formatList->formats); + formatList->formats = NULL; + formatList->numFormats = 0; + } +} diff --git a/channels/cliprdr/cliprdr_common.h b/channels/cliprdr/cliprdr_common.h new file mode 100644 index 0000000..b5d36b9 --- /dev/null +++ b/channels/cliprdr/cliprdr_common.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Cliprdr common + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_RDPECLIP_COMMON_H +#define FREERDP_CHANNEL_RDPECLIP_COMMON_H + +#include +#include + +#include +#include + +FREERDP_LOCAL wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen); +FREERDP_LOCAL wStream* +cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData); +FREERDP_LOCAL wStream* +cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData); +FREERDP_LOCAL wStream* +cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request); +FREERDP_LOCAL wStream* +cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response); +FREERDP_LOCAL wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames); + +FREERDP_LOCAL UINT cliprdr_read_lock_clipdata(wStream* s, + CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData); +FREERDP_LOCAL UINT cliprdr_read_unlock_clipdata(wStream* s, + CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData); +FREERDP_LOCAL UINT cliprdr_read_format_data_request(wStream* s, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest); +FREERDP_LOCAL UINT cliprdr_read_format_data_response(wStream* s, + CLIPRDR_FORMAT_DATA_RESPONSE* response); +FREERDP_LOCAL UINT +cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest); +FREERDP_LOCAL UINT cliprdr_read_file_contents_response(wStream* s, + CLIPRDR_FILE_CONTENTS_RESPONSE* response); +FREERDP_LOCAL UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames); + +FREERDP_LOCAL void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList); + +#endif /* FREERDP_CHANNEL_RDPECLIP_COMMON_H */ diff --git a/channels/cliprdr/server/CMakeLists.txt b/channels/cliprdr/server/CMakeLists.txt new file mode 100644 index 0000000..32ffbaa --- /dev/null +++ b/channels/cliprdr/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("cliprdr") + +set(${MODULE_PREFIX}_SRCS + cliprdr_main.c + cliprdr_main.h + ../cliprdr_common.h + ../cliprdr_common.c + ) + +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..7e0b681 --- /dev/null +++ b/channels/cliprdr/server/cliprdr_main.c @@ -0,0 +1,1420 @@ +/** + * 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" +#include "../cliprdr_common.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 + * + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_packet_send(CliprdrServerPrivate* cliprdr, wStream* s) +{ + UINT rc; + size_t pos, size; + BOOL status; + UINT32 dataLen; + UINT32 written; + pos = Stream_GetPosition(s); + if ((pos < 8) || (pos > UINT32_MAX)) + { + rc = ERROR_NO_DATA; + goto fail; + } + + dataLen = (UINT32)(pos - 8); + Stream_SetPosition(s, 4); + Stream_Write_UINT32(s, dataLen); + Stream_SetPosition(s, pos); + size = Stream_Length(s); + if (size > UINT32_MAX) + { + rc = ERROR_INVALID_DATA; + goto fail; + } + + status = WTSVirtualChannelWrite(cliprdr->ChannelHandle, (PCHAR)Stream_Buffer(s), (UINT32)size, + &written); + rc = status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +fail: + Stream_Free(s, TRUE); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_capabilities(CliprdrServerContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + size_t offset = 0; + UINT32 x; + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (capabilities->msgType != CB_CLIP_CAPS) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + capabilities->msgType); + + if (capabilities->cCapabilitiesSets > UINT16_MAX) + { + WLog_ERR(TAG, "Invalid number of capability sets in clipboard caps"); + return ERROR_INVALID_PARAMETER; + } + + 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, + (UINT16)capabilities->cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Write_UINT16(s, 0); /* pad1 (2 bytes) */ + for (x = 0; x < capabilities->cCapabilitiesSets; x++) + { + const CLIPRDR_CAPABILITY_SET* cap = + (const CLIPRDR_CAPABILITY_SET*)(((const BYTE*)capabilities->capabilitySets) + offset); + offset += cap->capabilitySetLength; + + switch (cap->capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + { + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = + (const CLIPRDR_GENERAL_CAPABILITY_SET*)cap; + 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) */ + } + break; + + default: + WLog_WARN(TAG, "Unknown capability set type %08" PRIx16, cap->capabilitySetType); + if (!Stream_SafeSeek(s, cap->capabilitySetLength)) + { + WLog_ERR(TAG, "%s: short stream", __FUNCTION__); + return ERROR_NO_DATA; + } + break; + } + } + 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, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (monitorReady->msgType != CB_MONITOR_READY) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + monitorReady->msgType); + + s = cliprdr_packet_new(CB_MONITOR_READY, monitorReady->msgFlags, monitorReady->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_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, + const CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + s = cliprdr_packet_format_list_new(formatList, context->useLongFormatNames); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + if (formatListResponse->msgType != CB_FORMAT_LIST_RESPONSE) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + formatListResponse->msgType); + + s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->msgFlags, + formatListResponse->dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_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, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + if (lockClipboardData->msgType != CB_LOCK_CLIPDATA) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + lockClipboardData->msgType); + + s = cliprdr_packet_lock_clipdata_new(lockClipboardData); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + if (unlockClipboardData->msgType != CB_UNLOCK_CLIPDATA) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + unlockClipboardData->msgType); + + s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + if (formatDataRequest->msgType != CB_FORMAT_DATA_REQUEST) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + formatDataRequest->msgType); + + s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 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_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, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (formatDataResponse->msgType != CB_FORMAT_DATA_RESPONSE) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + formatDataResponse->msgType); + + s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, 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_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, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (fileContentsRequest->msgType != CB_FILECONTENTS_REQUEST) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + fileContentsRequest->msgType); + + s = cliprdr_packet_file_contents_request_new(fileContentsRequest); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (fileContentsResponse->msgType != CB_FILECONTENTS_RESPONSE) + WLog_WARN(TAG, "[%s] called with invalid type %08" PRIx32, __FUNCTION__, + fileContentsResponse->msgType); + + s = cliprdr_packet_file_contents_response_new(fileContentsResponse); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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, + CLIPRDR_GENERAL_CAPABILITY_SET* cap_set) +{ + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cap_set->version); /* version (4 bytes) */ + Stream_Read_UINT32(s, cap_set->generalFlags); /* generalFlags (4 bytes) */ + + if (context->useLongFormatNames) + context->useLongFormatNames = + (cap_set->generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE; + + if (context->streamFileClipEnabled) + context->streamFileClipEnabled = + (cap_set->generalFlags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE; + + if (context->fileClipNoFilePaths) + context->fileClipNoFilePaths = + (cap_set->generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE; + + if (context->canLockClipData) + context->canLockClipData = (cap_set->generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE; + + if (context->hasHugeFileSupport) + context->hasHugeFileSupport = + (cap_set->generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) ? 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, + const CLIPRDR_HEADER* header) +{ + UINT16 index; + UINT16 capabilitySetType; + UINT16 capabilitySetLength; + UINT error = ERROR_INVALID_DATA; + size_t cap_sets_size = 0; + CLIPRDR_CAPABILITIES capabilities = { 0 }; + CLIPRDR_CAPABILITY_SET* capSet; + + WINPR_UNUSED(header); + + + WLog_DBG(TAG, "CliprdrClientCapabilities"); + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilities.cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Seek_UINT16(s); /* pad1 (2 bytes) */ + + for (index = 0; index < capabilities.cCapabilitiesSets; index++) + { + void* tmp = NULL; + if (Stream_GetRemainingLength(s) < 4) + goto out; + Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */ + + cap_sets_size += capabilitySetLength; + + if (cap_sets_size > 0) + tmp = realloc(capabilities.capabilitySets, cap_sets_size); + if (tmp == NULL) + { + WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!"); + free(capabilities.capabilitySets); + return CHANNEL_RC_NO_MEMORY; + } + + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp; + + capSet = &(capabilities.capabilitySets[index]); + + capSet->capabilitySetType = capabilitySetType; + capSet->capabilitySetLength = capabilitySetLength; + + switch (capSet->capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + error = cliprdr_server_receive_general_capability( + context, s, (CLIPRDR_GENERAL_CAPABILITY_SET*)capSet); + if (error) + { + WLog_ERR(TAG, + "cliprdr_server_receive_general_capability failed with error %" PRIu32 + "", + error); + goto out; + } + break; + + default: + WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", + capSet->capabilitySetType); + goto out; + } + } + + error = CHANNEL_RC_OK; + IFCALLRET(context->ClientCapabilities, error, context, &capabilities); +out: + free(capabilities.capabilitySets); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_temporary_directory(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + size_t length; + WCHAR* wszTempDir; + CLIPRDR_TEMP_DIRECTORY tempDirectory; + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + size_t slength; + UINT error = CHANNEL_RC_OK; + + WINPR_UNUSED(header); + if ((slength = Stream_GetRemainingLength(s)) < 260 * sizeof(WCHAR)) + { + 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[259] != 0) + { + WLog_ERR(TAG, "wszTempDir[259] 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 = strnlen(cliprdr->temporaryDirectory, 260); + + if (length >= 260) + length = 259; + + 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, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_LIST formatList; + UINT error = CHANNEL_RC_OK; + + formatList.msgType = CB_FORMAT_LIST; + formatList.msgFlags = header->msgFlags; + formatList.dataLen = header->dataLen; + + if ((error = cliprdr_read_format_list(s, &formatList, context->useLongFormatNames))) + goto out; + + 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: + cliprdr_free_format_list(&formatList); + 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, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + UINT error = CHANNEL_RC_OK; + + WINPR_UNUSED(s); + 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, + const 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, + const 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 ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData))) + return error; + + 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, + const 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 ((error = cliprdr_read_format_data_request(s, &formatDataRequest))) + return error; + + context->lastRequestedFormatId = formatDataRequest.requestedFormatId; + 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, + const 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; + + if ((error = cliprdr_read_format_data_response(s, &formatDataResponse))) + return error; + + IFCALLRET(context->ClientFormatDataResponse, error, context, &formatDataResponse); + + if (error) + WLog_ERR(TAG, "ClientFormatDataResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_filecontents_request(CliprdrServerContext* context, wStream* s, + const 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 ((error = cliprdr_read_file_contents_request(s, &request))) + return error; + + if (!context->hasHugeFileSupport) + { + if (request.nPositionHigh > 0) + return ERROR_INVALID_DATA; + if ((UINT64)request.nPositionLow + request.cbRequested > UINT32_MAX) + return ERROR_INVALID_DATA; + } + 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, + const 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 ((error = cliprdr_read_file_contents_response(s, &response))) + return error; + + 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, + const 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_ERR(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_GENERAL_CAPABILITY_SET generalCapabilitySet; + UINT error; + CLIPRDR_MONITOR_READY monitorReady = { 0 }; + CLIPRDR_CAPABILITIES capabilities = { 0 }; + + generalFlags = 0; + monitorReady.msgType = CB_MONITOR_READY; + capabilities.msgType = CB_CLIP_CAPS; + + 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; + + if (context->hasHugeFileSupport) + generalFlags |= CB_HUGE_FILE_SUPPORT_ENABLED; + + 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 + */ +static 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 = (UINT32)(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 = + (UINT32)((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 = CHANNEL_RC_OK; + + ChannelEvent = context->GetEventHandle(context); + nCount = 0; + events[nCount++] = cliprdr->StopEvent; + events[nCount++] = ChannelEvent; + + if (context->autoInitializationSequence) + { + 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->autoInitializationSequence = TRUE; + 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..fce0ddb --- /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..44afe99 --- /dev/null +++ b/channels/disp/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("disp") + +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/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..6376f6e --- /dev/null +++ b/channels/disp/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("disp") + +set(${MODULE_PREFIX}_SRCS + disp_main.c + disp_main.h + ../disp_common.c + ../disp_common.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 100644 index 0000000..d718958 --- /dev/null +++ b/channels/disp/client/disp_main.c @@ -0,0 +1,419 @@ +/** + * 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" +#include "../disp_common.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; + BOOL initialized; +}; +typedef struct _DISP_PLUGIN DISP_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callback, + UINT32 NumMonitors, + DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors) +{ + UINT status; + wStream* s; + UINT32 index; + DISP_PLUGIN* disp; + UINT32 MonitorLayoutSize; + DISPLAY_CONTROL_HEADER header; + disp = (DISP_PLUGIN*)callback->plugin; + MonitorLayoutSize = DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE; + header.length = 8 + 8 + (NumMonitors * MonitorLayoutSize); + header.type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT; + + s = Stream_New(NULL, header.length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((status = disp_write_header(s, &header))) + { + WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", status); + goto out; + } + + 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); + } + +out: + 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 + */ +static 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 + */ +static UINT disp_recv_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 error; + DISPLAY_CONTROL_HEADER header; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + if ((error = disp_read_header(s, &header))) + { + WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + if (!Stream_EnsureRemainingCapacity(s, header.length)) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + switch (header.type) + { + case DISPLAY_CONTROL_PDU_TYPE_CAPS: + return disp_recv_display_control_caps_pdu(callback, s); + + default: + WLog_ERR(TAG, "Type %" PRIu32 " not recognized!", header.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; + if (disp->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", DISP_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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, + &disp->listener_callback->iface, &(disp->listener)); + disp->listener->pInterface = disp->iface.pInterface; + + disp->initialized = status == CHANNEL_RC_OK; + 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; + + if (disp && disp->listener_callback) + { + IWTSVirtualChannelManager* mgr = disp->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, disp->listener); + } + + 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 + */ +static 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..45a4830 --- /dev/null +++ b/channels/disp/client/disp_main.h @@ -0,0 +1,38 @@ +/** + * 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 TAG CHANNELS_TAG("disp.client") + +#endif /* FREERDP_CHANNEL_DISP_CLIENT_MAIN_H */ diff --git a/channels/disp/disp_common.c b/channels/disp/disp_common.c new file mode 100644 index 0000000..f4313d1 --- /dev/null +++ b/channels/disp/disp_common.c @@ -0,0 +1,60 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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("disp.common") + +#include "disp_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header) +{ + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "header parsing failed: not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, header->type); + Stream_Read_UINT32(s, header->length); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header) +{ + Stream_Write_UINT32(s, header->type); + Stream_Write_UINT32(s, header->length); + return CHANNEL_RC_OK; +} diff --git a/channels/disp/disp_common.h b/channels/disp/disp_common.h new file mode 100644 index 0000000..386b8b3 --- /dev/null +++ b/channels/disp/disp_common.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_COMMON_H +#define FREERDP_CHANNEL_DISP_COMMON_H + +#include +#include + +#include +#include + +FREERDP_LOCAL UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header); +FREERDP_LOCAL UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header); + +#endif /* FREERDP_CHANNEL_DISP_COMMON_H */ diff --git a/channels/disp/server/CMakeLists.txt b/channels/disp/server/CMakeLists.txt new file mode 100644 index 0000000..dddc15b --- /dev/null +++ b/channels/disp/server/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Kobi Mizrachi +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("disp") + +set(${MODULE_PREFIX}_SRCS + disp_main.c + disp_main.h + ../disp_common.c + ../disp_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/disp/server/disp_main.c b/channels/disp/server/disp_main.c new file mode 100644 index 0000000..9caec45 --- /dev/null +++ b/channels/disp/server/disp_main.c @@ -0,0 +1,597 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "disp_main.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../disp_common.h" + +#define TAG CHANNELS_TAG("rdpedisp.server") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ + +static wStream* disp_server_single_packet_new(UINT32 type, UINT32 length) +{ + UINT error; + DISPLAY_CONTROL_HEADER header; + wStream* s = Stream_New(NULL, DISPLAY_CONTROL_HEADER_LENGTH + length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto error; + } + + header.type = type; + header.length = DISPLAY_CONTROL_HEADER_LENGTH + length; + + if ((error = disp_write_header(s, &header))) + { + WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error); + goto error; + } + + return s; +error: + Stream_Free(s, TRUE); + return NULL; +} + +static void disp_server_sanitize_monitor_layout(DISPLAY_CONTROL_MONITOR_LAYOUT* monitor) +{ + if (monitor->PhysicalWidth < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH || + monitor->PhysicalWidth > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH || + monitor->PhysicalHeight < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT || + monitor->PhysicalHeight > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT) + { + if (monitor->PhysicalWidth != 0 || monitor->PhysicalHeight != 0) + WLog_DBG( + TAG, + "Sanitizing invalid physical monitor size. Old physical monitor size: [%" PRIu32 + ", %" PRIu32 "]", + monitor->PhysicalWidth, monitor->PhysicalHeight); + + monitor->PhysicalWidth = monitor->PhysicalHeight = 0; + } +} + +static BOOL disp_server_is_monitor_layout_valid(DISPLAY_CONTROL_MONITOR_LAYOUT* monitor) +{ + if (monitor->Width < DISPLAY_CONTROL_MIN_MONITOR_WIDTH || + monitor->Width > DISPLAY_CONTROL_MAX_MONITOR_WIDTH) + { + WLog_WARN(TAG, "Received invalid value for monitor->Width: %" PRIu32 "", monitor->Width); + return FALSE; + } + + if (monitor->Height < DISPLAY_CONTROL_MIN_MONITOR_HEIGHT || + monitor->Height > DISPLAY_CONTROL_MAX_MONITOR_HEIGHT) + { + WLog_WARN(TAG, "Received invalid value for monitor->Height: %" PRIu32 "", monitor->Width); + return FALSE; + } + + switch (monitor->Orientation) + { + case ORIENTATION_LANDSCAPE: + case ORIENTATION_PORTRAIT: + case ORIENTATION_LANDSCAPE_FLIPPED: + case ORIENTATION_PORTRAIT_FLIPPED: + break; + + default: + WLog_WARN(TAG, "Received incorrect value for monitor->Orientation: %" PRIu32 "", + monitor->Orientation); + return FALSE; + } + + return TRUE; +} + +static UINT disp_recv_display_control_monitor_layout_pdu(wStream* s, DispServerContext* context) +{ + UINT32 error = CHANNEL_RC_OK; + UINT32 index; + DISPLAY_CONTROL_MONITOR_LAYOUT_PDU pdu; + DISPLAY_CONTROL_MONITOR_LAYOUT* monitor; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */ + + if (pdu.MonitorLayoutSize != DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE) + { + WLog_ERR(TAG, "MonitorLayoutSize is set to %" PRIu32 ". expected %" PRIu32 "", + pdu.MonitorLayoutSize, DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.NumMonitors); /* NumMonitors (4 bytes) */ + + if (pdu.NumMonitors > context->MaxNumMonitors) + { + WLog_ERR(TAG, "NumMonitors (%" PRIu32 ")> server MaxNumMonitors (%" PRIu32 ")", + pdu.NumMonitors, context->MaxNumMonitors); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE * pdu.NumMonitors) + { + WLog_ERR(TAG, "not enough data!"); + return ERROR_INVALID_DATA; + } + + pdu.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*)calloc(pdu.NumMonitors, + sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!pdu.Monitors) + { + WLog_ERR(TAG, "disp_recv_display_control_monitor_layout_pdu(): calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + WLog_DBG(TAG, "disp_recv_display_control_monitor_layout_pdu: NumMonitors=%" PRIu32 "", + pdu.NumMonitors); + + for (index = 0; index < pdu.NumMonitors; index++) + { + monitor = &(pdu.Monitors[index]); + Stream_Read_UINT32(s, monitor->Flags); /* Flags (4 bytes) */ + Stream_Read_UINT32(s, monitor->Left); /* Left (4 bytes) */ + Stream_Read_UINT32(s, monitor->Top); /* Top (4 bytes) */ + Stream_Read_UINT32(s, monitor->Width); /* Width (4 bytes) */ + Stream_Read_UINT32(s, monitor->Height); /* Height (4 bytes) */ + Stream_Read_UINT32(s, monitor->PhysicalWidth); /* PhysicalWidth (4 bytes) */ + Stream_Read_UINT32(s, monitor->PhysicalHeight); /* PhysicalHeight (4 bytes) */ + Stream_Read_UINT32(s, monitor->Orientation); /* Orientation (4 bytes) */ + Stream_Read_UINT32(s, monitor->DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */ + Stream_Read_UINT32(s, monitor->DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */ + + disp_server_sanitize_monitor_layout(monitor); + WLog_DBG(TAG, + "\t%d : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32 ") W/H=%" PRIu32 + "x%" PRIu32 ")", + index, monitor->Flags, monitor->Left, monitor->Top, monitor->Width, + monitor->Height); + WLog_DBG(TAG, + "\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 " Orientation: %" PRIu32 + "", + monitor->PhysicalWidth, monitor->PhysicalHeight, monitor->Orientation); + + if (!disp_server_is_monitor_layout_valid(monitor)) + { + error = ERROR_INVALID_DATA; + goto out; + } + } + + if (context) + IFCALLRET(context->DispMonitorLayout, error, context, &pdu); + +out: + free(pdu.Monitors); + return error; +} + +static UINT disp_server_receive_pdu(DispServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + size_t beg, end; + DISPLAY_CONTROL_HEADER header; + beg = Stream_GetPosition(s); + + if ((error = disp_read_header(s, &header))) + { + WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + switch (header.type) + { + case DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT: + if ((error = disp_recv_display_control_monitor_layout_pdu(s, context))) + WLog_ERR(TAG, + "disp_recv_display_control_monitor_layout_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.type); + break; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.length)) + { + WLog_ERR(TAG, "Unexpected DISP pdu end: Actual: %d, Expected: %" PRIu32 "", end, + (beg + header.length)); + Stream_SetPosition(s, (beg + header.length)); + } + + return error; +} + +static UINT disp_server_handle_messages(DispServerContext* context) +{ + DWORD BytesReturned; + void* buffer; + UINT ret = CHANNEL_RC_OK; + DispServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + /* Check whether the dynamic channel is ready */ + if (!priv->isReady) + { + if (WTSVirtualChannelQuery(priv->disp_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 disp dynamic channel is ready */ + Stream_SetPosition(s, 0); + + if (!WTSVirtualChannelRead(priv->disp_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->disp_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 = disp_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, + "disp_server_receive_pdu " + "failed with error %" PRIu32 "!", + ret); + return ret; + } + } + + return ret; +} + +static DWORD WINAPI disp_server_thread_func(LPVOID arg) +{ + DispServerContext* context = (DispServerContext*)arg; + DispServerPrivate* priv = context->priv; + DWORD status; + DWORD nCount; + HANDLE events[8]; + UINT error = CHANNEL_RC_OK; + nCount = 0; + events[nCount++] = priv->stopEvent; + events[nCount++] = priv->channelEvent; + + /* Main virtual channel loop. RDPEDISP 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 = disp_server_handle_messages(context))) + { + WLog_ERR(TAG, "disp_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_open(DispServerContext* context) +{ + UINT rc = ERROR_INTERNAL_ERROR; + DispServerPrivate* priv = context->priv; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + void* buffer; + buffer = NULL; + priv->SessionId = WTS_CURRENT_SESSION; + UINT32 channelId; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->disp_channel = (HANDLE)WTSVirtualChannelOpenEx(priv->SessionId, DISP_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!priv->disp_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + rc = GetLastError(); + goto out_close; + } + + channelId = WTSChannelGetIdByHandle(priv->disp_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + /* Query for channel event handle */ + if (!WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) || + (BytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "WTSVirtualChannelQuery failed " + "or invalid returned size(%" PRIu32 ")", + BytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE)); + WTSFreeMemory(buffer); + + if (priv->thread == NULL) + { + if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + rc = ERROR_INTERNAL_ERROR; + } + + if (!(priv->thread = + CreateThread(NULL, 0, disp_server_thread_func, (void*)context, 0, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + CloseHandle(priv->stopEvent); + priv->stopEvent = NULL; + rc = ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +out_close: + WTSVirtualChannelClose(priv->disp_channel); + priv->disp_channel = NULL; + priv->channelEvent = NULL; + return rc; +} + +static UINT disp_server_packet_send(DispServerContext* context, wStream* s) +{ + UINT ret; + ULONG written; + + if (!WTSVirtualChannelWrite(context->priv->disp_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + ret = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + + ret = CHANNEL_RC_OK; +out: + Stream_Free(s, TRUE); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_send_caps_pdu(DispServerContext* context) +{ + wStream* s = disp_server_single_packet_new(DISPLAY_CONTROL_PDU_TYPE_CAPS, 12); + + if (!s) + { + WLog_ERR(TAG, "disp_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, context->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */ + Stream_Write_UINT32(s, context->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */ + Stream_Write_UINT32(s, context->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */ + return disp_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_close(DispServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + DispServerPrivate* priv = context->priv; + + if (priv->thread) + { + SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(priv->thread); + CloseHandle(priv->stopEvent); + priv->thread = NULL; + priv->stopEvent = NULL; + } + + if (priv->disp_channel) + { + WTSVirtualChannelClose(priv->disp_channel); + priv->disp_channel = NULL; + } + + return error; +} + +DispServerContext* disp_server_context_new(HANDLE vcm) +{ + DispServerContext* context; + DispServerPrivate* priv; + context = (DispServerContext*)calloc(1, sizeof(DispServerContext)); + + if (!context) + { + WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerContext failed!"); + goto out_free; + } + + priv = context->priv = (DispServerPrivate*)calloc(1, sizeof(DispServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerPrivate failed!"); + goto out_free; + } + + priv->input_stream = Stream_New(NULL, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto out_free_priv; + } + + context->vcm = vcm; + context->Open = disp_server_open; + context->Close = disp_server_close; + context->DisplayControlCaps = disp_server_send_caps_pdu; + priv->isReady = FALSE; + return context; +out_free_priv: + free(context->priv); +out_free: + free(context); + return NULL; +} + +void disp_server_context_free(DispServerContext* context) +{ + if (!context) + return; + + disp_server_close(context); + + if (context->priv) + { + Stream_Free(context->priv->input_stream, TRUE); + free(context->priv); + } + + free(context); +} diff --git a/channels/disp/server/disp_main.h b/channels/disp/server/disp_main.h new file mode 100644 index 0000000..c47462b --- /dev/null +++ b/channels/disp/server/disp_main.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_SERVER_MAIN_H +#define FREERDP_CHANNEL_DISP_SERVER_MAIN_H + +#include + +struct _disp_server_private +{ + BOOL isReady; + wStream* input_stream; + HANDLE channelEvent; + HANDLE thread; + HANDLE stopEvent; + DWORD SessionId; + + void* disp_channel; +}; + +#endif /* FREERDP_CHANNEL_DISP_SERVER_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..b2005a9 --- /dev/null +++ b/channels/drdynvc/client/drdynvc_main.c @@ -0,0 +1,1826 @@ +/** + * 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 UINT dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId, + BOOL bSendClosePDU); +static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr); +static void dvcman_channel_free(void* channel); +static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, const BYTE* data, + UINT32 dataSize, BOOL* close); +static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s); + +static void dvcman_wtslistener_free(DVCMAN_LISTENER* listener) +{ + if (listener) + free(listener->channel_name); + + free(listener); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_get_configuration(IWTSListener* pListener, void** ppPropertyBag) +{ + WINPR_UNUSED(pListener); + *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; + + WLog_DBG(TAG, "create_listener: %d.%s.", ArrayList_Count(dvcman->listeners) + 1, + 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!"); + dvcman_wtslistener_free(listener); + return CHANNEL_RC_NO_MEMORY; + } + + listener->flags = ulFlags; + listener->listener_callback = pListenerCallback; + + if (ppListener) + *ppListener = (IWTSListener*)listener; + + if (ArrayList_Add(dvcman->listeners, listener) < 0) + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +static UINT dvcman_destroy_listener(IWTSVirtualChannelManager* pChannelMgr, IWTSListener* pListener) +{ + DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)pListener; + + WINPR_UNUSED(pChannelMgr); + + if (listener) + { + DVCMAN* dvcman = listener->dvcman; + if (dvcman) + ArrayList_Remove(dvcman->listeners, listener); + } + + return CHANNEL_RC_OK; +} + +/** + * 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 (ArrayList_Add(dvcman->plugin_names, _strdup(name)) < 0) + return ERROR_INTERNAL_ERROR; + if (ArrayList_Add(dvcman->plugins, pPlugin) < 0) + return ERROR_INTERNAL_ERROR; + + WLog_DBG(TAG, "register_plugin: num_plugins %d", ArrayList_Count(dvcman->plugins)); + return CHANNEL_RC_OK; +} + +static IWTSPlugin* dvcman_get_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name) +{ + IWTSPlugin* plugin = NULL; + size_t i, nc, pc; + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman; + if (!dvcman || !pEntryPoints || !name) + return NULL; + + nc = ArrayList_Count(dvcman->plugin_names); + pc = ArrayList_Count(dvcman->plugins); + if (nc != pc) + return NULL; + + ArrayList_Lock(dvcman->plugin_names); + ArrayList_Lock(dvcman->plugins); + for (i = 0; i < pc; i++) + { + const char* cur = ArrayList_GetItem(dvcman->plugin_names, i); + if (strcmp(cur, name) == 0) + { + plugin = ArrayList_GetItem(dvcman->plugins, i); + break; + } + } + ArrayList_Unlock(dvcman->plugin_names); + ArrayList_Unlock(dvcman->plugins); + return plugin; +} + +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) +{ + DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel; + return dvc->channel_id; +} + +static const char* dvcman_get_channel_name(IWTSVirtualChannel* channel) +{ + DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel; + return dvc->channel_name; +} + +static IWTSVirtualChannel* dvcman_find_channel_by_id(IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId) +{ + int index; + IWTSVirtualChannel* channel = NULL; + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + ArrayList_Lock(dvcman->channels); + for (index = 0; index < ArrayList_Count(dvcman->channels); index++) + { + DVCMAN_CHANNEL* cur = (DVCMAN_CHANNEL*)ArrayList_GetItem(dvcman->channels, index); + if (cur->channel_id == ChannelId) + { + channel = &cur->iface; + break; + } + } + + ArrayList_Unlock(dvcman->channels); + return channel; +} + +static void dvcman_plugin_terminate(void* plugin) +{ + IWTSPlugin* pPlugin = plugin; + + UINT error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Terminated, pPlugin); + if (error != CHANNEL_RC_OK) + WLog_ERR(TAG, "Terminated failed with error %" PRIu32 "!", error); +} + +static void wts_listener_free(void* arg) +{ + DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)arg; + dvcman_wtslistener_free(listener); +} +static IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin) +{ + wObject* obj; + DVCMAN* dvcman; + dvcman = (DVCMAN*)calloc(1, sizeof(DVCMAN)); + + if (!dvcman) + return NULL; + + dvcman->iface.CreateListener = dvcman_create_listener; + dvcman->iface.DestroyListener = dvcman_destroy_listener; + dvcman->iface.FindChannelById = dvcman_find_channel_by_id; + dvcman->iface.GetChannelId = dvcman_get_channel_id; + dvcman->iface.GetChannelName = dvcman_get_channel_name; + dvcman->drdynvc = plugin; + dvcman->channels = ArrayList_New(TRUE); + + if (!dvcman->channels) + goto fail; + + obj = ArrayList_Object(dvcman->channels); + obj->fnObjectFree = dvcman_channel_free; + + dvcman->pool = StreamPool_New(TRUE, 10); + if (!dvcman->pool) + goto fail; + + dvcman->listeners = ArrayList_New(TRUE); + if (!dvcman->listeners) + goto fail; + obj = ArrayList_Object(dvcman->listeners); + obj->fnObjectFree = wts_listener_free; + + dvcman->plugin_names = ArrayList_New(TRUE); + if (!dvcman->plugin_names) + goto fail; + obj = ArrayList_Object(dvcman->plugin_names); + obj->fnObjectFree = free; + + dvcman->plugins = ArrayList_New(TRUE); + if (!dvcman->plugins) + goto fail; + obj = ArrayList_Object(dvcman->plugins); + obj->fnObjectFree = dvcman_plugin_terminate; + return &dvcman->iface; +fail: + dvcman_free(plugin, &dvcman->iface); + return NULL; +} + +/** + * 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(&entryPoints.iface); + } + + 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) + goto fail; + + channel->dvcman = (DVCMAN*)pChannelMgr; + channel->channel_id = ChannelId; + channel->channel_name = _strdup(ChannelName); + + if (!channel->channel_name) + goto fail; + + if (!InitializeCriticalSectionEx(&(channel->lock), 0, 0)) + goto fail; + + return channel; +fail: + dvcman_channel_free(channel); + return NULL; +} + +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); + channel->channel_callback = NULL; + } + + 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_clear(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + + WINPR_UNUSED(drdynvc); + + ArrayList_Clear(dvcman->plugins); + ArrayList_Clear(dvcman->channels); + ArrayList_Clear(dvcman->plugin_names); + ArrayList_Clear(dvcman->listeners); +} + +static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + + WINPR_UNUSED(drdynvc); + + ArrayList_Free(dvcman->plugins); + ArrayList_Free(dvcman->channels); + ArrayList_Free(dvcman->plugin_names); + ArrayList_Free(dvcman->listeners); + + 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; + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + UINT error = CHANNEL_RC_OK; + + ArrayList_Lock(dvcman->plugins); + for (i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Initialize, pPlugin, pChannelMgr); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Initialize failed with error %" PRIu32 "!", + error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + return error; +} + +/** + * 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) +{ + BOOL close = FALSE; + UINT status; + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; + + WINPR_UNUSED(pReserved); + if (!channel || !channel->dvcman) + return CHANNEL_RC_BAD_CHANNEL; + + EnterCriticalSection(&(channel->lock)); + status = + drdynvc_write_data(channel->dvcman->drdynvc, channel->channel_id, pBuffer, cbSize, &close); + LeaveCriticalSection(&(channel->lock)); + /* Close delayed, it removes the channel struct */ + if (close) + dvcman_close_channel(channel->dvcman->drdynvc->channel_mgr, channel->channel_id, TRUE); + 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_CHANNEL* channel; + DrdynvcClientContext* context; + 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; + if (ArrayList_Add(dvcman->channels, channel) < 0) + return ERROR_INTERNAL_ERROR; + + ArrayList_Lock(dvcman->listeners); + for (i = 0; i < ArrayList_Count(dvcman->listeners); i++) + { + DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)ArrayList_GetItem(dvcman->listeners, i); + + if (strcmp(listener->channel_name, ChannelName) == 0) + { + IWTSVirtualChannelCallback* pCallback = NULL; + channel->iface.Write = dvcman_write_channel; + channel->iface.Close = dvcman_close_channel_iface; + bAccept = TRUE; + + if ((error = listener->listener_callback->OnNewChannelConnection( + listener->listener_callback, &channel->iface, 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); + + goto fail; + } + else + { + if (error) + { + WLog_Print(drdynvc->log, WLOG_ERROR, + "OnNewChannelConnection failed with error %" PRIu32 "!", error); + goto fail; + } + else + { + WLog_Print(drdynvc->log, WLOG_ERROR, + "OnNewChannelConnection returned with bAccept FALSE!"); + error = ERROR_INTERNAL_ERROR; + goto fail; + } + } + } + } + error = ERROR_INTERNAL_ERROR; +fail: + ArrayList_Unlock(dvcman->listeners); + + return 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); + if (error) + { + 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 + */ +UINT dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId, + BOOL bSendClosePDU) +{ + DVCMAN_CHANNEL* channel; + UINT error = CHANNEL_RC_OK; + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + drdynvcPlugin* drdynvc = dvcman->drdynvc; + 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; + } + + if (drdynvc && bSendClosePDU) + { + wStream* s = StreamPool_Take(dvcman->pool, 5); + if (!s) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + error = CHANNEL_RC_NO_MEMORY; + } + else + { + Stream_Write_UINT8(s, (CLOSE_REQUEST_PDU << 4) | 0x02); + Stream_Write_UINT32(s, ChannelId); + error = drdynvc_send(drdynvc, s); + } + } + + 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 > channel->dvc_data_length) + { + 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 UINT8 drdynvc_write_variable_uint(wStream* s, UINT32 val) +{ + UINT8 cb; + + if (val <= 0xFF) + { + cb = 0; + Stream_Write_UINT8(s, (UINT8)val); + } + else if (val <= 0xFFFF) + { + cb = 1; + Stream_Write_UINT16(s, (UINT16)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_Release(s); + return CHANNEL_RC_OK; + + case CHANNEL_RC_BAD_CHANNEL_HANDLE: + Stream_Release(s); + WLog_ERR(TAG, "VirtualChannelWriteEx failed with CHANNEL_RC_BAD_CHANNEL_HANDLE"); + return status; + + default: + Stream_Release(s); + 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, BOOL* close) +{ + wStream* data_out; + size_t pos; + UINT8 cbChId; + UINT8 cbLen; + unsigned long chunkLength; + UINT status = CHANNEL_RC_BAD_INIT_HANDLE; + DVCMAN* dvcman; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + + WLog_Print(drdynvc->log, WLOG_DEBUG, "write_data: ChannelId=%" PRIu32 " size=%" PRIu32 "", + ChannelId, dataSize); + data_out = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take 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) + { + *close = TRUE; + Stream_Release(data_out); + } + else if (dataSize <= CHANNEL_CHUNK_LENGTH - pos) + { + Stream_SetPosition(data_out, 0); + Stream_Write_UINT8(data_out, (DATA_PDU << 4) | 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, (DATA_FIRST_PDU << 4) | 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 = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take 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, (DATA_PDU << 4) | 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; + DVCMAN* dvcman; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + WLog_Print(drdynvc->log, WLOG_TRACE, "capability_response"); + s = StreamPool_Take(dvcman->pool, 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; + DVCMAN* dvcman; + + WINPR_UNUSED(Sp); + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + 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 = StreamPool_Take(dvcman->pool, pos + 4); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(data_out, (CREATE_REQUEST_PDU << 4) | 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, FALSE))) + 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 == CHANNEL_RC_OK) + status = dvcman_receive_channel_data(drdynvc, drdynvc->channel_mgr, ChannelId, s); + + if (status != CHANNEL_RC_OK) + status = dvcman_close_channel(drdynvc->channel_mgr, ChannelId, TRUE); + + return status; +} + +/** + * 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; + UINT status; + + 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); + status = dvcman_receive_channel_data(drdynvc, drdynvc->channel_mgr, ChannelId, s); + + if (status != CHANNEL_RC_OK) + status = dvcman_close_channel(drdynvc->channel_mgr, ChannelId, TRUE); + + return status; +} + +/** + * 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) +{ + UINT error; + 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_DEBUG, + "process_close_request: Sp=%d cbChId=%d, ChannelId=%" PRIu32 "", Sp, cbChId, + ChannelId); + + if ((error = dvcman_close_channel(drdynvc->channel_mgr, ChannelId, TRUE))) + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_close_channel failed with error %" PRIu32 "!", + 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) + { + DVCMAN* mgr = (DVCMAN*)drdynvc->channel_mgr; + if (drdynvc->data_in) + Stream_Release(drdynvc->data_in); + + drdynvc->data_in = StreamPool_Take(mgr->pool, totalLength); + } + + if (!(data_in = drdynvc->data_in)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + Stream_Release(drdynvc->data_in); + drdynvc->data_in = NULL; + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + const size_t cap = Stream_Capacity(data_in); + const size_t pos = Stream_GetPosition(data_in); + if (cap < pos) + { + 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!drdynvc || (drdynvc->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "drdynvc_virtual_channel_open_event: error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Release(s); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && drdynvc && 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))) + { + WLog_Print(drdynvc->log, WLOG_WARN, + "drdynvc_order_recv failed with error %" PRIu32 "!", error); + } + + Stream_Release(data); + } + } + + { + /* Disconnect remaining dynamic channels that the server did not. + * This is required to properly shut down channels by calling the appropriate + * event handlers. */ + size_t count = 0; + DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr; + + do + { + ArrayList_Lock(drdynvcMgr->channels); + count = ArrayList_Count(drdynvcMgr->channels); + if (count > 0) + { + IWTSVirtualChannel* channel = + (IWTSVirtualChannel*)ArrayList_GetItem(drdynvcMgr->channels, 0); + const UINT32 ChannelId = drdynvc->channel_mgr->GetChannelId(channel); + dvcman_close_channel(drdynvc->channel_mgr, ChannelId, FALSE); + count--; + } + ArrayList_Unlock(drdynvcMgr->channels); + } while (count > 0); + } + + 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_Release(s); +} + +static UINT drdynvc_virtual_channel_event_initialized(drdynvcPlugin* drdynvc, LPVOID pData, + UINT32 dataLength) +{ + UINT error = CHANNEL_RC_OK; + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + if (!drdynvc) + goto error; + + 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; + } + + return CHANNEL_RC_OK; +error: + return ERROR_INTERNAL_ERROR; +} + +/** + * 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; + + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + 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; + } + + 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) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (drdynvc->OpenHandle == 0) + return CHANNEL_RC_OK; + + 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; + } + + CloseHandle(drdynvc->thread); + 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); + } + + dvcman_clear(drdynvc, drdynvc->channel_mgr); + MessageQueue_Clear(drdynvc->queue); + drdynvc->OpenHandle = 0; + + if (drdynvc->data_in) + { + Stream_Release(drdynvc->data_in); + drdynvc->data_in = 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; + + MessageQueue_Free(drdynvc->queue); + drdynvc->queue = NULL; + + if (drdynvc->channel_mgr) + { + dvcman_free(drdynvc, drdynvc->channel_mgr); + drdynvc->channel_mgr = NULL; + } + + drdynvc->InitHandle = 0; + free(drdynvc->context); + free(drdynvc); + return CHANNEL_RC_OK; +} + +static UINT drdynvc_virtual_channel_event_attached(drdynvcPlugin* drdynvc) +{ + UINT error = CHANNEL_RC_OK; + 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; + + ArrayList_Lock(dvcman->plugins); + for (i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Attached, pPlugin); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Attach failed with error %" PRIu32 "!", error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + return error; +} + +static UINT drdynvc_virtual_channel_event_detached(drdynvcPlugin* drdynvc) +{ + UINT error = CHANNEL_RC_OK; + 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; + + ArrayList_Lock(dvcman->plugins); + for (i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Detached, pPlugin); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Detach failed with error %" PRIu32 "!", error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + + return error; +} + +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_INITIALIZED: + error = drdynvc_virtual_channel_event_initialized(drdynvc, pData, dataLength); + break; + 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..646c8fd --- /dev/null +++ b/channels/drdynvc/client/drdynvc_main.h @@ -0,0 +1,135 @@ +/** + * 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; + +struct _DVCMAN +{ + IWTSVirtualChannelManager iface; + + drdynvcPlugin* drdynvc; + + wArrayList* plugin_names; + wArrayList* plugins; + + wArrayList* listeners; + 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; + + UINT16 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..66440b2 --- /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..3054385 --- /dev/null +++ b/channels/drive/client/drive_file.c @@ -0,0 +1,934 @@ +/** + * 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 = _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, + size_t PathLength) +{ + WCHAR* fullpath; + size_t base_path_length; + + if (!base_path || (!path && (PathLength > 0))) + 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); + if (path) + 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; + HANDLE dir; + WCHAR* fullpath; + WCHAR* path_slash; + size_t 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 + { + size_t 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); + } + +#ifdef WIN32 + if (file->file_handle == INVALID_HANDLE_VALUE) + { + /* Get the error message, if any. */ + DWORD errorMessageID = GetLastError(); + + if (errorMessageID != 0) + { + 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); + /* restore original error code */ + SetLastError(errorMessageID); + } + } +#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 && (PathLength > 0))) + 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 = 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)) + { + DWORD lastError = GetLastError(); + drive_file_free(file); + SetLastError(lastError); + 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; + + if (Offset > INT64_MAX) + return FALSE; + + loffset.QuadPart = (LONGLONG)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.u.HighPart; + ftCreationTime.dwLowDateTime = liCreationTime.u.LowPart; + pftCreationTime = &ftCreationTime; + } + + if (liLastAccessTime.QuadPart != 0) + { + ftLastAccessTime.dwHighDateTime = liLastAccessTime.u.HighPart; + ftLastAccessTime.dwLowDateTime = liLastAccessTime.u.LowPart; + pftLastAccessTime = &ftLastAccessTime; + } + + if (liLastWriteTime.QuadPart != 0) + { + ftLastWriteTime.dwHighDateTime = liLastWriteTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liLastWriteTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart) + { + ftLastWriteTime.dwHighDateTime = liChangeTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liChangeTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + DEBUG_WSTR("SetFileTime %s", file->fullpath); + + SetFileAttributesW(file->fullpath, FileAttributes); + if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime, + pftLastWriteTime)) + { + WLog_ERR(TAG, "Unable to set file time to %s", file->fullpath); + return FALSE; + } + + 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; + } + + liSize.QuadPart = size; + + 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; + + if (length > UINT32_MAX - 64) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(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, (UINT32)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; + + if (length > UINT32_MAX - 68) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(68 + 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, (UINT32)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; + + if (length > UINT32_MAX - 93) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(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, (UINT32)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; + + if (length > UINT32_MAX - 12) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(12 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + default: + WLog_ERR(TAG, "unhandled FsInformationClass %" PRIu32, FsInformationClass); + /* 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..ed789d6 --- /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; + const 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..1b54225 --- /dev/null +++ b/channels/drive/client/drive_main.c @@ -0,0 +1,1112 @@ +/** + * 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 = (const 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; + void* ptr; + + 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 */ + ptr = Stream_Pointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + 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, ptr, 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 = ERROR_INTERNAL_ERROR; + + if (!pEntryPoints || !name || !path) + { + WLog_ERR(TAG, "[%s] Invalid parameters: pEntryPoints=%p, name=%p, path=%p", pEntryPoints, + name, path); + return ERROR_INVALID_PARAMETER; + } + + 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.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++) + { + /* Filter 2.2.1.3 Device Announce Header (DEVICE_ANNOUNCE) forbidden symbols */ + switch (name[i]) + { + case ':': + case '<': + case '>': + case '\"': + case '/': + case '\\': + case '|': + case ' ': + Stream_Write_UINT8(drive->device.data, '_'); + break; + default: + Stream_Write_UINT8(drive->device.data, (BYTE)name[i]); + break; + } + } + Stream_Write_UINT8(drive->device.data, '\0'); + + drive->device.name = (const char*)Stream_Buffer(drive->device.data); + if (!drive->device.name) + goto out_error; + + 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..7142940 --- /dev/null +++ b/channels/echo/client/echo_main.c @@ -0,0 +1,216 @@ +/** + * 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 +#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; + IWTSListener* listener; + BOOL initialized; +}; + +/** + * 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) +{ + UINT status; + ECHO_PLUGIN* echo = (ECHO_PLUGIN*)pPlugin; + if (echo->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", ECHO_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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; + + status = pChannelMgr->CreateListener(pChannelMgr, ECHO_DVC_CHANNEL_NAME, 0, + &echo->listener_callback->iface, &echo->listener); + + echo->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_plugin_terminated(IWTSPlugin* pPlugin) +{ + ECHO_PLUGIN* echo = (ECHO_PLUGIN*)pPlugin; + if (echo && echo->listener_callback) + { + IWTSVirtualChannelManager* mgr = echo->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, echo->listener); + } + 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..06262b1 --- /dev/null +++ b/channels/echo/client/echo_main.h @@ -0,0 +1,42 @@ +/** + * 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..1da2894 --- /dev/null +++ b/channels/echo/server/echo_main.c @@ -0,0 +1,372 @@ +/** + * 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) + { + UINT32 channelId; + BOOL status = TRUE; + + channelId = WTSChannelGetIdByHandle(echo->echo_channel); + + IFCALLRET(echo->context.ChannelIdAssigned, status, &echo->context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + 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..b384337 --- /dev/null +++ b/channels/encomsp/client/encomsp_main.c @@ -0,0 +1,1349 @@ +/** + * 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" + +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; +}; + +/** + * 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, const 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) + { + Stream_Free(s, TRUE); + 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) + { + Stream_Free(s, TRUE); + 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_FILTER_UPDATED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_APPLICATION_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + if (Stream_GetRemainingLength(s) < 6) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_APPLICATION_REMOVED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_WINDOW_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_WINDOW_REMOVED_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_SHOW_WINDOW_PDU pdu; + UINT error = CHANNEL_RC_OK; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + EncomspClientContext* context; + ENCOMSP_PARTICIPANT_CREATED_PDU pdu; + UINT error; + context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t 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; + + if (Stream_GetRemainingLength(s) < 12) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + beg = (Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + 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 = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + 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; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - 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 = 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, const ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu) +{ + wStream* s; + encomspPlugin* encomsp; + UINT error; + ENCOMSP_ORDER_HEADER header; + + encomsp = (encomspPlugin*)context->handle; + header.Type = ODTYPE_PARTICIPANT_CTRL_CHANGED; + header.Length = ENCOMSP_ORDER_HEADER_SIZE + 6; + s = Stream_New(NULL, header.Length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = encomsp_write_header(s, &header))) + { + 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + 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; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + end = 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, + const ENCOMSP_ORDER_HEADER* header) +{ + size_t beg, end, pos; + 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; + + pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + end = 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; + } + } + + 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, const 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, 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!encomsp || (encomsp->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && encomsp && 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); + Stream_Free(data, TRUE); + break; + } + + Stream_Free(data, TRUE); + } + } + + 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: + break; + } + + 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..ad43cea --- /dev/null +++ b/channels/encomsp/client/encomsp_main.h @@ -0,0 +1,42 @@ +/** + * 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") + +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..9b1693b --- /dev/null +++ b/channels/encomsp/server/encomsp_main.c @@ -0,0 +1,379 @@ +/** + * 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) < (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_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..ac9fdc4 --- /dev/null +++ b/channels/geometry/client/CMakeLists.txt @@ -0,0 +1,40 @@ +# 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) +if (NOT BUILTIN_CHANNELS OR NOT BUILD_SHARED_LIBS) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client) +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/geometry/client/geometry_main.c b/channels/geometry/client/geometry_main.c new file mode 100644 index 0000000..1258806 --- /dev/null +++ b/channels/geometry/client/geometry_main.c @@ -0,0 +1,501 @@ +/** + * 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 + +#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; + BOOL initialized; +}; +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; +} + +static 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; + INT32 x, y, w, h; + + 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, x); + Stream_Read_INT32(s, y); + Stream_Read_INT32(s, right); + Stream_Read_INT32(s, bottom); + if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX)) + return ERROR_INVALID_DATA; + w = right - x; + h = bottom - y; + if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX)) + return ERROR_INVALID_DATA; + rgndata->boundingRect.x = (INT16)x; + rgndata->boundingRect.y = (INT16)y; + rgndata->boundingRect.width = (INT16)w; + rgndata->boundingRect.height = (INT16)h; + len -= 32; + + if (len / (4 * 4) < rgndata->nRectCount) + { + WLog_ERR(TAG, "not enough data for region rectangles"); + } + + if (rgndata->nRectCount) + { + UINT32 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, x); + Stream_Read_INT32(s, y); + Stream_Read_INT32(s, right); + Stream_Read_INT32(s, bottom); + if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX)) + return ERROR_INVALID_DATA; + w = right - x; + h = bottom - y; + if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX)) + return ERROR_INVALID_DATA; + rgndata->rects[i].x = (INT16)x; + rgndata->rects[i].y = (INT16)y; + rgndata->rects[i].width = (INT16)w; + rgndata->rects[i].height = (INT16)h; + } + } + + 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; + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + 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; + if (geometry->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", GEOMETRY_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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, + &geometry->listener_callback->iface, &(geometry->listener)); + geometry->listener->pInterface = geometry->iface.pInterface; + + geometry->initialized = status == CHANNEL_RC_OK; + 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 (geometry && geometry->listener_callback) + { + IWTSVirtualChannelManager* mgr = geometry->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, geometry->listener); + } + + 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..ff6add2 --- /dev/null +++ b/channels/geometry/client/geometry_main.h @@ -0,0 +1,32 @@ +/** + * 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..993605a --- /dev/null +++ b/channels/parallel/client/parallel_main.c @@ -0,0 +1,497 @@ +/** + * 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; + WCHAR* ptr; + UINT32 PathLength; + if (!Stream_SafeSeek(irp->input, 28)) + return ERROR_INVALID_DATA; + /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */ + /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */ + if (Stream_GetRemainingLength(irp->input) < 4) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(irp->input, PathLength); + ptr = (WCHAR*)Stream_Pointer(irp->input); + if (!Stream_SafeSeek(irp->input, PathLength)) + return ERROR_INVALID_DATA; + status = ConvertFromUnicode(CP_UTF8, 0, ptr, 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; + if (Stream_GetRemainingLength(irp->input) < 12) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + + 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 + { + Length = status; + } + + 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; + void* ptr; + if (Stream_GetRemainingLength(irp->input) > 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + if (!Stream_SafeSeek(irp->input, 20)) /* Padding */ + return ERROR_INVALID_DATA; + ptr = Stream_Pointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + len = Length; + + while (len > 0) + { + status = write(parallel->file, ptr, 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..58d44f9 --- /dev/null +++ b/channels/printer/client/CMakeLists.txt @@ -0,0 +1,40 @@ +# 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) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr 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_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_CUPS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "cups" "") +endif() + +if(WIN32 AND NOT UWP) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "win" "") +endif() diff --git a/channels/printer/client/cups/CMakeLists.txt b/channels/printer/client/cups/CMakeLists.txt new file mode 100644 index 0000000..50d599e --- /dev/null +++ b/channels/printer/client/cups/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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("printer" "cups" "") + +set(${MODULE_PREFIX}_SRCS + printer_cups.c) + +include_directories(..) +include_directories(${CUPS_INCLUDE_DIR}) + +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} ${CUPS_LIBRARIES}) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/printer/client/cups/printer_cups.c b/channels/printer/client/cups/printer_cups.c new file mode 100644 index 0000000..baa46e5 --- /dev/null +++ b/channels/printer/client/cups/printer_cups.c @@ -0,0 +1,410 @@ +/** + * 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 + +#include + +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; + size_t references; +}; + +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, size_t size, size_t id) +{ + time_t tt; + struct tm tres; + struct tm* t; + + tt = time(NULL); + t = localtime_r(&tt, &tres); + sprintf_s(buf, size - 1, "FreeRDP Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIdz, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, id); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) +{ + rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob; + +#ifndef _CUPS_API_1_4 + + { + FILE* fp; + + fp = winpr_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), printjob->id); + + 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]; + +#if !defined(_CUPS_API_1_7) + cups_printjob->printjob_object = + httpConnectEncrypt(cupsServer(), ippPort(), HTTP_ENCRYPT_IF_REQUESTED); +#else + cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, + HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, NULL); +#endif + if (!cups_printjob->printjob_object) + { + free(cups_printjob); + return NULL; + } + + printer_cups_get_printjob_name(buf, sizeof(buf), cups_printjob->printjob.id); + + 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); + + if (printer->backend) + printer->backend->ReleaseRef(printer->backend); + free(printer->name); + free(printer->driver); + free(printer); +} + +static void printer_cups_add_ref_printer(rdpPrinter* printer) +{ + if (printer) + printer->references++; +} + +static void printer_cups_release_ref_printer(rdpPrinter* printer) +{ + if (!printer) + return; + if (printer->references <= 1) + printer_cups_free_printer(printer); + else + printer->references--; +} + +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.backend = &cups_driver->driver; + + 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.AddRef = printer_cups_add_ref_printer; + cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer; + + cups_printer->printer.AddRef(&cups_printer->printer); + cups_printer->printer.backend->AddRef(cups_printer->printer.backend); + return &cups_printer->printer; +} + +static void printer_cups_release_enum_printers(rdpPrinter** printers) +{ + rdpPrinter** cur = printers; + + while ((cur != NULL) && ((*cur) != NULL)) + { + if ((*cur)->ReleaseRef) + (*cur)->ReleaseRef(*cur); + cur++; + } + free(printers); +} + +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) + { + rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver, + dest->name, NULL, dest->is_default); + if (!current) + { + printer_cups_release_enum_printers(printers); + printers = NULL; + break; + } + + printers[num_printers++] = current; + } + } + 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 void printer_cups_add_ref_driver(rdpPrinterDriver* driver) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver; + if (cups_driver) + cups_driver->references++; +} + +/* Singleton */ +static rdpCupsPrinterDriver* uniq_cups_driver = NULL; + +static void printer_cups_release_ref_driver(rdpPrinterDriver* driver) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver; + if (cups_driver->references <= 1) + { + if (uniq_cups_driver == cups_driver) + uniq_cups_driver = NULL; + free(cups_driver); + cups_driver = NULL; + } + else + cups_driver->references--; +} + +#ifdef BUILTIN_CHANNELS +rdpPrinterDriver* cups_freerdp_printer_client_subsystem_entry(void) +#else +FREERDP_API rdpPrinterDriver* freerdp_printer_client_subsystem_entry(void) +#endif +{ + if (!uniq_cups_driver) + { + uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver)); + + if (!uniq_cups_driver) + return NULL; + + uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers; + uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers; + uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer; + + uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver; + uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver; + + uniq_cups_driver->id_sequence = 1; + uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver); + } + + return &uniq_cups_driver->driver; +} diff --git a/channels/printer/client/printer_main.c b/channels/printer/client/printer_main.c new file mode 100644 index 0000000..8019968 --- /dev/null +++ b/channels/printer/client/printer_main.c @@ -0,0 +1,1073 @@ +/** + * 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 +#include + +#include "../printer.h" + +#include + +#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]; +}; + +typedef enum +{ + PRN_CONF_PORT = 0, + PRN_CONF_PNP = 1, + PRN_CONF_DRIVER = 2, + PRN_CONF_DATA = 3 +} prn_conf_t; + +static const char* filemap[] = { "PortDosName", "PnPName", "DriverName", + "CachedPrinterConfigData" }; + +static char* get_printer_config_path(const rdpSettings* settings, const WCHAR* name, size_t length) +{ + char* dir = GetCombinedPath(settings->ConfigPath, "printers"); + char* bname = crypto_base64_encode((const BYTE*)name, (int)length); + char* config = GetCombinedPath(dir, bname); + + if (config && !winpr_PathFileExists(config)) + { + if (!winpr_PathMakePath(config, NULL)) + { + free(config); + config = NULL; + } + } + + free(dir); + free(bname); + return config; +} + +static BOOL printer_write_setting(const char* path, prn_conf_t type, const void* data, + size_t length) +{ + DWORD written = 0; + BOOL rc = FALSE; + HANDLE file; + size_t b64len; + char* base64 = NULL; + const char* name = filemap[type]; + char* abs = GetCombinedPath(path, name); + + if (!abs || (length > INT32_MAX)) + return FALSE; + + file = CreateFileA(abs, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + free(abs); + + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + if (length > 0) + { + base64 = crypto_base64_encode(data, length); + + if (!base64) + goto fail; + + /* base64 char represents 6bit -> 4*(n/3) is the length which is + * always smaller than 2*n */ + b64len = strnlen(base64, 2 * length); + rc = WriteFile(file, base64, b64len, &written, NULL); + + if (b64len != written) + rc = FALSE; + } + else + rc = TRUE; + +fail: + CloseHandle(file); + free(base64); + return rc; +} + +static BOOL printer_config_valid(const char* path) +{ + if (!path) + return FALSE; + + if (!winpr_PathFileExists(path)) + return FALSE; + + return TRUE; +} + +static BOOL printer_read_setting(const char* path, prn_conf_t type, void** data, UINT32* length) +{ + DWORD lowSize, highSize; + DWORD read = 0; + BOOL rc = FALSE; + HANDLE file; + char* fdata = NULL; + const char* name = filemap[type]; + char* abs = GetCombinedPath(path, name); + + if (!abs) + return FALSE; + + file = CreateFileA(abs, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + free(abs); + + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + lowSize = GetFileSize(file, &highSize); + + if ((lowSize == INVALID_FILE_SIZE) || (highSize != 0)) + goto fail; + + if (lowSize != 0) + { + fdata = malloc(lowSize); + + if (!fdata) + goto fail; + + rc = ReadFile(file, fdata, lowSize, &read, NULL); + + if (lowSize != read) + rc = FALSE; + } + +fail: + CloseHandle(file); + + if (rc && (lowSize <= INT_MAX)) + { + int blen = 0; + crypto_base64_decode(fdata, (int)lowSize, (BYTE**)data, &blen); + + if (*data && (blen > 0)) + *length = (UINT32)blen; + else + { + rc = FALSE; + *length = 0; + } + } + else + { + *length = 0; + *data = NULL; + } + + free(fdata); + return rc; +} + +static BOOL printer_save_to_config(const rdpSettings* settings, const char* PortDosName, + size_t PortDosNameLen, const WCHAR* PnPName, size_t PnPNameLen, + const WCHAR* DriverName, size_t DriverNameLen, + const WCHAR* PrinterName, size_t PrintNameLen, + const BYTE* CachedPrinterConfigData, size_t CacheFieldsLen) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, PrinterName, PrintNameLen); + + if (!path) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_PORT, PortDosName, PortDosNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_PNP, PnPName, PnPNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_DRIVER, DriverName, DriverNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_DATA, CachedPrinterConfigData, CacheFieldsLen)) + goto fail; + +fail: + free(path); + return rc; +} + +static BOOL printer_update_to_config(const rdpSettings* settings, const WCHAR* name, size_t length, + const BYTE* data, size_t datalen) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, name, length); + rc = printer_write_setting(path, PRN_CONF_DATA, data, datalen); + free(path); + return rc; +} + +static BOOL printer_remove_config(const rdpSettings* settings, const WCHAR* name, size_t length) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, name, length); + + if (!printer_config_valid(path)) + goto fail; + + rc = winpr_RemoveDirectory(path); +fail: + free(path); + return rc; +} + +static BOOL printer_move_config(const rdpSettings* settings, const WCHAR* oldName, size_t oldLength, + const WCHAR* newName, size_t newLength) +{ + BOOL rc = FALSE; + char* oldPath = get_printer_config_path(settings, oldName, oldLength); + char* newPath = get_printer_config_path(settings, newName, newLength); + + if (printer_config_valid(oldPath)) + rc = winpr_MoveFile(oldPath, newPath); + + free(oldPath); + free(newPath); + return rc; +} + +static BOOL printer_load_from_config(const rdpSettings* settings, rdpPrinter* printer, + PRINTER_DEVICE* printer_dev) +{ + BOOL res = FALSE; + WCHAR* wname = NULL; + size_t wlen; + char* path = NULL; + int rc; + UINT32 flags = 0; + void* DriverName = NULL; + UINT32 DriverNameLen = 0; + void* PnPName = NULL; + UINT32 PnPNameLen = 0; + void* CachedPrinterConfigData = NULL; + UINT32 CachedFieldsLen = 0; + UINT32 PrinterNameLen = 0; + + if (!settings || !printer) + return FALSE; + + rc = ConvertToUnicode(CP_UTF8, 0, printer->name, -1, &wname, 0); + + if (rc <= 0) + goto fail; + + wlen = _wcslen(wname) + 1; + path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR)); + PrinterNameLen = (wlen + 1) * sizeof(WCHAR); + + if (!path) + goto fail; + + if (printer->is_default) + flags |= RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER; + + if (!printer_read_setting(path, PRN_CONF_PNP, &PnPName, &PnPNameLen)) + { + } + + if (!printer_read_setting(path, PRN_CONF_DRIVER, &DriverName, &DriverNameLen)) + { + DriverNameLen = + ConvertToUnicode(CP_UTF8, 0, printer->driver, -1, (LPWSTR*)&DriverName, 0) * 2 + 1; + } + + if (!printer_read_setting(path, PRN_CONF_DATA, &CachedPrinterConfigData, &CachedFieldsLen)) + { + } + + Stream_SetPosition(printer_dev->device.data, 0); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, 24)) + goto fail; + + 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, PnPNameLen); /* PnPNameLen */ + Stream_Write_UINT32(printer_dev->device.data, DriverNameLen); + Stream_Write_UINT32(printer_dev->device.data, PrinterNameLen); + Stream_Write_UINT32(printer_dev->device.data, CachedFieldsLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PnPNameLen)) + goto fail; + + if (PnPNameLen > 0) + Stream_Write(printer_dev->device.data, PnPName, PnPNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, DriverNameLen)) + goto fail; + + Stream_Write(printer_dev->device.data, DriverName, DriverNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PrinterNameLen)) + goto fail; + + Stream_Write(printer_dev->device.data, wname, PrinterNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, CachedFieldsLen)) + goto fail; + + Stream_Write(printer_dev->device.data, CachedPrinterConfigData, CachedFieldsLen); + res = TRUE; +fail: + free(path); + free(wname); + free(PnPName); + free(DriverName); + free(CachedPrinterConfigData); + return res; +} + +static BOOL printer_save_default_config(const rdpSettings* settings, rdpPrinter* printer) +{ + BOOL res = FALSE; + WCHAR* wname = NULL; + WCHAR* driver = NULL; + size_t wlen, dlen; + char* path = NULL; + int rc; + + if (!settings || !printer) + return FALSE; + + rc = ConvertToUnicode(CP_UTF8, 0, printer->name, -1, &wname, 0); + + if (rc <= 0) + goto fail; + + rc = ConvertToUnicode(CP_UTF8, 0, printer->driver, -1, &driver, 0); + + if (rc <= 0) + goto fail; + + wlen = _wcslen(wname) + 1; + dlen = _wcslen(driver) + 1; + path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR)); + + if (!path) + goto fail; + + if (dlen > 1) + { + if (!printer_write_setting(path, PRN_CONF_DRIVER, driver, dlen * sizeof(WCHAR))) + goto fail; + } + + res = TRUE; +fail: + free(path); + free(wname); + free(driver); + return res; +} + +/** + * 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; + void* ptr; + + 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 */ + ptr = Stream_Pointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + 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, ptr, 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; +} + +static UINT printer_custom_component(DEVICE* device, UINT16 component, UINT16 packetId, wStream* s) +{ + UINT32 eventID; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device; + const rdpSettings* settings = printer_dev->rdpcontext->settings; + + if (component != RDPDR_CTYP_PRN) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, eventID); + + switch (packetId) + { + case PAKID_PRN_CACHE_DATA: + switch (eventID) + { + case RDPDR_ADD_PRINTER_EVENT: + { + char PortDosName[8]; + UINT32 PnPNameLen, DriverNameLen, PrintNameLen, CacheFieldsLen; + const WCHAR *PnPName, *DriverName, *PrinterName; + const BYTE* CachedPrinterConfigData; + + if (Stream_GetRemainingLength(s) < 24) + return ERROR_INVALID_DATA; + + Stream_Read(s, PortDosName, sizeof(PortDosName)); + Stream_Read_UINT32(s, PnPNameLen); + Stream_Read_UINT32(s, DriverNameLen); + Stream_Read_UINT32(s, PrintNameLen); + Stream_Read_UINT32(s, CacheFieldsLen); + + if (Stream_GetRemainingLength(s) < PnPNameLen) + return ERROR_INVALID_DATA; + + PnPName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, PnPNameLen); + + if (Stream_GetRemainingLength(s) < DriverNameLen) + return ERROR_INVALID_DATA; + + DriverName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, DriverNameLen); + + if (Stream_GetRemainingLength(s) < PrintNameLen) + return ERROR_INVALID_DATA; + + PrinterName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, PrintNameLen); + + if (Stream_GetRemainingLength(s) < CacheFieldsLen) + return ERROR_INVALID_DATA; + + CachedPrinterConfigData = Stream_Pointer(s); + Stream_Seek(s, CacheFieldsLen); + + if (!printer_save_to_config(settings, PortDosName, sizeof(PortDosName), PnPName, + PnPNameLen, DriverName, DriverNameLen, PrinterName, + PrintNameLen, CachedPrinterConfigData, + CacheFieldsLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + case RDPDR_UPDATE_PRINTER_EVENT: + { + UINT32 PrinterNameLen, ConfigDataLen; + const WCHAR* PrinterName; + const BYTE* ConfigData; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, ConfigDataLen); + + if (Stream_GetRemainingLength(s) < PrinterNameLen) + return ERROR_INVALID_DATA; + + PrinterName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, PrinterNameLen); + + if (Stream_GetRemainingLength(s) < ConfigDataLen) + return ERROR_INVALID_DATA; + + ConfigData = Stream_Pointer(s); + Stream_Seek(s, ConfigDataLen); + + if (!printer_update_to_config(settings, PrinterName, PrinterNameLen, ConfigData, + ConfigDataLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + case RDPDR_DELETE_PRINTER_EVENT: + { + UINT32 PrinterNameLen; + const WCHAR* PrinterName; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + + if (Stream_GetRemainingLength(s) < PrinterNameLen) + return ERROR_INVALID_DATA; + + PrinterName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, PrinterNameLen); + printer_remove_config(settings, PrinterName, PrinterNameLen); + } + break; + + case RDPDR_RENAME_PRINTER_EVENT: + { + UINT32 OldPrinterNameLen, NewPrinterNameLen; + const WCHAR* OldPrinterName; + const WCHAR* NewPrinterName; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OldPrinterNameLen); + Stream_Read_UINT32(s, NewPrinterNameLen); + + if (Stream_GetRemainingLength(s) < OldPrinterNameLen) + return ERROR_INVALID_DATA; + + OldPrinterName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, OldPrinterNameLen); + + if (Stream_GetRemainingLength(s) < NewPrinterNameLen) + return ERROR_INVALID_DATA; + + NewPrinterName = (const WCHAR*)Stream_Pointer(s); + Stream_Seek(s, NewPrinterNameLen); + + if (!printer_move_config(settings, OldPrinterName, OldPrinterNameLen, + NewPrinterName, NewPrinterNameLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + default: + WLog_ERR(TAG, "Unknown cache data eventID: 0x%08" PRIX32 "", eventID); + return ERROR_INVALID_DATA; + } + + break; + + case PAKID_PRN_USING_XPS: + { + UINT32 flags; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, flags); + WLog_ERR(TAG, + "Ignoring unhandled message PAKID_PRN_USING_XPS [printerID=%08" PRIx32 + ", flags=%08" PRIx32 "]", + eventID, flags); + } + break; + + default: + WLog_ERR(TAG, "Unknown printing component packetID: 0x%04" PRIX16 "", packetId); + return ERROR_INVALID_DATA; + } + + 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); + + /* The analyzer is confused by this premature return value. + * Since this case can not be handled gracefully silence the + * analyzer here. */ +#ifndef __clang_analyzer__ + return error; +#endif + } + + 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->ReleaseRef(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 + */ +static UINT printer_register(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, rdpPrinter* printer) +{ + PRINTER_DEVICE* printer_dev; + UINT error = ERROR_INTERNAL_ERROR; + printer_dev = (PRINTER_DEVICE*)calloc(1, sizeof(PRINTER_DEVICE)); + + if (!printer_dev) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + printer_dev->device.data = Stream_New(NULL, 1024); + + if (!printer_dev->device.data) + goto error_out; + + sprintf_s(printer_dev->port, sizeof(printer_dev->port), "PRN%" PRIdz, 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.CustomComponentRequest = printer_custom_component; + printer_dev->device.Free = printer_free; + printer_dev->rdpcontext = pEntryPoints->rdpcontext; + printer_dev->printer = printer; + 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; + } + + if (!printer_load_from_config(pEntryPoints->rdpcontext->settings, printer, printer_dev)) + 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; + } + + printer->AddRef(printer); + return CHANNEL_RC_OK; +error_out: + printer_free(&printer_dev->device); + return error; +} + +static rdpPrinterDriver* printer_load_backend(const char* backend) +{ + typedef rdpPrinterDriver* (*backend_load_t)(void); + union { + PVIRTUALCHANNELENTRY entry; + backend_load_t backend; + } fktconv; + + fktconv.entry = freerdp_load_channel_addin_entry("printer", backend, NULL, 0); + if (!fktconv.entry) + return NULL; + + return fktconv.backend(); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT +#ifdef BUILTIN_CHANNELS +printer_DeviceServiceEntry +#else + FREERDP_API + DeviceServiceEntry +#endif + (PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + int i; + char* name; + char* driver_name; + BOOL default_backend = TRUE; + RDPDR_PRINTER* device = NULL; + rdpPrinterDriver* driver = NULL; + UINT error = CHANNEL_RC_OK; + + if (!pEntryPoints || !pEntryPoints->device) + return ERROR_INVALID_PARAMETER; + + device = (RDPDR_PRINTER*)pEntryPoints->device; + name = device->Name; + driver_name = _strdup(device->DriverName); + + /* Secondary argument is one of the following: + * + * ... name of a printer driver + * : ... name of a printer driver and local printer backend to use + */ + if (driver_name) + { + char* sep = strstr(driver_name, ":"); + if (sep) + { + const char* backend = sep + 1; + *sep = '\0'; + driver = printer_load_backend(backend); + default_backend = FALSE; + } + } + + if (!driver && default_backend) + { + const char* backend = +#if defined(WITH_CUPS) + "cups" +#elif defined(_WIN32) + "win" +#else + "" +#endif + ; + + driver = printer_load_backend(backend); + } + + if (!driver) + { + WLog_ERR(TAG, "Could not get a printer driver!"); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + if (name && name[0]) + { + rdpPrinter* printer = driver->GetPrinter(driver, name, driver_name); + + if (!printer) + { + WLog_ERR(TAG, "Could not get printer %s!", name); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + if (!printer_save_default_config(pEntryPoints->rdpcontext->settings, printer)) + { + error = CHANNEL_RC_INITIALIZATION_ERROR; + printer->ReleaseRef(printer); + goto fail; + } + + if ((error = printer_register(pEntryPoints, printer))) + { + WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error); + printer->ReleaseRef(printer); + goto fail; + } + } + else + { + rdpPrinter** printers = driver->EnumPrinters(driver); + rdpPrinter** current = printers; + + for (i = 0; current[i]; i++) + { + rdpPrinter* printer = current[i]; + + if ((error = printer_register(pEntryPoints, printer))) + { + WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error); + break; + } + } + + driver->ReleaseEnumPrinters(printers); + } + +fail: + free(driver_name); + if (driver) + driver->ReleaseRef(driver); + + return error; +} diff --git a/channels/printer/client/win/CMakeLists.txt b/channels/printer/client/win/CMakeLists.txt new file mode 100644 index 0000000..aa648fd --- /dev/null +++ b/channels/printer/client/win/CMakeLists.txt @@ -0,0 +1,29 @@ +# 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("printer" "win" "") + +set(${MODULE_PREFIX}_SRCS + printer_win.c) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/channels/printer/client/win/printer_win.c b/channels/printer/client/win/printer_win.c new file mode 100644 index 0000000..86b5e66 --- /dev/null +++ b/channels/printer/client/win/printer_win.c @@ -0,0 +1,459 @@ +/** + * 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 + +#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 + +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; + + size_t id_sequence; + size_t references; +}; + +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 WCHAR* printer_win_get_printjob_name(size_t id) +{ + time_t tt; + struct tm tres; + struct tm* t; + WCHAR* str; + size_t len = 1024; + int rc; + + tt = time(NULL); + t = localtime_s(&tt, &tres); + + str = calloc(len, sizeof(WCHAR)); + if (!str) + return NULL; + + rc = swprintf_s(str, len, L"FreeRDP Print %04d-%02d-%02d% 02d-%02d-%02d - Job %lu\0", + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, + id); + + return str; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_win_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) +{ + rdpWinPrinter* printer; + LPCVOID pBuf = data; + DWORD cbBuf = size; + DWORD pcWritten; + + if (!printjob || !data) + return ERROR_BAD_ARGUMENTS; + + printer = (rdpWinPrinter*)printjob->printer; + if (!printer) + return ERROR_BAD_ARGUMENTS; + + if (!WritePrinter(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; + rdpWinPrinter* win_printer; + + if (!printjob) + return; + + win_printer = (rdpWinPrinter*)printjob->printer; + if (!win_printer) + return; + + if (!EndPagePrinter(win_printer->hPrinter)) + { + } + + if (!ClosePrinter(win_printer->hPrinter)) + { + } + + win_printer->printjob = NULL; + + free(win_printjob->di.pDocName); + 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 = printer_win_get_printjob_name(id); + 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->di.pDocName); + free(win_printjob); + return NULL; + } + + if (!StartPagePrinter(win_printer->hPrinter)) + { + free(win_printjob->di.pDocName); + 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 &win_printjob->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); + + if (printer->backend) + printer->backend->ReleaseRef(printer->backend); + + free(printer->name); + free(printer->driver); + free(printer); +} + +static void printer_win_add_ref_printer(rdpPrinter* printer) +{ + if (printer) + printer->references++; +} + +static void printer_win_release_ref_printer(rdpPrinter* printer) +{ + if (!printer) + return; + if (printer->references <= 1) + printer_win_free_printer(printer); + else + printer->references--; +} + +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.backend = &win_driver->driver; + 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.AddRef = printer_win_add_ref_printer; + win_printer->printer.ReleaseRef = printer_win_release_ref_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; + } + + win_printer->printer.AddRef(&win_printer->printer); + win_printer->printer.backend->AddRef(win_printer->printer.backend); + return &win_printer->printer; +} + +static void printer_win_release_enum_printers(rdpPrinter** printers) +{ + rdpPrinter** cur = printers; + + while ((cur != NULL) && ((*cur) != NULL)) + { + if ((*cur)->ReleaseRef) + (*cur)->ReleaseRef(*cur); + cur++; + } + free(printers); +} + +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++) + { + rdpPrinter* current = printers[num_printers]; + current = printer_win_new_printer((rdpWinPrinterDriver*)driver, prninfo[i].pPrinterName, + prninfo[i].pDriverName, 0); + if (!current) + { + printer_win_release_enum_printers(printers); + printers = NULL; + break; + } + printers[num_printers++] = current; + } + + GlobalFree(prninfo); + return printers; +} + +static rdpPrinter* printer_win_get_printer(rdpPrinterDriver* driver, const char* name, + const char* driverName) +{ + WCHAR* driverNameW = NULL; + WCHAR* nameW = NULL; + rdpWinPrinterDriver* win_driver = (rdpWinPrinterDriver*)driver; + rdpPrinter* myPrinter = NULL; + + if (name) + { + ConvertToUnicode(CP_UTF8, 0, name, -1, &nameW, 0); + if (!driverNameW) + return NULL; + } + if (driverName) + { + ConvertToUnicode(CP_UTF8, 0, driverName, -1, &driverNameW, 0); + if (!driverNameW) + return NULL; + } + + myPrinter = printer_win_new_printer(win_driver, nameW, driverNameW, + win_driver->id_sequence == 1 ? TRUE : FALSE); + free(driverNameW); + free(nameW); + + return myPrinter; +} + +static void printer_win_add_ref_driver(rdpPrinterDriver* driver) +{ + rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; + if (win) + win->references++; +} + +/* Singleton */ +static rdpWinPrinterDriver* win_driver = NULL; + +static void printer_win_release_ref_driver(rdpPrinterDriver* driver) +{ + rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; + if (win->references <= 1) + { + free(win); + win_driver = NULL; + } + else + win->references--; +} + +#ifdef BUILTIN_CHANNELS +rdpPrinterDriver* win_freerdp_printer_client_subsystem_entry(void) +#else +FREERDP_API rdpPrinterDriver* freerdp_printer_client_subsystem_entry(void) +#endif +{ + 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.ReleaseEnumPrinters = printer_win_release_enum_printers; + win_driver->driver.GetPrinter = printer_win_get_printer; + + win_driver->driver.AddRef = printer_win_add_ref_driver; + win_driver->driver.ReleaseRef = printer_win_release_ref_driver; + + win_driver->id_sequence = 1; + win_driver->driver.AddRef(&win_driver->driver); + } + + return &win_driver->driver; +} diff --git a/channels/printer/printer.h b/channels/printer/printer.h new file mode 100644 index 0000000..ae0902d --- /dev/null +++ b/channels/printer/printer.h @@ -0,0 +1,36 @@ +/** + * 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..d372dda --- /dev/null +++ b/channels/rail/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("rail") + +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/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..e19cb92 --- /dev/null +++ b/channels/rail/client/rail_main.c @@ -0,0 +1,895 @@ +/** + * 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) + { + Stream_Free(s, TRUE); + 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, wStream* src) +{ + wStream* s; + size_t length; + + if (!rail || !src) + return ERROR_INVALID_PARAMETER; + + length = Stream_GetPosition(src); + s = Stream_New(NULL, length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(s, Stream_Buffer(src), 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 (!utf8_string_to_rail_string(exec->RemoteApplicationProgram, + &ruExeOrFile) || /* RemoteApplicationProgram */ + !utf8_string_to_rail_string(exec->RemoteApplicationWorkingDir, + &ruWorkingDir) || /* ShellWorkingDirectory */ + !utf8_string_to_rail_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; + BOOL extendedSpiSupported; + + 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; + + case SPI_SETFILTERKEYS: + length += 20; + break; + + case SPI_SETSTICKYKEYS: + case SPI_SETCARETWIDTH: + case SPI_SETTOGGLEKEYS: + length += 4; + break; + + default: + return ERROR_BAD_ARGUMENTS; + } + + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags); + if ((error = rail_write_sysparam_order(s, sysparam, extendedSpiSupported))) + { + 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, TS_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_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_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_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); +} + +static UINT rail_client_language_ime_info(RailClientContext* context, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + railPlugin* rail; + + if (!context || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_languageime_info_order(rail, langImeInfo); +} + +/** + * 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); +} + +static UINT rail_client_compartment_info(RailClientContext* context, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + railPlugin* rail; + + if (!context || !compartmentInfo || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_compartment_info_order(rail, compartmentInfo); +} + +static UINT rail_client_cloak(RailClientContext* context, const RAIL_CLOAK* cloak) +{ + railPlugin* rail; + + if (!context || !cloak || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_cloak_order(rail, cloak); +} + +static UINT rail_client_snap_arrange(RailClientContext* context, const RAIL_SNAP_ARRANGE* snap) +{ + railPlugin* rail; + + if (!context || !snap || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_snap_arrange_order(rail, snap); +} + +/** + * 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, 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rail || (rail->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rail && 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) +{ + RailClientContext* context = rail_get_client_interface(rail); + 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; + } + + if (context) + { + IFCALLRET(context->OnOpen, status, context, &rail->sendHandshake); + + if (status != CHANNEL_RC_OK) + WLog_ERR(TAG, "context->OnOpen failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), 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; + } + + /* Default to automatically replying to server handshakes */ + rail->sendHandshake = TRUE; + 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_SVC_CHANNEL_NAME); + 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->ClientSystemCommand = rail_client_system_command; + context->ClientHandshake = rail_client_handshake; + context->ClientNotifyEvent = rail_client_notify_event; + context->ClientWindowMove = rail_client_window_move; + context->ClientInformation = rail_client_information; + context->ClientSystemMenu = rail_client_system_menu; + context->ClientLanguageBarInfo = rail_client_language_bar_info; + context->ClientLanguageIMEInfo = rail_client_language_ime_info; + context->ClientGetAppIdRequest = rail_client_get_appid_request; + context->ClientSnapArrange = rail_client_snap_arrange; + context->ClientCloak = rail_client_cloak; + context->ClientCompartmentInfo = rail_client_compartment_info; + 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..63e522e --- /dev/null +++ b/channels/rail/client/rail_main.h @@ -0,0 +1,63 @@ +/** + * 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; + DWORD channelBuildNumber; + DWORD channelFlags; + RAIL_CLIENT_STATUS_ORDER clientStatus; + BOOL sendHandshake; +}; +typedef struct rail_plugin railPlugin; + +RailClientContext* rail_get_client_interface(railPlugin* rail); +UINT rail_send_channel_data(railPlugin* rail, wStream* s); + +#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..4070b0f --- /dev/null +++ b/channels/rail/client/rail_orders.c @@ -0,0 +1,1591 @@ +/** + * 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" + +static BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType) +{ + char buffer[128] = { 0 }; + 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_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + return rail_send_channel_data(rail, s); +} + +/** + * 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) < RAIL_EXEC_RESULT_ORDER_LENGTH) + { + 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_minmaxinfo_order(wStream* s, RAIL_MINMAXINFO_ORDER* minmaxinfo) +{ + if (!s || !minmaxinfo) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < RAIL_MINMAXINFO_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, minmaxinfo->windowId); /* windowId (4 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxWidth); /* maxWidth (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxHeight); /* maxHeight (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxPosX); /* maxPosX (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxPosY); /* maxPosY (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->minTrackWidth); /* minTrackWidth (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->minTrackHeight); /* minTrackHeight (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxTrackWidth); /* maxTrackWidth (2 bytes) */ + Stream_Read_INT16(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) < RAIL_LOCALMOVESIZE_ORDER_LENGTH) + { + 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_INT16(s, localMoveSize->posX); /* posX (2 bytes) */ + Stream_Read_INT16(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) < RAIL_GET_APPID_RESP_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, getAppidResp->windowId); /* windowId (4 bytes) */ + Stream_Read_UTF16_String( + s, getAppidResp->applicationId, + ARRAYSIZE(getAppidResp->applicationId)); /* applicationId (260 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) < RAIL_LANGBAR_INFO_ORDER_LENGTH) + { + 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; +} + +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_INT16(s, sysmenu->left); /* left (2 bytes) */ + Stream_Write_INT16(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_INT16(s, windowMove->left); /* left (2 bytes) */ + Stream_Write_INT16(s, windowMove->top); /* top (2 bytes) */ + Stream_Write_INT16(s, windowMove->right); /* right (2 bytes) */ + Stream_Write_INT16(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; +} + +static UINT rail_write_languageime_info_order(wStream* s, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + if (!s || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, langImeInfo->ProfileType); + Stream_Write_UINT16(s, langImeInfo->LanguageID); + Stream_Write(s, &langImeInfo->LanguageProfileCLSID, sizeof(langImeInfo->LanguageProfileCLSID)); + Stream_Write(s, &langImeInfo->ProfileGUID, sizeof(langImeInfo->ProfileGUID)); + Stream_Write_UINT32(s, langImeInfo->KeyboardLayout); + return ERROR_SUCCESS; +} + +static UINT rail_write_compartment_info_order(wStream* s, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (!s || !compartmentInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, compartmentInfo->ImeState); + Stream_Write_UINT32(s, compartmentInfo->ImeConvMode); + Stream_Write_UINT32(s, compartmentInfo->ImeSentenceMode); + Stream_Write_UINT32(s, compartmentInfo->KanaMode); + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_HANDSHAKE_ORDER serverHandshake = { 0 }; + UINT error; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_handshake_order(s, &serverHandshake))) + { + WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error); + return error; + } + + rail->channelBuildNumber = serverHandshake.buildNumber; + + if (rail->sendHandshake) + { + RAIL_HANDSHAKE_ORDER clientHandshake = { 0 }; + clientHandshake.buildNumber = 0x00001DB0; + error = context->ClientHandshake(context, &clientHandshake); + } + + if (error != CHANNEL_RC_OK) + return error; + + if (context->custom) + { + IFCALLRET(context->ServerHandshake, error, context, &serverHandshake); + + if (error) + WLog_ERR(TAG, "context.ServerHandshake failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_compartment_info_order(wStream* s, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */ + return CHANNEL_RC_OK; +} + +static UINT rail_recv_compartmentinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_COMPARTMENT_INFO_ORDER pdu = { 0 }; + UINT error; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_compartment_info_order(s, &pdu))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientCompartmentInfo, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask) +{ + UINT32 supported, masked; + + if (!context || !context->settings) + return FALSE; + + supported = context->settings->RemoteApplicationSupportLevel & + context->settings->RemoteApplicationSupportMask; + masked = (supported & featureMask); + + if (masked != featureMask) + { + char mask[256] = { 0 }; + char actual[256] = { 0 }; + + WLog_WARN(TAG, "[%s] have %s, require %s", __func__, + freerdp_rail_support_flags_to_string(supported, actual, sizeof(actual)), + freerdp_rail_support_flags_to_string(featureMask, mask, sizeof(mask))); + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_ex_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_HANDSHAKE_EX_ORDER serverHandshake = { 0 }; + UINT error; + + if (!rail || !context || !s) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_handshake_ex_order(s, &serverHandshake))) + { + WLog_ERR(TAG, "rail_read_handshake_ex_order failed with error %" PRIu32 "!", error); + return error; + } + + rail->channelBuildNumber = serverHandshake.buildNumber; + rail->channelFlags = serverHandshake.railHandshakeFlags; + + if (rail->sendHandshake) + { + RAIL_HANDSHAKE_ORDER clientHandshake = { 0 }; + clientHandshake.buildNumber = 0x00001DB0; + /* 2.2.2.2.3 HandshakeEx PDU (TS_RAIL_ORDER_HANDSHAKE_EX) + * Client response is really a Handshake PDU */ + error = context->ClientHandshake(context, &clientHandshake); + } + + if (error != CHANNEL_RC_OK) + return error; + + if (context->custom) + { + IFCALLRET(context->ServerHandshakeEx, error, context, &serverHandshake); + + if (error) + WLog_ERR(TAG, "context.ServerHandshakeEx 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, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_EXEC_RESULT_ORDER execResult = { 0 }; + UINT error; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_exec_result_order(s, &execResult))) + { + WLog_ERR(TAG, "rail_read_server_exec_result_order failed with error %" PRIu32 "!", error); + goto fail; + } + + if (context->custom) + { + IFCALLRET(context->ServerExecuteResult, error, context, &execResult); + + if (error) + WLog_ERR(TAG, "context.ServerExecuteResult failed with error %" PRIu32 "", error); + } + +fail: + free(execResult.exeOrFile.string); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_sysparam_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_SYSPARAM_ORDER sysparam; + UINT error; + BOOL extendedSpiSupported; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags); + if ((error = rail_read_sysparam_order(s, &sysparam, extendedSpiSupported))) + { + WLog_ERR(TAG, "rail_read_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, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_MINMAXINFO_ORDER minMaxInfo = { 0 }; + UINT error; + + if (!context || !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, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_LOCALMOVESIZE_ORDER localMoveSize = { 0 }; + UINT error; + + if (!context || !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, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_GET_APPID_RESP_ORDER getAppIdResp = { 0 }; + UINT error; + + if (!context || !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, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_LANGBAR_INFO_ORDER langBarInfo = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + 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; +} + +static UINT rail_read_taskbar_info_order(wStream* s, RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + if (!s || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < RAIL_TASKBAR_INFO_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, taskbarInfo->TaskbarMessage); + Stream_Read_UINT32(s, taskbarInfo->WindowIdTab); + Stream_Read_UINT32(s, taskbarInfo->Body); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_taskbar_info_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_TASKBAR_INFO_ORDER taskBarInfo = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.14.1 Taskbar Tab Info PDU (TS_RAIL_ORDER_TASKBARINFO) + * server -> client message only supported if announced. */ + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_taskbar_info_order(s, &taskBarInfo))) + { + WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerTaskBarInfo, error, context, &taskBarInfo); + + if (error) + WLog_ERR(TAG, "context.ServerTaskBarInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_zorder_sync_order(wStream* s, RAIL_ZORDER_SYNC* zorder) +{ + if (!s || !zorder) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < RAIL_Z_ORDER_SYNC_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, zorder->windowIdMarker); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_zorder_sync_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_ZORDER_SYNC zorder = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_ZORDER_SYNC) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_zorder_sync_order(s, &zorder))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerZOrderSync, error, context, &zorder); + + if (error) + WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak) +{ + BYTE cloaked; + + if (Stream_GetRemainingLength(s) < RAIL_CLOAK_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */ + cloak->cloak = (cloaked != 0) ? TRUE : FALSE; + return CHANNEL_RC_OK; +} + +static UINT rail_recv_cloak_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_CLOAK cloak = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.12.1 Window Cloak State Change PDU (TS_RAIL_ORDER_CLOAK) + * server -> client message only supported if announced. */ + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_cloak_order(s, &cloak))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerCloak, error, context, &cloak); + + if (error) + WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_power_display_request_order(wStream* s, RAIL_POWER_DISPLAY_REQUEST* power) +{ + UINT32 active; + + if (!s || !power) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, active); + power->active = active != 0; + return CHANNEL_RC_OK; +} + +static UINT rail_recv_power_display_request_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_POWER_DISPLAY_REQUEST power = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.13.1 Power Display Request PDU(TS_RAIL_ORDER_POWER_DISPLAY_REQUEST) + */ + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_power_display_request_order(s, &power))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerPowerDisplayRequest, error, context, &power); + + if (error) + WLog_ERR(TAG, "context.ServerPowerDisplayRequest failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_get_application_id_extended_response_order(wStream* s, + RAIL_GET_APPID_RESP_EX* id) +{ + if (!s || !id) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, id->windowID); + + if (!Stream_Read_UTF16_String(s, id->applicationID, ARRAYSIZE(id->applicationID))) + return ERROR_INVALID_DATA; + + if (_wcsnlen(id->applicationID, ARRAYSIZE(id->applicationID)) >= ARRAYSIZE(id->applicationID)) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, id->processId); + + if (!Stream_Read_UTF16_String(s, id->processImageName, ARRAYSIZE(id->processImageName))) + return ERROR_INVALID_DATA; + + if (_wcsnlen(id->applicationID, ARRAYSIZE(id->processImageName)) >= + ARRAYSIZE(id->processImageName)) + return ERROR_INVALID_DATA; + + return CHANNEL_RC_OK; +} + +static UINT rail_recv_get_application_id_extended_response_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_GET_APPID_RESP_EX id = { 0 }; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_get_application_id_extended_response_order(s, &id))) + { + WLog_ERR(TAG, + "rail_read_get_application_id_extended_response_order failed with error %" PRIu32 + "!", + error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerGetAppidResponseExtended, error, context, &id); + + if (error) + WLog_ERR(TAG, "context.ServerGetAppidResponseExtended failed with error %" PRIu32 "", + error); + } + + return error; +} + +static UINT rail_read_textscaleinfo_order(wStream* s, UINT32* pTextScaleFactor) +{ + WINPR_ASSERT(pTextScaleFactor); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, *pTextScaleFactor); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_textscaleinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT32 TextScaleFactor = 0; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_textscaleinfo_order(s, &TextScaleFactor))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor); + + if (error) + WLog_ERR(TAG, "context.ClientTextScale failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_caretblinkinfo_order(wStream* s, UINT32* pCaretBlinkRate) +{ + WINPR_ASSERT(pCaretBlinkRate); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, *pCaretBlinkRate); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_caretblinkinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT32 CaretBlinkRate = 0; + UINT error; + + if (!context) + return ERROR_INVALID_PARAMETER; + if ((error = rail_read_caretblinkinfo_order(s, &CaretBlinkRate))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate); + + if (error) + WLog_ERR(TAG, "context.ClientCaretBlinkRate 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) +{ + char buffer[128] = { 0 }; + UINT16 orderType; + UINT16 orderLength; + UINT error = CHANNEL_RC_OK; + + 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_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + + switch (orderType) + { + case TS_RAIL_ORDER_HANDSHAKE: + error = rail_recv_handshake_order(rail, s); + break; + + case TS_RAIL_ORDER_COMPARTMENTINFO: + error = rail_recv_compartmentinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_HANDSHAKE_EX: + error = rail_recv_handshake_ex_order(rail, s); + break; + + case TS_RAIL_ORDER_EXEC_RESULT: + error = rail_recv_exec_result_order(rail, s); + break; + + case TS_RAIL_ORDER_SYSPARAM: + error = rail_recv_server_sysparam_order(rail, s); + break; + + case TS_RAIL_ORDER_MINMAXINFO: + error = rail_recv_server_minmaxinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_LOCALMOVESIZE: + error = rail_recv_server_localmovesize_order(rail, s); + break; + + case TS_RAIL_ORDER_GET_APPID_RESP: + error = rail_recv_server_get_appid_resp_order(rail, s); + break; + + case TS_RAIL_ORDER_LANGBARINFO: + error = rail_recv_langbar_info_order(rail, s); + break; + + case TS_RAIL_ORDER_TASKBARINFO: + error = rail_recv_taskbar_info_order(rail, s); + break; + + case TS_RAIL_ORDER_ZORDER_SYNC: + error = rail_recv_zorder_sync_order(rail, s); + break; + + case TS_RAIL_ORDER_CLOAK: + error = rail_recv_cloak_order(rail, s); + break; + + case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST: + error = rail_recv_power_display_request_order(rail, s); + break; + + case TS_RAIL_ORDER_GET_APPID_RESP_EX: + error = rail_recv_get_application_id_extended_response_order(rail, s); + break; + + case TS_RAIL_ORDER_TEXTSCALEINFO: + error = rail_recv_textscaleinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_CARETBLINKINFO: + error = rail_recv_caretblinkinfo_order(rail, s); + break; + + default: + WLog_ERR(TAG, "Unknown RAIL PDU %s received.", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer))); + return ERROR_INVALID_DATA; + } + + if (error != CHANNEL_RC_OK) + { + char buffer[128] = { 0 }; + WLog_Print(rail->log, WLOG_ERROR, "Failed to process rail %s PDU, length:%" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + } + + return error; +} + +/** + * 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, TS_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, TS_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; + + rail->clientStatus = *clientStatus; + 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, TS_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, TS_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 + */ +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, TS_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, TS_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, TS_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, TS_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, TS_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, TS_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; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + 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, TS_RAIL_ORDER_LANGBARINFO); + + Stream_Free(s, TRUE); + return error; +} + +UINT rail_send_client_languageime_info_order(railPlugin* rail, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + wStream* s; + UINT error; + + if (!rail || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + s = rail_pdu_init(RAIL_LANGUAGEIME_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_languageime_info_order(s, langImeInfo); + + if (ERROR_SUCCESS == error) + error = rail_send_pdu(rail, s, TS_RAIL_ORDER_LANGUAGEIMEINFO); + + Stream_Free(s, TRUE); + return error; +} + +UINT rail_send_client_compartment_info_order(railPlugin* rail, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + wStream* s; + UINT error; + + if (!rail || !compartmentInfo) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + s = rail_pdu_init(RAIL_COMPARTMENT_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_compartment_info_order(s, compartmentInfo); + + if (ERROR_SUCCESS == error) + error = rail_send_pdu(rail, s, TS_RAIL_ORDER_COMPARTMENTINFO); + + Stream_Free(s, TRUE); + return error; +} + +UINT rail_send_client_cloak_order(railPlugin* rail, const RAIL_CLOAK* cloak) +{ + wStream* s; + UINT error; + + if (!rail || !cloak) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(5); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, cloak->windowId); + Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); + error = rail_send_pdu(rail, s, TS_RAIL_ORDER_CLOAK); + Stream_Free(s, TRUE); + return error; +} + +UINT rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap) +{ + wStream* s; + UINT error; + + if (!rail) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.7.5 Client Window Snap PDU (TS_RAIL_ORDER_SNAP_ARRANGE) */ + if ((rail->channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED) == 0) + { + RAIL_WINDOW_MOVE_ORDER move = { 0 }; + move.top = snap->top; + move.left = snap->left; + move.right = snap->right; + move.bottom = snap->bottom; + move.windowId = snap->windowId; + return rail_send_client_window_move_order(rail, &move); + } + + s = rail_pdu_init(12); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, snap->windowId); + Stream_Write_INT16(s, snap->left); + Stream_Write_INT16(s, snap->top); + Stream_Write_INT16(s, snap->right); + Stream_Write_INT16(s, snap->bottom); + error = rail_send_pdu(rail, s, TS_RAIL_ORDER_SNAP_ARRANGE); + 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..89ba6cc --- /dev/null +++ b/channels/rail/client/rail_orders.h @@ -0,0 +1,60 @@ +/** + * 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_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); +UINT rail_send_client_languageime_info_order(railPlugin* rail, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo); +UINT rail_send_client_cloak_order(railPlugin* rail, const RAIL_CLOAK* cloak); +UINT rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap); +UINT rail_send_client_compartment_info_order(railPlugin* rail, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo); + +#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..e77616d --- /dev/null +++ b/channels/rail/rail_common.c @@ -0,0 +1,618 @@ +/** + * 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 +#include + +#define TAG CHANNELS_TAG("rail.common") + +const char* rail_get_order_type_string(UINT16 orderType) +{ + switch (orderType) + { + case TS_RAIL_ORDER_EXEC: + return "TS_RAIL_ORDER_EXEC"; + case TS_RAIL_ORDER_ACTIVATE: + return "TS_RAIL_ORDER_ACTIVATE"; + case TS_RAIL_ORDER_SYSPARAM: + return "TS_RAIL_ORDER_SYSPARAM"; + case TS_RAIL_ORDER_SYSCOMMAND: + return "TS_RAIL_ORDER_SYSCOMMAND"; + case TS_RAIL_ORDER_HANDSHAKE: + return "TS_RAIL_ORDER_HANDSHAKE"; + case TS_RAIL_ORDER_NOTIFY_EVENT: + return "TS_RAIL_ORDER_NOTIFY_EVENT"; + case TS_RAIL_ORDER_WINDOWMOVE: + return "TS_RAIL_ORDER_WINDOWMOVE"; + case TS_RAIL_ORDER_LOCALMOVESIZE: + return "TS_RAIL_ORDER_LOCALMOVESIZE"; + case TS_RAIL_ORDER_MINMAXINFO: + return "TS_RAIL_ORDER_MINMAXINFO"; + case TS_RAIL_ORDER_CLIENTSTATUS: + return "TS_RAIL_ORDER_CLIENTSTATUS"; + case TS_RAIL_ORDER_SYSMENU: + return "TS_RAIL_ORDER_SYSMENU"; + case TS_RAIL_ORDER_LANGBARINFO: + return "TS_RAIL_ORDER_LANGBARINFO"; + case TS_RAIL_ORDER_GET_APPID_REQ: + return "TS_RAIL_ORDER_GET_APPID_REQ"; + case TS_RAIL_ORDER_GET_APPID_RESP: + return "TS_RAIL_ORDER_GET_APPID_RESP"; + case TS_RAIL_ORDER_TASKBARINFO: + return "TS_RAIL_ORDER_TASKBARINFO"; + case TS_RAIL_ORDER_LANGUAGEIMEINFO: + return "TS_RAIL_ORDER_LANGUAGEIMEINFO"; + case TS_RAIL_ORDER_COMPARTMENTINFO: + return "TS_RAIL_ORDER_COMPARTMENTINFO"; + case TS_RAIL_ORDER_HANDSHAKE_EX: + return "TS_RAIL_ORDER_HANDSHAKE_EX"; + case TS_RAIL_ORDER_ZORDER_SYNC: + return "TS_RAIL_ORDER_ZORDER_SYNC"; + case TS_RAIL_ORDER_CLOAK: + return "TS_RAIL_ORDER_CLOAK"; + case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST: + return "TS_RAIL_ORDER_POWER_DISPLAY_REQUEST"; + case TS_RAIL_ORDER_SNAP_ARRANGE: + return "TS_RAIL_ORDER_SNAP_ARRANGE"; + case TS_RAIL_ORDER_GET_APPID_RESP_EX: + return "TS_RAIL_ORDER_GET_APPID_RESP_EX"; + case TS_RAIL_ORDER_EXEC_RESULT: + return "TS_RAIL_ORDER_EXEC_RESULT"; + case TS_RAIL_ORDER_TEXTSCALEINFO: + return "TS_RAIL_ORDER_TEXTSCALEINFO"; + case TS_RAIL_ORDER_CARETBLINKINFO: + return "TS_RAIL_ORDER_CARETBLINKINFO"; + default: + return "TS_RAIL_ORDER_UNKNOWN"; + } +} + +const char* rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length) +{ + _snprintf(buffer, length, "%s[0x%04" PRIx16 "]", rail_get_order_type_string(orderType), + orderType); + return buffer; +} + +/** + * 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) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +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 + */ +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 + */ +static UINT rail_read_high_contrast(wStream* s, RAIL_HIGH_CONTRAST* highContrast) +{ + if (!s || !highContrast) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, highContrast->flags); /* flags (4 bytes) */ + Stream_Read_UINT32(s, highContrast->colorSchemeLength); /* colorSchemeLength (4 bytes) */ + + if (!rail_read_unicode_string(s, &highContrast->colorScheme)) /* colorScheme */ + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +/** + * 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; + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return CHANNEL_RC_NO_MEMORY; + + 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_filterkeys(wStream* s, TS_FILTERKEYS* filterKeys) +{ + if (!s || !filterKeys) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 20) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, filterKeys->Flags); + Stream_Read_UINT32(s, filterKeys->WaitTime); + Stream_Read_UINT32(s, filterKeys->DelayTime); + Stream_Read_UINT32(s, filterKeys->RepeatTime); + Stream_Read_UINT32(s, filterKeys->BounceTime); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_filterkeys(wStream* s, const TS_FILTERKEYS* filterKeys) +{ + if (!s || !filterKeys) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 20)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, filterKeys->Flags); + Stream_Write_UINT32(s, filterKeys->WaitTime); + Stream_Write_UINT32(s, filterKeys->DelayTime); + Stream_Write_UINT32(s, filterKeys->RepeatTime); + Stream_Write_UINT32(s, filterKeys->BounceTime); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported) +{ + BYTE body; + UINT error = CHANNEL_RC_OK; + + 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) */ + + sysparam->params = 0; /* bitflags of received params */ + + switch (sysparam->param) + { + /* Client sysparams */ + case SPI_SET_DRAG_FULL_WINDOWS: + sysparam->params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->dragFullWindows = body != 0; + break; + + case SPI_SET_KEYBOARD_CUES: + sysparam->params |= SPI_MASK_SET_KEYBOARD_CUES; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->keyboardCues = body != 0; + break; + + case SPI_SET_KEYBOARD_PREF: + sysparam->params |= SPI_MASK_SET_KEYBOARD_PREF; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->keyboardPref = body != 0; + break; + + case SPI_SET_MOUSE_BUTTON_SWAP: + sysparam->params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->mouseButtonSwap = body != 0; + break; + + case SPI_SET_WORK_AREA: + sysparam->params |= SPI_MASK_SET_WORK_AREA; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */ + break; + + case SPI_DISPLAY_CHANGE: + sysparam->params |= SPI_MASK_DISPLAY_CHANGE; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */ + break; + + case SPI_TASKBAR_POS: + sysparam->params |= SPI_MASK_TASKBAR_POS; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */ + break; + + case SPI_SET_HIGH_CONTRAST: + sysparam->params |= SPI_MASK_SET_HIGH_CONTRAST; + if (Stream_GetRemainingLength(s) < 8) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + error = rail_read_high_contrast(s, &sysparam->highContrast); + break; + + case SPI_SETCARETWIDTH: + sysparam->params |= SPI_MASK_SET_CARET_WIDTH; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, sysparam->caretWidth); + + if (sysparam->caretWidth < 0x0001) + return ERROR_INVALID_DATA; + + break; + + case SPI_SETSTICKYKEYS: + sysparam->params |= SPI_MASK_SET_STICKY_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, sysparam->stickyKeys); + break; + + case SPI_SETTOGGLEKEYS: + sysparam->params |= SPI_MASK_SET_TOGGLE_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, sysparam->toggleKeys); + break; + + case SPI_SETFILTERKEYS: + sysparam->params |= SPI_MASK_SET_FILTER_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 20) + { + WLog_ERR(TAG, "Stream_GetRemainingLength failed!"); + return ERROR_INVALID_DATA; + } + + error = rail_read_filterkeys(s, &sysparam->filterKeys); + break; + + /* Server sysparams */ + case SPI_SETSCREENSAVEACTIVE: + sysparam->params |= SPI_MASK_SET_SCREEN_SAVE_ACTIVE; + + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->setScreenSaveActive = body != 0; + break; + + case SPI_SETSCREENSAVESECURE: + sysparam->params |= SPI_MASK_SET_SET_SCREEN_SAVE_SECURE; + + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->setScreenSaveSecure = body != 0; + break; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 err2or code + */ +UINT rail_write_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam, + BOOL extendedSpiSupported) +{ + BYTE body; + UINT error = CHANNEL_RC_OK; + + if (!s || !sysparam) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 12)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, sysparam->param); /* systemParam (4 bytes) */ + + switch (sysparam->param) + { + /* Client sysparams */ + 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; + + case SPI_SETCARETWIDTH: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (sysparam->caretWidth < 0x0001) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->caretWidth); + break; + + case SPI_SETSTICKYKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->stickyKeys); + break; + + case SPI_SETTOGGLEKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->toggleKeys); + break; + + case SPI_SETFILTERKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + error = rail_write_filterkeys(s, &sysparam->filterKeys); + break; + + /* Server sysparams */ + case SPI_SETSCREENSAVEACTIVE: + body = sysparam->setScreenSaveActive ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SETSCREENSAVESECURE: + body = sysparam->setScreenSaveSecure ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + default: + return ERROR_INVALID_PARAMETER; + } + + return error; +} + +BOOL rail_is_extended_spi_supported(UINT32 channelFlags) +{ + return channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED; +} diff --git a/channels/rail/rail_common.h b/channels/rail/rail_common.h new file mode 100644 index 0000000..34b6fa0 --- /dev/null +++ b/channels/rail/rail_common.h @@ -0,0 +1,76 @@ +/** + * 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 + +#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_EXEC_RESULT_ORDER_LENGTH 12 /* variable */ +#define RAIL_SYSPARAM_ORDER_LENGTH 4 /* variable */ +#define RAIL_MINMAXINFO_ORDER_LENGTH 20 /* fixed */ +#define RAIL_LOCALMOVESIZE_ORDER_LENGTH 12 /* fixed */ +#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_SNAP_ARRANGE_ORDER_LENGTH 12 /* fixed */ +#define RAIL_GET_APPID_REQ_ORDER_LENGTH 4 /* fixed */ +#define RAIL_LANGBAR_INFO_ORDER_LENGTH 4 /* fixed */ +#define RAIL_LANGUAGEIME_INFO_ORDER_LENGTH 42 /* fixed */ +#define RAIL_COMPARTMENT_INFO_ORDER_LENGTH 16 /* fixed */ +#define RAIL_CLOAK_ORDER_LENGTH 5 /* fixed */ +#define RAIL_TASKBAR_INFO_ORDER_LENGTH 12 /* fixed */ +#define RAIL_Z_ORDER_SYNC_ORDER_LENGTH 4 /* fixed */ +#define RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH 4 /* fixed */ +#define RAIL_GET_APPID_RESP_ORDER_LENGTH 524 /* fixed */ +#define RAIL_GET_APPID_RESP_EX_ORDER_LENGTH 1048 /* fixed */ + +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); + +UINT rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string); +UINT rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string); + +UINT rail_read_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported); +UINT rail_write_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam, + BOOL extendedSpiSupported); +BOOL rail_is_extended_spi_supported(UINT32 channelsFlags); +const char* rail_get_order_type_string(UINT16 orderType); +const char* rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length); + +#endif /* FREERDP_CHANNEL_RAIL_COMMON_H */ diff --git a/channels/rail/server/CMakeLists.txt b/channels/rail/server/CMakeLists.txt new file mode 100644 index 0000000..c7f1c7a --- /dev/null +++ b/channels/rail/server/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Mati Shabtay +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("rail") + +set(${MODULE_PREFIX}_SRCS + ../rail_common.c + ../rail_common.h + rail_main.c + rail_main.h) + +include_directories(..) + +add_channel_server_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}/Server") diff --git a/channels/rail/server/rail_main.c b/channels/rail/server/rail_main.c new file mode 100644 index 0000000..4949fb7 --- /dev/null +++ b/channels/rail/server/rail_main.c @@ -0,0 +1,1688 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2019 Mati Shabtay + * + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "rail_main.h" + +#define TAG CHANNELS_TAG("rail.server") + +/** + * Sends a single rail PDU on the channel + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send(RailServerContext* context, wStream* s, ULONG length) +{ + UINT status = CHANNEL_RC_OK; + ULONG written; + + if (!context) + return CHANNEL_RC_BAD_INIT_HANDLE; + + if (!WTSVirtualChannelWrite(context->priv->rail_channel, (PCHAR)Stream_Buffer(s), length, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + status = ERROR_INTERNAL_ERROR; + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_send_pdu(RailServerContext* context, wStream* s, UINT16 orderType) +{ + char buffer[128] = { 0 }; + UINT16 orderLength; + + if (!context || !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_DBG(TAG, "Sending %s PDU, length: %" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + return rail_send(context, s, orderLength); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_local_move_size_order(wStream* s, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + if (!s || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, localMoveSize->windowId); /* WindowId (4 bytes) */ + Stream_Write_UINT16(s, localMoveSize->isMoveSizeStart ? 1 : 0); /* IsMoveSizeStart (2 bytes) */ + Stream_Write_UINT16(s, localMoveSize->moveSizeType); /* MoveSizeType (2 bytes) */ + Stream_Write_UINT16(s, localMoveSize->posX); /* PosX (2 bytes) */ + Stream_Write_UINT16(s, localMoveSize->posY); /* PosY (2 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_min_max_info_order(wStream* s, const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + if (!s || !minMaxInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, minMaxInfo->windowId); /* WindowId (4 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxWidth); /* MaxWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxHeight); /* MaxHeight (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxPosX); /* MaxPosX (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxPosY); /* MaxPosY (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->minTrackWidth); /* MinTrackWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->minTrackHeight); /* MinTrackHeight (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxTrackWidth); /* MaxTrackWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxTrackHeight); /* MaxTrackHeight (2 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_taskbar_info_order(wStream* s, const RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + if (!s || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, taskbarInfo->TaskbarMessage); /* TaskbarMessage (4 bytes) */ + Stream_Write_UINT32(s, taskbarInfo->WindowIdTab); /* WindowIdTab (4 bytes) */ + Stream_Write_UINT32(s, taskbarInfo->Body); /* Body (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +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_write_exec_result_order(wStream* s, const RAIL_EXEC_RESULT_ORDER* execResult) +{ + if (!s || !execResult) + return ERROR_INVALID_PARAMETER; + + if (execResult->exeOrFile.length > 520 || execResult->exeOrFile.length < 1) + return ERROR_INVALID_DATA; + + Stream_Write_UINT16(s, execResult->flags); /* Flags (2 bytes) */ + Stream_Write_UINT16(s, execResult->execResult); /* ExecResult (2 bytes) */ + Stream_Write_UINT32(s, execResult->rawResult); /* RawResult (4 bytes) */ + Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */ + Stream_Write_UINT16(s, execResult->exeOrFile.length); /* ExeOrFileLength (2 bytes) */ + Stream_Write(s, execResult->exeOrFile.string, + execResult->exeOrFile.length); /* ExeOrFile (variable) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_z_order_sync_order(wStream* s, const RAIL_ZORDER_SYNC* zOrderSync) +{ + if (!s || !zOrderSync) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, zOrderSync->windowIdMarker); /* WindowIdMarker (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_cloak_order(wStream* s, const RAIL_CLOAK* cloak) +{ + if (!s || !cloak) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); /* Cloaked (1 byte) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +rail_write_power_display_request_order(wStream* s, + const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest) +{ + if (!s || !powerDisplayRequest) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, powerDisplayRequest->active ? 1 : 0); /* Active (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_get_app_id_resp_order(wStream* s, + const RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + if (!s || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidResp->windowId); /* WindowId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidResp->applicationId, + ARRAYSIZE(getAppidResp->applicationId)); /* ApplicationId (512 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_get_appid_resp_ex_order(wStream* s, + const RAIL_GET_APPID_RESP_EX* getAppidRespEx) +{ + if (!s || !getAppidRespEx) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidRespEx->windowID); /* WindowId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidRespEx->applicationID, + ARRAYSIZE(getAppidRespEx->applicationID)); /* ApplicationId (520 bytes) */ + Stream_Write_UINT32(s, getAppidRespEx->processId); /* ProcessId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidRespEx->processImageName, + ARRAYSIZE(getAppidRespEx->processImageName)); /* ProcessImageName (520 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_handshake(RailServerContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + wStream* s; + UINT error; + + if (!context || !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_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_handshake_ex(RailServerContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + wStream* s; + UINT error; + + if (!context || !handshakeEx || !context->priv) + 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_server_set_handshake_ex_flags(context, handshakeEx->railHandshakeFlags); + + rail_write_handshake_ex_order(s, handshakeEx); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE_EX); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_sysparam(RailServerContext* context, + const RAIL_SYSPARAM_ORDER* sysparam) +{ + wStream* s; + UINT error; + RailServerPrivate* priv; + BOOL extendedSpiSupported; + + if (!context || !sysparam) + return ERROR_INVALID_PARAMETER; + + priv = context->priv; + + if (!priv) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags); + s = rail_pdu_init(RAIL_SYSPARAM_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_sysparam_order(s, sysparam, extendedSpiSupported); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_SYSPARAM); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_local_move_size(RailServerContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + wStream* s; + UINT error; + + if (!context || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_LOCALMOVESIZE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_local_move_size_order(s, localMoveSize); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LOCALMOVESIZE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_min_max_info(RailServerContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + wStream* s; + UINT error; + + if (!context || !minMaxInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_MINMAXINFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_min_max_info_order(s, minMaxInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_MINMAXINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_taskbar_info(RailServerContext* context, + const RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + wStream* s; + UINT error; + + if (!context || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_TASKBAR_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_taskbar_info_order(s, taskbarInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_TASKBARINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_langbar_info(RailServerContext* context, + const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + wStream* s; + UINT error; + + if (!context || !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; + } + + rail_write_langbar_info_order(s, langbarInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LANGBARINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_exec_result(RailServerContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + wStream* s; + UINT error; + + if (!context || !execResult) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_EXEC_RESULT_ORDER_LENGTH + execResult->exeOrFile.length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_exec_result_order(s, execResult); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_EXEC_RESULT); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_z_order_sync(RailServerContext* context, + const RAIL_ZORDER_SYNC* zOrderSync) +{ + wStream* s; + UINT error; + + if (!context || !zOrderSync) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_Z_ORDER_SYNC_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_z_order_sync_order(s, zOrderSync); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_ZORDER_SYNC); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_cloak(RailServerContext* context, const RAIL_CLOAK* cloak) +{ + wStream* s; + UINT error; + + if (!context || !cloak) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_CLOAK_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_cloak_order(s, cloak); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_CLOAK); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +rail_send_server_power_display_request(RailServerContext* context, + const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest) +{ + wStream* s; + UINT error; + + if (!context || !powerDisplayRequest) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_power_display_request_order(s, powerDisplayRequest); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_POWER_DISPLAY_REQUEST); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error coie + */ +static UINT rail_send_server_get_app_id_resp(RailServerContext* context, + const RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + wStream* s; + UINT error; + + if (!context || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_RESP_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_get_app_id_resp_order(s, getAppidResp); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_get_appid_resp_ex(RailServerContext* context, + const RAIL_GET_APPID_RESP_EX* getAppidRespEx) +{ + wStream* s; + UINT error; + + if (!context || !getAppidRespEx) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_RESP_EX_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_get_appid_resp_ex_order(s, getAppidRespEx); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP_EX); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_client_status_order(wStream* s, RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + if (Stream_GetRemainingLength(s) < RAIL_CLIENT_STATUS_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, clientStatus->flags); /* Flags (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_exec_order(wStream* s, RAIL_EXEC_ORDER* exec) +{ + RAIL_EXEC_ORDER order = { 0 }; + UINT16 exeLen, workLen, argLen; + + if (Stream_GetRemainingLength(s) < RAIL_EXEC_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, exec->flags); /* Flags (2 bytes) */ + Stream_Read_UINT16(s, exeLen); /* ExeOrFileLength (2 bytes) */ + Stream_Read_UINT16(s, workLen); /* WorkingDirLength (2 bytes) */ + Stream_Read_UINT16(s, argLen); /* ArgumentsLength (2 bytes) */ + + if (Stream_GetRemainingLength(s) < (size_t)exeLen + workLen + argLen) + return ERROR_INVALID_DATA; + + { + const int len = exeLen / sizeof(WCHAR); + int rc; + const WCHAR* str = (const WCHAR*)Stream_Pointer(s); + rc = ConvertFromUnicode(CP_UTF8, 0, str, len, &exec->RemoteApplicationProgram, 0, NULL, + NULL); + if (rc != len) + goto fail; + Stream_Seek(s, exeLen); + } + { + const int len = workLen / sizeof(WCHAR); + int rc; + + const WCHAR* str = (const WCHAR*)Stream_Pointer(s); + rc = ConvertFromUnicode(CP_UTF8, 0, str, len, &exec->RemoteApplicationProgram, 0, NULL, + NULL); + if (rc != len) + goto fail; + Stream_Seek(s, workLen); + } + { + const int len = argLen / sizeof(WCHAR); + int rc; + const WCHAR* str = (const WCHAR*)Stream_Pointer(s); + rc = ConvertFromUnicode(CP_UTF8, 0, str, len, &exec->RemoteApplicationProgram, 0, NULL, + NULL); + if (rc != len) + goto fail; + Stream_Seek(s, argLen); + } + + return CHANNEL_RC_OK; +fail: + free(exec->RemoteApplicationProgram); + free(exec->RemoteApplicationArguments); + free(exec->RemoteApplicationWorkingDir); + *exec = order; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_activate_order(wStream* s, RAIL_ACTIVATE_ORDER* activate) +{ + BYTE enabled; + + if (Stream_GetRemainingLength(s) < RAIL_ACTIVATE_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, activate->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, enabled); /* Enabled (1 byte) */ + activate->enabled = (enabled != 0) ? TRUE : FALSE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_sysmenu_order(wStream* s, RAIL_SYSMENU_ORDER* sysmenu) +{ + if (Stream_GetRemainingLength(s) < RAIL_SYSMENU_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysmenu->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, sysmenu->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, sysmenu->top); /* Top (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_syscommand_order(wStream* s, RAIL_SYSCOMMAND_ORDER* syscommand) +{ + if (Stream_GetRemainingLength(s) < RAIL_SYSCOMMAND_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, syscommand->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT16(s, syscommand->command); /* Command (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_notify_event_order(wStream* s, RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + if (Stream_GetRemainingLength(s) < RAIL_NOTIFY_EVENT_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, notifyEvent->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT32(s, notifyEvent->notifyIconId); /* NotifyIconId (4 bytes) */ + Stream_Read_UINT32(s, notifyEvent->message); /* Message (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_get_appid_req_order(wStream* s, RAIL_GET_APPID_REQ_ORDER* getAppidReq) +{ + if (Stream_GetRemainingLength(s) < RAIL_GET_APPID_REQ_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, getAppidReq->windowId); /* WindowId (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_window_move_order(wStream* s, RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + if (Stream_GetRemainingLength(s) < RAIL_WINDOW_MOVE_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, windowMove->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, windowMove->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, windowMove->top); /* Top (2 bytes) */ + Stream_Read_INT16(s, windowMove->right); /* Right (2 bytes) */ + Stream_Read_INT16(s, windowMove->bottom); /* Bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_snap_arange_order(wStream* s, RAIL_SNAP_ARRANGE* snapArrange) +{ + if (Stream_GetRemainingLength(s) < RAIL_SNAP_ARRANGE_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, snapArrange->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, snapArrange->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, snapArrange->top); /* Top (2 bytes) */ + Stream_Read_INT16(s, snapArrange->right); /* Right (2 bytes) */ + Stream_Read_INT16(s, snapArrange->bottom); /* Bottom (2 bytes) */ + 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 (Stream_GetRemainingLength(s) < RAIL_LANGBAR_INFO_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_language_ime_info_order(wStream* s, + RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo) +{ + if (Stream_GetRemainingLength(s) < RAIL_LANGUAGEIME_INFO_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, languageImeInfo->ProfileType); /* ProfileType (4 bytes) */ + Stream_Read_UINT16(s, languageImeInfo->LanguageID); /* LanguageID (2 bytes) */ + Stream_Read( + s, &languageImeInfo->LanguageProfileCLSID, + sizeof(languageImeInfo->LanguageProfileCLSID)); /* LanguageProfileCLSID (16 bytes) */ + Stream_Read(s, &languageImeInfo->ProfileGUID, + sizeof(languageImeInfo->ProfileGUID)); /* ProfileGUID (16 bytes) */ + Stream_Read_UINT32(s, languageImeInfo->KeyboardLayout); /* KeyboardLayout (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_compartment_info_order(wStream* s, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (Stream_GetRemainingLength(s) < RAIL_COMPARTMENT_INFO_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak) +{ + BYTE cloaked; + + if (Stream_GetRemainingLength(s) < RAIL_CLOAK_ORDER_LENGTH) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */ + cloak->cloak = (cloaked != 0) ? TRUE : FALSE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_handshake_order(RailServerContext* context, + RAIL_HANDSHAKE_ORDER* handshake, wStream* s) +{ + 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; + } + + IFCALLRET(context->ClientHandshake, error, context, handshake); + + if (error) + WLog_ERR(TAG, "context.ClientHandshake failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_client_status_order(RailServerContext* context, + RAIL_CLIENT_STATUS_ORDER* clientStatus, wStream* s) +{ + UINT error; + + if (!context || !clientStatus || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_client_status_order(s, clientStatus))) + { + WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientClientStatus, error, context, clientStatus); + + if (error) + WLog_ERR(TAG, "context.ClientClientStatus failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_exec_order(RailServerContext* context, wStream* s) +{ + UINT error; + RAIL_EXEC_ORDER exec = { 0 }; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_exec_order(s, &exec))) + { + WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientExec, error, context, &exec); + + if (error) + WLog_ERR(TAG, "context.Exec failed with error %" PRIu32 "", error); + + free(exec.RemoteApplicationProgram); + free(exec.RemoteApplicationArguments); + free(exec.RemoteApplicationWorkingDir); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_sysparam_order(RailServerContext* context, + RAIL_SYSPARAM_ORDER* sysparam, wStream* s) +{ + UINT error; + BOOL extendedSpiSupported; + + if (!context || !sysparam || !s) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags); + if ((error = rail_read_sysparam_order(s, sysparam, extendedSpiSupported))) + { + WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSysparam, error, context, sysparam); + + if (error) + WLog_ERR(TAG, "context.ClientSysparam failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_activate_order(RailServerContext* context, + RAIL_ACTIVATE_ORDER* activate, wStream* s) +{ + UINT error; + + if (!context || !activate || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_activate_order(s, activate))) + { + WLog_ERR(TAG, "rail_read_activate_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientActivate, error, context, activate); + + if (error) + WLog_ERR(TAG, "context.ClientActivate failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_sysmenu_order(RailServerContext* context, RAIL_SYSMENU_ORDER* sysmenu, + wStream* s) +{ + UINT error; + + if (!context || !sysmenu || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_sysmenu_order(s, sysmenu))) + { + WLog_ERR(TAG, "rail_read_sysmenu_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSysmenu, error, context, sysmenu); + + if (error) + WLog_ERR(TAG, "context.ClientSysmenu failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_syscommand_order(RailServerContext* context, + RAIL_SYSCOMMAND_ORDER* syscommand, wStream* s) +{ + UINT error; + + if (!context || !syscommand || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_syscommand_order(s, syscommand))) + { + WLog_ERR(TAG, "rail_read_syscommand_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSyscommand, error, context, syscommand); + + if (error) + WLog_ERR(TAG, "context.ClientSyscommand failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_notify_event_order(RailServerContext* context, + RAIL_NOTIFY_EVENT_ORDER* notifyEvent, wStream* s) +{ + UINT error; + + if (!context || !notifyEvent || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_notify_event_order(s, notifyEvent))) + { + WLog_ERR(TAG, "rail_read_notify_event_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientNotifyEvent, error, context, notifyEvent); + + if (error) + WLog_ERR(TAG, "context.ClientNotifyEvent failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_window_move_order(RailServerContext* context, + RAIL_WINDOW_MOVE_ORDER* windowMove, wStream* s) +{ + UINT error; + + if (!context || !windowMove || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_window_move_order(s, windowMove))) + { + WLog_ERR(TAG, "rail_read_window_move_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientWindowMove, error, context, windowMove); + + if (error) + WLog_ERR(TAG, "context.ClientWindowMove failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_snap_arrange_order(RailServerContext* context, + RAIL_SNAP_ARRANGE* snapArrange, wStream* s) +{ + UINT error; + + if (!context || !snapArrange || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_snap_arange_order(s, snapArrange))) + { + WLog_ERR(TAG, "rail_read_snap_arange_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSnapArrange, error, context, snapArrange); + + if (error) + WLog_ERR(TAG, "context.ClientSnapArrange failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_get_appid_req_order(RailServerContext* context, + RAIL_GET_APPID_REQ_ORDER* getAppidReq, wStream* s) +{ + UINT error; + + if (!context || !getAppidReq || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_get_appid_req_order(s, getAppidReq))) + { + WLog_ERR(TAG, "rail_read_get_appid_req_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientGetAppidReq, error, context, getAppidReq); + + if (error) + WLog_ERR(TAG, "context.ClientGetAppidReq failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_langbar_info_order(RailServerContext* context, + RAIL_LANGBAR_INFO_ORDER* langbarInfo, wStream* s) +{ + UINT error; + + if (!context || !langbarInfo || !s) + 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; + } + + IFCALLRET(context->ClientLangbarInfo, error, context, langbarInfo); + + if (error) + WLog_ERR(TAG, "context.ClientLangbarInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_language_ime_info_order(RailServerContext* context, + RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo, + wStream* s) +{ + UINT error; + + if (!context || !languageImeInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_language_ime_info_order(s, languageImeInfo))) + { + WLog_ERR(TAG, "rail_read_language_ime_info_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientLanguageImeInfo, error, context, languageImeInfo); + + if (error) + WLog_ERR(TAG, "context.ClientLanguageImeInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_compartment_info(RailServerContext* context, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo, + wStream* s) +{ + UINT error; + + if (!context || !compartmentInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_compartment_info_order(s, compartmentInfo))) + { + WLog_ERR(TAG, "rail_read_compartment_info_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientCompartmentInfo, error, context, compartmentInfo); + + if (error) + WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_cloak_order(RailServerContext* context, RAIL_CLOAK* cloak, wStream* s) +{ + UINT error; + + if (!context || !cloak || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_cloak_order(s, cloak))) + { + WLog_ERR(TAG, "rail_read_cloak_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientCloak, error, context, cloak); + + if (error) + WLog_ERR(TAG, "context.Cloak failed with error %" PRIu32 "", error); + + return error; +} + +static DWORD WINAPI rail_server_thread(LPVOID arg) +{ + RailServerContext* context = (RailServerContext*)arg; + RailServerPrivate* priv = context->priv; + DWORD status; + DWORD nCount = 0; + HANDLE events[8]; + UINT error = CHANNEL_RC_OK; + events[nCount++] = priv->channelEvent; + events[nCount++] = 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; + + status = WaitForSingleObject(context->priv->channelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR( + TAG, + "WaitForSingleObject(context->priv->channelEvent, 0) failed with error %" PRIu32 + "!", + error); + break; + } + + if (status == WAIT_OBJECT_0) + { + if ((error = rail_server_handle_messages(context))) + { + WLog_ERR(TAG, "rail_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rail_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_start(RailServerContext* context) +{ + void* buffer = NULL; + DWORD bytesReturned; + RailServerPrivate* priv = context->priv; + UINT error = ERROR_INTERNAL_ERROR; + priv->rail_channel = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RAIL_SVC_CHANNEL_NAME); + + if (!priv->rail_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return error; + } + + if (!WTSVirtualChannelQuery(priv->rail_channel, 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); + context->priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!context->priv->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_close; + } + + context->priv->thread = CreateThread(NULL, 0, rail_server_thread, (void*)context, 0, NULL); + + if (!context->priv->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_stop_event; + } + + return CHANNEL_RC_OK; +out_stop_event: + CloseHandle(context->priv->stopEvent); + context->priv->stopEvent = NULL; +out_close: + WTSVirtualChannelClose(context->priv->rail_channel); + context->priv->rail_channel = NULL; + return error; +} + +static BOOL rail_server_stop(RailServerContext* context) +{ + RailServerPrivate* priv = (RailServerPrivate*)context->priv; + + if (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; + } + + if (priv->rail_channel) + { + WTSVirtualChannelClose(priv->rail_channel); + priv->rail_channel = NULL; + } + + priv->channelEvent = NULL; + return TRUE; +} + +RailServerContext* rail_server_context_new(HANDLE vcm) +{ + RailServerContext* context; + RailServerPrivate* priv; + context = (RailServerContext*)calloc(1, sizeof(RailServerContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + context->vcm = vcm; + context->Start = rail_server_start; + context->Stop = rail_server_stop; + context->ServerHandshake = rail_send_server_handshake; + context->ServerHandshakeEx = rail_send_server_handshake_ex; + context->ServerSysparam = rail_send_server_sysparam; + context->ServerLocalMoveSize = rail_send_server_local_move_size; + context->ServerMinMaxInfo = rail_send_server_min_max_info; + context->ServerTaskbarInfo = rail_send_server_taskbar_info; + context->ServerLangbarInfo = rail_send_server_langbar_info; + context->ServerExecResult = rail_send_server_exec_result; + context->ServerGetAppidResp = rail_send_server_get_app_id_resp; + context->ServerZOrderSync = rail_send_server_z_order_sync; + context->ServerCloak = rail_send_server_cloak; + context->ServerPowerDisplayRequest = rail_send_server_power_display_request; + context->ServerGetAppidRespEx = rail_send_server_get_appid_resp_ex; + context->priv = priv = (RailServerPrivate*)calloc(1, sizeof(RailServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto out_free; + } + + /* Create shared input stream */ + priv->input_stream = Stream_New(NULL, 4096); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto out_free_priv; + } + + return context; +out_free_priv: + free(context->priv); +out_free: + free(context); + return NULL; +} + +void rail_server_context_free(RailServerContext* context) +{ + if (context->priv) + Stream_Free(context->priv->input_stream, TRUE); + + free(context->priv); + free(context); +} + +void rail_server_set_handshake_ex_flags(RailServerContext* context, DWORD flags) +{ + RailServerPrivate* priv; + + if (!context || !context->priv) + return; + + priv = context->priv; + priv->channelFlags = flags; +} + +UINT rail_server_handle_messages(RailServerContext* context) +{ + char buffer[128] = { 0 }; + UINT status = CHANNEL_RC_OK; + DWORD bytesReturned; + UINT16 orderType; + UINT16 orderLength; + RailServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + /* Read header */ + if (!Stream_EnsureRemainingCapacity(s, RAIL_PDU_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed, RAIL_PDU_HEADER_LENGTH"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!WTSVirtualChannelRead(priv->rail_channel, 0, (PCHAR)Stream_Pointer(s), + RAIL_PDU_HEADER_LENGTH, &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + /* Parse header */ + if ((status = rail_read_pdu_header(s, &orderType, &orderLength)) != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", status); + return status; + } + + if (!Stream_EnsureRemainingCapacity(s, orderLength - RAIL_PDU_HEADER_LENGTH)) + { + WLog_ERR(TAG, + "Stream_EnsureRemainingCapacity failed, orderLength - RAIL_PDU_HEADER_LENGTH"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Read body */ + if (!WTSVirtualChannelRead(priv->rail_channel, 0, (PCHAR)Stream_Pointer(s), + orderLength - RAIL_PDU_HEADER_LENGTH, &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "Received %s PDU, length:%" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + + switch (orderType) + { + case TS_RAIL_ORDER_HANDSHAKE: + { + RAIL_HANDSHAKE_ORDER handshake; + return rail_recv_client_handshake_order(context, &handshake, s); + } + + case TS_RAIL_ORDER_CLIENTSTATUS: + { + RAIL_CLIENT_STATUS_ORDER clientStatus; + return rail_recv_client_client_status_order(context, &clientStatus, s); + } + + case TS_RAIL_ORDER_EXEC: + return rail_recv_client_exec_order(context, s); + + case TS_RAIL_ORDER_SYSPARAM: + { + RAIL_SYSPARAM_ORDER sysparam = { 0 }; + return rail_recv_client_sysparam_order(context, &sysparam, s); + } + + case TS_RAIL_ORDER_ACTIVATE: + { + RAIL_ACTIVATE_ORDER activate; + return rail_recv_client_activate_order(context, &activate, s); + } + + case TS_RAIL_ORDER_SYSMENU: + { + RAIL_SYSMENU_ORDER sysmenu; + return rail_recv_client_sysmenu_order(context, &sysmenu, s); + } + + case TS_RAIL_ORDER_SYSCOMMAND: + { + RAIL_SYSCOMMAND_ORDER syscommand; + return rail_recv_client_syscommand_order(context, &syscommand, s); + } + + case TS_RAIL_ORDER_NOTIFY_EVENT: + { + RAIL_NOTIFY_EVENT_ORDER notifyEvent; + return rail_recv_client_notify_event_order(context, ¬ifyEvent, s); + } + + case TS_RAIL_ORDER_WINDOWMOVE: + { + RAIL_WINDOW_MOVE_ORDER windowMove; + return rail_recv_client_window_move_order(context, &windowMove, s); + } + + case TS_RAIL_ORDER_SNAP_ARRANGE: + { + RAIL_SNAP_ARRANGE snapArrange; + return rail_recv_client_snap_arrange_order(context, &snapArrange, s); + } + + case TS_RAIL_ORDER_GET_APPID_REQ: + { + RAIL_GET_APPID_REQ_ORDER getAppidReq; + return rail_recv_client_get_appid_req_order(context, &getAppidReq, s); + } + + case TS_RAIL_ORDER_LANGBARINFO: + { + RAIL_LANGBAR_INFO_ORDER langbarInfo; + return rail_recv_client_langbar_info_order(context, &langbarInfo, s); + } + + case TS_RAIL_ORDER_LANGUAGEIMEINFO: + { + RAIL_LANGUAGEIME_INFO_ORDER languageImeInfo; + return rail_recv_client_language_ime_info_order(context, &languageImeInfo, s); + } + + case TS_RAIL_ORDER_COMPARTMENTINFO: + { + RAIL_COMPARTMENT_INFO_ORDER compartmentInfo; + return rail_recv_client_compartment_info(context, &compartmentInfo, s); + } + + case TS_RAIL_ORDER_CLOAK: + { + RAIL_CLOAK cloak; + return rail_recv_client_cloak_order(context, &cloak, s); + } + + default: + WLog_ERR(TAG, "Unknown RAIL PDU order received."); + return ERROR_INVALID_DATA; + } + + Stream_SetPosition(s, 0); + return status; +} diff --git a/channels/rail/server/rail_main.h b/channels/rail/server/rail_main.h new file mode 100644 index 0000000..5c56331 --- /dev/null +++ b/channels/rail/server/rail_main.h @@ -0,0 +1,44 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2019 Mati Shabtay + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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_SERVER_MAIN_H +#define FREERDP_CHANNEL_RAIL_SERVER_MAIN_H + +#include +#include + +#include +#include +#include + +#include "../rail_common.h" + +struct _rail_server_private +{ + HANDLE thread; + HANDLE stopEvent; + HANDLE channelEvent; + void* rail_channel; + + wStream* input_stream; + + DWORD channelFlags; +}; + +#endif /* FREERDP_CHANNEL_RAIL_SERVER_MAIN_H */ \ No newline at end of file diff --git a/channels/rdp2tcp/CMakeLists.txt b/channels/rdp2tcp/CMakeLists.txt new file mode 100644 index 0000000..31de127 --- /dev/null +++ b/channels/rdp2tcp/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("rdp2tcp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdp2tcp/ChannelOptions.cmake b/channels/rdp2tcp/ChannelOptions.cmake new file mode 100644 index 0000000..3cad9b1 --- /dev/null +++ b/channels/rdp2tcp/ChannelOptions.cmake @@ -0,0 +1,10 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "rdp2tcp" TYPE "static" + DESCRIPTION "Tunneling TCP over RDP" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/rdp2tcp/client/CMakeLists.txt b/channels/rdp2tcp/client/CMakeLists.txt new file mode 100644 index 0000000..2e51020 --- /dev/null +++ b/channels/rdp2tcp/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("rdp2tcp") + +set(${MODULE_PREFIX}_SRCS + rdp2tcp_main.c) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "VirtualChannelEntryEx") + +target_link_libraries(${MODULE_NAME} freerdp) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/rdp2tcp/client/rdp2tcp_main.c b/channels/rdp2tcp/client/rdp2tcp_main.c new file mode 100644 index 0000000..58a2ef5 --- /dev/null +++ b/channels/rdp2tcp/client/rdp2tcp_main.c @@ -0,0 +1,327 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * rdp2tcp Virtual Channel Extension + * + * Copyright 2017 Artur Zaprzala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#define RDP2TCP_CHAN_NAME "rdp2tcp" + +#include +#define TAG CLIENT_TAG(RDP2TCP_CHAN_NAME) + +static int const debug = 0; + +typedef struct +{ + HANDLE hStdOutputRead; + HANDLE hStdInputWrite; + HANDLE hProcess; + HANDLE copyThread; + HANDLE writeComplete; + DWORD openHandle; + void* initHandle; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + char buffer[16 * 1024]; +} Plugin; + +static int init_external_addin(Plugin* plugin) +{ + SECURITY_ATTRIBUTES saAttr; + STARTUPINFO siStartInfo; + PROCESS_INFORMATION procInfo; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + + // Create pipes + if (!CreatePipe(&plugin->hStdOutputRead, &siStartInfo.hStdOutput, &saAttr, 0)) + { + WLog_ERR(TAG, "stdout CreatePipe"); + return -1; + } + + if (!SetHandleInformation(plugin->hStdOutputRead, HANDLE_FLAG_INHERIT, 0)) + { + WLog_ERR(TAG, "stdout SetHandleInformation"); + return -1; + } + + if (!CreatePipe(&siStartInfo.hStdInput, &plugin->hStdInputWrite, &saAttr, 0)) + { + WLog_ERR(TAG, "stdin CreatePipe"); + return -1; + } + + if (!SetHandleInformation(plugin->hStdInputWrite, HANDLE_FLAG_INHERIT, 0)) + { + WLog_ERR(TAG, "stdin SetHandleInformation"); + return -1; + } + + // Execute plugin + if (!CreateProcess(NULL, + plugin->channelEntryPoints.pExtendedData, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &procInfo // receives PROCESS_INFORMATION + )) + { + WLog_ERR(TAG, "fork for addin"); + return -1; + } + + plugin->hProcess = procInfo.hProcess; + CloseHandle(procInfo.hThread); + CloseHandle(siStartInfo.hStdOutput); + CloseHandle(siStartInfo.hStdInput); + return 0; +} + +static void dumpData(char* data, unsigned length) +{ + unsigned const limit = 98; + unsigned l = length > limit ? limit / 2 : length; + unsigned i; + + for (i = 0; i < l; ++i) + { + printf("%02hhx", data[i]); + } + + if (length > limit) + { + printf("..."); + + for (i = length - l; i < length; ++i) + printf("%02hhx", data[i]); + } + + puts(""); +} + +static DWORD WINAPI copyThread(void* data) +{ + Plugin* plugin = (Plugin*)data; + size_t const bufsize = 16 * 1024; + + while (1) + { + DWORD dwRead; + char* buffer = malloc(bufsize); + + if (!buffer) + { + fprintf(stderr, "rdp2tcp copyThread: malloc failed\n"); + goto fail; + } + + // if (!ReadFile(plugin->hStdOutputRead, plugin->buffer, sizeof plugin->buffer, &dwRead, + // NULL)) + if (!ReadFile(plugin->hStdOutputRead, buffer, bufsize, &dwRead, NULL)) + { + free(buffer); + goto fail; + } + + if (debug > 1) + { + printf(">%8u ", (unsigned)dwRead); + dumpData(buffer, dwRead); + } + + if (plugin->channelEntryPoints.pVirtualChannelWriteEx( + plugin->initHandle, plugin->openHandle, buffer, dwRead, buffer) != CHANNEL_RC_OK) + { + free(buffer); + fprintf(stderr, "rdp2tcp copyThread failed %i\n", (int)dwRead); + goto fail; + } + + WaitForSingleObject(plugin->writeComplete, INFINITE); + ResetEvent(plugin->writeComplete); + } + +fail: + ExitThread(0); + return 0; +} + +static void closeChannel(Plugin* plugin) +{ + if (debug) + puts("rdp2tcp closing channel"); + + plugin->channelEntryPoints.pVirtualChannelCloseEx(plugin->initHandle, plugin->openHandle); +} + +static void dataReceived(Plugin* plugin, void* pData, UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + DWORD dwWritten; + + if (dataFlags & CHANNEL_FLAG_SUSPEND) + { + if (debug) + puts("rdp2tcp Channel Suspend"); + + return; + } + + if (dataFlags & CHANNEL_FLAG_RESUME) + { + if (debug) + puts("rdp2tcp Channel Resume"); + + return; + } + + if (debug > 1) + { + printf("<%c%3u/%3u ", dataFlags & CHANNEL_FLAG_FIRST ? ' ' : '+', totalLength, dataLength); + dumpData(pData, dataLength); + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (!WriteFile(plugin->hStdInputWrite, &totalLength, sizeof(totalLength), &dwWritten, NULL)) + closeChannel(plugin); + } + + if (!WriteFile(plugin->hStdInputWrite, pData, dataLength, &dwWritten, NULL)) + closeChannel(plugin); +} + +static void VCAPITYPE VirtualChannelOpenEventEx(LPVOID lpUserParam, DWORD openHandle, UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + Plugin* plugin = (Plugin*)lpUserParam; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + dataReceived(plugin, pData, dataLength, totalLength, dataFlags); + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + free(pData); + break; + case CHANNEL_EVENT_WRITE_COMPLETE: + SetEvent(plugin->writeComplete); + free(pData); + break; + } +} + +static VOID VCAPITYPE VirtualChannelInitEventEx(LPVOID lpUserParam, LPVOID pInitHandle, UINT event, + LPVOID pData, UINT dataLength) +{ + Plugin* plugin = (Plugin*)lpUserParam; + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if (debug) + puts("rdp2tcp connected"); + + plugin->writeComplete = CreateEvent(NULL, TRUE, FALSE, NULL); + plugin->copyThread = CreateThread(NULL, 0, copyThread, plugin, 0, NULL); + + if (plugin->channelEntryPoints.pVirtualChannelOpenEx( + pInitHandle, &plugin->openHandle, RDP2TCP_CHAN_NAME, + VirtualChannelOpenEventEx) != CHANNEL_RC_OK) + return; + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if (debug) + puts("rdp2tcp disconnected"); + + break; + + case CHANNEL_EVENT_TERMINATED: + if (debug) + puts("rdp2tcp terminated"); + + if (plugin->copyThread) + { + TerminateThread(plugin->copyThread, 0); + CloseHandle(plugin->writeComplete); + } + + CloseHandle(plugin->hStdInputWrite); + CloseHandle(plugin->hStdOutputRead); + TerminateProcess(plugin->hProcess, 0); + CloseHandle(plugin->hProcess); + free(plugin); + break; + } +} + +#if 1 +#define VirtualChannelEntryEx rdp2tcp_VirtualChannelEntryEx +#else +#define VirtualChannelEntryEx FREERDP_API VirtualChannelEntryEx +#endif +BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + CHANNEL_DEF channelDef; + Plugin* plugin = (Plugin*)calloc(1, sizeof(Plugin)); + + if (!plugin) + return FALSE; + + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + assert(pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX) && + pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER); + plugin->initHandle = pInitHandle; + plugin->channelEntryPoints = *pEntryPointsEx; + + if (init_external_addin(plugin) < 0) + return FALSE; + + strncpy(channelDef.name, RDP2TCP_CHAN_NAME, sizeof(channelDef.name)); + channelDef.options = + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP; + + if (pEntryPointsEx->pVirtualChannelInitEx(plugin, NULL, pInitHandle, &channelDef, 1, + VIRTUAL_CHANNEL_VERSION_WIN2000, + VirtualChannelInitEventEx) != CHANNEL_RC_OK) + return FALSE; + + return TRUE; +} + +// vim:ts=4 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..af3cdd6 --- /dev/null +++ b/channels/rdpdr/client/devman.c @@ -0,0 +1,231 @@ +/** + * 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; +} + +DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type) +{ + DEVICE* device = NULL; + ULONG_PTR* keys; + int count, x; + + if (!devman) + return NULL; + + ListDictionary_Lock(devman->devices); + count = ListDictionary_GetKeys(devman->devices, &keys); + + for (x = 0; x < count; x++) + { + DEVICE* cur = (DEVICE*)ListDictionary_GetItemValue(devman->devices, (void*)keys[x]); + + if (!cur) + continue; + + if (cur->type != type) + continue; + + device = cur; + break; + } + + free(keys); + ListDictionary_Unlock(devman->devices); + return device; +} + +static const char DRIVE_SERVICE_NAME[] = "drive"; +static const char PRINTER_SERVICE_NAME[] = "printer"; +static const char SMARTCARD_SERVICE_NAME[] = "smartcard"; +static const char SERIAL_SERVICE_NAME[] = "serial"; +static const char PARALLEL_SERVICE_NAME[] = "parallel"; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT devman_load_device_service(DEVMAN* devman, const RDPDR_DEVICE* device, rdpContext* rdpcontext) +{ + const char* ServiceName = NULL; + DEVICE_SERVICE_ENTRY_POINTS ep; + PDEVICE_SERVICE_ENTRY entry = NULL; + union { + const RDPDR_DEVICE* cdp; + RDPDR_DEVICE* dp; + } devconv; + + devconv.cdp = device; + 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 = devconv.dp; + 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..2e6019e --- /dev/null +++ b/channels/rdpdr/client/devman.h @@ -0,0 +1,36 @@ +/** + * 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, const RDPDR_DEVICE* device, rdpContext* rdpcontext); +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id); +DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type); + +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..b818b67 --- /dev/null +++ b/channels/rdpdr/client/irp.c @@ -0,0 +1,150 @@ +/** + * 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; + + return irp_free(irp); +} + +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..97c876b --- /dev/null +++ b/channels/rdpdr/client/rdpdr_capabilities.c @@ -0,0 +1,281 @@ +/** + * 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) +{ + WINPR_UNUSED(rdpdr); + 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; + WINPR_UNUSED(rdpdr); + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (capabilityLength < 4) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4U); + return CHANNEL_RC_OK; +} + +/* Output printer direction capability set */ +static void rdpdr_write_printer_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + 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; + WINPR_UNUSED(rdpdr); + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (capabilityLength < 4) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4U); + return CHANNEL_RC_OK; +} + +/* Output port redirection capability set */ +static void rdpdr_write_port_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + 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; + WINPR_UNUSED(rdpdr); + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (capabilityLength < 4U) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4U); + return CHANNEL_RC_OK; +} + +/* Output drive redirection capability set */ +static void rdpdr_write_drive_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + 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; + WINPR_UNUSED(rdpdr); + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (capabilityLength < 4) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4U); + return CHANNEL_RC_OK; +} + +/* Output smart card redirection capability set */ +static void rdpdr_write_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + 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; + WINPR_UNUSED(rdpdr); + + if (Stream_GetRemainingLength(s) < 2) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityLength); + + if (capabilityLength < 4) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + return ERROR_INVALID_DATA; + + Stream_Seek(s, capabilityLength - 4U); + 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..681ebce --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.c @@ -0,0 +1,1922 @@ +/** + * 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 +#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); +} + +#if defined(_UWP) || defined(__IOS__) + +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(const 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) +{ + size_t i; + DWORD unitmask = GetLogicalDrives(); + + for (i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '\\', '\0' }; + char drive_name[] = { 'c', '\0' }; + RDPDR_DRIVE drive = { 0 }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + drive.Type = RDPDR_DTYP_FILESYSTEM; + drive.Path = drive_path; + drive.Name = drive_name; + drive.automount = TRUE; + devman_load_device_service(rdpdr->devman, (const 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; + + for (i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '/', '\0' }; + char drive_name[] = { 'c', '\0' }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + RDPDR_DRIVE drive = { 0 }; + + drive.Type = RDPDR_DTYP_FILESYSTEM; + drive.Path = drive_path; + drive.automount = TRUE; + drive.Name = drive_name; + devman_load_device_service(rdpdr->devman, + (const 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->device.type != RDPDR_DTYP_FILESYSTEM) + continue; + + 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 defined(__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); + if (stat(fullpath, &buf) != 0) + continue; + + 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->device.type != RDPDR_DTYP_FILESYSTEM) + 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++) + { + if (dev_array[i].to_add) + { + RDPDR_DRIVE drive = { 0 }; + char* name; + + drive.Type = RDPDR_DTYP_FILESYSTEM; + drive.Path = dev_array[i].path; + drive.automount = TRUE; + name = strrchr(drive.Path, '/') + 1; + drive.Name = name; + + if (!drive.Name) + { + 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!"); + 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; + size_t 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; +} + +#else + +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] = { 0 }; + uid_t uid = getuid(); + char uname[MAX_PATH] = { 0 }; + ULONG size = sizeof(uname) - 1; + + if (!GetUserNameExA(NameSamCompatible, uname, &size)) + return FALSE; + + 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; +} + +#define MAX_USB_DEVICES 100 + +typedef struct _hotplug_dev +{ + char* path; + BOOL to_add; +} hotplug_dev; + +static void handle_mountpoint(hotplug_dev* dev_array, size_t* size, const char* mountpoint) +{ + if (!mountpoint) + return; + /* copy hotpluged device mount point to the dev_array */ + if (isAutomountLocation(mountpoint) && (*size < MAX_USB_DEVICES)) + { + dev_array[*size].path = _strdup(mountpoint); + dev_array[*size].to_add = TRUE; + (*size)++; + } +} + +#ifdef __sun +#include +static UINT handle_platform_mounts_sun(hotplug_dev* dev_array, size_t* size) +{ + FILE* f; + struct mnttab ent; + f = fopen("/etc/mnttab", "r"); + if (f == NULL) + { + WLog_ERR(TAG, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while (getmntent(f, &ent) == 0) + { + handle_mountpoint(dev_array, size, ent.mnt_mountp); + } + fclose(f); + return ERROR_SUCCESS; +} +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#include +static UINT handle_platform_mounts_bsd(hotplug_dev* dev_array, size_t* size) +{ + int mntsize; + size_t idx; + struct statfs* mntbuf = NULL; + + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + if (!mntsize) + { + /* TODO: handle 'errno' */ + WLog_ERR(TAG, "getmntinfo failed!"); + return ERROR_OPEN_FAILED; + } + for (idx = 0; idx < (size_t)mntsize; idx++) + { + handle_mountpoint(dev_array, size, mntbuf[idx].f_mntonname); + } + free(mntbuf); + return ERROR_SUCCESS; +} +#endif + +#if defined(__LINUX__) || defined(__linux__) +#include +static UINT handle_platform_mounts_linux(hotplug_dev* dev_array, size_t* size) +{ + FILE* f; + struct mntent* ent; + f = fopen("/proc/mounts", "r"); + if (f == NULL) + { + WLog_ERR(TAG, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while ((ent = getmntent(f)) != NULL) + { + handle_mountpoint(dev_array, size, ent->mnt_dir); + } + fclose(f); + return ERROR_SUCCESS; +} +#endif + +static UINT handle_platform_mounts(hotplug_dev* dev_array, size_t* size) +{ +#ifdef __sun + return handle_platform_mounts_sun(dev_array, size); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + return handle_platform_mounts_bsd(dev_array, size); +#elif defined(__LINUX__) || defined(__linux__) + return handle_platform_mounts_linux(dev_array, size); +#endif + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static BOOL device_already_plugged(rdpdrPlugin* rdpdr, const hotplug_dev* device) +{ + BOOL rc = FALSE; + int count, x; + ULONG_PTR* keys = NULL; + WCHAR* path = NULL; + int status; + + if (!rdpdr || !device) + return TRUE; + if (!device->to_add) + return TRUE; + + status = ConvertToUnicode(CP_UTF8, 0, device->path, -1, &path, 0); + if (status <= 0) + return TRUE; + + ListDictionary_Lock(rdpdr->devman->devices); + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + for (x = 0; x < count; x++) + { + DEVICE_DRIVE_EXT* device_ext = + (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[x]); + + if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path) + continue; + if (_wcscmp(device_ext->path, path) == 0) + { + rc = TRUE; + break; + } + } + free(keys); + free(path); + ListDictionary_Unlock(rdpdr->devman->devices); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT handle_hotplug(rdpdrPlugin* rdpdr) +{ + hotplug_dev dev_array[MAX_USB_DEVICES] = { 0 }; + size_t i; + size_t size = 0; + int count, j; + ULONG_PTR* keys = NULL; + UINT32 ids[1]; + UINT error = ERROR_SUCCESS; + + error = handle_platform_mounts(dev_array, &size); + + /* 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->device.type != RDPDR_DTYP_FILESYSTEM) || + !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++) + { + if (!device_already_plugged(rdpdr, &dev_array[i])) + { + RDPDR_DRIVE drive = { 0 }; + char* name; + + drive.Type = RDPDR_DTYP_FILESYSTEM; + drive.Path = dev_array[i].path; + drive.automount = TRUE; + name = strrchr(drive.Path, '/') + 1; + drive.Name = name; + + if (!drive.Name) + { + WLog_ERR(TAG, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + if ((error = devman_load_device_service(rdpdr->devman, (const RDPDR_DEVICE*)&drive, + rdpdr->rdpcontext))) + { + WLog_ERR(TAG, "devman_load_device_service failed!"); + 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; + 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"); + + ExitThread(error); + return error; +} + +#endif + +#if !defined(_WIN32) && !defined(__IOS__) +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error; + + if (rdpdr->hotplugThread) + { + SetEvent(rdpdr->stopEvent); +#ifdef __MACOSX__ + CFRunLoopStop(rdpdr->runLoop); +#endif + + if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + CloseHandle(rdpdr->hotplugThread); + CloseHandle(rdpdr->stopEvent); + rdpdr->stopEvent = NULL; + 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; + 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++) + { + const RDPDR_DEVICE* device = settings->DeviceArray[index]; + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + { + const char DynamicDrives[] = "DynamicDrives"; + const RDPDR_DRIVE* drive = (const RDPDR_DRIVE*)device; + BOOL hotplugAll = strncmp(drive->Path, "*", 2) == 0; + BOOL hotplugLater = strncmp(drive->Path, DynamicDrives, sizeof(DynamicDrives)) == 0; + if (drive->Path && (hotplugAll || hotplugLater)) + { + if (hotplugAll) + first_hotplug(rdpdr); +#ifndef _WIN32 + + if (!(rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + +#endif + + if (!(rdpdr->hotplugThread = + CreateThread(NULL, 0, drive_hotplug_thread_func, rdpdr, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); +#ifndef _WIN32 + CloseHandle(rdpdr->stopEvent); + rdpdr->stopEvent = NULL; +#endif + 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]) + { + DWORD size = sizeof(rdpdr->computerName) - 1; + GetComputerNameA(rdpdr->computerName, &size); + } + + 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); +} + +static UINT dummy_irp_response(rdpdrPlugin* rdpdr, wStream* s) +{ + + UINT32 DeviceId; + UINT32 FileId; + UINT32 CompletionId; + + wStream* output = Stream_New(NULL, 256); // RDPDR_DEVICE_IO_RESPONSE_LENGTH + if (!output) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(s, 4); /* see "rdpdr_process_receive" */ + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */ + + Stream_Write_UINT16(output, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(output, PAKID_CORE_DEVICE_IOCOMPLETION); /* PacketId (2 bytes) */ + Stream_Write_UINT32(output, DeviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(output, CompletionId); /* CompletionId (4 bytes) */ + Stream_Write_UINT32(output, STATUS_UNSUCCESSFUL); /* IoStatus (4 bytes) */ + + Stream_Zero(output, 256 - RDPDR_DEVICE_IO_RESPONSE_LENGTH); + // or usage + // Stream_Write_UINT32(output, 0); /* Length */ + // Stream_Write_UINT8(output, 0); /* Padding */ + + return rdpdr_send(rdpdr, output); +} + +/** + * 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); + + if (error == CHANNEL_RC_OK) + { + return dummy_irp_response(rdpdr, s); + } + + return error; + } + + IFCALLRET(irp->device->IRPRequest, error, irp->device, irp); + + if (error) + WLog_ERR(TAG, "device->IRPRequest failed with error %" PRIu32 "", error); + + return error; +} + +static UINT rdpdr_process_component(rdpdrPlugin* rdpdr, UINT16 component, UINT16 packetId, + wStream* s) +{ + UINT32 type; + DEVICE* device; + + switch (component) + { + case RDPDR_CTYP_PRN: + type = RDPDR_DTYP_PRINT; + break; + + default: + return ERROR_INVALID_DATA; + } + + device = devman_get_device_by_type(rdpdr->devman, type); + + if (!device) + return ERROR_INVALID_PARAMETER; + + return IFCALLRESULT(ERROR_INVALID_PARAMETER, device->CustomComponentRequest, device, component, + packetId, s); +} + +/** + * 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 = ERROR_INVALID_DATA; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + if (Stream_GetRemainingLength(s) >= 4) + { + 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))) + { + } + else if ((error = rdpdr_send_client_announce_reply(rdpdr))) + { + WLog_ERR(TAG, + "rdpdr_send_client_announce_reply failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_send_client_name_request(rdpdr))) + { + WLog_ERR(TAG, + "rdpdr_send_client_name_request failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_process_init(rdpdr))) + { + WLog_ERR(TAG, "rdpdr_process_init failed with error %" PRIu32 "", error); + } + + break; + + case PAKID_CORE_SERVER_CAPABILITY: + if ((error = rdpdr_process_capability_request(rdpdr, s))) + { + } + else if ((error = rdpdr_send_capability_response(rdpdr))) + { + WLog_ERR(TAG, + "rdpdr_send_capability_response failed with error %" PRIu32 "", + error); + } + + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + if ((error = rdpdr_process_server_clientid_confirm(rdpdr, s))) + { + } + else if ((error = rdpdr_send_device_list_announce_request(rdpdr, FALSE))) + { + WLog_ERR( + TAG, + "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "", + 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); + } + + break; + + case PAKID_CORE_DEVICE_REPLY: + + /* connect to a specific resource */ + if (Stream_GetRemainingLength(s) >= 8) + { + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, status); + error = CHANNEL_RC_OK; + } + + 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; + } + else + s = NULL; + + break; + + default: + WLog_ERR(TAG, "RDPDR_CTYP_CORE unknown PacketId: 0x%04" PRIX16 "", packetId); + error = ERROR_INVALID_DATA; + break; + } + } + else + { + error = rdpdr_process_component(rdpdr, component, packetId, s); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, + "Unknown message: Component: 0x%04" PRIX16 " PacketId: 0x%04" PRIX16 "", + component, packetId); + } + } + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * 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) + { + Stream_Free(s, TRUE); + return CHANNEL_RC_NULL_DATA; + } + + if (!plugin) + { + Stream_Free(s, TRUE); + 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, 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rdpdr || !pData || (rdpdr->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rdpdr && 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; +} + +static void queue_free(void* obj) +{ + wStream* s = obj; + Stream_Free(s, TRUE); +} + +/** + * 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; + } + + rdpdr->queue->object.fnObjectFree = queue_free; + + 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..4c372da --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.h @@ -0,0 +1,85 @@ +/** + * 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; +#endif +#ifdef __MACOSX__ + CFRunLoopRef runLoop; +#endif +#ifndef _WIN32 + 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..2dcb2a0 --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.c @@ -0,0 +1,2626 @@ +/** + * 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) +{ + UINT32 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) +{ + UINT32 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; + winpr_str_append("\\*.*", irp->PathName, ARRAYSIZE(irp->PathName), NULL); + + 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..f3f54cc --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.h @@ -0,0 +1,91 @@ +/** + * 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/rdpecam/CMakeLists.txt b/channels/rdpecam/CMakeLists.txt new file mode 100644 index 0000000..63ed410 --- /dev/null +++ b/channels/rdpecam/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("rdpecam") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpecam/ChannelOptions.cmake b/channels/rdpecam/ChannelOptions.cmake new file mode 100644 index 0000000..7528d11 --- /dev/null +++ b/channels/rdpecam/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpecam" TYPE "dynamic" + DESCRIPTION "Video Capture Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPECAM]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpecam/server/CMakeLists.txt b/channels/rdpecam/server/CMakeLists.txt new file mode 100644 index 0000000..2a65ba7 --- /dev/null +++ b/channels/rdpecam/server/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("rdpecam") + +set(${MODULE_PREFIX}_SRCS + camera_device_enumerator_main.c + camera_device_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/rdpecam/server/camera_device_enumerator_main.c b/channels/rdpecam/server/camera_device_enumerator_main.c new file mode 100644 index 0000000..a17eeee --- /dev/null +++ b/channels/rdpecam/server/camera_device_enumerator_main.c @@ -0,0 +1,612 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#define TAG CHANNELS_TAG("rdpecam-enumerator.server") + +typedef enum +{ + ENUMERATOR_INITIAL, + ENUMERATOR_OPENED, +} eEnumeratorChannelState; + +typedef struct +{ + CamDevEnumServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* enumerator_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eEnumeratorChannelState state; + + wStream* buffer; +} enumerator_server; + +static UINT enumerator_server_initialize(CamDevEnumServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (enumerator->isOpened) + { + WLog_WARN(TAG, "Application error: Camera Device Enumerator channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + enumerator->externalThread = externalThread; + + return error; +} + +static UINT enumerator_server_open_channel(enumerator_server* enumerator) +{ + CamDevEnumServerContext* context = &enumerator->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(enumerator); + + if (WTSQuerySessionInformationA(enumerator->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(enumerator->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + enumerator->enumerator_channel = WTSVirtualChannelOpenEx( + enumerator->SessionId, RDPECAM_CONTROL_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!enumerator->enumerator_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(enumerator->enumerator_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT enumerator_server_handle_select_version_request(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SELECT_VERSION_REQUEST pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SelectVersionRequest, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SelectVersionRequest failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_added_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_ADDED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length; + WCHAR* channel_name_start; + char* tmp; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + /* + * RequiredLength 4: + * + * Nullterminator DeviceName (2), + * VirtualChannelName (>= 1), + * Nullterminator VirtualChannelName (1) + */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + pdu.DeviceName = (WCHAR*)Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + channel_name_start = (WCHAR*)Stream_Pointer(s); + + /* Search for null terminator of DeviceName */ + for (i = 0; i < remaining_length; i += sizeof(WCHAR), ++channel_name_start) + { + if (*channel_name_start == L'\0') + break; + } + + if (*channel_name_start != L'\0') + { + WLog_ERR(TAG, "enumerator_server_recv_device_added_notification: Invalid DeviceName!"); + return ERROR_INVALID_DATA; + } + + pdu.VirtualChannelName = (char*)++channel_name_start; + ++i; + + if (i >= remaining_length || *pdu.VirtualChannelName == '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + tmp = pdu.VirtualChannelName; + for (; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceAddedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceAddedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_removed_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_REMOVED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length; + char* tmp; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_NO_DATA; + + pdu.VirtualChannelName = (char*)Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + tmp = (char*)(Stream_Pointer(s) + 1); + + for (i = 1; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_removed_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceRemovedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceRemovedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_process_message(enumerator_server* enumerator) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + CAM_SHARED_MSG_HEADER header = { 0 }; + wStream* s; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(enumerator->enumerator_channel); + + s = enumerator->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(enumerator->enumerator_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(enumerator->enumerator_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, header.Version); + Stream_Read_UINT8(s, header.MessageId); + + switch (header.MessageId) + { + case CAM_MSG_ID_SelectVersionRequest: + error = + enumerator_server_handle_select_version_request(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceAddedNotification: + error = + enumerator_server_recv_device_added_notification(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceRemovedNotification: + error = enumerator_server_recv_device_removed_notification(&enumerator->context, s, + &header); + break; + default: + WLog_ERR(TAG, "enumerator_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT enumerator_server_context_poll_int(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(enumerator); + + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_open_channel(enumerator); + if (error) + WLog_ERR(TAG, "enumerator_server_open_channel failed with error %" PRIu32 "!", + error); + else + enumerator->state = ENUMERATOR_OPENED; + break; + case ENUMERATOR_OPENED: + error = enumerator_process_message(enumerator); + break; + } + + return error; +} + +static HANDLE enumerator_server_get_channel_handle(enumerator_server* enumerator) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(enumerator); + + if (WTSVirtualChannelQuery(enumerator->enumerator_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI enumerator_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + enumerator_server* enumerator = (enumerator_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(enumerator); + + nCount = 0; + events[nCount++] = enumerator->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_context_poll_int(&enumerator->context); + if (error == CHANNEL_RC_OK) + { + events[1] = enumerator_server_get_channel_handle(enumerator); + nCount = 2; + } + break; + case ENUMERATOR_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = enumerator_server_context_poll_int(&enumerator->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = NULL; + + if (error && enumerator->context.rdpcontext) + setChannelError(enumerator->context.rdpcontext, error, + "enumerator_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT enumerator_server_open(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && (enumerator->thread == NULL)) + { + enumerator->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!enumerator->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->thread = + CreateThread(NULL, 0, enumerator_server_thread_func, enumerator, 0, NULL); + if (!enumerator->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(enumerator->stopEvent); + enumerator->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + enumerator->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT enumerator_server_close(CamDevEnumServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && enumerator->thread) + { + SetEvent(enumerator->stopEvent); + + if (WaitForSingleObject(enumerator->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(enumerator->thread); + CloseHandle(enumerator->stopEvent); + enumerator->thread = NULL; + enumerator->stopEvent = NULL; + } + if (enumerator->externalThread) + { + if (enumerator->state != ENUMERATOR_INITIAL) + { + WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = NULL; + enumerator->state = ENUMERATOR_INITIAL; + } + } + enumerator->isOpened = FALSE; + + return error; +} + +static UINT enumerator_server_context_poll(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread) + return ERROR_INTERNAL_ERROR; + + return enumerator_server_context_poll_int(context); +} + +static BOOL enumerator_server_context_handle(CamDevEnumServerContext* context, HANDLE* handle) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(handle); + + if (!enumerator->externalThread) + return FALSE; + if (enumerator->state == ENUMERATOR_INITIAL) + return FALSE; + + *handle = enumerator_server_get_channel_handle(enumerator); + + return TRUE; +} + +static UINT enumerator_server_packet_send(CamDevEnumServerContext* context, wStream* s) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written; + + if (!WTSVirtualChannelWrite(enumerator->enumerator_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT enumerator_send_select_version_response_pdu( + CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse) +{ + wStream* s; + + s = Stream_New(NULL, CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, selectVersionResponse->Header.Version); + Stream_Write_UINT8(s, selectVersionResponse->Header.MessageId); + + return enumerator_server_packet_send(context, s); +} + +CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm) +{ + enumerator_server* enumerator = (enumerator_server*)calloc(1, sizeof(enumerator_server)); + + if (!enumerator) + return NULL; + + enumerator->context.vcm = vcm; + enumerator->context.Initialize = enumerator_server_initialize; + enumerator->context.Open = enumerator_server_open; + enumerator->context.Close = enumerator_server_close; + enumerator->context.Poll = enumerator_server_context_poll; + enumerator->context.ChannelHandle = enumerator_server_context_handle; + + enumerator->context.SelectVersionResponse = enumerator_send_select_version_response_pdu; + + enumerator->buffer = Stream_New(NULL, 4096); + if (!enumerator->buffer) + goto fail; + + return &enumerator->context; +fail: + cam_dev_enum_server_context_free(&enumerator->context); + return NULL; +} + +void cam_dev_enum_server_context_free(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + if (enumerator) + { + enumerator_server_close(context); + Stream_Free(enumerator->buffer, TRUE); + } + + free(enumerator); +} diff --git a/channels/rdpecam/server/camera_device_main.c b/channels/rdpecam/server/camera_device_main.c new file mode 100644 index 0000000..00bea94 --- /dev/null +++ b/channels/rdpecam/server/camera_device_main.c @@ -0,0 +1,971 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#define TAG CHANNELS_TAG("rdpecam.server") + +typedef enum +{ + CAMERA_DEVICE_INITIAL, + CAMERA_DEVICE_OPENED, +} eCameraDeviceChannelState; + +typedef struct +{ + CameraDeviceServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* device_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eCameraDeviceChannelState state; + + wStream* buffer; +} device_server; + +static UINT device_server_initialize(CameraDeviceServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (device->isOpened) + { + WLog_WARN(TAG, "Application error: Camera channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + device->externalThread = externalThread; + + return error; +} + +static UINT device_server_open_channel(device_server* device) +{ + CameraDeviceServerContext* context = &device->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(device); + + if (WTSQuerySessionInformationA(device->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(device->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + device->device_channel = WTSVirtualChannelOpenEx(device->SessionId, context->virtualChannelName, + WTS_CHANNEL_OPTION_DYNAMIC); + if (!device->device_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(device->device_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT device_server_handle_success_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SUCCESS_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SuccessResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SuccessResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_ERROR_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.ErrorCode); + + IFCALLRET(context->ErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->ErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_stream_list_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_STREAM_LIST_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + BYTE i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = MIN(Stream_GetRemainingLength(s) / 5, 255); + + for (i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_STREAM_DESCRIPTION* StreamDescription = &pdu.StreamDescriptions[i]; + + Stream_Read_UINT16(s, StreamDescription->FrameSourceTypes); + Stream_Read_UINT8(s, StreamDescription->StreamCategory); + Stream_Read_UINT8(s, StreamDescription->Selected); + Stream_Read_UINT8(s, StreamDescription->CanBeShared); + } + + IFCALLRET(context->StreamListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->StreamListResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_media_type_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_MEDIA_TYPE_LIST_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + BYTE i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = Stream_GetRemainingLength(s) / 26; + + pdu.MediaTypeDescriptions = calloc(pdu.N_Descriptions, sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!pdu.MediaTypeDescriptions) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_MEDIA_TYPE_DESCRIPTION structs", + pdu.N_Descriptions); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions = &pdu.MediaTypeDescriptions[i]; + + Stream_Read_UINT8(s, MediaTypeDescriptions->Format); + Stream_Read_UINT32(s, MediaTypeDescriptions->Width); + Stream_Read_UINT32(s, MediaTypeDescriptions->Height); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateDenominator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioDenominator); + Stream_Read_UINT8(s, MediaTypeDescriptions->Flags); + } + + IFCALLRET(context->MediaTypeListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->MediaTypeListResponse failed with error %" PRIu32 "", error); + + free(pdu.MediaTypeDescriptions); + + return error; +} + +static UINT device_server_recv_current_media_type_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_CURRENT_MEDIA_TYPE_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.MediaTypeDescription.Format); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Width); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Height); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateDenominator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioDenominator); + Stream_Read_UINT8(s, pdu.MediaTypeDescription.Flags); + + IFCALLRET(context->CurrentMediaTypeResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->CurrentMediaTypeResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + + pdu.SampleSize = Stream_GetRemainingLength(s); + pdu.Sample = Stream_Pointer(s); + + IFCALLRET(context->SampleResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_ERROR_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + Stream_Read_UINT32(s, pdu.ErrorCode); + + IFCALLRET(context->SampleErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_property_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_LIST_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + pdu.N_Properties = Stream_GetRemainingLength(s) / 19; + + if (pdu.N_Properties > 0) + { + size_t i; + + pdu.Properties = calloc(pdu.N_Properties, sizeof(CAM_PROPERTY_DESCRIPTION)); + if (!pdu.Properties) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_PROPERTY_DESCRIPTION structs", + pdu.N_Properties); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (i = 0; i < pdu.N_Properties; ++i) + { + Stream_Read_UINT8(s, pdu.Properties[i].PropertySet); + Stream_Read_UINT8(s, pdu.Properties[i].PropertyId); + Stream_Read_UINT8(s, pdu.Properties[i].Capabilities); + Stream_Read_INT32(s, pdu.Properties[i].MinValue); + Stream_Read_INT32(s, pdu.Properties[i].MaxValue); + Stream_Read_INT32(s, pdu.Properties[i].Step); + Stream_Read_INT32(s, pdu.Properties[i].DefaultValue); + } + } + + IFCALLRET(context->PropertyListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->PropertyListResponse failed with error %" PRIu32 "", error); + + free(pdu.Properties); + + return error; +} + +static UINT device_server_recv_property_value_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_VALUE_RESPONSE pdu = { 0 }; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.PropertyValue.Mode); + Stream_Read_INT32(s, pdu.PropertyValue.Value); + + IFCALLRET(context->PropertyValueResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->PropertyValueResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_process_message(device_server* device) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + CAM_SHARED_MSG_HEADER header = { 0 }; + wStream* s; + + WINPR_ASSERT(device); + WINPR_ASSERT(device->device_channel); + + s = device->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(device->device_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(device->device_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, header.Version); + Stream_Read_UINT8(s, header.MessageId); + + switch (header.MessageId) + { + case CAM_MSG_ID_SuccessResponse: + error = device_server_handle_success_response(&device->context, s, &header); + break; + case CAM_MSG_ID_ErrorResponse: + error = device_server_recv_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_StreamListResponse: + error = device_server_recv_stream_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_MediaTypeListResponse: + error = device_server_recv_media_type_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_CurrentMediaTypeResponse: + error = device_server_recv_current_media_type_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleResponse: + error = device_server_recv_sample_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleErrorResponse: + error = device_server_recv_sample_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyListResponse: + error = device_server_recv_property_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyValueResponse: + error = device_server_recv_property_value_response(&device->context, s, &header); + break; + default: + WLog_ERR(TAG, "device_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT device_server_context_poll_int(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(device); + + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_open_channel(device); + if (error) + WLog_ERR(TAG, "device_server_open_channel failed with error %" PRIu32 "!", error); + else + device->state = CAMERA_DEVICE_OPENED; + break; + case CAMERA_DEVICE_OPENED: + error = device_process_message(device); + break; + } + + return error; +} + +static HANDLE device_server_get_channel_handle(device_server* device) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(device); + + if (WTSVirtualChannelQuery(device->device_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI device_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + device_server* device = (device_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(device); + + nCount = 0; + events[nCount++] = device->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_context_poll_int(&device->context); + if (error == CHANNEL_RC_OK) + { + events[1] = device_server_get_channel_handle(device); + nCount = 2; + } + break; + case CAMERA_DEVICE_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = device_server_context_poll_int(&device->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(device->device_channel); + device->device_channel = NULL; + + if (error && device->context.rdpcontext) + setChannelError(device->context.rdpcontext, error, + "device_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT device_server_open(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && (device->thread == NULL)) + { + device->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!device->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->thread = CreateThread(NULL, 0, device_server_thread_func, device, 0, NULL); + if (!device->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(device->stopEvent); + device->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + device->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT device_server_close(CameraDeviceServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && device->thread) + { + SetEvent(device->stopEvent); + + if (WaitForSingleObject(device->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(device->thread); + CloseHandle(device->stopEvent); + device->thread = NULL; + device->stopEvent = NULL; + } + if (device->externalThread) + { + if (device->state != CAMERA_DEVICE_INITIAL) + { + WTSVirtualChannelClose(device->device_channel); + device->device_channel = NULL; + device->state = CAMERA_DEVICE_INITIAL; + } + } + device->isOpened = FALSE; + + return error; +} + +static UINT device_server_context_poll(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread) + return ERROR_INTERNAL_ERROR; + + return device_server_context_poll_int(context); +} + +static BOOL device_server_context_handle(CameraDeviceServerContext* context, HANDLE* handle) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + WINPR_ASSERT(handle); + + if (!device->externalThread) + return FALSE; + if (device->state == CAMERA_DEVICE_INITIAL) + return FALSE; + + *handle = device_server_get_channel_handle(device); + + return TRUE; +} + +static wStream* device_server_packet_new(size_t size, BYTE version, BYTE messageId) +{ + wStream* s; + + WINPR_ASSERT(size > 0); + + /* Allocate what we need plus header bytes */ + s = Stream_New(NULL, size + CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return NULL; + } + + Stream_Write_UINT8(s, version); + Stream_Write_UINT8(s, messageId); + + return s; +} + +static UINT device_server_packet_send(CameraDeviceServerContext* context, wStream* s) +{ + device_server* device = (device_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + if (!WTSVirtualChannelWrite(device->device_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT device_server_write_and_send_header(CameraDeviceServerContext* context, BYTE messageId) +{ + wStream* s; + + WINPR_ASSERT(context); + + s = device_server_packet_new(0, context->protocolVersion, messageId); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + return device_server_packet_send(context, s); +} + +static UINT +device_send_activate_device_request_pdu(CameraDeviceServerContext* context, + const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_ActivateDeviceRequest); +} + +static UINT device_send_deactivate_device_request_pdu( + CameraDeviceServerContext* context, + const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_DeactivateDeviceRequest); +} + +static UINT device_send_stream_list_request_pdu(CameraDeviceServerContext* context, + const CAM_STREAM_LIST_REQUEST* streamListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StreamListRequest); +} + +static UINT +device_send_media_type_list_request_pdu(CameraDeviceServerContext* context, + const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(mediaTypeListRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_MediaTypeListRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, mediaTypeListRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT device_send_current_media_type_request_pdu( + CameraDeviceServerContext* context, + const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(currentMediaTypeRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_CurrentMediaTypeRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, currentMediaTypeRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT +device_send_start_streams_request_pdu(CameraDeviceServerContext* context, + const CAM_START_STREAMS_REQUEST* startStreamsRequest) +{ + wStream* s; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(startStreamsRequest); + + s = device_server_packet_new(startStreamsRequest->N_Infos * 27ul, context->protocolVersion, + CAM_MSG_ID_StartStreamsRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + for (i = 0; i < startStreamsRequest->N_Infos; ++i) + { + const CAM_START_STREAM_INFO* info = &startStreamsRequest->StartStreamsInfo[i]; + const CAM_MEDIA_TYPE_DESCRIPTION* description = &info->MediaTypeDescription; + + Stream_Write_UINT8(s, info->StreamIndex); + + Stream_Write_UINT8(s, description->Format); + Stream_Write_UINT32(s, description->Width); + Stream_Write_UINT32(s, description->Height); + Stream_Write_UINT32(s, description->FrameRateNumerator); + Stream_Write_UINT32(s, description->FrameRateDenominator); + Stream_Write_UINT32(s, description->PixelAspectRatioNumerator); + Stream_Write_UINT32(s, description->PixelAspectRatioDenominator); + Stream_Write_UINT8(s, description->Flags); + } + + return device_server_packet_send(context, s); +} + +static UINT device_send_stop_streams_request_pdu(CameraDeviceServerContext* context, + const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StopStreamsRequest); +} + +static UINT device_send_sample_request_pdu(CameraDeviceServerContext* context, + const CAM_SAMPLE_REQUEST* sampleRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(sampleRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_SampleRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, sampleRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT +device_send_property_list_request_pdu(CameraDeviceServerContext* context, + const CAM_PROPERTY_LIST_REQUEST* propertyListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_PropertyListRequest); +} + +static UINT +device_send_property_value_request_pdu(CameraDeviceServerContext* context, + const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(propertyValueRequest); + + s = device_server_packet_new(2, context->protocolVersion, CAM_MSG_ID_PropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, propertyValueRequest->PropertySet); + Stream_Write_UINT8(s, propertyValueRequest->PropertyId); + + return device_server_packet_send(context, s); +} + +static UINT device_send_set_property_value_request_pdu( + CameraDeviceServerContext* context, + const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(setPropertyValueRequest); + + s = device_server_packet_new(2 + 5, context->protocolVersion, + CAM_MSG_ID_SetPropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, setPropertyValueRequest->PropertySet); + Stream_Write_UINT8(s, setPropertyValueRequest->PropertyId); + + Stream_Write_UINT8(s, setPropertyValueRequest->PropertyValue.Mode); + Stream_Write_INT32(s, setPropertyValueRequest->PropertyValue.Value); + + return device_server_packet_send(context, s); +} + +CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm) +{ + device_server* device = (device_server*)calloc(1, sizeof(device_server)); + + if (!device) + return NULL; + + device->context.vcm = vcm; + device->context.Initialize = device_server_initialize; + device->context.Open = device_server_open; + device->context.Close = device_server_close; + device->context.Poll = device_server_context_poll; + device->context.ChannelHandle = device_server_context_handle; + + device->context.ActivateDeviceRequest = device_send_activate_device_request_pdu; + device->context.DeactivateDeviceRequest = device_send_deactivate_device_request_pdu; + + device->context.StreamListRequest = device_send_stream_list_request_pdu; + device->context.MediaTypeListRequest = device_send_media_type_list_request_pdu; + device->context.CurrentMediaTypeRequest = device_send_current_media_type_request_pdu; + + device->context.StartStreamsRequest = device_send_start_streams_request_pdu; + device->context.StopStreamsRequest = device_send_stop_streams_request_pdu; + device->context.SampleRequest = device_send_sample_request_pdu; + + device->context.PropertyListRequest = device_send_property_list_request_pdu; + device->context.PropertyValueRequest = device_send_property_value_request_pdu; + device->context.SetPropertyValueRequest = device_send_set_property_value_request_pdu; + + device->buffer = Stream_New(NULL, 4096); + if (!device->buffer) + goto fail; + + return &device->context; +fail: + camera_device_server_context_free(&device->context); + return NULL; +} + +void camera_device_server_context_free(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + if (device) + { + device_server_close(context); + Stream_Free(device->buffer, TRUE); + } + + free(context->virtualChannelName); + + free(device); +} 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..1d95054 --- /dev/null +++ b/channels/rdpei/client/rdpei_main.c @@ -0,0 +1,1391 @@ +/** + * 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 64 +#define MAX_PEN_CONTACTS 4 + +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; + + UINT32 version; + UINT32 features; + UINT16 maxTouchContacts; + UINT64 currentFrameTime; + UINT64 previousFrameTime; + RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS]; + + UINT64 currentPenFrameTime; + UINT64 previousPenFrameTime; + UINT16 maxPenContacts; + RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS]; + + CRITICAL_SECTION lock; + rdpContext* rdpcontext; + BOOL initialized; + HANDLE thread; + HANDLE event; +}; +typedef struct _RDPEI_PLUGIN RDPEI_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame); + +#ifdef WITH_DEBUG_RDPEI +static const char* rdpei_eventid_string(UINT16 event) +{ + switch (event) + { + case EVENTID_SC_READY: + return "EVENTID_SC_READY"; + case EVENTID_CS_READY: + return "EVENTID_CS_READY"; + case EVENTID_TOUCH: + return "EVENTID_TOUCH"; + case EVENTID_SUSPEND_TOUCH: + return "EVENTID_SUSPEND_TOUCH"; + case EVENTID_RESUME_TOUCH: + return "EVENTID_RESUME_TOUCH"; + case EVENTID_DISMISS_HOVERING_CONTACT: + return "EVENTID_DISMISS_HOVERING_CONTACT"; + case EVENTID_PEN: + return "EVENTID_PEN"; + default: + return "EVENTID_UNKNOWN"; + } +} +#endif + +static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active) +{ + UINT16 i; + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + + if (!contactPoint->active && active) + continue; + else if (!contactPoint->active && !active) + { + contactPoint->contactId = i; + contactPoint->externalId = externalId; + contactPoint->active = TRUE; + return contactPoint; + } + else if (contactPoint->externalId == externalId) + { + return contactPoint; + } + } + return NULL; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_frame(RdpeiClientContext* context) +{ + UINT16 i; + RDPEI_PLUGIN* rdpei; + RDPINPUT_TOUCH_FRAME frame = { 0 }; + RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = { 0 }; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + frame.contacts = contacts; + + for (i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + RDPINPUT_CONTACT_DATA* contact = &contactPoint->data; + + if (contactPoint->dirty) + { + contacts[frame.contactCount] = *contact; + rdpei->contactPoints[i].dirty = FALSE; + frame.contactCount++; + } + else if (contactPoint->active) + { + if (contact->contactFlags & CONTACT_FLAG_DOWN) + { + contact->contactFlags = CONTACT_FLAG_UPDATE; + contact->contactFlags |= CONTACT_FLAG_INRANGE; + contact->contactFlags |= CONTACT_FLAG_INCONTACT; + } + + contacts[frame.contactCount] = *contact; + frame.contactCount++; + } + if (contact->contactFlags & CONTACT_FLAG_UP) + { + contactPoint->active = FALSE; + contactPoint->externalId = 0; + contactPoint->contactId = 0; + } + } + + if (frame.contactCount > 0) + { + UINT error = rdpei_send_frame(context, &frame); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_send_frame failed with error %" PRIu32 "!", error); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s, UINT16 eventId, + UINT32 pduLength) +{ + UINT status; + if (!callback || !s || !callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + 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_string(eventId), pduLength, status); +#endif + return status; +} + +static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame) +{ + UINT16 x; + if (!s || !frame) + return ERROR_INTERNAL_ERROR; + + if (!rdpei_write_2byte_unsigned(s, frame->contactCount)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_8byte_unsigned(s, frame->frameOffset)) + return ERROR_OUTOFMEMORY; + for (x = 0; x < frame->contactCount; x++) + { + const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x]; + + if (!Stream_EnsureRemainingCapacity(s, 1)) + return ERROR_OUTOFMEMORY; + Stream_Write_UINT8(s, contact->deviceId); + if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->x)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->y)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_unsigned(s, contact->contactFlags)) + return ERROR_OUTOFMEMORY; + if (contact->fieldsPresent & PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->penFlags)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->pressure)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_write_2byte_unsigned(s, contact->rotation)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltX)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltY)) + return ERROR_OUTOFMEMORY; + } + } + return CHANNEL_RC_OK; +} + +static UINT rdpei_send_pen_event_pdu(RDPEI_CHANNEL_CALLBACK* callback, UINT32 frameOffset, + const RDPINPUT_PEN_FRAME* frames, UINT16 count) +{ + UINT status; + wStream* s; + UINT16 x; + + if (!frames || (count == 0)) + return ERROR_INTERNAL_ERROR; + + s = Stream_New(NULL, 64); + + 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, frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + rdpei_write_2byte_unsigned(s, count); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + + for (x = 0; x < count; x++) + { + if ((status = rdpei_write_pen_frame(s, &frames[x]))) + { + WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %" PRIu32 "!", status); + Stream_Free(s, TRUE); + return status; + } + } + Stream_SealLength(s); + + status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s)); + Stream_Free(s, TRUE); + return status; +} + +static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame) +{ + const UINT64 currentTime = GetTickCount64(); + RDPEI_PLUGIN* rdpei; + RDPEI_CHANNEL_CALLBACK* callback; + UINT error; + + if (!context) + return ERROR_INTERNAL_ERROR; + rdpei = (RDPEI_PLUGIN*)context->handle; + if (!rdpei || !rdpei->listener_callback) + return ERROR_INTERNAL_ERROR; + + callback = rdpei->listener_callback->channel_callback; + + if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime) + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = 0; + } + else + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime; + } + + if ((error = rdpei_send_pen_event_pdu(callback, frame->frameOffset, frame, 1))) + return error; + + rdpei->previousPenFrameTime = rdpei->currentPenFrameTime; + return error; +} + +static UINT rdpei_add_pen_frame(RdpeiClientContext* context) +{ + UINT16 i; + RDPEI_PLUGIN* rdpei; + RDPINPUT_PEN_FRAME penFrame = { 0 }; + RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = { 0 }; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + penFrame.contacts = penContacts; + + for (i = 0; i < rdpei->maxPenContacts; i++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]); + + if (contact->dirty) + { + penContacts[penFrame.contactCount++] = contact->data; + contact->dirty = FALSE; + } + else if (contact->active) + { + if (contact->data.contactFlags & CONTACT_FLAG_DOWN) + { + contact->data.contactFlags = CONTACT_FLAG_UPDATE; + contact->data.contactFlags |= CONTACT_FLAG_INRANGE; + contact->data.contactFlags |= CONTACT_FLAG_INCONTACT; + } + + penContacts[penFrame.contactCount++] = contact->data; + } + if (contact->data.contactFlags & CONTACT_FLAG_UP) + { + contact->externalId = 0; + contact->active = FALSE; + } + } + + if (penFrame.contactCount > 0) + return rdpei_send_pen_frame(context, &penFrame); + return CHANNEL_RC_OK; +} + +static UINT rdpei_update(RdpeiClientContext* context) +{ + UINT error = rdpei_add_frame(context); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error); + return error; + } + + return rdpei_add_pen_frame(context); +} + +static DWORD WINAPI rdpei_periodic_update(LPVOID arg) +{ + DWORD status; + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg; + UINT error = CHANNEL_RC_OK; + RdpeiClientContext* context; + + if (!rdpei) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + context = (RdpeiClientContext*)rdpei->iface.pInterface; + + if (!context) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + while (rdpei->initialized) + { + status = WaitForSingleObject(rdpei->event, 20); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + EnterCriticalSection(&rdpei->lock); + + error = rdpei_update(context); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rdpei_add_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 + */ +static UINT rdpei_send_cs_ready_pdu(RDPEI_CHANNEL_CALLBACK* callback) +{ + UINT status; + wStream* s; + UINT32 flags; + UINT32 pduLength; + RDPEI_PLUGIN* rdpei; + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)callback->plugin; + flags = 0; + flags |= CS_READY_FLAGS_SHOW_TOUCH_VISUALS; + if (rdpei->version > RDPINPUT_PROTOCOL_V10) + flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION; + flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_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, rdpei->version); /* 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; +} + +static 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 + */ +static UINT rdpei_write_touch_frame(wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + UINT32 index; + int rectSize = 2; + RDPINPUT_CONTACT_DATA* contact; + if (!s || !frame) + return ERROR_INTERNAL_ERROR; +#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 + */ +static UINT rdpei_send_touch_event_pdu(RDPEI_CHANNEL_CALLBACK* callback, + RDPINPUT_TOUCH_FRAME* frame) +{ + UINT status; + wStream* s; + UINT32 pduLength; + if (!frame) + return ERROR_INTERNAL_ERROR; + 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 + */ +static UINT rdpei_recv_sc_ready_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 features = 0; + UINT32 size; + UINT32 protocolVersion; + RDPEI_PLUGIN* rdpei; + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)callback->plugin; + + size = Stream_GetRemainingLength(s); + if (size < 4) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */ + + if (protocolVersion >= RDPINPUT_PROTOCOL_V300) + { + if (size < 8) + return ERROR_INVALID_DATA; + } + if (size >= 9) + Stream_Read_UINT32(s, features); + + if (rdpei->version > protocolVersion) + rdpei->version = protocolVersion; + rdpei->features = features; +#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 + */ +static UINT rdpei_recv_suspend_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + RdpeiClientContext* rdpei; + + WINPR_UNUSED(s); + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + rdpei = (RdpeiClientContext*)callback->plugin->pInterface; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + 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 + */ +static UINT rdpei_recv_resume_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + RdpeiClientContext* rdpei; + UINT error = CHANNEL_RC_OK; + if (!s || !callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + rdpei = (RdpeiClientContext*)callback->plugin->pInterface; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + 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 + */ +static UINT rdpei_recv_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT16 eventId; + UINT32 pduLength; + UINT error; + if (!s) + return ERROR_INTERNAL_ERROR; + if (Stream_GetRemainingLength(s) < 6) + return ERROR_INVALID_DATA; + + 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_string(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; + if (!listener_callback) + return ERROR_INTERNAL_ERROR; + callback = (RDPEI_CHANNEL_CALLBACK*)calloc(1, sizeof(RDPEI_CHANNEL_CALLBACK)); + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + 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_terminated(IWTSPlugin* pPlugin) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)pPlugin; + + if (!pPlugin) + return ERROR_INVALID_PARAMETER; + + if (rdpei && rdpei->listener_callback) + { + IWTSVirtualChannelManager* mgr = rdpei->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, rdpei->listener); + + rdpei->initialized = FALSE; + if (rdpei->event) + SetEvent(rdpei->event); + + if (rdpei->thread) + { + WaitForSingleObject(rdpei->thread, INFINITE); + CloseHandle(rdpei->thread); + } + if (rdpei->event) + CloseHandle(rdpei->event); + } + DeleteCriticalSection(&rdpei->lock); + free(rdpei->listener_callback); + free(rdpei->context); + free(rdpei); + 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; + + if (rdpei->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", RDPEI_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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, + &rdpei->listener_callback->iface, &(rdpei->listener)))) + { + WLog_ERR(TAG, "ChannelMgr->CreateListener failed with error %" PRIu32 "!", error); + goto error_out; + } + + rdpei->listener->pInterface = rdpei->iface.pInterface; + + InitializeCriticalSection(&rdpei->lock); + rdpei->event = CreateEventA(NULL, TRUE, FALSE, NULL); + if (!rdpei->event) + goto error_out; + rdpei->thread = CreateThread(NULL, 0, rdpei_periodic_update, rdpei, 0, NULL); + if (!rdpei->thread) + goto error_out; + rdpei->initialized = TRUE; + return error; +error_out: + rdpei_plugin_terminated(pPlugin); + return error; +} + +/** + * Channel Client Interface + */ + +static UINT32 rdpei_get_version(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei; + if (!context || !context->handle) + return -1; + rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->version; +} + +static UINT32 rdpei_get_features(RdpeiClientContext* context) +{ + RDPEI_PLUGIN* rdpei; + if (!context || !context->handle) + return -1; + rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->features; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame) +{ + 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; + frame->frameOffset = 0; + } + else + { + rdpei->currentFrameTime = currentTime; + frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime; + } + + if ((error = rdpei_send_touch_event_pdu(callback, frame))) + { + WLog_ERR(TAG, "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error); + return error; + } + + rdpei->previousFrameTime = rdpei->currentFrameTime; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact) +{ + RDPINPUT_CONTACT_POINT* contactPoint; + RDPEI_PLUGIN* rdpei; + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + contactPoint = &rdpei->contactPoints[contact->contactId]; + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + SetEvent(rdpei->event); + LeaveCriticalSection(&rdpei->lock); + + return CHANNEL_RC_OK; +} + +static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + INT32 x, INT32 y, INT32* contactId) +{ + INT64 contactIdlocal = -1; + RDPINPUT_CONTACT_POINT* contactPoint; + RDPEI_PLUGIN* rdpei; + BOOL begin; + UINT error = CHANNEL_RC_OK; + + if (!context || !contactId || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + /* Create a new contact point in an empty slot */ + EnterCriticalSection(&rdpei->lock); + begin = contactFlags & CONTACT_FLAG_DOWN; + contactPoint = rdpei_contact(rdpei, externalId, !begin); + if (contactPoint) + contactIdlocal = contactPoint->contactId; + LeaveCriticalSection(&rdpei->lock); + + if (contactIdlocal >= 0) + { + RDPINPUT_CONTACT_DATA contact = { 0 }; + contact.x = x; + contact.y = y; + contact.contactId = contactIdlocal; + contact.contactFlags = contactFlags; + error = context->AddContact(context, &contact); + } + + if (contactId) + *contactId = contactIdlocal; + return error; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + return rdpei_touch_process(context, externalId, + CONTACT_FLAG_DOWN | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, x, + y, contactId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + return rdpei_touch_process(context, externalId, + CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, + x, y, contactId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT error = rdpei_touch_process( + context, externalId, CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, x, + y, contactId); + if (error != CHANNEL_RC_OK) + return error; + return rdpei_touch_process(context, externalId, CONTACT_FLAG_UP, x, y, contactId); +} + +static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, + BOOL active) +{ + UINT32 x; + if (!rdpei) + return NULL; + + for (x = 0; x < rdpei->maxPenContacts; x++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x]; + if (active) + { + if (contact->active) + { + if (contact->externalId == externalId) + return contact; + } + } + else + { + if (!contact->active) + { + contact->externalId = externalId; + contact->active = TRUE; + return contact; + } + } + } + return NULL; +} + +static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId, + const RDPINPUT_PEN_CONTACT* contact) +{ + RDPEI_PLUGIN* rdpei; + RDPINPUT_PEN_CONTACT_POINT* contactPoint; + + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE); + if (contactPoint) + { + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + SetEvent(rdpei->event); + } + LeaveCriticalSection(&rdpei->lock); + + return CHANNEL_RC_OK; +} + +static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + UINT32 fieldFlags, INT32 x, INT32 y, va_list ap) +{ + RDPINPUT_PEN_CONTACT_POINT* contactPoint; + RDPEI_PLUGIN* rdpei; + BOOL begin; + UINT error = CHANNEL_RC_OK; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + rdpei = (RDPEI_PLUGIN*)context->handle; + begin = contactFlags & CONTACT_FLAG_DOWN; + + EnterCriticalSection(&rdpei->lock); + contactPoint = rdpei_pen_contact(rdpei, externalId, !begin); + LeaveCriticalSection(&rdpei->lock); + if (contactPoint != NULL) + { + RDPINPUT_PEN_CONTACT contact = { 0 }; + + contact.x = x; + contact.y = y; + contact.fieldsPresent = fieldFlags; + + contact.contactFlags = contactFlags; + if (fieldFlags & PEN_CONTACT_PENFLAGS_PRESENT) + contact.penFlags = va_arg(ap, UINT32); + if (fieldFlags & PEN_CONTACT_PRESSURE_PRESENT) + contact.pressure = va_arg(ap, UINT32); + if (fieldFlags & PEN_CONTACT_ROTATION_PRESENT) + contact.rotation = va_arg(ap, UINT32); + if (fieldFlags & PEN_CONTACT_TILTX_PRESENT) + contact.tiltX = va_arg(ap, INT32); + if (fieldFlags & PEN_CONTACT_TILTY_PRESENT) + contact.tiltY = va_arg(ap, INT32); + + error = context->AddPen(context, externalId, &contact); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + CONTACT_FLAG_DOWN | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x, + INT32 y, ...) +{ + UINT error; + va_list ap; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + if (error == CHANNEL_RC_OK) + error = rdpei_pen_process(context, externalId, CONTACT_FLAG_UP, fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +#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) + { + 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 = RDPINPUT_PROTOCOL_V300; + rdpei->currentFrameTime = 0; + rdpei->previousFrameTime = 0; + rdpei->maxTouchContacts = MAX_CONTACTS; + rdpei->maxPenContacts = MAX_PEN_CONTACTS; + rdpei->rdpcontext = + ((freerdp*)((rdpSettings*)pEntryPoints->GetRdpSettings(pEntryPoints))->instance) + ->context; + + 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->GetFeatures = rdpei_get_features; + context->AddContact = rdpei_add_contact; + context->TouchBegin = rdpei_touch_begin; + context->TouchUpdate = rdpei_touch_update; + context->TouchEnd = rdpei_touch_end; + context->AddPen = rdpei_add_pen; + context->PenBegin = rdpei_pen_begin; + context->PenUpdate = rdpei_pen_update; + context->PenEnd = rdpei_pen_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); + return error; +} diff --git a/channels/rdpei/client/rdpei_main.h b/channels/rdpei/client/rdpei_main.h new file mode 100644 index 0000000..01ecd5f --- /dev/null +++ b/channels/rdpei/client/rdpei_main.h @@ -0,0 +1,93 @@ +/** + * 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") + +/** + * 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 +{ + BOOL dirty; + BOOL active; + UINT32 contactId; + INT32 externalId; + RDPINPUT_CONTACT_DATA data; +}; +typedef struct _RDPINPUT_CONTACT_POINT RDPINPUT_CONTACT_POINT; + +struct _RDPINPUT_PEN_CONTACT_POINT +{ + BOOL dirty; + BOOL active; + INT32 externalId; + RDPINPUT_PEN_CONTACT data; +}; +typedef struct _RDPINPUT_PEN_CONTACT_POINT RDPINPUT_PEN_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..32df8a0 --- /dev/null +++ b/channels/rdpei/rdpei_common.c @@ -0,0 +1,643 @@ +/** + * 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, UINT16* 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, UINT16 value) +{ + BYTE byte; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + 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, INT16* 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, INT16 value) +{ + BYTE byte; + BOOL negative = FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return 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 (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value <= 0x3FUL) + { + Stream_Write_UINT8(s, value); + } + else if (value <= 0x3FFFUL) + { + byte = (value >> 8) & 0x3F; + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFFUL) + { + 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 <= 0x3FFFFFFFUL) + { + 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 (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value <= 0x1FL) + { + byte = value & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFL) + { + 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 <= 0x1FFFFFL) + { + 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 <= 0x1FFFFFFFL) + { + 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 (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + if (value <= 0x1FULL) + { + byte = value & 0x1F; + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFULL) + { + byte = (value >> 8) & 0x1F; + byte |= (1 << 5); + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFULL) + { + 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 <= 0x1FFFFFFFULL) + { + 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 <= 0x1FFFFFFFFFULL) + { + 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 <= 0x1FFFFFFFFFFFULL) + { + 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 <= 0x1FFFFFFFFFFFFFULL) + { + 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 <= 0x1FFFFFFFFFFFFFFFULL) + { + 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) +{ + UINT16 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; +} + +void pen_event_reset(RDPINPUT_PEN_EVENT* event) +{ + UINT16 i; + + for (i = 0; i < event->frameCount; i++) + pen_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = NULL; + event->frameCount = 0; +} + +void pen_frame_reset(RDPINPUT_PEN_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..3a5362f --- /dev/null +++ b/channels/rdpei/rdpei_common.h @@ -0,0 +1,57 @@ +/** + * 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, + EVENTID_PEN = 0x0008 +}; + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value); +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value); +BOOL rdpei_read_2byte_signed(wStream* s, INT16* value); +BOOL rdpei_write_2byte_signed(wStream* s, INT16 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); + +void pen_event_reset(RDPINPUT_PEN_EVENT* event); +void pen_frame_reset(RDPINPUT_PEN_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..433b2cf --- /dev/null +++ b/channels/rdpei/server/rdpei_main.c @@ -0,0 +1,746 @@ +/** + * 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; + RDPINPUT_PEN_EVENT penEvent; + + 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 fail; + + priv->inputStream = Stream_New(NULL, 256); + if (!priv->inputStream) + goto fail; + + priv->outputStream = Stream_New(NULL, 200); + if (!priv->inputStream) + goto fail; + + ret->vcm = vcm; + rdpei_server_context_reset(ret); + return ret; + +fail: + rdpei_server_context_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; + UINT32 channelId; + BOOL status = TRUE; + + 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; + } + + channelId = WTSChannelGetIdByHandle(priv->channelHandle); + + IFCALLRET(context->onChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->onChannelIdAssigned failed!"); + goto out_close; + } + + 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; + + if (!context) + return; + priv = context->priv; + if (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: + case RDPINPUT_PROTOCOL_V200: + case RDPINPUT_PROTOCOL_V300: + 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) +{ + UINT16 tmp; + WINPR_UNUSED(context); + 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, &tmp) || !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; + } + contactData->fieldsPresent = tmp; + + if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + INT16 tmp[4] = { 0 }; + if (!rdpei_read_2byte_signed(s, &tmp[0]) || !rdpei_read_2byte_signed(s, &tmp[1]) || + !rdpei_read_2byte_signed(s, &tmp[2]) || !rdpei_read_2byte_signed(s, &tmp[3])) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + contactData->contactRectLeft = tmp[0]; + contactData->contactRectTop = tmp[1]; + contactData->contactRectRight = tmp[2]; + contactData->contactRectBottom = tmp[3]; + } + + 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; +} + +static UINT read_pen_contact(RdpeiServerContext* context, wStream* s, + RDPINPUT_PEN_CONTACT* contactData) +{ + WINPR_UNUSED(context); + if (Stream_GetRemainingLength(s) < 1) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT8(s, contactData->deviceId); + 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 & PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->pressure)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_read_2byte_unsigned(s, &contactData->rotation)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltX)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltY)) + return ERROR_INVALID_DATA; + } + + 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) +{ + UINT32 i; + UINT16 tmp; + RDPINPUT_CONTACT_DATA* contact; + UINT error; + + if (!rdpei_read_2byte_unsigned(s, &tmp) || !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + frame->contactCount = tmp; + + 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; +} + +static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame) +{ + UINT32 i; + RDPINPUT_PEN_CONTACT* 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_pen_contact(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + frame->contactCount = i; + pen_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) +{ + UINT16 frameCount; + UINT32 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; +} + +static UINT read_pen_event(RdpeiServerContext* context, wStream* s) +{ + UINT16 frameCount; + UINT32 i; + RDPINPUT_PEN_EVENT* event = &context->priv->penEvent; + RDPINPUT_PEN_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_PEN_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (i = 0; i < frameCount; i++, frame++) + { + if ((error = read_pen_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error); + event->frameCount = i; + goto out_cleanup; + } + } + + IFCALLRET(context->onPenEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error); + +out_cleanup: + pen_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; + case EVENTID_PEN: + if ((error = read_pen_event(context, s))) + { + WLog_ERR(TAG, "read_pen_event 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; +} + +UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version) +{ + return rdpei_server_send_sc_ready_ex(context, version, 0); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_send_sc_ready_ex(RdpeiServerContext* context, UINT32 version, UINT32 features) +{ + ULONG written; + RdpeiServerPrivate* priv = context->priv; + UINT32 pduLen = 4; + + 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 (version >= RDPINPUT_PROTOCOL_V300) + pduLen += 4; + + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen)) + { + 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 + pduLen); + Stream_Write_UINT32(priv->outputStream, version); + if (version >= RDPINPUT_PROTOCOL_V300) + Stream_Write_UINT32(priv->outputStream, features); + + 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..cf3e3cb --- /dev/null +++ b/channels/rdpei/server/rdpei_main.h @@ -0,0 +1,32 @@ +/** + * 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..d89278c --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_codec.c @@ -0,0 +1,309 @@ +/** + * 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); + + 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..ceb4c18 --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_main.c @@ -0,0 +1,2219 @@ +/** + * 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") + +static void free_surfaces(RdpgfxClientContext* context, wHashTable* SurfaceTable) +{ + UINT error = 0; + ULONG_PTR* pKeys = NULL; + int count; + int index; + + count = HashTable_GetKeys(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_ERR(TAG, "context->DeleteSurface failed with error %" PRIu32 "", error); + } + } + } + + free(pKeys); +} + +static void evict_cache_slots(RdpgfxClientContext* context, UINT16 MaxCacheSlots, void** CacheSlots) +{ + UINT16 index; + + for (index = 0; index < MaxCacheSlots; index++) + { + if (CacheSlots[index]) + { + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu; + pdu.cacheSlot = (UINT16)index + 1; + + if (context && context->EvictCacheEntry) + { + context->EvictCacheEntry(context, &pdu); + } + + CacheSlots[index] = NULL; + } + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_caps_advertise_pdu(RdpgfxClientContext* context, + const RDPGFX_CAPS_ADVERTISE_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + UINT16 index; + RDPGFX_HEADER header; + RDPGFX_CAPSET* capsSet; + RDPGFX_PLUGIN* gfx; + RDPGFX_CHANNEL_CALLBACK* callback; + wStream* s; + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->listener_callback) + return ERROR_BAD_ARGUMENTS; + + callback = gfx->listener_callback->channel_callback; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_CAPSADVERTISE; + header.pduLength = RDPGFX_HEADER_SIZE + 2; + + for (index = 0; index < pdu->capsSetCount; index++) + { + capsSet = &(pdu->capsSets[index]); + header.pduLength += RDPGFX_CAPSET_BASE_SIZE + capsSet->length; + } + + DEBUG_RDPGFX(gfx->log, "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, capsSet->length); /* capsDataLength (4 bytes) */ + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + Stream_Zero(s, capsSet->length - 4); + } + + Stream_SealLength(s); + error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + NULL); +fail: + Stream_Free(s, TRUE); + return error; +} + +static BOOL rdpgfx_is_capability_filtered(RDPGFX_PLUGIN* gfx, UINT32 caps) +{ + const UINT32 filter = gfx->capsFilter; + const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81, + RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101, + RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103, + RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105, + RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR, + RDPGFX_CAPVERSION_107 }; + UINT32 x; + + for (x = 0; x < ARRAYSIZE(capList); x++) + { + if (caps == capList[x]) + return (filter & (1 << x)) != 0; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_supported_caps(RDPGFX_CHANNEL_CALLBACK* callback) +{ + RDPGFX_PLUGIN* gfx; + RdpgfxClientContext* context; + RDPGFX_CAPSET* capsSet; + RDPGFX_CAPSET capsSets[RDPGFX_NUMBER_CAPSETS] = { 0 }; + RDPGFX_CAPS_ADVERTISE_PDU pdu = { 0 }; + + if (!callback) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)callback->plugin; + + if (!gfx) + return ERROR_BAD_CONFIGURATION; + + context = (RdpgfxClientContext*)gfx->iface.pInterface; + + if (!context) + return ERROR_BAD_CONFIGURATION; + + pdu.capsSetCount = 0; + pdu.capsSets = (RDPGFX_CAPSET*)capsSets; + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_8)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_8; + capsSet->length = 4; + 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; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_81)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_81; + capsSet->length = 4; + 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) + { + UINT32 caps10Flags = 0; + + if (gfx->SmallCache) + caps10Flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + +#ifdef WITH_GFX_H264 + + if (!gfx->AVC444) + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; + +#else + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; +#endif + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_10)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_10; + capsSet->length = 4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_101)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_101; + capsSet->length = 0x10; + capsSet->flags = 0; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_102)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_102; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (gfx->ThinClient) + { + if ((caps10Flags & RDPGFX_CAPS_FLAG_AVC_DISABLED) == 0) + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_THINCLIENT; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_103)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_103; + capsSet->length = 0x4; + capsSet->flags = caps10Flags & ~RDPGFX_CAPS_FLAG_SMALL_CACHE; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_104)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_104; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_105)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_105; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_106; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106_ERR)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_106_ERR; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_107)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_107; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; +#if !defined(CAIRO_FOUND) && !defined(SWSCALE_FOUND) + capsSet->flags |= RDPGFX_CAPS_FLAG_SCALEDMAP_DISABLE; +#endif + } + } + + return IFCALLRESULT(ERROR_BAD_CONFIGURATION, context->CapsAdvertise, context, &pdu); +} + +/** + * 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; + RDPGFX_CAPS_CONFIRM_PDU pdu; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + 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, capsSet.length); /* capsDataLength (4 bytes) */ + Stream_Read_UINT32(s, capsSet.flags); /* capsData (4 bytes) */ + gfx->ConnectionCaps = capsSet; + DEBUG_RDPGFX(gfx->log, "RecvCapsConfirmPdu: version: 0x%08" PRIX32 " flags: 0x%08" PRIX32 "", + capsSet.version, capsSet.flags); + + if (!context) + return ERROR_BAD_CONFIGURATION; + + return IFCALLRESULT(CHANNEL_RC_OK, context->CapsConfirm, context, &pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_frame_acknowledge_pdu(RdpgfxClientContext* context, + const RDPGFX_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error; + wStream* s; + RDPGFX_HEADER header; + RDPGFX_PLUGIN* gfx; + RDPGFX_CHANNEL_CALLBACK* callback; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_FRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + DEBUG_RDPGFX(gfx->log, "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); + + if (error == CHANNEL_RC_OK) /* frame successfully acked */ + gfx->UnacknowledgedFrames--; + +fail: + Stream_Free(s, TRUE); + return error; +} + +static UINT rdpgfx_send_qoe_frame_acknowledge_pdu(RdpgfxClientContext* context, + const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error; + wStream* s; + RDPGFX_HEADER header; + RDPGFX_CHANNEL_CALLBACK* callback; + RDPGFX_PLUGIN* gfx; + header.flags = 0; + header.cmdId = RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + DEBUG_RDPGFX(gfx->log, "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_send_cache_import_offer_pdu(RdpgfxClientContext* context, + const RDPGFX_CACHE_IMPORT_OFFER_PDU* pdu) +{ + UINT16 index; + UINT error = CHANNEL_RC_OK; + wStream* s; + RDPGFX_PLUGIN* gfx; + RDPGFX_CHANNEL_CALLBACK* callback; + RDPGFX_HEADER header; + RDPGFX_CACHE_ENTRY_METADATA* cacheEntries; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_CACHEIMPORTOFFER; + header.pduLength = RDPGFX_HEADER_SIZE + 2 + pdu->cacheEntriesCount * 12; + DEBUG_RDPGFX(gfx->log, "SendCacheImportOfferPdu: cacheEntriesCount: %" PRIu16 "", + pdu->cacheEntriesCount); + 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; + + if (pdu->cacheEntriesCount <= 0) + { + WLog_ERR(TAG, "Invalid cacheEntriesCount: %" PRIu16 "", pdu->cacheEntriesCount); + error = ERROR_INVALID_DATA; + goto fail; + } + + /* cacheEntriesCount (2 bytes) */ + Stream_Write_UINT16(s, pdu->cacheEntriesCount); + + for (index = 0; index < pdu->cacheEntriesCount; index++) + { + cacheEntries = &(pdu->cacheEntries[index]); + Stream_Write_UINT64(s, cacheEntries->cacheKey); /* cacheKey (8 bytes) */ + Stream_Write_UINT32(s, cacheEntries->bitmapLength); /* bitmapLength (4 bytes) */ + } + + 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) */ + DEBUG_RDPGFX(gfx->log, + "RecvResetGraphicsPdu: width: %" PRIu32 " height: %" PRIu32 " count: %" PRIu32 "", + pdu.width, pdu.height, pdu.monitorCount); + + for (index = 0; index < pdu.monitorCount; index++) + { + monitor = &(pdu.monitorDefArray[index]); + DEBUG_RDPGFX(gfx->log, + "RecvResetGraphicsPdu: monitor left:%" PRIi32 " top:%" PRIi32 " right:%" PRIi32 + " bottom:%" 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, "libfreerdp"); + 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) */ + } + + DEBUG_RDPGFX(gfx->log, "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) */ + DEBUG_RDPGFX(gfx->log, + "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) */ + DEBUG_RDPGFX(gfx->log, "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) */ + DEBUG_RDPGFX(gfx->log, "RecvStartFramePdu: frameId: %" PRIu32 " timestamp: 0x%08" PRIX32 "", + pdu.frameId, pdu.timestamp); + gfx->StartDecodingTime = GetTickCount64(); + + 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) */ + DEBUG_RDPGFX(gfx->log, "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->TotalDecodedFrames++; + + if (!gfx->sendFrameAcks) + return error; + + 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(context, &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(context, &ack))) + WLog_Print(gfx->log, WLOG_ERROR, + "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: + case RDPGFX_CAPVERSION_104: + case RDPGFX_CAPVERSION_105: + case RDPGFX_CAPVERSION_106: + case RDPGFX_CAPVERSION_106_ERR: + case RDPGFX_CAPVERSION_107: + if (gfx->SendQoeAck) + { + RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU qoe; + UINT64 diff = (GetTickCount64() - 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(context, &qoe))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_send_qoe_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); + + DEBUG_RDPGFX(gfx->log, + "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 (cmd.right < cmd.left) + { + WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu right=%" PRIu32 " < left=%" PRIu32, + cmd.right, cmd.left); + return ERROR_INVALID_DATA; + } + if (cmd.bottom < cmd.top) + { + WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu bottom=%" PRIu32 " < top=%" PRIu32, + cmd.bottom, cmd.top); + return ERROR_INVALID_DATA; + } + + 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); + DEBUG_RDPGFX(gfx->log, + "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; + + 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 = 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) */ + + DEBUG_RDPGFX(gfx->log, + "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; + } + } + DEBUG_RDPGFX(gfx->log, "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; + } + } + + DEBUG_RDPGFX(gfx->log, + "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; + } + + DEBUG_RDPGFX(gfx->log, + "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; + } + } + + DEBUG_RDPGFX(gfx->log, + "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) */ + DEBUG_RDPGFX(gfx->log, + "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; +} + +static UINT rdpgfx_recv_map_surface_to_scaled_output_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_SCALED_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) < 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_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) */ + Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToScaledOutputPdu: surfaceId: %" PRIu16 " outputOriginX: %" PRIu32 + " outputOriginY: %" PRIu32 " targetWidth: %" PRIu32 " targetHeight: %" PRIu32, + pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY, pdu.targetWidth, + pdu.targetHeight); + + if (context) + { + IFCALLRET(context->MapSurfaceToScaledOutput, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToScaledOutput 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) */ + DEBUG_RDPGFX(gfx->log, + "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; +} + +static UINT rdpgfx_recv_map_surface_to_scaled_window_pdu(RDPGFX_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_SCALED_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) < 26) + { + 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) */ + Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToScaledWindowPdu: surfaceId: %" PRIu16 " windowId: 0x%016" PRIX64 + " mappedWidth: %" PRIu32 " mappedHeight: %" PRIu32 " targetWidth: %" PRIu32 + " targetHeight: %" PRIu32 "", + pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight, pdu.targetWidth, + pdu.targetHeight); + + if (context && context->MapSurfaceToScaledWindow) + { + IFCALLRET(context->MapSurfaceToScaledWindow, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToScaledWindow 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; + } + + DEBUG_RDPGFX( + gfx->log, "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; + + case RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW: + if ((error = rdpgfx_recv_map_surface_to_scaled_window_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_scaled_window_pdu failed with error %" PRIu32 + "!", + error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT: + if ((error = rdpgfx_recv_map_surface_to_scaled_output_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_scaled_output_pdu failed with error %" PRIu32 + "!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + break; + } + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "Error while processing 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; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + UINT error = CHANNEL_RC_OK; + BOOL do_caps_advertise = TRUE; + gfx->sendFrameAcks = TRUE; + + if (context) + { + IFCALLRET(context->OnOpen, error, context, &do_caps_advertise, &gfx->sendFrameAcks); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->OnOpen failed with error %" PRIu32 "", + error); + } + + if (do_caps_advertise) + error = rdpgfx_send_supported_caps(callback); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*)pChannelCallback; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + + DEBUG_RDPGFX(gfx->log, "OnClose"); + free_surfaces(context, gfx->SurfaceTable); + evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots); + + free(callback); + gfx->UnacknowledgedFrames = 0; + gfx->TotalDecodedFrames = 0; + + if (context) + { + IFCALL(context->OnClose, context); + } + + 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; + if (gfx->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", RDPGFX_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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, + &gfx->listener_callback->iface, &(gfx->listener)); + gfx->listener->pInterface = gfx->iface.pInterface; + DEBUG_RDPGFX(gfx->log, "Initialize"); + + gfx->initialized = error == CHANNEL_RC_OK; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_plugin_terminated(IWTSPlugin* pPlugin) +{ + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)pPlugin; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + DEBUG_RDPGFX(gfx->log, "Terminated"); + if (gfx && gfx->listener_callback) + { + IWTSVirtualChannelManager* mgr = gfx->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, gfx->listener); + } + rdpgfx_client_context_free(context); + 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; + + /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */ + if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots) + { + WLog_ERR(TAG, "%s: invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "", + __FUNCTION__, cacheSlot, gfx->MaxCacheSlots); + return ERROR_INVALID_INDEX; + } + + gfx->CacheSlots[cacheSlot - 1] = 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; + + /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */ + if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots) + { + WLog_ERR(TAG, "%s: invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "", + __FUNCTION__, cacheSlot, gfx->MaxCacheSlots); + return NULL; + } + + pData = gfx->CacheSlots[cacheSlot - 1]; + return pData; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry rdpgfx_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +RdpgfxClientContext* rdpgfx_client_context_new(rdpSettings* settings) +{ + RDPGFX_PLUGIN* gfx; + RdpgfxClientContext* context; + + gfx = (RDPGFX_PLUGIN*)calloc(1, sizeof(RDPGFX_PLUGIN)); + + if (!gfx) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + gfx->log = WLog_Get(TAG); + + if (!gfx->log) + { + free(gfx); + WLog_ERR(TAG, "Failed to acquire reference to WLog %s", TAG); + return NULL; + } + + gfx->settings = settings; + gfx->rdpcontext = ((freerdp*)gfx->settings->instance)->context; + gfx->SurfaceTable = HashTable_New(TRUE); + + if (!gfx->SurfaceTable) + { + free(gfx); + WLog_ERR(TAG, "HashTable_New failed!"); + return NULL; + } + + 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; + gfx->capsFilter = gfx->settings->GfxCapsFilter; + + if (gfx->H264) + gfx->SmallCache = TRUE; + + gfx->MaxCacheSlots = gfx->SmallCache ? 4096 : 25600; + context = (RdpgfxClientContext*)calloc(1, sizeof(RdpgfxClientContext)); + + if (!context) + { + free(gfx); + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + 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; + context->CapsAdvertise = rdpgfx_send_caps_advertise_pdu; + context->FrameAcknowledge = rdpgfx_send_frame_acknowledge_pdu; + context->CacheImportOffer = rdpgfx_send_cache_import_offer_pdu; + context->QoeFrameAcknowledge = rdpgfx_send_qoe_frame_acknowledge_pdu; + + 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 NULL; + } + + return context; +} + +void rdpgfx_client_context_free(RdpgfxClientContext* context) +{ + + RDPGFX_PLUGIN* gfx; + + if (!context) + return; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + free_surfaces(context, gfx->SurfaceTable); + evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots); + + if (gfx->listener_callback) + { + free(gfx->listener_callback); + gfx->listener_callback = NULL; + } + + if (gfx->zgfx) + { + zgfx_context_free(gfx->zgfx); + gfx->zgfx = NULL; + } + + HashTable_Free(gfx->SurfaceTable); + free(context); + free(gfx); +} + +/** + * 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) + { + context = + rdpgfx_client_context_new((rdpSettings*)pEntryPoints->GetRdpSettings(pEntryPoints)); + + if (!context) + { + WLog_ERR(TAG, "rdpgfx_client_context_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + gfx = (RDPGFX_PLUGIN*)context->handle; + + gfx->iface.Initialize = rdpgfx_plugin_initialize; + gfx->iface.Connected = NULL; + gfx->iface.Disconnected = NULL; + gfx->iface.Terminated = rdpgfx_plugin_terminated; + + 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..760d1be --- /dev/null +++ b/channels/rdpgfx/client/rdpgfx_main.h @@ -0,0 +1,92 @@ +/** + * 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; + UINT32 capsFilter; + + ZGFX_CONTEXT* zgfx; + UINT32 UnacknowledgedFrames; + UINT32 TotalDecodedFrames; + UINT64 StartDecodingTime; + BOOL suspendFrameAcks; + BOOL sendFrameAcks; + + wHashTable* SurfaceTable; + + UINT16 MaxCacheSlots; + void* CacheSlots[25600]; + rdpContext* rdpcontext; + + wLog* log; + RDPGFX_CAPSET ConnectionCaps; + BOOL SendQoeAck; + BOOL initialized; +}; +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..03fc97c --- /dev/null +++ b/channels/rdpgfx/rdpgfx_common.c @@ -0,0 +1,251 @@ +/** + * 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 +#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", + "RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT", + "RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW" }; + +const char* rdpgfx_get_cmd_id_string(UINT16 cmdId) +{ + if (cmdId <= RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW) + 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) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(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) */ + + if ((header->pduLength < 8) || (Stream_GetRemainingLength(s) < (header->pduLength - 8))) + { + WLog_ERR(TAG, "header->pduLength %u less than 8!", header->pduLength); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_header(wStream* s, const RDPGFX_HEADER* header) +{ + if (!Stream_EnsureRemainingCapacity(s, 8)) + return ERROR_INTERNAL_ERROR; + 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, const 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) */ + if (rect16->left >= rect16->right) + return ERROR_INVALID_DATA; + if (rect16->top >= rect16->bottom) + return ERROR_INVALID_DATA; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_rect16(wStream* s, const 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, const 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..664c9cc --- /dev/null +++ b/channels/rdpgfx/rdpgfx_common.h @@ -0,0 +1,55 @@ +/** + * 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, const RDPGFX_HEADER* header); + +FREERDP_LOCAL UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16); +FREERDP_LOCAL UINT rdpgfx_write_point16(wStream* s, const RDPGFX_POINT16* point16); + +FREERDP_LOCAL UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16); +FREERDP_LOCAL UINT rdpgfx_write_rect16(wStream* s, const RECTANGLE_16* rect16); + +FREERDP_LOCAL UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32); +FREERDP_LOCAL UINT rdpgfx_write_color32(wStream* s, const RDPGFX_COLOR32* color32); + +#ifdef WITH_DEBUG_RDPGFX +#define DEBUG_RDPGFX(_LOGGER, ...) WLog_Print(_LOGGER, WLOG_DEBUG, __VA_ARGS__) +#else +#define DEBUG_RDPGFX(_LOGGER, ...) \ + do \ + { \ + } while (0) +#endif + +#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..283bb88 --- /dev/null +++ b/channels/rdpgfx/server/rdpgfx_main.c @@ -0,0 +1,1720 @@ +/** + * 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 BOOL rdpgfx_server_packet_complete_header(wStream* s, size_t start) +{ + const size_t current = Stream_GetPosition(s); + const size_t cap = Stream_Capacity(s); + if (cap < start + RDPGFX_HEADER_SIZE) + return FALSE; + /* 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); + return TRUE; +} + +/** + * 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, + const RDPGFX_CAPS_CONFIRM_PDU* capsConfirm) +{ + RDPGFX_CAPSET* capsSet = capsConfirm->capsSet; + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_CAPSCONFIRM, + RDPGFX_CAPSET_BASE_SIZE + capsSet->length); + + 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, capsSet->length); /* capsDataLength (4 bytes) */ + + if (capsSet->length >= 4) + { + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + Stream_Zero(s, capsSet->length - 4); + } + else + Stream_Zero(s, capsSet->length); + + 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, + const 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, + const 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, + const 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, + const 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, + const 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 BOOL rdpgfx_write_start_frame_pdu(wStream* s, const RDPGFX_START_FRAME_PDU* pdu) +{ + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + Stream_Write_UINT32(s, pdu->timestamp); /* timestamp (4 bytes) */ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + return TRUE; +} + +static INLINE BOOL rdpgfx_write_end_frame_pdu(wStream* s, const RDPGFX_END_FRAME_PDU* pdu) +{ + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_start_frame_pdu(RdpgfxServerContext* context, + const 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, const 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(const 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(const 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(const 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, const 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, const 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) + { + if (!Stream_EnsureRemainingCapacity(s, 13 + cmd->length)) + return ERROR_INTERNAL_ERROR; + /* 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 */ + if (!Stream_EnsureRemainingCapacity(s, 17)) + return ERROR_INTERNAL_ERROR; + 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) */ + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_INTERNAL_ERROR; + 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 + { + if (!Stream_EnsureRemainingCapacity(s, cmd->length)) + return ERROR_INTERNAL_ERROR; + Stream_Write(s, cmd->data, cmd->length); + } + + /* Fill actual bitmap data length */ + bitmapDataLength = Stream_GetPosition(s) - bitmapDataStart; + Stream_SetPosition(s, bitmapDataStart - sizeof(UINT32)); + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_INTERNAL_ERROR; + Stream_Write_UINT32(s, bitmapDataLength); /* bitmapDataLength (4 bytes) */ + if (!Stream_SafeSeek(s, bitmapDataLength)) + return ERROR_INTERNAL_ERROR; + } + + 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, + const 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, + const RDPGFX_SURFACE_COMMAND* cmd, + const RDPGFX_START_FRAME_PDU* startFrame, + const 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; + } + + if (!rdpgfx_write_start_frame_pdu(s, startFrame) || + !rdpgfx_server_packet_complete_header(s, position)) + goto error; + } + + /* 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; + } + + if (!rdpgfx_server_packet_complete_header(s, position)) + goto error; + + /* 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; + } + + if (!rdpgfx_write_end_frame_pdu(s, endFrame) || + !rdpgfx_server_packet_complete_header(s, position)) + goto error; + } + + 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, + const 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, + const 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, + const 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, + const 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, + const 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, + const 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, + const 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); +} + +static UINT +rdpgfx_send_map_surface_to_scaled_window_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW, 26); + + 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) */ + Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (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) */ + Stream_Read_UINT32(s, pdu.totalFramesDecoded); /* totalFramesDecoded (4 bytes) */ + + 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 = { 0 }; + 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); + + /* 2.2.2.16 RDPGFX_CACHE_IMPORT_OFFER_PDU */ + if (pdu.cacheEntriesCount >= 5462) + { + 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; + } + + if (pdu.cacheEntriesCount > 0) + { + 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) */ + Stream_Read_UINT32(s, cacheEntries->bitmapLength); /* bitmapLength (4 bytes) */ + } + + 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 = NULL; + RDPGFX_CAPS_ADVERTISE_PDU pdu = { 0 }; + UINT error = ERROR_INVALID_DATA; + + if (!context) + return ERROR_BAD_ARGUMENTS; + + 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 (pdu.capsSetCount > 0) + { + capsSets = calloc(pdu.capsSetCount, (RDPGFX_CAPSET_BASE_SIZE + 4)); + if (!capsSets) + return ERROR_OUTOFMEMORY; + } + + pdu.capsSets = capsSets; + + for (index = 0; index < pdu.capsSetCount; index++) + { + RDPGFX_CAPSET* capsSet = &(pdu.capsSets[index]); + + if (Stream_GetRemainingLength(s) < 8) + goto fail; + + Stream_Read_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Read_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */ + + if (capsSet->length >= 4) + { + if (Stream_GetRemainingLength(s) < 4) + goto fail; + + Stream_Peek_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + } + + if (!Stream_SafeSeek(s, capsSet->length)) + goto fail; + } + + error = ERROR_BAD_CONFIGURATION; + IFCALLRET(context->CapsAdvertise, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->CapsAdvertise failed with error %" PRIu32 "", error); + +fail: + 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; +} + +static UINT +rdpgfx_send_map_surface_to_scaled_output_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* pdu) +{ + wStream* s = rdpgfx_server_single_packet_new(RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT, 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_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) */ + Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * 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; + } + +#ifdef WITH_DEBUG_RDPGFX + 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); +#endif + + 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; + UINT32 channelId; + BOOL status = TRUE; + + 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; + } + + channelId = WTSChannelGetIdByHandle(priv->rdpgfx_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + goto out_close; + } + + /* 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->MapSurfaceToScaledOutput = rdpgfx_send_map_surface_to_scaled_output_pdu; + context->MapSurfaceToScaledWindow = rdpgfx_send_map_surface_to_scaled_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..948ba97 --- /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;dynamic" + 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..70f4aa2 --- /dev/null +++ b/channels/rdpsnd/client/CMakeLists.txt @@ -0,0 +1,64 @@ +# 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;DVCPluginEntry") + +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() + +if (WITH_SERVER) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "proxy" "") +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..a903596 --- /dev/null +++ b/channels/rdpsnd/client/alsa/rdpsnd_alsa.c @@ -0,0 +1,576 @@ +/** + * 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; + + 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; +} + +/** + * 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; + 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 } }; + 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..fe77cda --- /dev/null +++ b/channels/rdpsnd/client/fake/rdpsnd_fake.c @@ -0,0 +1,155 @@ +/** + * 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 UINT rdpsnd_fake_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + return CHANNEL_RC_OK; +} + +/** + * 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; + COMMAND_LINE_ARGUMENT_A rdpsnd_fake_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } }; + 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 = CHANNEL_RC_OK; + 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.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; + } + } + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &fake->device); + return ret; +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..b29f611 --- /dev/null +++ b/channels/rdpsnd/client/ios/TPCircularBuffer.c @@ -0,0 +1,153 @@ +// +// 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..d246efa --- /dev/null +++ b/channels/rdpsnd/client/ios/TPCircularBuffer.h @@ -0,0 +1,217 @@ +// +// 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..29feb97 --- /dev/null +++ b/channels/rdpsnd/client/ios/rdpsnd_ios.c @@ -0,0 +1,296 @@ +/** + * 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..8b84656 --- /dev/null +++ b/channels/rdpsnd/client/mac/CMakeLists.txt @@ -0,0 +1,49 @@ +# 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(COCOA_LIBRARY Cocoa REQUIRED) +FIND_LIBRARY(CORE_FOUNDATION CoreFoundation) +FIND_LIBRARY(CORE_AUDIO CoreAudio REQUIRED) +FIND_LIBRARY(AUDIO_TOOL AudioToolbox REQUIRED) +FIND_LIBRARY(AV_FOUNDATION AVFoundation REQUIRED) + +set(${MODULE_PREFIX}_SRCS + rdpsnd_mac.m) + +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} + ${AV_FOUNDATION} + ${CORE_AUDIO} + ${COCOA_LIBRARY} + ${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.m b/channels/rdpsnd/client/mac/rdpsnd_mac.m new file mode 100644 index 0000000..94ef150 --- /dev/null +++ b/channels/rdpsnd/client/mac/rdpsnd_mac.m @@ -0,0 +1,403 @@ +/** + * 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 + +#include + +#include +#include + +#include "rdpsnd_main.h" + +struct rdpsnd_mac_plugin +{ + rdpsndDevicePlugin device; + + BOOL isOpen; + BOOL isPlaying; + + UINT32 latency; + AUDIO_FORMAT format; + + AVAudioEngine *engine; + AVAudioPlayerNode *player; + UINT64 diff; +}; +typedef struct rdpsnd_mac_plugin rdpsndMacPlugin; + +static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, + UINT32 latency) +{ + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + if (!mac || !format) + return FALSE; + + mac->latency = latency; + mac->format = *format; + + 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 void rdpsnd_mac_release(rdpsndMacPlugin *mac) +{ + if (mac->player) + [mac->player release]; + mac->player = NULL; + + if (mac->engine) + [mac->engine release]; + mac->engine = NULL; +} + +static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency) +{ + @autoreleasepool + { + AudioDeviceID outputDeviceID; + UInt32 propertySize; + OSStatus err; + NSError *error; + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + AudioObjectPropertyAddress propertyAddress = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, +#if defined(MAC_OS_VERSION_12_0) + kAudioObjectPropertyElementMain +#else + kAudioObjectPropertyElementMaster +#endif + }; + + if (mac->isOpen) + return TRUE; + + if (!rdpsnd_mac_set_format(device, format, latency)) + return FALSE; + + propertySize = sizeof(outputDeviceID); + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, + &propertySize, &outputDeviceID); + if (err) + { + WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err)); + return FALSE; + } + + mac->engine = [[AVAudioEngine alloc] init]; + if (!mac->engine) + return FALSE; + + err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit, + kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, + 0, &outputDeviceID, sizeof(outputDeviceID)); + if (err) + { + rdpsnd_mac_release(mac); + WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err)); + return FALSE; + } + + mac->player = [[AVAudioPlayerNode alloc] init]; + if (!mac->player) + { + rdpsnd_mac_release(mac); + WLog_ERR(TAG, "AVAudioPlayerNode::init() failed"); + return FALSE; + } + + [mac->engine attachNode:mac->player]; + + [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil]; + + [mac->engine prepare]; + + if (![mac->engine startAndReturnError:&error]) + { + device->Close(device); + WLog_ERR(TAG, "Failed to start audio player %s", + [error.localizedDescription UTF8String]); + return FALSE; + } + + mac->isOpen = TRUE; + return TRUE; + } +} + +static void rdpsnd_mac_close(rdpsndDevicePlugin *device) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (mac->isPlaying) + { + [mac->player stop]; + mac->isPlaying = FALSE; + } + + if (mac->isOpen) + { + [mac->engine stop]; + mac->isOpen = FALSE; + } + + rdpsnd_mac_release(mac); + } +} + +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) +{ + WINPR_UNUSED(device); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->wBitsPerSample != 16) + return FALSE; + return TRUE; + + default: + return FALSE; + } +} + +static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value) +{ + @autoreleasepool + { + Float32 fVolume; + UINT16 volumeLeft; + UINT16 volumeRight; + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (!mac->player) + return FALSE; + + volumeLeft = (value & 0xFFFF); + volumeRight = ((value >> 16) & 0xFFFF); + fVolume = ((float)volumeLeft) / 65535.0f; + + mac->player.volume = fVolume; + + return TRUE; + } +} + +static void rdpsnd_mac_start(rdpsndDevicePlugin *device) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (!mac->isPlaying) + { + if (!mac->engine.isRunning) + { + NSError *error; + + if (![mac->engine startAndReturnError:&error]) + { + device->Close(device); + WLog_ERR(TAG, "Failed to start audio player %s", + [error.localizedDescription UTF8String]); + return; + } + } + + [mac->player play]; + + mac->isPlaying = TRUE; + mac->diff = 100; /* Initial latency, corrected after first sample is played. */ + } + } +} + +static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + AVAudioPCMBuffer *buffer; + AVAudioFormat *format; + float *const *db; + size_t pos, step, x; + AVAudioFrameCount count; + UINT64 start = GetTickCount64(); + + if (!mac->isOpen) + return 0; + + step = 2 * mac->format.nChannels; + + count = size / step; + format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 + sampleRate:mac->format.nSamplesPerSec + channels:mac->format.nChannels + interleaved:NO]; + if (!format) + { + WLog_WARN(TAG, "AVAudioFormat::init() failed"); + return 0; + } + buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count]; + [format release]; + if (!buffer) + { + WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed"); + return 0; + } + + buffer.frameLength = buffer.frameCapacity; + db = buffer.floatChannelData; + + for (pos = 0; pos < count; pos++) + { + const BYTE *d = &data[pos * step]; + for (x = 0; x < mac->format.nChannels; x++) + { + const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f; + db[x][pos] = val; + d += sizeof(int16_t); + } + } + + rdpsnd_mac_start(device); + + [mac->player scheduleBuffer:buffer + completionHandler:^{ + UINT64 stop = GetTickCount64(); + if (start > stop) + mac->diff = 0; + else + mac->diff = stop - start; + }]; + [buffer release]; + + return mac->diff > UINT_MAX ? UINT_MAX : mac->diff; + } +} + +#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..61afc10 --- /dev/null +++ b/channels/rdpsnd/client/opensles/opensl_io.c @@ -0,0 +1,421 @@ +/* +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..57dc048 --- /dev/null +++ b/channels/rdpsnd/client/opensles/opensl_io.h @@ -0,0 +1,110 @@ +/* +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..3481d91 --- /dev/null +++ b/channels/rdpsnd/client/opensles/rdpsnd_opensles.c @@ -0,0 +1,386 @@ +/** + * 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 int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + 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 } + }; + + 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..f8e15d2 --- /dev/null +++ b/channels/rdpsnd/client/oss/rdpsnd_oss.c @@ -0,0 +1,475 @@ +/** + * 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; + + 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 ((size_t)status <= size) + size -= (size_t)status; + else + size = 0; + } + + return 10; /* TODO: Get real latency in [ms] */ +} + +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; + 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 } }; + 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/proxy/CMakeLists.txt b/channels/rdpsnd/client/proxy/CMakeLists.txt new file mode 100644 index 0000000..2d40750 --- /dev/null +++ b/channels/rdpsnd/client/proxy/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" "proxy" "") + +set(${MODULE_PREFIX}_SRCS + rdpsnd_proxy.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/proxy") diff --git a/channels/rdpsnd/client/proxy/rdpsnd_proxy.c b/channels/rdpsnd/client/proxy/rdpsnd_proxy.c new file mode 100644 index 0000000..dd6af04 --- /dev/null +++ b/channels/rdpsnd/client/proxy/rdpsnd_proxy.c @@ -0,0 +1,144 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP rdpsnd proxy subsystem + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "rdpsnd_main.h" +#include "../../../../server/proxy/pf_context.h" + +typedef struct rdpsnd_proxy_plugin rdpsndProxyPlugin; + +struct rdpsnd_proxy_plugin +{ + rdpsndDevicePlugin device; + RdpsndServerContext* rdpsnd_server; +}; + +static BOOL rdpsnd_proxy_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndProxyPlugin* proxy = (rdpsndProxyPlugin*)device; + + /* update proxy's rdpsnd server latency */ + proxy->rdpsnd_server->latency = latency; + return TRUE; +} + +static void rdpsnd_proxy_close(rdpsndDevicePlugin* device) +{ + /* do nothing */ +} + +static BOOL rdpsnd_proxy_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndProxyPlugin* proxy = (rdpsndProxyPlugin*)device; + proxy->rdpsnd_server->SetVolume(proxy->rdpsnd_server, value, value); + return TRUE; +} + +static void rdpsnd_proxy_free(rdpsndDevicePlugin* device) +{ + rdpsndProxyPlugin* proxy = (rdpsndProxyPlugin*)device; + + if (!proxy) + return; + + free(proxy); +} + +static BOOL rdpsnd_proxy_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + rdpsndProxyPlugin* proxy = (rdpsndProxyPlugin*)device; + + /* use the same format that proxy's server used */ + if (proxy->rdpsnd_server->selected_client_format == format->wFormatTag) + return TRUE; + + return FALSE; +} + +static UINT rdpsnd_proxy_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + rdpsndProxyPlugin* proxy = (rdpsndProxyPlugin*)device; + UINT64 start = GetTickCount(); + proxy->rdpsnd_server->SendSamples(proxy->rdpsnd_server, data, size / 4, start); + return GetTickCount() - start; +} + + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ + +#ifdef BUILTIN_CHANNELS +#define freerdp_rdpsnd_client_subsystem_entry proxy_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; + rdpsndProxyPlugin* proxy; + pClientContext* pc; + proxy = (rdpsndProxyPlugin*)calloc(1, sizeof(rdpsndProxyPlugin)); + + if (!proxy) + return CHANNEL_RC_NO_MEMORY; + + proxy->device.Open = rdpsnd_proxy_open; + proxy->device.FormatSupported = rdpsnd_proxy_format_supported; + proxy->device.SetVolume = rdpsnd_proxy_set_volume; + proxy->device.Play = rdpsnd_proxy_play; + proxy->device.Close = rdpsnd_proxy_close; + proxy->device.Free = rdpsnd_proxy_free; + args = pEntryPoints->args; + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &proxy->device); + pc = (pClientContext*)freerdp_rdpsnd_get_context(pEntryPoints->rdpsnd); + if (pc == NULL) + { + free(proxy); + return ERROR_INTERNAL_ERROR; + } + + proxy->rdpsnd_server = pc->pdata->ps->rdpsnd; + 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..7a95470 --- /dev/null +++ b/channels/rdpsnd/client/pulse/rdpsnd_pulse.c @@ -0,0 +1,631 @@ +/** + * 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); +} + +static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired, + AUDIO_FORMAT* defaultFormat) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + if (!pulse || !defaultFormat) + return FALSE; + + *defaultFormat = *desired; + defaultFormat->data = NULL; + defaultFormat->cbSize = 0; + defaultFormat->wFormatTag = WAVE_FORMAT_PCM; + if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX)) + defaultFormat->nChannels = 2; + if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX)) + defaultFormat->nSamplesPerSec = 44100; + if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16)) + defaultFormat->wBitsPerSample = 16; + + defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8; + defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec; + return TRUE; +} + +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; + + default: + 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; +} + +/** + * 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; + 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 } }; + 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.Close = rdpsnd_pulse_close; + pulse->device.Free = rdpsnd_pulse_free; + pulse->device.DefaultFormat = rdpsnd_pulse_default_format; + 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..f624058 --- /dev/null +++ b/channels/rdpsnd/client/rdpsnd_main.c @@ -0,0 +1,1706 @@ +/** + * 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_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; +typedef struct _RDPSND_CHANNEL_CALLBACK RDPSND_CHANNEL_CALLBACK; + +struct _RDPSND_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + RDPSND_CHANNEL_CALLBACK* channel_callback; +}; +typedef struct _RDPSND_LISTENER_CALLBACK RDPSND_LISTENER_CALLBACK; + +struct rdpsnd_plugin +{ + IWTSPlugin iface; + IWTSListener* listener; + RDPSND_LISTENER_CALLBACK* listener_callback; + + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + wStreamPool* pool; + wStream* data_in; + + void* InitHandle; + DWORD OpenHandle; + + wLog* log; + + BYTE cBlockNo; + UINT16 wQualityMode; + UINT16 wCurrentFormatNo; + + AUDIO_FORMAT* ServerFormats; + UINT16 NumberOfServerFormats; + + AUDIO_FORMAT* ClientFormats; + UINT16 NumberOfClientFormats; + + BOOL attached; + BOOL connected; + BOOL dynamic; + + BOOL expectingWave; + BYTE waveData[4]; + UINT16 waveDataSize; + UINT16 wTimeStamp; + UINT64 wArrivalTime; + + UINT32 latency; + BOOL isOpen; + AUDIO_FORMAT* fixed_format; + + UINT32 startPlayTime; + size_t totalPlaySize; + + char* subsystem; + char* device_name; + + /* Device plugin */ + rdpsndDevicePlugin* device; + rdpContext* rdpcontext; + + FREERDP_DSP_CONTEXT* dsp_context; + + HANDLE thread; + wMessageQueue* queue; + BOOL initialized; +}; + +static const char* rdpsnd_is_dyn_str(BOOL dynamic) +{ + if (dynamic) + return "[dynamic]"; + return "[static]"; +} + +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, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, "%s QualityMode: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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 || !rdpsnd->device) + 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; + + if (!rdpsnd->device || (!rdpsnd->dynamic && (rdpsnd->OpenHandle == 0))) + return CHANNEL_RC_INITIALIZATION_ERROR; + + 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, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, "%s Client Audio Formats", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, "%s Server Audio Formats", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, + "%s Training Response: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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, + "%s Training Request: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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; + + IFCALL(rdpsnd->device->Close, rdpsnd->device); + supported = IFCALLRESULT(FALSE, rdpsnd->device->FormatSupported, rdpsnd->device, format); + + if (!supported) + { + if (!IFCALLRESULT(FALSE, rdpsnd->device->DefaultFormat, rdpsnd->device, format, + &deviceFormat)) + { + deviceFormat.wFormatTag = WAVE_FORMAT_PCM; + deviceFormat.wBitsPerSample = 16; + deviceFormat.cbSize = 0; + } + } + + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Opening device with format %s [backend %s]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), + 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; + rdpsnd->startPlayTime = 0; + rdpsnd->totalPlaySize = 0; + } + + 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 = GetTickCount64(); + 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, + "%s WaveInfo: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 " [%s]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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 BOOL rdpsnd_detect_overrun(rdpsndPlugin* rdpsnd, const AUDIO_FORMAT* format, size_t size) +{ + UINT32 bpf; + UINT32 now; + UINT32 duration; + UINT32 totalDuration; + UINT32 remainingDuration; + UINT32 maxDuration; + + if (!rdpsnd || !format) + return FALSE; + + /* Older windows RDP servers do not limit the send buffer, which can + * cause quite a large amount of sound data buffered client side. + * If e.g. sound is paused server side the client will keep playing + * for a long time instead of pausing playback. + * + * To avoid this we check: + * + * 1. Is the sound sample received from a known format these servers + * support + * 2. If it is calculate the size of the client side sound buffer + * 3. If the buffer is too large silently drop the sample which will + * trigger a retransmit later on. + * + * This check must only be applied to these known formats, because + * with newer and other formats the sample size can not be calculated + * without decompressing the sample first. + */ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_DVI_ADPCM: + case WAVE_FORMAT_ADPCM: + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + break; + case WAVE_FORMAT_MSG723: + case WAVE_FORMAT_GSM610: + case WAVE_FORMAT_AAC_MS: + default: + return FALSE; + } + + audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format); + bpf = format->nChannels * format->wBitsPerSample * format->nSamplesPerSec / 8; + if (bpf == 0) + return FALSE; + + duration = (UINT32)(1000 * size / bpf); + totalDuration = (UINT32)(1000 * rdpsnd->totalPlaySize / bpf); + now = GetTickCountPrecise(); + if (rdpsnd->startPlayTime == 0) + { + rdpsnd->startPlayTime = now; + rdpsnd->totalPlaySize = size; + return FALSE; + } + else if (now - rdpsnd->startPlayTime > totalDuration + 10) + { + /* Buffer underrun */ + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer underrun by %u ms", + rdpsnd_is_dyn_str(rdpsnd->dynamic), + (UINT)(now - rdpsnd->startPlayTime - totalDuration)); + rdpsnd->startPlayTime = now; + rdpsnd->totalPlaySize = size; + return FALSE; + } + else + { + /* Calculate remaining duration to be played */ + remainingDuration = totalDuration - (now - rdpsnd->startPlayTime); + + /* Maximum allow duration calculation */ + maxDuration = duration * 2 + rdpsnd->latency; + + if (remainingDuration + duration > maxDuration) + { + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer overrun pending %u ms dropping %u ms", + rdpsnd_is_dyn_str(rdpsnd->dynamic), remainingDuration, duration); + return TRUE; + } + + rdpsnd->totalPlaySize += size; + return FALSE; + } +} + +static UINT rdpsnd_treat_wave(rdpsndPlugin* rdpsnd, wStream* s, size_t size) +{ + BYTE* data; + AUDIO_FORMAT* format; + UINT64 end; + UINT64 diffMS, ts; + UINT latency = 0; + UINT error; + + if (Stream_GetRemainingLength(s) < size) + return ERROR_BAD_LENGTH; + + if (rdpsnd->wCurrentFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INTERNAL_ERROR; + + /* + * Send the first WaveConfirm PDU. The server side uses this to determine the + * network latency. + * See also [MS-RDPEA] 2.2.3.8 Wave Confirm PDU + */ + error = rdpsnd_send_wave_confirm_pdu(rdpsnd, rdpsnd->wTimeStamp, rdpsnd->cBlockNo); + if (error) + return error; + + data = Stream_Pointer(s); + format = &rdpsnd->ClientFormats[rdpsnd->wCurrentFormatNo]; + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Wave: cBlockNo: %" PRIu8 " wTimeStamp: %" PRIu16 ", size: %" PRIdz, + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, rdpsnd->wTimeStamp, size); + + if (rdpsnd->device && rdpsnd->attached && !rdpsnd_detect_overrun(rdpsnd, format, size)) + { + 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; + + Stream_Release(pcmData); + + if (status != CHANNEL_RC_OK) + return status; + } + + end = GetTickCount64(); + diffMS = end - rdpsnd->wArrivalTime + latency; + ts = (rdpsnd->wTimeStamp + diffMS) % UINT16_MAX; + + /* + * Send the second WaveConfirm PDU. With the first WaveConfirm PDU, + * the server side uses this second WaveConfirm PDU to determine the actual + * render latency. + */ + return rdpsnd_send_wave_confirm_pdu(rdpsnd, (UINT16)ts, 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); + if (wFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INVALID_DATA; + format = &rdpsnd->ClientFormats[wFormatNo]; + rdpsnd->waveDataSize = BodySize - 12; + rdpsnd->wArrivalTime = GetTickCount64(); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Wave2PDU: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 " [%s] , align=%hu", + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, wFormatNo, + audio_format_get_tag_string(format->wFormatTag), 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, "%s Closing device", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + } + else + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Device already closed", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_volume_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + BOOL rc = FALSE; + UINT32 dwVolume; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT32(s, dwVolume); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Volume: 0x%08" PRIX32 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), dwVolume); + if (rdpsnd->device) + rc = IFCALLRESULT(FALSE, rdpsnd->device->SetVolume, rdpsnd->device, dwVolume); + + if (!rc) + { + WLog_ERR(TAG, "%s error setting volume", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + 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, "%s unknown msgType %" PRIu8 "", rdpsnd_is_dyn_str(rdpsnd->dynamic), + msgType); + break; + } + +out: + Stream_Release(s); + return status; +} + +static void rdpsnd_register_device_plugin(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device) +{ + if (rdpsnd->device) + { + WLog_ERR(TAG, "%s existing device, abort.", rdpsnd_is_dyn_str(FALSE)); + 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; + DWORD flags = FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX; + if (rdpsnd->dynamic) + flags = FREERDP_ADDIN_CHANNEL_DYNAMIC; + entry = + (PFREERDP_RDPSND_DEVICE_ENTRY)freerdp_load_channel_addin_entry("rdpsnd", name, NULL, flags); + + 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 %s entry returns error %" PRIu32 "", rdpsnd_is_dyn_str(rdpsnd->dynamic), + name, error); + + WLog_INFO(TAG, "%s Loaded %s backend for rdpsnd", rdpsnd_is_dyn_str(rdpsnd->dynamic), name); + return error; +} + +static BOOL rdpsnd_set_subsystem(rdpsndPlugin* rdpsnd, const char* subsystem) +{ + free(rdpsnd->subsystem); + rdpsnd->subsystem = _strdup(subsystem); + return (rdpsnd->subsystem != NULL); +} + +static 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); +} + +/** + * 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; + 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 } + }; + 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 = (UINT16)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 = (UINT16)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, + "%s Unable to load sound playback subsystem %s because of error %" PRIu32 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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, + "%s Unable to load sound playback subsystem %s because of error %" PRIu32 + "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), 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; +} + +/** + * 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) + { + if (rdpsnd->dynamic) + { + IWTSVirtualChannel* channel; + if (rdpsnd->listener_callback) + { + channel = rdpsnd->listener_callback->channel_callback->channel; + status = channel->Write(channel, (UINT32)Stream_Length(s), Stream_Buffer(s), NULL); + } + Stream_Free(s, TRUE); + } + else + { + 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, "%s pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(FALSE), 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 (!MessageQueue_Post(plugin->queue, NULL, 0, plugin->data_in, NULL)) + 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rdpsnd) + return; + + if (rdpsnd->OpenHandle != openHandle) + { + WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return; + } + if ((error = rdpsnd_virtual_channel_event_data_received(rdpsnd, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, + "%s rdpsnd_virtual_channel_event_data_received failed with error %" PRIu32 + "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rdpsnd && rdpsnd->rdpcontext) + { + char buffer[8192]; + _snprintf(buffer, sizeof(buffer), + "%s rdpsnd_virtual_channel_open_event_ex reported an error", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + setChannelError(rdpsnd->rdpcontext, error, buffer); + } +} + +/** + * 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; + DWORD opened = 0; + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + status = rdpsnd->channelEntryPoints.pVirtualChannelOpenEx( + rdpsnd->InitHandle, &opened, rdpsnd->channelDef.name, rdpsnd_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "%s pVirtualChannelOpenEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(status), status); + goto fail; + } + + if (rdpsnd_process_connect(rdpsnd) != CHANNEL_RC_OK) + goto fail; + + rdpsnd->OpenHandle = opened; + return CHANNEL_RC_OK; +fail: + if (opened != 0) + rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened); + + return CHANNEL_RC_NO_MEMORY; +} + +static void cleanup_internals(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd) + return; + + if (rdpsnd->pool) + StreamPool_Return(rdpsnd->pool, rdpsnd->data_in); + + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = NULL; + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = NULL; + + rdpsnd->data_in = NULL; +} + +/** + * 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) + { + DWORD opened = rdpsnd->OpenHandle; + rdpsnd->OpenHandle = 0; + + if (rdpsnd->device) + IFCALL(rdpsnd->device->Close, rdpsnd->device); + + error = rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened); + + if (CHANNEL_RC_OK != error) + { + WLog_ERR(TAG, "%s pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(error), error); + return error; + } + } + + cleanup_internals(rdpsnd); + + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = NULL; + } + + return CHANNEL_RC_OK; +} + +static void _queue_free(void* obj) +{ + wStream* s = obj; + Stream_Release(s); +} + +static void free_internals(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd) + return; + + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Free(rdpsnd->pool); + rdpsnd->pool = NULL; + rdpsnd->dsp_context = NULL; +} + +static BOOL allocate_internals(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd->pool) + { + rdpsnd->pool = StreamPool_New(TRUE, 4096); + if (!rdpsnd->pool) + return FALSE; + } + + if (!rdpsnd->dsp_context) + { + rdpsnd->dsp_context = freerdp_dsp_context_new(FALSE); + if (!rdpsnd->dsp_context) + return FALSE; + } + + return TRUE; +} + +static DWORD WINAPI play_thread(LPVOID arg) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = arg; + + if (!rdpsnd || !rdpsnd->queue) + return ERROR_INVALID_PARAMETER; + + while (TRUE) + { + int rc; + wMessage message; + wStream* s; + HANDLE handle = MessageQueue_Event(rdpsnd->queue); + WaitForSingleObject(handle, INFINITE); + + rc = MessageQueue_Peek(rdpsnd->queue, &message, TRUE); + if (rc < 1) + continue; + + if (message.id == WMQ_QUIT) + break; + + s = message.wParam; + error = rdpsnd_recv_pdu(rdpsnd, s); + + if (error) + return error; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_virtual_channel_event_initialized(rdpsndPlugin* rdpsnd) +{ + wObject obj = { 0 }; + + if (!rdpsnd) + return ERROR_INVALID_PARAMETER; + + obj.fnObjectFree = _queue_free; + rdpsnd->queue = MessageQueue_New(&obj); + if (!rdpsnd->queue) + return CHANNEL_RC_NO_MEMORY; + + if (!allocate_internals(rdpsnd)) + return CHANNEL_RC_NO_MEMORY; + + rdpsnd->thread = CreateThread(NULL, 0, play_thread, rdpsnd, 0, NULL); + if (!rdpsnd->thread) + return CHANNEL_RC_INITIALIZATION_ERROR; + + return CHANNEL_RC_OK; +} + +void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd) +{ + if (rdpsnd) + { + if (rdpsnd->queue) + MessageQueue_PostQuit(rdpsnd->queue, 0); + if (rdpsnd->thread) + { + WaitForSingleObject(rdpsnd->thread, INFINITE); + CloseHandle(rdpsnd->thread); + } + MessageQueue_Free(rdpsnd->queue); + + free_internals(rdpsnd); + audio_formats_free(rdpsnd->fixed_format, 1); + free(rdpsnd->subsystem); + free(rdpsnd->device_name); + 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) + return; + + if (plugin->InitHandle != pInitHandle) + { + WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(plugin->dynamic)); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + error = rdpsnd_virtual_channel_event_initialized(plugin); + break; + + case CHANNEL_EVENT_CONNECTED: + error = rdpsnd_virtual_channel_event_connected(plugin, pData, dataLength); + break; + + case CHANNEL_EVENT_DISCONNECTED: + error = rdpsnd_virtual_channel_event_disconnected(plugin); + break; + + case CHANNEL_EVENT_TERMINATED: + rdpsnd_virtual_channel_event_terminated(plugin); + plugin = NULL; + break; + + case CHANNEL_EVENT_ATTACHED: + plugin->attached = TRUE; + break; + + case CHANNEL_EVENT_DETACHED: + plugin->attached = FALSE; + break; + + default: + break; + } + + if (error && plugin && plugin->rdpcontext) + { + char buffer[8192]; + _snprintf(buffer, sizeof(buffer), "%s %s reported an error", + rdpsnd_is_dyn_str(plugin->dynamic), __FUNCTION__); + setChannelError(plugin->rdpcontext, error, buffer); + } +} + +rdpContext* freerdp_rdpsnd_get_context(rdpsndPlugin* plugin) +{ + if (!plugin) + return NULL; + + return plugin->rdpcontext; +} + +static rdpsndPlugin* allocatePlugin(void) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)calloc(1, sizeof(rdpsndPlugin)); + if (!rdpsnd) + goto fail; + + rdpsnd->fixed_format = audio_format_new(); + if (!rdpsnd->fixed_format) + goto fail; + rdpsnd->log = WLog_Get("com.freerdp.channels.rdpsnd.client"); + if (!rdpsnd->log) + goto fail; + + rdpsnd->attached = TRUE; + return rdpsnd; + +fail: + if (rdpsnd) + audio_format_free(rdpsnd->fixed_format); + return NULL; +} +/* rdpsnd is always built-in */ +BOOL VCAPITYPE rdpsnd_VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, PVOID pInitHandle) +{ + UINT rc; + rdpsndPlugin* rdpsnd; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx; + + if (!pEntryPoints) + return FALSE; + + rdpsnd = allocatePlugin(); + + if (!rdpsnd) + return FALSE; + + 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; + } + + 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, "%s pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(FALSE), WTSErrorToString(rc), rc); + rdpsnd_virtual_channel_event_terminated(rdpsnd); + return FALSE; + } + + return TRUE; +} + +static UINT rdpsnd_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)callback->plugin; + + if (!allocate_internals(rdpsnd)) + return ERROR_OUTOFMEMORY; + + return rdpsnd_process_connect(rdpsnd); +} + +static UINT rdpsnd_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* plugin; + wStream* copy; + size_t len; + + len = Stream_GetRemainingLength(data); + + if (!callback || !callback->plugin) + return ERROR_INVALID_PARAMETER; + plugin = (rdpsndPlugin*)callback->plugin; + + copy = StreamPool_Take(plugin->pool, len); + if (!copy) + return ERROR_OUTOFMEMORY; + Stream_Copy(data, copy, len); + Stream_SealLength(copy); + Stream_SetPosition(copy, 0); + + if (!MessageQueue_Post(plugin->queue, NULL, 0, copy, NULL)) + { + Stream_Release(copy); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback = (RDPSND_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)callback->plugin; + + if (rdpsnd->device) + IFCALL(rdpsnd->device->Close, rdpsnd->device); + + cleanup_internals(rdpsnd); + + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = NULL; + } + + free_internals(rdpsnd); + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + RDPSND_CHANNEL_CALLBACK* callback; + RDPSND_LISTENER_CALLBACK* listener_callback = (RDPSND_LISTENER_CALLBACK*)pListenerCallback; + callback = (RDPSND_CHANNEL_CALLBACK*)calloc(1, sizeof(RDPSND_CHANNEL_CALLBACK)); + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + if (!callback) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnOpen = rdpsnd_on_open; + callback->iface.OnDataReceived = rdpsnd_on_data_received; + callback->iface.OnClose = rdpsnd_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; +} + +static UINT rdpsnd_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + if (rdpsnd->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", RDPSND_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + rdpsnd->listener_callback = + (RDPSND_LISTENER_CALLBACK*)calloc(1, sizeof(RDPSND_LISTENER_CALLBACK)); + + if (!rdpsnd->listener_callback) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + rdpsnd->listener_callback->iface.OnNewChannelConnection = rdpsnd_on_new_channel_connection; + rdpsnd->listener_callback->plugin = pPlugin; + rdpsnd->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener(pChannelMgr, RDPSND_DVC_CHANNEL_NAME, 0, + &rdpsnd->listener_callback->iface, &(rdpsnd->listener)); + rdpsnd->listener->pInterface = rdpsnd->iface.pInterface; + status = rdpsnd_virtual_channel_event_initialized(rdpsnd); + + rdpsnd->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_plugin_terminated(IWTSPlugin* pPlugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + if (rdpsnd) + { + if (rdpsnd->listener_callback) + { + IWTSVirtualChannelManager* mgr = rdpsnd->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, rdpsnd->listener); + } + free(rdpsnd->listener_callback); + free(rdpsnd->iface.pInterface); + } + rdpsnd_virtual_channel_event_terminated(rdpsnd); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpsnd_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pEntryPoints->GetPlugin(pEntryPoints, "rdpsnd"); + + if (!rdpsnd) + { + rdpsnd = allocatePlugin(); + if (!rdpsnd) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + rdpsnd->iface.Initialize = rdpsnd_plugin_initialize; + rdpsnd->iface.Connected = NULL; + rdpsnd->iface.Disconnected = NULL; + rdpsnd->iface.Terminated = rdpsnd_plugin_terminated; + rdpsnd->dynamic = TRUE; + + /* user data pointer is not const, cast to avoid warning. */ + rdpsnd->channelEntryPoints.pExtendedData = (void*)pEntryPoints->GetPluginData(pEntryPoints); + + error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpsnd", &rdpsnd->iface); + } + else + { + WLog_ERR(TAG, "%s could not get rdpsnd Plugin.", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/channels/rdpsnd/client/rdpsnd_main.h b/channels/rdpsnd/client/rdpsnd_main.h new file mode 100644 index 0000000..b3b478f --- /dev/null +++ b/channels/rdpsnd/client/rdpsnd_main.h @@ -0,0 +1,43 @@ +/** + * 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") + +FREERDP_API rdpContext* freerdp_rdpsnd_get_context(rdpsndPlugin* rdpsnd); + +#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..0c51f77 --- /dev/null +++ b/channels/rdpsnd/client/winmm/rdpsnd_winmm.c @@ -0,0 +1,356 @@ +/** + * 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 + +#include "rdpsnd_main.h" + +typedef struct rdpsnd_winmm_plugin rdpsndWinmmPlugin; + +struct rdpsnd_winmm_plugin +{ + rdpsndDevicePlugin device; + + HWAVEOUT hWaveOut; + WAVEFORMATEX format; + UINT32 volume; + wLog* log; + UINT32 latency; + HANDLE hThread; + DWORD threadId; + CRITICAL_SECTION cs; +}; + +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; + + winmm->latency = latency; + if (!rdpsnd_winmm_convert_format(format, &winmm->format)) + return FALSE; + + return TRUE; +} + +static DWORD WINAPI waveOutProc(LPVOID lpParameter) +{ + MSG msg; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter; + while (GetMessage(&msg, NULL, 0, 0)) + { + if (msg.message == MM_WOM_CLOSE) + { + /* device was closed - exit thread */ + break; + } + else if (msg.message == MM_WOM_DONE) + { + /* free buffer */ + LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam; + EnterCriticalSection(&winmm->cs); + waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR)); + LeaveCriticalSection(&winmm->cs); + free(waveHdr->lpData); + free(waveHdr); + } + } + + return 0; +} + +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; + + winmm->hThread = CreateThread(NULL, 0, waveOutProc, winmm, 0, &winmm->threadId); + if (!winmm->hThread) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError()); + return FALSE; + } + + mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format, + (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult); + return FALSE; + } + + mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_winmm_close(rdpsndDevicePlugin* device) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm->hWaveOut) + { + EnterCriticalSection(&winmm->cs); + + mmResult = waveOutReset(winmm->hWaveOut); + if (mmResult != MMSYSERR_NOERROR) + WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult); + + mmResult = waveOutClose(winmm->hWaveOut); + if (mmResult != MMSYSERR_NOERROR) + WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult); + + LeaveCriticalSection(&winmm->cs); + + winmm->hWaveOut = NULL; + } + + if (winmm->hThread) + { + WaitForSingleObject(winmm->hThread, INFINITE); + CloseHandle(winmm->hThread); + winmm->hThread = NULL; + } +} + +static void rdpsnd_winmm_free(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm) + { + rdpsnd_winmm_close(device); + DeleteCriticalSection(&winmm->cs); + free(winmm); + } +} + +static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + MMRESULT result; + WAVEFORMATEX out; + + WINPR_UNUSED(device); + 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) +{ + MMRESULT mmResult; + DWORD dwVolume = UINT32_MAX; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (!winmm->hWaveOut) + return dwVolume; + + mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult); + dwVolume = UINT32_MAX; + } + return dwVolume; +} + +static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + winmm->volume = value; + + if (!winmm->hWaveOut) + return TRUE; + + mmResult = waveOutSetVolume(winmm->hWaveOut, value); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult); + return FALSE; + } + return TRUE; +} + +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; + + if (size > UINT32_MAX) + return 0; + + lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR)); + if (!lpWaveHdr) + return 0; + + lpWaveHdr->dwFlags = 0; + lpWaveHdr->dwLoops = 0; + lpWaveHdr->lpData = malloc(size); + if (!lpWaveHdr->lpData) + goto fail; + memcpy(lpWaveHdr->lpData, data, size); + lpWaveHdr->dwBufferLength = (DWORD)size; + + EnterCriticalSection(&winmm->cs); + + mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult); + goto failCS; + } + + mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult); + waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + goto failCS; + } + + LeaveCriticalSection(&winmm->cs); + return winmm->latency; +failCS: + LeaveCriticalSection(&winmm->cs); +fail: + if (lpWaveHdr) + free(lpWaveHdr->lpData); + free(lpWaveHdr); + return 0; +} + +static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + WINPR_UNUSED(device); + WINPR_UNUSED(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; + + if (waveOutGetNumDevs() == 0) + { + WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!"); + return ERROR_DEVICE_NOT_AVAILABLE; + } + + 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.Play = rdpsnd_winmm_play; + winmm->device.Close = rdpsnd_winmm_close; + winmm->device.Free = rdpsnd_winmm_free; + winmm->log = WLog_Get(TAG); + InitializeCriticalSection(&winmm->cs); + + 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..363fac7 --- /dev/null +++ b/channels/rdpsnd/server/rdpsnd_main.c @@ -0,0 +1,1228 @@ +/** + * 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 + +#include "rdpsnd_common.h" +#include "rdpsnd_main.h" + +static wStream* rdpsnd_server_get_buffer(RdpsndServerContext* context) +{ + wStream* s; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + s = context->priv->rdpsnd_pdu; + Stream_SetPosition(s, 0); + return s; +} + +/** + * Send Server Audio Formats and Version PDU (2.2.2.1) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_formats(RdpsndServerContext* context) +{ + wStream* s = rdpsnd_server_get_buffer(context); + size_t pos; + UINT16 i; + BOOL status = FALSE; + ULONG written; + + if (!Stream_EnsureRemainingCapacity(s, 24)) + return ERROR_OUTOFMEMORY; + + 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++) + { + const AUDIO_FORMAT* format = &context->server_formats[i]; + + 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); + + WINPR_ASSERT(context->priv); + 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; +} + +/** + * Read Wave Confirm PDU (2.2.3.8) and handle callback + * + * @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; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + 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; +} + +/** + * Read Training Confirm PDU (2.2.3.2) and handle callback + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_trainingconfirm(RdpsndServerContext* context, wStream* s) +{ + UINT16 timestamp; + UINT16 packsize; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, timestamp); + Stream_Read_UINT16(s, packsize); + + IFCALLRET(context->TrainingConfirm, error, context, timestamp, packsize); + if (error) + WLog_ERR(TAG, "context->TrainingConfirm failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Read Quality Mode PDU (2.2.2.3) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_quality_mode(RdpsndServerContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, context->qualityMode); /* wQualityMode */ + Stream_Seek_UINT16(s); /* Reserved */ + + WLog_DBG(TAG, "Client requested sound quality: 0x%04" PRIX16 "", context->qualityMode); + + return CHANNEL_RC_OK; +} + +/** + * Read Client Audio Formats and Version PDU (2.2.2.2) + * + * @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; + UINT16 udpPort; + BYTE lastblock; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, context->capsFlags); /* dwFlags */ + Stream_Read_UINT32(s, context->initialVolume); /* dwVolume */ + Stream_Read_UINT32(s, context->initialPitch); /* 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_CheckAndLogRequiredLength(TAG, s, 18ull * context->num_client_formats)) + 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++) + { + AUDIO_FORMAT* format = &context->client_formats[i]; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + { + WLog_ERR(TAG, "not enough data in stream!"); + error = ERROR_INVALID_DATA; + goto out_free; + } + + Stream_Read_UINT16(s, format->wFormatTag); + Stream_Read_UINT16(s, format->nChannels); + Stream_Read_UINT32(s, format->nSamplesPerSec); + Stream_Read_UINT32(s, format->nAvgBytesPerSec); + Stream_Read_UINT16(s, format->nBlockAlign); + Stream_Read_UINT16(s, format->wBitsPerSample); + Stream_Read_UINT16(s, format->cbSize); + + if (format->cbSize > 0) + { + if (!Stream_SafeSeek(s, format->cbSize)) + { + WLog_ERR(TAG, "Stream_SafeSeek failed!"); + error = ERROR_INTERNAL_ERROR; + goto out_free; + } + } + + if (format->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 = 0, status; + HANDLE events[2] = { 0 }; + RdpsndServerContext* context = (RdpsndServerContext*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + events[nCount++] = context->priv->channelEvent; + events[nCount++] = context->priv->StopEvent; + + WINPR_ASSERT(nCount <= ARRAYSIZE(events)); + + 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) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + 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; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + 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; +} + +/** + * Send Training PDU (2.2.3.1) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_training(RdpsndServerContext* context, UINT16 timestamp, UINT16 packsize, + BYTE* data) +{ + size_t end = 0; + ULONG written; + BOOL status; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return ERROR_INTERNAL_ERROR; + + Stream_Write_UINT8(s, SNDC_TRAINING); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + Stream_Write_UINT16(s, timestamp); + Stream_Write_UINT16(s, packsize); + + if (packsize > 0) + { + if (!Stream_EnsureRemainingCapacity(s, packsize)) + { + Stream_SetPosition(s, 0); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, data, packsize); + } + + end = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, end - 4); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), end, + &written); + + Stream_SetPosition(s, 0); + + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_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; + UINT error = CHANNEL_RC_OK; + wStream* s = rdpsnd_server_get_buffer(context); + + if (context->selected_client_format > context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(context->client_formats); + + format = &context->client_formats[context->selected_client_format]; + /* WaveInfo PDU */ + Stream_SetPosition(s, 0); + + if (!Stream_EnsureRemainingCapacity(s, 16)) + return ERROR_OUTOFMEMORY; + + 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 * 1ULL; + + 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); + + 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; + } + + context->block_no = (context->block_no + 1) % 256; + +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 formatNo, + const BYTE* data, size_t size, BOOL encoded, + UINT16 timestamp, UINT32 audioTimeStamp) +{ + size_t end = 0; + ULONG written; + UINT error = CHANNEL_RC_OK; + BOOL status; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 16)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + /* Wave2 PDU */ + Stream_Write_UINT8(s, SNDC_WAVE2); /* msgType */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT16(s, 0); /* BodySize */ + Stream_Write_UINT16(s, timestamp); /* wTimeStamp */ + Stream_Write_UINT16(s, formatNo); /* wFormatNo */ + Stream_Write_UINT8(s, context->block_no); /* cBlockNo */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT32(s, audioTimeStamp); /* dwAudioTimeStamp */ + + if (encoded) + { + if (!Stream_EnsureRemainingCapacity(s, size)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + Stream_Write(s, data, size); + } + else + { + AUDIO_FORMAT* format; + + if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, data, size, s)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + format = &context->client_formats[formatNo]; + if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + } + + end = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, end - 4); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), end, + &written); + + if (!status || (end != written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed! [stream length=%" PRIdz " - written=%" PRIu32, + end, written); + error = ERROR_INTERNAL_ERROR; + } + + context->block_no = (context->block_no + 1) % 256; + +out: + 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) +{ + const BYTE* src; + size_t length; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->selected_client_format >= context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + src = context->priv->out_buffer; + length = context->priv->out_pending_frames * context->priv->src_bytes_per_frame; + + if (context->clientVersion >= CHANNEL_VERSION_WIN_8) + return rdpsnd_server_send_wave2_pdu(context, context->selected_client_format, src, length, + FALSE, wTimestamp, 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) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + 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) + { + const size_t cframes = + MIN(nframes, context->priv->out_frames - context->priv->out_pending_frames); + size_t 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 = (const 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; +} + +/** + * Send encoded audio samples using a Wave2 PDU. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_samples2(RdpsndServerContext* context, UINT16 formatNo, + const void* buf, size_t size, UINT16 timestamp, + UINT32 audioTimeStamp) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->clientVersion < CHANNEL_VERSION_WIN_8) + return ERROR_INTERNAL_ERROR; + + EnterCriticalSection(&context->priv->lock); + + error = + rdpsnd_server_send_wave2_pdu(context, formatNo, buf, size, TRUE, timestamp, audioTimeStamp); + + 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 len; + BOOL status; + ULONG written; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, SNDC_SETVOLUME); + Stream_Write_UINT8(s, 0); + Stream_Write_UINT16(s, 4); /* Payload length */ + Stream_Write_UINT16(s, left); + Stream_Write_UINT16(s, right); + len = Stream_GetPosition(s); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), + (ULONG)len, &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; + UINT error = CHANNEL_RC_OK; + wStream* s = rdpsnd_server_get_buffer(context); + + 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; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_OUTOFMEMORY; + + 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; + UINT error = ERROR_INTERNAL_ERROR; + PULONG pSessionId = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + priv = context->priv; + priv->SessionId = WTS_CURRENT_SESSION; + + if (context->use_dynamic_virtual_channel) + { + UINT32 channelId; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &bytesReturned)) + { + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->ChannelHandle = (HANDLE)WTSVirtualChannelOpenEx( + priv->SessionId, "AUDIO_PLAYBACK_DVC", WTS_CHANNEL_OPTION_DYNAMIC); + if (!priv->ChannelHandle) + { + WLog_ERR(TAG, "Open audio dynamic virtual channel (AUDIO_PLAYBACK_DVC) failed!"); + return ERROR_INTERNAL_ERROR; + } + + channelId = WTSChannelGetIdByHandle(priv->ChannelHandle); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + goto out_close; + } + } + else + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, "rdpsnd"); + if (!priv->ChannelHandle) + { + WLog_ERR(TAG, "Open audio static virtual channel (rdpsnd) 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))) + { + 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; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!context->priv->StopEvent) + return error; + + 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); + context->priv->Thread = NULL; + context->priv->StopEvent = NULL; + } + } + + DeleteCriticalSection(&context->priv->lock); + + if (context->priv->rdpsnd_pdu) + { + Stream_Free(context->priv->rdpsnd_pdu, TRUE); + context->priv->rdpsnd_pdu = NULL; + } + + if (context->priv->ChannelHandle) + { + WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = NULL; + } + + return error; +} + +RdpsndServerContext* rdpsnd_server_context_new(HANDLE vcm) +{ + RdpsndServerPrivate* priv; + RdpsndServerContext* context = (RdpsndServerContext*)calloc(1, sizeof(RdpsndServerContext)); + + if (!context) + goto fail; + + context->vcm = vcm; + context->Start = rdpsnd_server_start; + context->Stop = rdpsnd_server_stop; + context->selected_client_format = 0xFFFF; + context->Initialize = rdpsnd_server_initialize; + context->SendFormats = rdpsnd_server_send_formats; + context->SelectFormat = rdpsnd_server_select_format; + context->Training = rdpsnd_server_training; + context->SendSamples = rdpsnd_server_send_samples; + context->SendSamples2 = rdpsnd_server_send_samples2; + 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 fail; + } + + priv->dsp_context = freerdp_dsp_context_new(TRUE); + + if (!priv->dsp_context) + { + WLog_ERR(TAG, "freerdp_dsp_context_new failed!"); + goto fail; + } + + priv->input_stream = Stream_New(NULL, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + priv->expectedBytes = 4; + priv->waitingHeader = TRUE; + priv->ownThread = TRUE; + return context; +fail: + rdpsnd_server_context_free(context); + return NULL; +} + +void rdpsnd_server_context_reset(RdpsndServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + context->priv->expectedBytes = 4; + context->priv->waitingHeader = TRUE; + Stream_SetPosition(context->priv->input_stream, 0); +} + +void rdpsnd_server_context_free(RdpsndServerContext* context) +{ + if (!context) + return; + + if (context->priv) + { + rdpsnd_server_stop(context); + + 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->server_formats); + free(context->client_formats); + free(context->priv); + free(context); +} + +HANDLE rdpsnd_server_get_event_handle(RdpsndServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + 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; + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + priv = context->priv; + 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_TRAINING: + ret = rdpsnd_server_recv_trainingconfirm(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); + + 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..9849273 --- /dev/null +++ b/channels/rdpsnd/server/rdpsnd_main.h @@ -0,0 +1,59 @@ +/** + * 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; + DWORD SessionId; + + 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..54d9c60 --- /dev/null +++ b/channels/remdesk/client/remdesk_main.c @@ -0,0 +1,1071 @@ +/** + * 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!"); + Stream_Free(s, TRUE); + 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) + { + 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 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 = (strnlen(header->ChannelName, sizeof(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) */ + + if ((versionMajor != 1) || (versionMinor > 2) || (versionMinor == 0)) + { + WLog_ERR(TAG, "Unsupported protocol version %" PRId32 ".%" PRId32, versionMajor, + versionMinor); + } + + remdesk->Version = versionMinor; + 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); + + 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); + + 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); + + 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); + + 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, const 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, 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; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!remdesk || (remdesk->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + 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_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + + default: + WLog_ERR(TAG, "unhandled event %" PRIu32 "!", event); + error = ERROR_INTERNAL_ERROR; + } + + if (error && remdesk && 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); + Stream_Free(data, TRUE); + break; + } + + Stream_Free(data, TRUE); + } + } + + 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..852e18e --- /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; + size_t 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..aeaa332 --- /dev/null +++ b/channels/remdesk/server/remdesk_main.c @@ -0,0 +1,787 @@ +/** + * 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 = (strnlen(header->ChannelName, sizeof(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..f47d037 --- /dev/null +++ b/channels/remdesk/server/remdesk_main.h @@ -0,0 +1,41 @@ +/** + * 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..afe67b4 --- /dev/null +++ b/channels/serial/client/serial_main.c @@ -0,0 +1,968 @@ +/** + * 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_SafeSeek(irp->input, PathLength)) /* Path (variable) */ + return ERROR_INVALID_DATA; + + 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; + void* ptr; + 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) */ + if (!Stream_SafeSeek(irp->input, 20)) /* Padding (20 bytes) */ + return ERROR_INVALID_DATA; + + /* 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); + + ptr = Stream_Pointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + /* FIXME: CommWriteFile to be replaced by WriteFile */ + if (CommWriteFile(serial->hComm, ptr, 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 + size_t 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..2223ef8 --- /dev/null +++ b/channels/server/channels.c @@ -0,0 +1,131 @@ +/** + * 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 +#include +#include +#include + +#if defined(CHANNEL_RDPECAM_SERVER) +#include +#include +#endif + +#if defined(CHANNEL_AINPUT_SERVER) +#include +#endif + +extern void freerdp_channels_dummy(void); + +void freerdp_channels_dummy(void) +{ + audin_server_context* audin; + RdpsndServerContext* rdpsnd; + CliprdrServerContext* cliprdr; + echo_server_context* echo; + RdpdrServerContext* rdpdr; + DrdynvcServerContext* drdynvc; + RdpeiServerContext* rdpei; + RemdeskServerContext* remdesk; + EncomspServerContext* encomsp; + RailServerContext* rail; + TelemetryServerContext* telemetry; + RdpgfxServerContext* rdpgfx; + DispServerContext* disp; +#if defined (CHANNEL_RDPECAM_SERVER) + CamDevEnumServerContext* camera_enumerator; + CameraDeviceServerContext* camera_device; +#endif + 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); + rail = rail_server_context_new(NULL); + rail_server_context_free(rail); + telemetry = telemetry_server_context_new(NULL); + telemetry_server_context_free(telemetry); + rdpgfx = rdpgfx_server_context_new(NULL); + rdpgfx_server_context_free(rdpgfx); + disp = disp_server_context_new(NULL); + disp_server_context_free(disp); + +#if defined (CHANNEL_RDPECAM_SERVER) + camera_enumerator = cam_dev_enum_server_context_new(NULL); + cam_dev_enum_server_context_free(camera_enumerator); + camera_device = camera_device_server_context_new(NULL); + camera_device_server_context_free(camera_device); +#endif + +#if defined(CHANNEL_AINPUT_SERVER) + { + ainput_server_context* ainput = ainput_server_context_new(NULL); + ainput_server_context_free(ainput); + } +#endif +} + +/** + * end of ugly symbols import workaround + */ diff --git a/channels/server/channels.h b/channels/server/channels.h new file mode 100644 index 0000000..a6c4791 --- /dev/null +++ b/channels/server/channels.h @@ -0,0 +1,24 @@ +/** + * 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..ef89087 --- /dev/null +++ b/channels/smartcard/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("smartcard") + +set(${MODULE_PREFIX}_SRCS + smartcard_main.c + smartcard_main.h + smartcard_pack.c + smartcard_pack.h + smartcard_operations.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..2df4c14 --- /dev/null +++ b/channels/smartcard/client/smartcard_main.c @@ -0,0 +1,810 @@ +/** + * 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); + SCardReleaseContext(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. + */ + + ListDictionary_Lock(smartcard->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_GetItemValue( + smartcard->rgSCardContextList, (void*)pKeys[index]); + + if (!pContext) + continue; + + hContext = pContext->hContext; + + if (SCardIsValidContext(hContext) == SCARD_S_SUCCESS) + { + SCardCancel(hContext); + } + } + + free(pKeys); + } + ListDictionary_Unlock(smartcard->rgSCardContextList); + + /* Put thread to sleep so that PC/SC can process the cancel requests. This fixes a race + * condition that sometimes caused the pc/sc daemon to crash on MacOS (_xpc_api_misuse) */ + SleepEx(100, FALSE); + + /** + * Call SCardReleaseContext on remaining contexts and remove them from rgSCardContextList. + */ + + ListDictionary_Lock(smartcard->rgSCardContextList); + if (ListDictionary_Count(smartcard->rgSCardContextList) > 0) + { + pKeys = NULL; + keyCount = ListDictionary_GetKeys(smartcard->rgSCardContextList, &pKeys); + + for (index = 0; index < keyCount; index++) + { + ListDictionary_SetItemValue(smartcard->rgSCardContextList, (void*)pKeys[index], NULL); + } + + free(pKeys); + } + ListDictionary_Unlock(smartcard->rgSCardContextList); +} + +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_RELEASETARTEDEVENT: + 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..4d543d1 --- /dev/null +++ b/channels/smartcard/client/smartcard_main.h @@ -0,0 +1,73 @@ +/** + * 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 + +#include "smartcard_operations.h" + +#define TAG CHANNELS_TAG("smartcard.client") + +typedef struct _SMARTCARD_DEVICE SMARTCARD_DEVICE; + +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..4639b17 --- /dev/null +++ b/channels/smartcard/client/smartcard_operations.c @@ -0,0 +1,2695 @@ +/** + * 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_operations.h" +#include "smartcard_main.h" + +static LONG smartcard_call_to_operation_handle(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + if (!smartcard || !operation) + return SCARD_E_INVALID_HANDLE; + operation->hContext = + smartcard_scard_context_native_from_redir(smartcard, &(operation->call.handles.hContext)); + operation->hCard = + smartcard_scard_handle_native_from_redir(smartcard, &(operation->call.handles.hCard)); + + return SCARD_S_SUCCESS; +} + +static LONG log_status_error(const char* tag, const char* what, LONG status) +{ + if (status != SCARD_S_SUCCESS) + { + DWORD level = WLOG_ERROR; + switch (status) + { + case SCARD_E_TIMEOUT: + level = WLOG_DEBUG; + break; + case SCARD_E_NO_READERS_AVAILABLE: + level = WLOG_INFO; + break; + default: + break; + } + WLog_Print(WLog_Get(tag), level, "%s failed with error %s [%" PRId32 "]", what, + SCardGetErrorString(status), status); + } + return status; +} + +static 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_RELEASETARTEDEVENT: + return funcName ? "SCardReleaseStartedEvent" : "SCARD_IOCTL_RELEASETARTEDEVENT"; + + 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"; + } +} + +static LONG smartcard_EstablishContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + + irp = operation->irp; + status = smartcard_unpack_establish_context_call(smartcard, irp->input, + &operation->call.establishContext); + if (status != SCARD_S_SUCCESS) + { + return log_status_error(TAG, "smartcard_unpack_establish_context_call", status); + } + + return SCARD_S_SUCCESS; +} + +static LONG smartcard_EstablishContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + SCARDCONTEXT hContext = { 0 }; + EstablishContext_Return ret = { 0 }; + IRP* irp = operation->irp; + EstablishContext_Call* call = &operation->call.establishContext; + 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 + { + return log_status_error(TAG, "SCardEstablishContext", status); + } + + smartcard_scard_context_native_to_redir(smartcard, &(ret.hContext), hContext); + + status = smartcard_pack_establish_context_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + { + return log_status_error(TAG, "smartcard_pack_establish_context_return", status); + } + + return ret.ReturnCode; +} + +static LONG smartcard_ReleaseContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + + irp = operation->irp; + status = smartcard_unpack_context_call(smartcard, irp->input, &operation->call.context, + "ReleaseContext"); + if (status != SCARD_S_SUCCESS) + log_status_error(TAG, "smartcard_unpack_context_call", status); + + return status; +} + +static LONG smartcard_ReleaseContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + 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 + { + return log_status_error(TAG, "SCardReleaseContext", ret.ReturnCode); + } + + smartcard_trace_long_return(smartcard, &ret, "ReleaseContext"); + return ret.ReturnCode; +} + +static LONG smartcard_IsValidContext_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + + irp = operation->irp; + status = smartcard_unpack_context_call(smartcard, irp->input, &operation->call.context, + "IsValidContext"); + + return status; +} + +static LONG smartcard_IsValidContext_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + + ret.ReturnCode = SCardIsValidContext(operation->hContext); + smartcard_trace_long_return(smartcard, &ret, "IsValidContext"); + return ret.ReturnCode; +} + +static LONG smartcard_ListReaderGroupsA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_list_reader_groups_call(smartcard, irp->input, + &operation->call.listReaderGroups, FALSE); + + return status; +} + +static LONG smartcard_ListReaderGroupsA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Return ret = { 0 }; + LPSTR mszGroups = NULL; + DWORD cchGroups = 0; + IRP* irp = operation->irp; + cchGroups = SCARD_AUTOALLOCATE; + ret.ReturnCode = SCardListReaderGroupsA(operation->hContext, (LPSTR)&mszGroups, &cchGroups); + ret.msz = (BYTE*)mszGroups; + ret.cBytes = cchGroups; + + status = smartcard_pack_list_reader_groups_return(smartcard, irp->output, &ret, FALSE); + + 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_list_reader_groups_call(smartcard, irp->input, + &operation->call.listReaderGroups, TRUE); + + return status; +} + +static LONG smartcard_ListReaderGroupsW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaderGroups_Return ret = { 0 }; + LPWSTR mszGroups = NULL; + DWORD cchGroups = 0; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + + irp = operation->irp; + cchGroups = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = + SCardListReaderGroupsW(operation->hContext, (LPWSTR)&mszGroups, &cchGroups); + ret.msz = (BYTE*)mszGroups; + ret.cBytes = cchGroups * sizeof(WCHAR); + + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_pack_list_reader_groups_return(smartcard, irp->output, &ret, TRUE); + + 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 (*mszReaders == NULL || 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 + 1); + + 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 (DWORD)wpos; +} + +static DWORD filter_device_by_name_w(wLinkedList* list, LPWSTR* mszReaders, DWORD cchReaders) +{ + int res; + DWORD rc; + LPSTR readers = NULL; + + if (LinkedList_Count(list) < 1) + return cchReaders; + + res = ConvertFromUnicode(CP_UTF8, 0, *mszReaders, (int)cchReaders, &readers, 0, NULL, NULL); + + /* When res==0, readers may have been set to NULL by ConvertFromUnicode */ + if ((res < 0) || ((DWORD)res != cchReaders) || (readers == 0)) + return 0; + + free(*mszReaders); + *mszReaders = NULL; + rc = filter_device_by_name_a(list, &readers, cchReaders); + res = ConvertToUnicode(CP_UTF8, 0, readers, (int)rc, mszReaders, 0); + + if ((res < 0) || ((DWORD)res != rc)) + rc = 0; + + free(readers); + return rc; +} + +static LONG smartcard_ListReadersA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_list_readers_call(smartcard, irp->input, &operation->call.listReaders, + FALSE); + + return status; +} + +static LONG smartcard_ListReadersA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Return ret = { 0 }; + LPSTR mszReaders = NULL; + DWORD cchReaders = 0; + IRP* irp = operation->irp; + ListReaders_Call* call = &operation->call.listReaders; + cchReaders = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = SCardListReadersA(operation->hContext, (LPCSTR)call->mszGroups, + (LPSTR)&mszReaders, &cchReaders); + + if (call->mszGroups) + { + free(call->mszGroups); + call->mszGroups = NULL; + } + + if (status != SCARD_S_SUCCESS) + { + return log_status_error(TAG, "SCardListReadersA", status); + } + + cchReaders = filter_device_by_name_a(smartcard->names, &mszReaders, cchReaders); + ret.msz = (BYTE*)mszReaders; + ret.cBytes = cchReaders; + + status = smartcard_pack_list_readers_return(smartcard, irp->output, &ret, FALSE); + if (status != SCARD_S_SUCCESS) + { + return log_status_error(TAG, "smartcard_pack_list_readers_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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_list_readers_call(smartcard, irp->input, &operation->call.listReaders, + TRUE); + + return status; +} + +static LONG smartcard_context_and_two_strings_a_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_context_and_two_strings_a_call(smartcard, irp->input, + &operation->call.contextAndTwoStringA); + + return status; +} + +static LONG smartcard_context_and_two_strings_w_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_context_and_two_strings_w_call(smartcard, irp->input, + &operation->call.contextAndTwoStringW); + + return status; +} + +static LONG smartcard_context_and_string_a_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_context_and_string_a_call(smartcard, irp->input, + &operation->call.contextAndStringA); + + return status; +} + +static LONG smartcard_context_and_string_w_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_context_and_string_w_call(smartcard, irp->input, + &operation->call.contextAndStringW); + + return status; +} + +static LONG smartcard_LocateCardsA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = + smartcard_unpack_locate_cards_a_call(smartcard, irp->input, &operation->call.locateCardsA); + + return status; +} + +static LONG smartcard_LocateCardsW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = + smartcard_unpack_locate_cards_w_call(smartcard, irp->input, &operation->call.locateCardsW); + + return status; +} + +static LONG smartcard_ListReadersW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ListReaders_Return ret = { 0 }; + DWORD cchReaders = 0; + IRP* irp = operation->irp; + ListReaders_Call* call = &operation->call.listReaders; + union { + const BYTE* bp; + const char* sz; + const WCHAR* wz; + } string; + union { + WCHAR** ppw; + WCHAR* pw; + CHAR* pc; + BYTE* pb; + } mszReaders; + + string.bp = call->mszGroups; + cchReaders = SCARD_AUTOALLOCATE; + status = ret.ReturnCode = + SCardListReadersW(operation->hContext, string.wz, (LPWSTR)&mszReaders.pw, &cchReaders); + + if (call->mszGroups) + { + free(call->mszGroups); + call->mszGroups = NULL; + } + + if (status != SCARD_S_SUCCESS) + return log_status_error(TAG, "SCardListReadersW", status); + + cchReaders = filter_device_by_name_w(smartcard->names, &mszReaders.pw, cchReaders); + ret.msz = mszReaders.pb; + ret.cBytes = cchReaders; + status = smartcard_pack_list_readers_return(smartcard, irp->output, &ret, TRUE); + + if (mszReaders.pb) + SCardFreeMemory(operation->hContext, mszReaders.pb); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_IntroduceReaderGroupA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndStringA_Call* call = &operation->call.contextAndStringA; + ret.ReturnCode = SCardIntroduceReaderGroupA(operation->hContext, call->sz); + log_status_error(TAG, "SCardIntroduceReaderGroupA", ret.ReturnCode); + if (call->sz) + { + free(call->sz); + call->sz = NULL; + } + + smartcard_trace_long_return(smartcard, &ret, "IntroduceReaderGroupA"); + return ret.ReturnCode; +} + +static LONG smartcard_IntroduceReaderGroupW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndStringW_Call* call = &operation->call.contextAndStringW; + ret.ReturnCode = SCardIntroduceReaderGroupW(operation->hContext, call->sz); + log_status_error(TAG, "SCardIntroduceReaderGroupW", ret.ReturnCode); + if (call->sz) + { + free(call->sz); + call->sz = NULL; + } + + smartcard_trace_long_return(smartcard, &ret, "IntroduceReaderGroupW"); + return ret.ReturnCode; +} + +static LONG smartcard_IntroduceReaderA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringA_Call* call = &operation->call.contextAndTwoStringA; + ret.ReturnCode = SCardIntroduceReaderA(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardIntroduceReaderA", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "IntroduceReaderA"); + return ret.ReturnCode; +} + +static LONG smartcard_IntroduceReaderW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringW_Call* call = &operation->call.contextAndTwoStringW; + ret.ReturnCode = SCardIntroduceReaderW(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardIntroduceReaderW", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "IntroduceReaderW"); + return ret.ReturnCode; +} + +static LONG smartcard_ForgetReaderA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndStringA_Call* call = &operation->call.contextAndStringA; + ret.ReturnCode = SCardForgetReaderA(operation->hContext, call->sz); + log_status_error(TAG, "SCardForgetReaderA", ret.ReturnCode); + if (call->sz) + { + free(call->sz); + call->sz = NULL; + } + + smartcard_trace_long_return(smartcard, &ret, "SCardForgetReaderA"); + return ret.ReturnCode; +} + +static LONG smartcard_ForgetReaderW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndStringW_Call* call = &operation->call.contextAndStringW; + ret.ReturnCode = SCardForgetReaderW(operation->hContext, call->sz); + log_status_error(TAG, "SCardForgetReaderW", ret.ReturnCode); + if (call->sz) + { + free(call->sz); + call->sz = NULL; + } + + smartcard_trace_long_return(smartcard, &ret, "SCardForgetReaderW"); + return ret.ReturnCode; +} + +static LONG smartcard_AddReaderToGroupA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringA_Call* call = &operation->call.contextAndTwoStringA; + ret.ReturnCode = SCardAddReaderToGroupA(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardAddReaderToGroupA", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "SCardAddReaderToGroupA"); + return ret.ReturnCode; +} + +static LONG smartcard_AddReaderToGroupW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringW_Call* call = &operation->call.contextAndTwoStringW; + ret.ReturnCode = SCardAddReaderToGroupW(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardAddReaderToGroupW", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "SCardAddReaderToGroupA"); + return ret.ReturnCode; +} + +static LONG smartcard_RemoveReaderFromGroupA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringA_Call* call = &operation->call.contextAndTwoStringA; + ret.ReturnCode = SCardRemoveReaderFromGroupA(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardRemoveReaderFromGroupA", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "SCardRemoveReaderFromGroupA"); + return ret.ReturnCode; +} + +static LONG smartcard_RemoveReaderFromGroupW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + ContextAndTwoStringW_Call* call = &operation->call.contextAndTwoStringW; + ret.ReturnCode = SCardRemoveReaderFromGroupW(operation->hContext, call->sz1, call->sz2); + log_status_error(TAG, "SCardRemoveReaderFromGroupW", ret.ReturnCode); + free(call->sz1); + call->sz1 = NULL; + free(call->sz2); + call->sz2 = NULL; + + smartcard_trace_long_return(smartcard, &ret, "SCardRemoveReaderFromGroupW"); + return ret.ReturnCode; +} + +static LONG smartcard_LocateCardsA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + UINT32 x; + LONG status; + LocateCards_Return ret = { 0 }; + LocateCardsA_Call* call = &operation->call.locateCardsA; + IRP* irp = operation->irp; + + ret.ReturnCode = SCardLocateCardsA(operation->hContext, call->mszCards, call->rgReaderStates, + call->cReaders); + log_status_error(TAG, "SCardLocateCardsA", ret.ReturnCode); + ret.cReaders = call->cReaders; + ret.rgReaderStates = NULL; + + free(call->mszCards); + + if (ret.cReaders > 0) + { + ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return)); + + if (!ret.rgReaderStates) + return STATUS_NO_MEMORY; + } + + for (x = 0; x < ret.cReaders; x++) + { + ret.rgReaderStates[x].dwCurrentState = call->rgReaderStates[x].dwCurrentState; + ret.rgReaderStates[x].dwEventState = call->rgReaderStates[x].dwEventState; + ret.rgReaderStates[x].cbAtr = call->rgReaderStates[x].cbAtr; + CopyMemory(&(ret.rgReaderStates[x].rgbAtr), &(call->rgReaderStates[x].rgbAtr), + sizeof(ret.rgReaderStates[x].rgbAtr)); + } + + status = smartcard_pack_locate_cards_return(smartcard, irp->output, &ret); + + for (x = 0; x < call->cReaders; x++) + { + SCARD_READERSTATEA* state = &call->rgReaderStates[x]; + free(state->szReader); + } + + free(call->rgReaderStates); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_LocateCardsW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + UINT32 x; + LONG status; + LocateCards_Return ret = { 0 }; + LocateCardsW_Call* call = &operation->call.locateCardsW; + IRP* irp = operation->irp; + + ret.ReturnCode = SCardLocateCardsW(operation->hContext, call->mszCards, call->rgReaderStates, + call->cReaders); + log_status_error(TAG, "SCardLocateCardsW", ret.ReturnCode); + ret.cReaders = call->cReaders; + ret.rgReaderStates = NULL; + + free(call->mszCards); + + if (ret.cReaders > 0) + { + ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return)); + + if (!ret.rgReaderStates) + return STATUS_NO_MEMORY; + } + + for (x = 0; x < ret.cReaders; x++) + { + ret.rgReaderStates[x].dwCurrentState = call->rgReaderStates[x].dwCurrentState; + ret.rgReaderStates[x].dwEventState = call->rgReaderStates[x].dwEventState; + ret.rgReaderStates[x].cbAtr = call->rgReaderStates[x].cbAtr; + CopyMemory(&(ret.rgReaderStates[x].rgbAtr), &(call->rgReaderStates[x].rgbAtr), + sizeof(ret.rgReaderStates[x].rgbAtr)); + } + + status = smartcard_pack_locate_cards_return(smartcard, irp->output, &ret); + + for (x = 0; x < call->cReaders; x++) + { + SCARD_READERSTATEW* state = &call->rgReaderStates[x]; + free(state->szReader); + } + + free(call->rgReaderStates); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_ReadCacheA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ReadCache_Return ret = { 0 }; + ReadCacheA_Call* call = &operation->call.readCacheA; + IRP* irp = operation->irp; + BOOL autoalloc = (call->Common.cbDataLen == SCARD_AUTOALLOCATE); + + if (!call->Common.fPbDataIsNULL) + { + ret.cbDataLen = call->Common.cbDataLen; + if (!autoalloc) + { + ret.pbData = malloc(ret.cbDataLen); + if (!ret.pbData) + return SCARD_F_INTERNAL_ERROR; + } + } + + if (autoalloc) + ret.ReturnCode = SCardReadCacheA(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + (BYTE*)&ret.pbData, &ret.cbDataLen); + else + ret.ReturnCode = SCardReadCacheA(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + ret.pbData, &ret.cbDataLen); + if ((ret.ReturnCode != SCARD_W_CACHE_ITEM_NOT_FOUND) && + (ret.ReturnCode != SCARD_W_CACHE_ITEM_STALE)) + { + log_status_error(TAG, "SCardReadCacheA", ret.ReturnCode); + } + free(call->szLookupName); + free(call->Common.CardIdentifier); + + status = smartcard_pack_read_cache_return(smartcard, irp->output, &ret); + if (autoalloc) + SCardFreeMemory(operation->hContext, ret.pbData); + else + free(ret.pbData); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_ReadCacheW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + ReadCache_Return ret = { 0 }; + ReadCacheW_Call* call = &operation->call.readCacheW; + IRP* irp = operation->irp; + BOOL autoalloc = (call->Common.cbDataLen == SCARD_AUTOALLOCATE); + if (!call->Common.fPbDataIsNULL) + { + ret.cbDataLen = call->Common.cbDataLen; + if (!autoalloc) + { + ret.pbData = malloc(ret.cbDataLen); + if (!ret.pbData) + return SCARD_F_INTERNAL_ERROR; + } + } + + if (autoalloc) + ret.ReturnCode = SCardReadCacheW(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + (BYTE*)&ret.pbData, &ret.cbDataLen); + else + ret.ReturnCode = SCardReadCacheW(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + ret.pbData, &ret.cbDataLen); + if ((ret.ReturnCode != SCARD_W_CACHE_ITEM_NOT_FOUND) && + (ret.ReturnCode != SCARD_W_CACHE_ITEM_STALE)) + { + log_status_error(TAG, "SCardReadCacheA", ret.ReturnCode); + } + free(call->szLookupName); + free(call->Common.CardIdentifier); + + status = smartcard_pack_read_cache_return(smartcard, irp->output, &ret); + if (autoalloc) + SCardFreeMemory(operation->hContext, ret.pbData); + else + free(ret.pbData); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_WriteCacheA_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + WriteCacheA_Call* call = &operation->call.writeCacheA; + + ret.ReturnCode = SCardWriteCacheA(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + call->Common.pbData, call->Common.cbDataLen); + log_status_error(TAG, "SCardWriteCacheA", ret.ReturnCode); + free(call->szLookupName); + free(call->Common.CardIdentifier); + free(call->Common.pbData); + + smartcard_trace_long_return(smartcard, &ret, "SCardWriteCacheA"); + return ret.ReturnCode; +} + +static LONG smartcard_WriteCacheW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + WriteCacheW_Call* call = &operation->call.writeCacheW; + + ret.ReturnCode = SCardWriteCacheW(operation->hContext, call->Common.CardIdentifier, + call->Common.FreshnessCounter, call->szLookupName, + call->Common.pbData, call->Common.cbDataLen); + log_status_error(TAG, "SCardWriteCacheW", ret.ReturnCode); + free(call->szLookupName); + free(call->Common.CardIdentifier); + free(call->Common.pbData); + + smartcard_trace_long_return(smartcard, &ret, "SCardWriteCacheW"); + return ret.ReturnCode; +} + +static LONG smartcard_GetTransmitCount_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + GetTransmitCount_Return ret = { 0 }; + IRP* irp = operation->irp; + + ret.ReturnCode = SCardGetTransmitCount(operation->hCard, &ret.cTransmitCount); + log_status_error(TAG, "SCardGetTransmitCount", ret.ReturnCode); + status = smartcard_pack_get_transmit_count_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_ReleaseStartedEvent_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + WINPR_UNUSED(smartcard); + WINPR_UNUSED(operation); + + WLog_WARN(TAG, "According to [MS-RDPESC] 3.1.4 Message Processing Events and Sequencing Rules " + "this is not supported?!?"); + return SCARD_E_UNSUPPORTED_FEATURE; +} + +static LONG smartcard_GetReaderIcon_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + GetReaderIcon_Return ret = { 0 }; + GetReaderIcon_Call* call = &operation->call.getReaderIcon; + IRP* irp = operation->irp; + + ret.cbDataLen = SCARD_AUTOALLOCATE; + ret.ReturnCode = SCardGetReaderIconW(operation->hContext, call->szReaderName, + (LPBYTE)&ret.pbData, &ret.cbDataLen); + log_status_error(TAG, "SCardGetReaderIconW", ret.ReturnCode); + free(call->szReaderName); + status = smartcard_pack_get_reader_icon_return(smartcard, irp->output, &ret); + SCardFreeMemory(operation->hContext, ret.pbData); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_GetDeviceTypeId_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + GetDeviceTypeId_Return ret = { 0 }; + GetDeviceTypeId_Call* call = &operation->call.getDeviceTypeId; + IRP* irp = operation->irp; + + ret.ReturnCode = + SCardGetDeviceTypeIdW(operation->hContext, call->szReaderName, &ret.dwDeviceId); + log_status_error(TAG, "SCardGetDeviceTypeIdW", ret.ReturnCode); + free(call->szReaderName); + + status = smartcard_pack_device_type_id_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_GetStatusChangeA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_get_status_change_a_call(smartcard, irp->input, + &operation->call.getStatusChangeA); + + return status; +} + +static LONG smartcard_GetStatusChangeA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + UINT32 index; + GetStatusChange_Return ret = { 0 }; + LPSCARD_READERSTATEA rgReaderState = NULL; + IRP* irp = operation->irp; + GetStatusChangeA_Call* call = &operation->call.getStatusChangeA; + ret.ReturnCode = SCardGetStatusChangeA(operation->hContext, call->dwTimeOut, + call->rgReaderStates, call->cReaders); + log_status_error(TAG, "SCardGetStatusChangeA", ret.ReturnCode); + 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), + sizeof(ret.rgReaderStates[index].rgbAtr)); + } + + smartcard_pack_get_status_change_return(smartcard, irp->output, &ret, FALSE); + + 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_get_status_change_w_call(smartcard, irp->input, + &operation->call.getStatusChangeW); + + return status; +} + +static LONG smartcard_GetStatusChangeW_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + UINT32 index; + GetStatusChange_Return ret = { 0 }; + LPSCARD_READERSTATEW rgReaderState = NULL; + IRP* irp = operation->irp; + GetStatusChangeW_Call* call = &operation->call.getStatusChangeW; + ret.ReturnCode = SCardGetStatusChangeW(operation->hContext, call->dwTimeOut, + call->rgReaderStates, call->cReaders); + log_status_error(TAG, "SCardGetStatusChangeW", ret.ReturnCode); + 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), + sizeof(ret.rgReaderStates[index].rgbAtr)); + } + + status = smartcard_pack_get_status_change_return(smartcard, irp->output, &ret, TRUE); + + if (call->rgReaderStates) + { + for (index = 0; index < call->cReaders; index++) + { + rgReaderState = &call->rgReaderStates[index]; + free((void*)rgReaderState->szReader); + } + + free(call->rgReaderStates); + } + + free(ret.rgReaderStates); + if (status != SCARD_S_SUCCESS) + return status; + return ret.ReturnCode; +} + +static LONG smartcard_Cancel_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = + smartcard_unpack_context_call(smartcard, irp->input, &operation->call.context, "Cancel"); + + return status; +} + +static LONG smartcard_Cancel_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + + ret.ReturnCode = SCardCancel(operation->hContext); + log_status_error(TAG, "SCardCancel", ret.ReturnCode); + smartcard_trace_long_return(smartcard, &ret, "Cancel"); + return ret.ReturnCode; +} + +static LONG smartcard_ConnectA_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_connect_a_call(smartcard, irp->input, &operation->call.connectA); + + 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.connectA; + + if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) && + (call->Common.dwShareMode != SCARD_SHARE_DIRECT)) + { + call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx; + } + + 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); + + status = smartcard_pack_connect_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_connect_w_call(smartcard, irp->input, &operation->call.connectW); + + 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.connectW; + + if ((call->Common.dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED) && + (call->Common.dwShareMode != SCARD_SHARE_DIRECT)) + { + call->Common.dwPreferredProtocols = SCARD_PROTOCOL_Tx; + } + + 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); + + status = smartcard_pack_connect_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_reconnect_call(smartcard, irp->input, &operation->call.reconnect); + + return status; +} + +static LONG smartcard_Reconnect_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Reconnect_Return ret = { 0 }; + IRP* irp = operation->irp; + Reconnect_Call* call = &operation->call.reconnect; + ret.ReturnCode = SCardReconnect(operation->hCard, call->dwShareMode, call->dwPreferredProtocols, + call->dwInitialization, &ret.dwActiveProtocol); + log_status_error(TAG, "SCardReconnect", ret.ReturnCode); + status = smartcard_pack_reconnect_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_Disconnect_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_hcard_and_disposition_call( + smartcard, irp->input, &operation->call.hCardAndDisposition, "Disconnect"); + + return status; +} + +static LONG smartcard_Disconnect_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + HCardAndDisposition_Call* call = &operation->call.hCardAndDisposition; + + ret.ReturnCode = SCardDisconnect(operation->hCard, call->dwDisposition); + log_status_error(TAG, "SCardDisconnect", ret.ReturnCode); + smartcard_trace_long_return(smartcard, &ret, "Disconnect"); + + return ret.ReturnCode; +} + +static LONG smartcard_BeginTransaction_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_hcard_and_disposition_call( + smartcard, irp->input, &operation->call.hCardAndDisposition, "BeginTransaction"); + + return status; +} + +static LONG smartcard_BeginTransaction_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + + ret.ReturnCode = SCardBeginTransaction(operation->hCard); + log_status_error(TAG, "SCardBeginTransaction", 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_hcard_and_disposition_call( + smartcard, irp->input, &operation->call.hCardAndDisposition, "EndTransaction"); + + return status; +} + +static LONG smartcard_EndTransaction_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + HCardAndDisposition_Call* call = &operation->call.hCardAndDisposition; + + ret.ReturnCode = SCardEndTransaction(operation->hCard, call->dwDisposition); + log_status_error(TAG, "SCardEndTransaction", 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_state_call(smartcard, irp->input, &operation->call.state); + + return status; +} + +static LONG smartcard_State_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + State_Return ret = { 0 }; + IRP* irp = operation->irp; + ret.cbAtrLen = SCARD_ATR_LENGTH; + ret.ReturnCode = SCardState(operation->hCard, &ret.dwState, &ret.dwProtocol, (BYTE*)&ret.rgAtr, + &ret.cbAtrLen); + + log_status_error(TAG, "SCardState", ret.ReturnCode); + status = smartcard_pack_state_return(smartcard, irp->output, &ret); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_StatusA_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_status_call(smartcard, irp->input, &operation->call.status, FALSE); + + 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.status; + + 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); + + log_status_error(TAG, "SCardStatusA", status); + if (status == SCARD_S_SUCCESS) + { + if (!call->fmszReaderNamesIsNULL) + ret.mszReaderNames = (BYTE*)mszReaderNames; + + ret.cBytes = cchReaderLen; + + if (call->cbAtrLen) + ret.cbAtrLen = cbAtrLen; + } + + status = smartcard_pack_status_return(smartcard, irp->output, &ret, FALSE); + + if (mszReaderNames) + SCardFreeMemory(operation->hContext, mszReaderNames); + + if (status != SCARD_S_SUCCESS) + return status; + return ret.ReturnCode; +} + +static LONG smartcard_StatusW_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_status_call(smartcard, irp->input, &operation->call.status, TRUE); + + return status; +} + +static LONG smartcard_StatusW_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Status_Return ret = { 0 }; + LPWSTR mszReaderNames = NULL; + IRP* irp = operation->irp; + Status_Call* call = &operation->call.status; + DWORD cbAtrLen; + + /** + * [MS-RDPESC] + * According to 2.2.2.18 Status_Call cbAtrLen is unused an must be ignored upon receipt. + */ + cbAtrLen = call->cbAtrLen = 32; + + if (call->fmszReaderNamesIsNULL) + ret.cBytes = 0; + else + ret.cBytes = SCARD_AUTOALLOCATE; + + status = ret.ReturnCode = + SCardStatusW(operation->hCard, call->fmszReaderNamesIsNULL ? NULL : (LPWSTR)&mszReaderNames, + &ret.cBytes, &ret.dwState, &ret.dwProtocol, (BYTE*)&ret.pbAtr, &cbAtrLen); + log_status_error(TAG, "SCardStatusW", status); + if (status == SCARD_S_SUCCESS) + { + if (!call->fmszReaderNamesIsNULL) + ret.mszReaderNames = (BYTE*)mszReaderNames; + + ret.cbAtrLen = cbAtrLen; + } + + /* SCardStatusW returns number of characters, we need number of bytes */ + ret.cBytes *= sizeof(WCHAR); + + status = smartcard_pack_status_return(smartcard, irp->output, &ret, TRUE); + if (status != SCARD_S_SUCCESS) + return status; + + if (mszReaderNames) + SCardFreeMemory(operation->hContext, mszReaderNames); + + return ret.ReturnCode; +} + +static LONG smartcard_Transmit_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_transmit_call(smartcard, irp->input, &operation->call.transmit); + + return status; +} + +static LONG smartcard_Transmit_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Transmit_Return ret = { 0 }; + IRP* irp = operation->irp; + Transmit_Call* call = &operation->call.transmit; + 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)); + + log_status_error(TAG, "SCardTransmit", ret.ReturnCode); + + status = smartcard_pack_transmit_return(smartcard, irp->output, &ret); + free(call->pbSendBuffer); + free(ret.pbRecvBuffer); + free(call->pioSendPci); + free(call->pioRecvPci); + + if (status != SCARD_S_SUCCESS) + return status; + return ret.ReturnCode; +} + +static LONG smartcard_Control_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_control_call(smartcard, irp->input, &operation->call.control); + + return status; +} + +static LONG smartcard_Control_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + Control_Return ret = { 0 }; + IRP* irp = operation->irp; + Control_Call* call = &operation->call.control; + 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); + log_status_error(TAG, "SCardControl", ret.ReturnCode); + status = smartcard_pack_control_return(smartcard, irp->output, &ret); + + free(call->pvInBuffer); + free(ret.pvOutBuffer); + if (status != SCARD_S_SUCCESS) + return status; + return ret.ReturnCode; +} + +static LONG smartcard_GetAttrib_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_get_attrib_call(smartcard, irp->input, &operation->call.getAttrib); + + return status; +} + +static LONG smartcard_SetAttrib_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_set_attrib_call(smartcard, irp->input, &operation->call.setAttrib); + + return status; +} + +static LONG smartcard_GetAttrib_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + BOOL autoAllocate = FALSE; + LONG status; + DWORD cbAttrLen = 0; + LPBYTE pbAttr = NULL; + GetAttrib_Return ret = { 0 }; + IRP* irp = operation->irp; + const GetAttrib_Call* call = &operation->call.getAttrib; + + if (!call->fpbAttrIsNULL) + { + autoAllocate = (call->cbAttrLen == SCARD_AUTOALLOCATE) ? TRUE : FALSE; + cbAttrLen = call->cbAttrLen; + if (cbAttrLen && !autoAllocate) + { + ret.pbAttr = (BYTE*)malloc(cbAttrLen); + + if (!ret.pbAttr) + return SCARD_E_NO_MEMORY; + } + + pbAttr = autoAllocate ? (LPBYTE) & (ret.pbAttr) : ret.pbAttr; + } + + ret.ReturnCode = SCardGetAttrib(operation->hCard, call->dwAttrId, pbAttr, &cbAttrLen); + log_status_error(TAG, "SCardGetAttrib", ret.ReturnCode); + ret.cbAttrLen = cbAttrLen; + + status = smartcard_pack_get_attrib_return(smartcard, irp->output, &ret, call->dwAttrId, + call->cbAttrLen); + + if (autoAllocate) + SCardFreeMemory(operation->hContext, ret.pbAttr); + else + free(ret.pbAttr); + return status; +} + +static LONG smartcard_SetAttrib_Call(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + Long_Return ret = { 0 }; + SetAttrib_Call* call = &operation->call.setAttrib; + + ret.ReturnCode = + SCardSetAttrib(operation->hCard, call->dwAttrId, call->pbAttr, call->cbAttrLen); + log_status_error(TAG, "SCardSetAttrib", ret.ReturnCode); + free(call->pbAttr); + smartcard_trace_long_return(smartcard, &ret, "SetAttrib"); + + return ret.ReturnCode; +} + +static LONG smartcard_AccessStartedEvent_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + IRP* irp; + WINPR_UNUSED(smartcard); + irp = operation->irp; + + if (!operation || !operation->irp) + 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_INT32(irp->input, operation->call.lng.LongValue); /* Unused (4 bytes) */ + + return SCARD_S_SUCCESS; +} + +static LONG smartcard_AccessStartedEvent_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status = SCARD_S_SUCCESS; + WINPR_UNUSED(operation); + + 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; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_locate_cards_by_atr_a_call(smartcard, irp->input, + &operation->call.locateCardsByATRA); + + return status; +} + +static LONG smartcard_LocateCardsByATRW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_locate_cards_by_atr_w_call(smartcard, irp->input, + &operation->call.locateCardsByATRW); + + return status; +} + +static LONG smartcard_ReadCacheA_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_read_cache_a_call(smartcard, irp->input, &operation->call.readCacheA); + + return status; +} + +static LONG smartcard_ReadCacheW_Decode(SMARTCARD_DEVICE* smartcard, SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + + status = smartcard_unpack_read_cache_w_call(smartcard, irp->input, &operation->call.readCacheW); + + return status; +} + +static LONG smartcard_WriteCacheA_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = + smartcard_unpack_write_cache_a_call(smartcard, irp->input, &operation->call.writeCacheA); + + return status; +} + +static LONG smartcard_WriteCacheW_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = + smartcard_unpack_write_cache_w_call(smartcard, irp->input, &operation->call.writeCacheW); + + return status; +} + +static LONG smartcard_GetTransmitCount_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_get_transmit_count_call(smartcard, irp->input, + &operation->call.getTransmitCount); + + return status; +} + +static LONG smartcard_ReleaseStartedEvent_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + WINPR_UNUSED(smartcard); + WINPR_UNUSED(operation); + WLog_WARN(TAG, "According to [MS-RDPESC] 3.1.4 Message Processing Events and Sequencing Rules " + "SCARD_IOCTL_RELEASETARTEDEVENT is not supported"); + return SCARD_E_UNSUPPORTED_FEATURE; +} + +static LONG smartcard_GetReaderIcon_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + + status = smartcard_unpack_get_reader_icon_call(smartcard, irp->input, + &operation->call.getReaderIcon); + + return status; +} + +static LONG smartcard_GetDeviceTypeId_Decode(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + IRP* irp; + + if (!operation || !operation->irp) + return STATUS_NO_MEMORY; + irp = operation->irp; + status = smartcard_unpack_get_device_type_id_call(smartcard, irp->input, + &operation->call.getDeviceTypeId); + + return status; +} + +static LONG smartcard_LocateCardsByATRA_Call(SMARTCARD_DEVICE* smartcard, + SMARTCARD_OPERATION* operation) +{ + LONG status; + DWORD i, j, k; + GetStatusChange_Return ret = { 0 }; + LPSCARD_READERSTATEA state = NULL; + LPSCARD_READERSTATEA states = NULL; + IRP* irp = operation->irp; + LocateCardsByATRA_Call* call = &operation->call.locateCardsByATRA; + 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 = (LPSTR)call->rgReaderStates[i].szReader; + states[i].dwCurrentState = call->rgReaderStates[i].dwCurrentState; + states[i].dwEventState = call->rgReaderStates[i].dwEventState; + states[i].cbAtr = call->rgReaderStates[i].cbAtr; + CopyMemory(&(states[i].rgbAtr), &(call->rgReaderStates[i].rgbAtr), 36); + } + + status = ret.ReturnCode = + SCardGetStatusChangeA(operation->hContext, 0x000001F4, states, call->cReaders); + + log_status_error(TAG, "SCardGetStatusChangeA", status); + 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) + { + free(states); + 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), + sizeof(ret.rgReaderStates[i].rgbAtr)); + } + + free(states); + + status = smartcard_pack_get_status_change_return(smartcard, irp->output, &ret, FALSE); + + 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); + if (status != SCARD_S_SUCCESS) + return status; + 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; + + if (!operation || !operation->irp) + return SCARD_E_NO_MEMORY; + 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_RELEASETARTEDEVENT)) + { + status = smartcard_unpack_common_type_header(smartcard, irp->input); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_private_type_header(smartcard, irp->input); + if (status != SCARD_S_SUCCESS) + return status; + } + + /* Decode */ + 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 = smartcard_context_and_string_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + status = smartcard_context_and_string_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERGROUPA: + status = smartcard_context_and_string_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERGROUPW: + status = smartcard_context_and_string_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERA: + status = smartcard_context_and_two_strings_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERW: + status = smartcard_context_and_two_strings_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERA: + status = smartcard_context_and_string_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERW: + status = smartcard_context_and_string_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + status = smartcard_context_and_two_strings_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + status = smartcard_context_and_two_strings_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + status = smartcard_context_and_two_strings_a_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + status = smartcard_context_and_two_strings_w_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSA: + status = smartcard_LocateCardsA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSW: + status = smartcard_LocateCardsW_Decode(smartcard, operation); + 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 = smartcard_SetAttrib_Decode(smartcard, operation); + 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 = smartcard_LocateCardsByATRW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_READCACHEA: + status = smartcard_ReadCacheA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_READCACHEW: + status = smartcard_ReadCacheW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_WRITECACHEA: + status = smartcard_WriteCacheA_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_WRITECACHEW: + status = smartcard_WriteCacheW_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + status = smartcard_GetTransmitCount_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_RELEASETARTEDEVENT: + status = smartcard_ReleaseStartedEvent_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_GETREADERICON: + status = smartcard_GetReaderIcon_Decode(smartcard, operation); + break; + + case SCARD_IOCTL_GETDEVICETYPEID: + status = smartcard_GetDeviceTypeId_Decode(smartcard, operation); + break; + + default: + status = SCARD_F_INTERNAL_ERROR; + break; + } + + smartcard_call_to_operation_handle(smartcard, operation); + + if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioControlCode != SCARD_IOCTL_RELEASETARTEDEVENT)) + { + 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 (%s [0x%08" PRIX32 "]): Actual: %" PRIuz + ", Expected: %" PRIuz ", Difference: %" PRIuz "", + smartcard_get_ioctl_string(ioControlCode, TRUE), + smartcard_get_ioctl_string(ioControlCode, FALSE), 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); + } + + 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. + */ + if (!Stream_EnsureRemainingCapacity(irp->output, 2048)) + return SCARD_E_NO_MEMORY; + + /* 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 = smartcard_IntroduceReaderGroupA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + result = smartcard_IntroduceReaderGroupW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERGROUPA: + result = smartcard_ForgetReaderA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERGROUPW: + result = smartcard_ForgetReaderW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERA: + result = smartcard_IntroduceReaderA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_INTRODUCEREADERW: + result = smartcard_IntroduceReaderW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERA: + result = smartcard_ForgetReaderA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_FORGETREADERW: + result = smartcard_ForgetReaderW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + result = smartcard_AddReaderToGroupA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + result = smartcard_AddReaderToGroupW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + result = smartcard_RemoveReaderFromGroupA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + result = smartcard_RemoveReaderFromGroupW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSA: + result = smartcard_LocateCardsA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_LOCATECARDSW: + result = smartcard_LocateCardsW_Call(smartcard, operation); + 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 = smartcard_SetAttrib_Call(smartcard, operation); + 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 = smartcard_LocateCardsW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_READCACHEA: + result = smartcard_ReadCacheA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_READCACHEW: + result = smartcard_ReadCacheW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_WRITECACHEA: + result = smartcard_WriteCacheA_Call(smartcard, operation); + break; + + case SCARD_IOCTL_WRITECACHEW: + result = smartcard_WriteCacheW_Call(smartcard, operation); + break; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + result = smartcard_GetTransmitCount_Call(smartcard, operation); + break; + + case SCARD_IOCTL_RELEASETARTEDEVENT: + result = smartcard_ReleaseStartedEvent_Call(smartcard, operation); + break; + + case SCARD_IOCTL_GETREADERICON: + result = smartcard_GetReaderIcon_Call(smartcard, operation); + break; + + case SCARD_IOCTL_GETDEVICETYPEID: + result = smartcard_GetDeviceTypeId_Call(smartcard, operation); + break; + + default: + result = STATUS_UNSUCCESSFUL; + break; + } + + /** + * [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_RELEASETARTEDEVENT)) + { + 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) && + (result != SCARD_W_CACHE_ITEM_NOT_FOUND) && (result != SCARD_W_CACHE_ITEM_STALE)) + { + 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 & 0xC0000000L) == 0xC0000000L) + { + /* 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_INT32(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_operations.h b/channels/smartcard/client/smartcard_operations.h new file mode 100644 index 0000000..39add18 --- /dev/null +++ b/channels/smartcard/client/smartcard_operations.h @@ -0,0 +1,546 @@ +/** + * 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_OPERATIONS_MAIN_H +#define FREERDP_CHANNEL_SMARTCARD_OPERATIONS_MAIN_H + +#include + +#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_RELEASETARTEDEVENT RDP_SCARD_CTL_CODE(57) /* SCardReleaseStartedEvent */ +#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_GETREADERICON RDP_SCARD_CTL_CODE(65) /* SCardGetReaderIconA */ +#define SCARD_IOCTL_GETDEVICETYPEID RDP_SCARD_CTL_CODE(66) /* SCardGetDeviceTypeIdA */ + +#pragma pack(push, 1) + +/* 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_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 _EstablishContext_Return +{ + LONG ReturnCode; + REDIR_SCARDCONTEXT hContext; +} EstablishContext_Return; + +typedef struct _ReaderState_Return +{ + DWORD dwCurrentState; + DWORD dwEventState; + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; +} ReaderState_Return; + +typedef struct _LocateCards_ATRMask +{ + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; + BYTE rgbMask[36]; +} LocateCards_ATRMask; + +typedef struct _GetStatusChange_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderState_Return* rgReaderStates; +} LocateCards_Return; + +typedef struct _GetStatusChange_Return GetStatusChange_Return; + +typedef struct _GetReaderIcon_Return +{ + LONG ReturnCode; + ULONG cbDataLen; + BYTE* pbData; +} GetReaderIcon_Return; + +typedef struct _GetDeviceTypeId_Return +{ + LONG ReturnCode; + ULONG dwDeviceId; +} GetDeviceTypeId_Return; + +typedef struct _Connect_Return +{ + LONG ReturnCode; + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwActiveProtocol; +} Connect_Return; + +typedef struct Reconnect_Return +{ + LONG ReturnCode; + DWORD dwActiveProtocol; +} Reconnect_Return; + +typedef struct _State_Return +{ + LONG ReturnCode; + DWORD dwState; + DWORD dwProtocol; + /* [range] */ DWORD cbAtrLen; + /* [size_is][unique] */ BYTE rgAtr[36]; +} State_Return; + +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_Return +{ + LONG ReturnCode; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + /* [range] */ DWORD cbRecvLength; + /* [size_is][unique] */ BYTE* pbRecvBuffer; +} Transmit_Return; + +typedef struct _GetTransmitCount_Return +{ + LONG ReturnCode; + DWORD cTransmitCount; +} GetTransmitCount_Return; + +typedef struct _Control_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbOutBufferSize; + /* [size_is][unique] */ BYTE* pvOutBuffer; +} Control_Return; + +typedef struct _GetAttrib_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbAttrLen; + /* [size_is][unique] */ BYTE* pbAttr; +} GetAttrib_Return; + +typedef struct _ReadCache_Return +{ + LONG ReturnCode; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} ReadCache_Return; +#pragma pack(pop) + +typedef struct _Handles_Call +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; +} Handles_Call; + +typedef struct _ListReaderGroups_Call +{ + Handles_Call handles; + LONG fmszGroupsIsNULL; + DWORD cchGroups; +} ListReaderGroups_Call; + +typedef struct _ListReaders_Call +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* mszGroups; + LONG fmszReadersIsNULL; + DWORD cchReaders; +} ListReaders_Call; + +typedef struct _GetStatusChangeA_Call +{ + Handles_Call handles; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} GetStatusChangeA_Call; + +typedef struct _LocateCardsA_Call +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is] */ CHAR* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} LocateCardsA_Call; + +typedef struct _LocateCardsW_Call +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is] */ WCHAR* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} LocateCardsW_Call; + +typedef struct _LocateCardsByATRA_Call +{ + Handles_Call handles; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} LocateCardsByATRA_Call; + +typedef struct _LocateCardsByATRW_Call +{ + Handles_Call handles; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} LocateCardsByATRW_Call; + +typedef struct _GetStatusChangeW_Call +{ + Handles_Call handles; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} GetStatusChangeW_Call; + +typedef struct _GetReaderIcon_Call +{ + Handles_Call handles; + WCHAR* szReaderName; +} GetReaderIcon_Call; + +typedef struct _GetDeviceTypeId_Call +{ + Handles_Call handles; + WCHAR* szReaderName; +} GetDeviceTypeId_Call; + +typedef struct _Connect_Common_Call +{ + Handles_Call handles; + DWORD dwShareMode; + DWORD dwPreferredProtocols; +} Connect_Common_Call; + +typedef struct _ConnectA_Call +{ + Connect_Common_Call Common; + /* [string] */ CHAR* szReader; +} ConnectA_Call; + +typedef struct _ConnectW_Call +{ + Connect_Common_Call Common; + /* [string] */ WCHAR* szReader; +} ConnectW_Call; + +typedef struct _Reconnect_Call +{ + Handles_Call handles; + DWORD dwShareMode; + DWORD dwPreferredProtocols; + DWORD dwInitialization; +} Reconnect_Call; + +typedef struct _HCardAndDisposition_Call +{ + Handles_Call handles; + DWORD dwDisposition; +} HCardAndDisposition_Call; + +typedef struct _State_Call +{ + Handles_Call handles; + LONG fpbAtrIsNULL; + DWORD cbAtrLen; +} State_Call; + +typedef struct _Status_Call +{ + Handles_Call handles; + LONG fmszReaderNamesIsNULL; + DWORD cchReaderLen; + DWORD cbAtrLen; +} Status_Call; + +typedef struct _Transmit_Call +{ + Handles_Call handles; + LPSCARD_IO_REQUEST pioSendPci; + /* [range] */ DWORD cbSendLength; + /* [size_is] */ BYTE* pbSendBuffer; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + LONG fpbRecvBufferIsNULL; + DWORD cbRecvLength; +} Transmit_Call; + +typedef struct _Long_Call +{ + Handles_Call handles; + LONG LongValue; +} Long_Call; + +typedef struct _Context_Call +{ + Handles_Call handles; +} Context_Call; + +typedef struct _ContextAndStringA_Call +{ + Handles_Call handles; + /* [string] */ char* sz; +} ContextAndStringA_Call; + +typedef struct _ContextAndStringW_Call +{ + Handles_Call handles; + /* [string] */ WCHAR* sz; +} ContextAndStringW_Call; + +typedef struct _ContextAndTwoStringA_Call +{ + Handles_Call handles; + /* [string] */ char* sz1; + /* [string] */ char* sz2; +} ContextAndTwoStringA_Call; + +typedef struct _ContextAndTwoStringW_Call +{ + Handles_Call handles; + /* [string] */ WCHAR* sz1; + /* [string] */ WCHAR* sz2; +} ContextAndTwoStringW_Call; + +typedef struct _EstablishContext_Call +{ + Handles_Call handles; + DWORD dwScope; +} EstablishContext_Call; + +typedef struct _GetTranmitCount_Call +{ + Handles_Call handles; +} GetTransmitCount_Call; + +typedef struct _Control_Call +{ + Handles_Call handles; + DWORD dwControlCode; + /* [range] */ DWORD cbInBufferSize; + /* [size_is][unique] */ BYTE* pvInBuffer; + LONG fpvOutBufferIsNULL; + DWORD cbOutBufferSize; +} Control_Call; + +typedef struct _GetAttrib_Call +{ + Handles_Call handles; + DWORD dwAttrId; + LONG fpbAttrIsNULL; + DWORD cbAttrLen; +} GetAttrib_Call; + +typedef struct _SetAttrib_Call +{ + Handles_Call handles; + DWORD dwAttrId; + /* [range] */ DWORD cbAttrLen; + /* [size_is] */ BYTE* pbAttr; +} SetAttrib_Call; + +typedef struct _ReadCache_Common +{ + Handles_Call handles; + UUID* CardIdentifier; + DWORD FreshnessCounter; + LONG fPbDataIsNULL; + DWORD cbDataLen; +} ReadCache_Common; + +typedef struct _ReadCacheA_Call +{ + ReadCache_Common Common; + /* [string] */ char* szLookupName; +} ReadCacheA_Call; + +typedef struct _ReadCacheW_Call +{ + ReadCache_Common Common; + /* [string] */ WCHAR* szLookupName; +} ReadCacheW_Call; + +typedef struct _WriteCache_Common +{ + Handles_Call handles; + UUID* CardIdentifier; + DWORD FreshnessCounter; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} WriteCache_Common; + +typedef struct _WriteCacheA_Call +{ + WriteCache_Common Common; + /* [string] */ char* szLookupName; +} WriteCacheA_Call; + +typedef struct _WriteCacheW_Call +{ + WriteCache_Common Common; + /* [string] */ WCHAR* szLookupName; +} WriteCacheW_Call; + +struct _SMARTCARD_OPERATION +{ + IRP* irp; + union + { + Handles_Call handles; + Long_Call lng; + Context_Call context; + ContextAndStringA_Call contextAndStringA; + ContextAndStringW_Call contextAndStringW; + ContextAndTwoStringA_Call contextAndTwoStringA; + ContextAndTwoStringW_Call contextAndTwoStringW; + EstablishContext_Call establishContext; + ListReaderGroups_Call listReaderGroups; + ListReaders_Call listReaders; + GetStatusChangeA_Call getStatusChangeA; + LocateCardsA_Call locateCardsA; + LocateCardsW_Call locateCardsW; + LocateCards_ATRMask locateCardsATRMask; + LocateCardsByATRA_Call locateCardsByATRA; + LocateCardsByATRW_Call locateCardsByATRW; + GetStatusChangeW_Call getStatusChangeW; + GetReaderIcon_Call getReaderIcon; + GetDeviceTypeId_Call getDeviceTypeId; + Connect_Common_Call connect; + ConnectA_Call connectA; + ConnectW_Call connectW; + Reconnect_Call reconnect; + HCardAndDisposition_Call hCardAndDisposition; + State_Call state; + Status_Call status; + SCardIO_Request scardIO; + Transmit_Call transmit; + GetTransmitCount_Call getTransmitCount; + Control_Call control; + GetAttrib_Call getAttrib; + SetAttrib_Call setAttrib; + ReadCache_Common readCache; + ReadCacheA_Call readCacheA; + ReadCacheW_Call readCacheW; + WriteCache_Common writeCache; + WriteCacheA_Call writeCacheA; + WriteCacheW_Call writeCacheW; + } call; + UINT32 ioControlCode; + SCARDCONTEXT hContext; + SCARDHANDLE hCard; +}; +typedef struct _SMARTCARD_OPERATION SMARTCARD_OPERATION; + +#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_OPERATIONS_H */ diff --git a/channels/smartcard/client/smartcard_pack.c b/channels/smartcard/client/smartcard_pack.c new file mode 100644 index 0000000..f70eb4e --- /dev/null +++ b/channels/smartcard/client/smartcard_pack.c @@ -0,0 +1,3888 @@ +/** + * 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 + * Copyright 2020 Armin Novak + * Copyright 2020 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 "smartcard_pack.h" + +static const DWORD g_LogLevel = WLOG_DEBUG; + +#define smartcard_unpack_redir_scard_context(smartcard, s, context, index) \ + smartcard_unpack_redir_scard_context_((smartcard), (s), (context), (index), __FILE__, \ + __FUNCTION__, __LINE__) +#define smartcard_unpack_redir_scard_handle(smartcard, s, context, index) \ + smartcard_unpack_redir_scard_handle_((smartcard), (s), (context), (index), __FILE__, \ + __FUNCTION__, __LINE__) + +static LONG smartcard_unpack_redir_scard_context_(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context, UINT32* index, + const char* file, const char* function, int line); +static LONG smartcard_pack_redir_scard_context(SMARTCARD_DEVICE* smartcard, wStream* s, + const REDIR_SCARDCONTEXT* context, DWORD* index); +static LONG smartcard_unpack_redir_scard_handle_(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle, UINT32* index, + const char* file, const char* function, int line); +static LONG smartcard_pack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + const REDIR_SCARDHANDLE* handle, DWORD* index); +static LONG smartcard_unpack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context); +static LONG smartcard_pack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + const REDIR_SCARDCONTEXT* context); + +static LONG smartcard_unpack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle); +static LONG smartcard_pack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + const REDIR_SCARDHANDLE* handle); + +typedef enum +{ + NDR_PTR_FULL, + NDR_PTR_SIMPLE, + NDR_PTR_FIXED +} ndr_ptr_t; + +/* Reads a NDR pointer and checks if the value read has the expected relative + * addressing */ +#define smartcard_ndr_pointer_read(s, index, ptr) \ + smartcard_ndr_pointer_read_((s), (index), (ptr), __FILE__, __FUNCTION__, __LINE__) +static BOOL smartcard_ndr_pointer_read_(wStream* s, UINT32* index, UINT32* ptr, const char* file, + const char* fkt, int line) +{ + const UINT32 expect = 0x20000 + (*index) * 4; + UINT32 ndrPtr; + WINPR_UNUSED(file); + if (!s) + return FALSE; + if (Stream_GetRemainingLength(s) < 4) + return FALSE; + + Stream_Read_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */ + if (ptr) + *ptr = ndrPtr; + if (expect != ndrPtr) + { + /* Allow NULL pointer if we read the result */ + if (ptr && (ndrPtr == 0)) + return TRUE; + WLog_WARN(TAG, "[%s:%d] Read context pointer 0x%08" PRIx32 ", expected 0x%08" PRIx32, fkt, + line, ndrPtr, expect); + return FALSE; + } + + (*index) = (*index) + 1; + return TRUE; +} + +static LONG smartcard_ndr_read(wStream* s, BYTE** data, size_t min, size_t elementSize, + ndr_ptr_t type) +{ + size_t len, offset, len2; + void* r; + size_t required; + + switch (type) + { + case NDR_PTR_FULL: + required = 12; + break; + case NDR_PTR_SIMPLE: + required = 4; + break; + case NDR_PTR_FIXED: + required = min; + break; + } + + if (Stream_GetRemainingLength(s) < required) + { + WLog_ERR(TAG, "Short data while trying to read NDR pointer, expected 4, got %" PRIu32, + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + switch (type) + { + case NDR_PTR_FULL: + Stream_Read_UINT32(s, len); + Stream_Read_UINT32(s, offset); + Stream_Read_UINT32(s, len2); + if (len != offset + len2) + { + WLog_ERR(TAG, + "Invalid data when reading full NDR pointer: total=%" PRIu32 + ", offset=%" PRIu32 ", remaining=%" PRIu32, + len, offset, len2); + return STATUS_BUFFER_TOO_SMALL; + } + break; + case NDR_PTR_SIMPLE: + Stream_Read_UINT32(s, len); + + if ((len != min) && (min > 0)) + { + WLog_ERR(TAG, + "Invalid data when reading simple NDR pointer: total=%" PRIu32 + ", expected=%" PRIu32, + len, min); + return STATUS_BUFFER_TOO_SMALL; + } + break; + case NDR_PTR_FIXED: + len = (UINT32)min; + break; + } + + if (min > len) + { + WLog_ERR(TAG, "Invalid length read from NDR pointer, minimum %" PRIu32 ", got %" PRIu32, + min, len); + return STATUS_DATA_ERROR; + } + + if (len > SIZE_MAX / 2) + return STATUS_BUFFER_TOO_SMALL; + + if (Stream_GetRemainingLength(s) / elementSize < len) + { + WLog_ERR(TAG, + "Short data while trying to read data from NDR pointer, expected %" PRIu32 + ", got %" PRIu32, + len, Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + len *= elementSize; + + r = calloc(len + 1, sizeof(CHAR)); + if (!r) + return SCARD_E_NO_MEMORY; + Stream_Read(s, r, len); + smartcard_unpack_read_size_align(NULL, s, len, 4); + *data = r; + return STATUS_SUCCESS; +} + +static BOOL smartcard_ndr_pointer_write(wStream* s, UINT32* index, DWORD length) +{ + const UINT32 ndrPtr = 0x20000 + (*index) * 4; + + if (!s) + return FALSE; + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (length > 0) + { + Stream_Write_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */ + (*index) = (*index) + 1; + } + else + Stream_Write_UINT32(s, 0); + return TRUE; +} + +static LONG smartcard_ndr_write(wStream* s, const BYTE* data, UINT32 size, UINT32 elementSize, + ndr_ptr_t type) +{ + const UINT32 offset = 0; + const UINT32 len = size; + const UINT32 dataLen = size * elementSize; + size_t required; + + if (size == 0) + return SCARD_S_SUCCESS; + + switch (type) + { + case NDR_PTR_FULL: + required = 12; + break; + case NDR_PTR_SIMPLE: + required = 4; + break; + case NDR_PTR_FIXED: + required = 0; + break; + } + + if (!Stream_EnsureRemainingCapacity(s, required + dataLen + 4)) + return STATUS_BUFFER_TOO_SMALL; + + switch (type) + { + case NDR_PTR_FULL: + Stream_Write_UINT32(s, len); + Stream_Write_UINT32(s, offset); + Stream_Write_UINT32(s, len); + break; + case NDR_PTR_SIMPLE: + Stream_Write_UINT32(s, len); + break; + case NDR_PTR_FIXED: + break; + } + + if (data) + Stream_Write(s, data, dataLen); + else + Stream_Zero(s, dataLen); + return smartcard_pack_write_size_align(NULL, s, len, 4); +} + +static LONG smartcard_ndr_write_state(wStream* s, const ReaderState_Return* data, UINT32 size, + ndr_ptr_t type) +{ + union + { + const ReaderState_Return* reader; + const BYTE* data; + } cnv; + + cnv.reader = data; + return smartcard_ndr_write(s, cnv.data, size, sizeof(ReaderState_Return), type); +} + +static LONG smartcard_ndr_read_atrmask(wStream* s, LocateCards_ATRMask** data, size_t min, + ndr_ptr_t type) +{ + union + { + LocateCards_ATRMask** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, min, sizeof(LocateCards_ATRMask), type); +} + +static LONG smartcard_ndr_read_fixed_string_a(wStream* s, CHAR** data, size_t min, ndr_ptr_t type) +{ + union + { + CHAR** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, min, sizeof(CHAR), type); +} + +static LONG smartcard_ndr_read_fixed_string_w(wStream* s, WCHAR** data, size_t min, ndr_ptr_t type) +{ + union + { + WCHAR** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, min, sizeof(WCHAR), type); +} + +static LONG smartcard_ndr_read_a(wStream* s, CHAR** data, ndr_ptr_t type) +{ + union + { + CHAR** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, 0, sizeof(CHAR), type); +} + +static LONG smartcard_ndr_read_w(wStream* s, WCHAR** data, ndr_ptr_t type) +{ + union + { + WCHAR** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, 0, sizeof(WCHAR), type); +} + +static LONG smartcard_ndr_read_u(wStream* s, UUID** data) +{ + union + { + UUID** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(s, u.ppv, 1, sizeof(UUID), NDR_PTR_FIXED); +} + +static char* smartcard_convert_string_list(const void* in, size_t bytes, BOOL unicode) +{ + size_t index, length; + union + { + const void* pv; + const char* sz; + const WCHAR* wz; + } string; + char* mszA = NULL; + + string.pv = in; + + if (bytes < 1) + return NULL; + + if (in == NULL) + return NULL; + + if (unicode) + { + length = (bytes / sizeof(WCHAR)) - 1; + mszA = (char*)calloc(length + 1, sizeof(WCHAR)); + if (!mszA) + return NULL; + if (ConvertFromUnicode(CP_UTF8, 0, string.wz, (int)length, &mszA, length + 1, NULL, NULL) != + (int)length) + { + free(mszA); + return NULL; + } + } + else + { + length = bytes; + mszA = (char*)calloc(length, sizeof(char)); + if (!mszA) + return NULL; + CopyMemory(mszA, string.sz, length - 1); + mszA[length - 1] = '\0'; + } + + for (index = 0; index < length - 1; index++) + { + if (mszA[index] == '\0') + mszA[index] = ','; + } + + return mszA; +} + +static char* smartcard_msz_dump_a(const char* msz, size_t len, char* buffer, size_t bufferLen) +{ + char* buf = buffer; + const char* cur = msz; + + while ((len > 0) && cur && cur[0] != '\0' && (bufferLen > 0)) + { + size_t clen = strnlen(cur, len); + int rc = _snprintf(buf, bufferLen, "%s", cur); + bufferLen -= (size_t)rc; + buf += rc; + + cur += clen; + } + + return buffer; +} + +static char* smartcard_msz_dump_w(const WCHAR* msz, size_t len, char* buffer, size_t bufferLen) +{ + char* sz = NULL; + ConvertFromUnicode(CP_UTF8, 0, msz, (int)len, &sz, 0, NULL, NULL); + return smartcard_msz_dump_a(sz, len, buffer, bufferLen); +} + +static char* smartcard_array_dump(const void* pd, size_t len, char* buffer, size_t bufferLen) +{ + const BYTE* data = pd; + size_t x; + int rc; + char* start = buffer; + + /* Ensure '\0' termination */ + if (bufferLen > 0) + { + buffer[bufferLen - 1] = '\0'; + bufferLen--; + } + + rc = _snprintf(buffer, bufferLen, "{ "); + if ((rc < 0) || ((size_t)rc > bufferLen)) + goto fail; + buffer += rc; + bufferLen -= (size_t)rc; + + for (x = 0; x < len; x++) + { + rc = _snprintf(buffer, bufferLen, "%02X", data[x]); + if ((rc < 0) || ((size_t)rc > bufferLen)) + goto fail; + buffer += rc; + bufferLen -= (size_t)rc; + } + + rc = _snprintf(buffer, bufferLen, " }"); + if ((rc < 0) || ((size_t)rc > bufferLen)) + goto fail; + buffer += rc; + bufferLen -= (size_t)rc; + +fail: + return start; +} +static void smartcard_log_redir_handle(const char* tag, const REDIR_SCARDHANDLE* pHandle) +{ + char buffer[128]; + + WLog_LVL(tag, g_LogLevel, " hContext: %s", + smartcard_array_dump(pHandle->pbHandle, pHandle->cbHandle, buffer, sizeof(buffer))); +} + +static void smartcard_log_context(const char* tag, const REDIR_SCARDCONTEXT* phContext) +{ + char buffer[128]; + WLog_DBG( + tag, "hContext: %s", + smartcard_array_dump(phContext->pbContext, phContext->cbContext, buffer, sizeof(buffer))); +} + +static void smartcard_trace_context_and_string_call_a(const char* name, + const REDIR_SCARDCONTEXT* phContext, + const CHAR* sz) +{ + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "%s {", name); + smartcard_log_context(TAG, phContext); + WLog_LVL(TAG, g_LogLevel, " sz=%s", sz); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_context_and_string_call_w(const char* name, + const REDIR_SCARDCONTEXT* phContext, + const WCHAR* sz) +{ + char* tmp = NULL; + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "%s {", name); + smartcard_log_context(TAG, phContext); + ConvertFromUnicode(CP_UTF8, 0, sz, -1, &tmp, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, " sz=%s", tmp); + free(tmp); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_context_call(SMARTCARD_DEVICE* smartcard, const Context_Call* call, + const char* name) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "%s_Call {", name); + smartcard_log_context(TAG, &call->handles.hContext); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, + const ListReaderGroups_Call* call, BOOL unicode) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ListReaderGroups%S_Call {", unicode ? "W" : "A"); + smartcard_log_context(TAG, &call->handles.hContext); + + WLog_LVL(TAG, g_LogLevel, "fmszGroupsIsNULL: %" PRId32 " cchGroups: 0x%08" PRIx32, + call->fmszGroupsIsNULL, call->cchGroups); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, + const GetStatusChangeW_Call* call) +{ + UINT32 index; + char* szEventState; + char* szCurrentState; + LPSCARD_READERSTATEW readerState; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetStatusChangeW_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + + WLog_LVL(TAG, g_LogLevel, "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_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index, + szReaderA, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index, + szCurrentState, readerState->dwCurrentState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index, + szEventState, readerState->dwEventState); + free(szCurrentState); + free(szEventState); + free(szReaderA); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, + const ListReaderGroups_Return* ret, + BOOL unicode) +{ + char* mszA = NULL; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + mszA = smartcard_convert_string_list(ret->msz, ret->cBytes, unicode); + + WLog_LVL(TAG, g_LogLevel, "ListReaderGroups%s_Return {", unicode ? "W" : "A"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIx32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " msz: %s", ret->cBytes, mszA); + WLog_LVL(TAG, g_LogLevel, "}"); + free(mszA); +} + +static void smartcard_trace_list_readers_call(SMARTCARD_DEVICE* smartcard, + const ListReaders_Call* call, BOOL unicode) +{ + char* mszGroupsA = NULL; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + mszGroupsA = smartcard_convert_string_list(call->mszGroups, call->cBytes, unicode); + + WLog_LVL(TAG, g_LogLevel, "ListReaders%s_Call {", unicode ? "W" : "A"); + smartcard_log_context(TAG, &call->handles.hContext); + + WLog_LVL(TAG, g_LogLevel, + "cBytes: %" PRIu32 " mszGroups: %s fmszReadersIsNULL: %" PRId32 + " cchReaders: 0x%08" PRIX32 "", + call->cBytes, mszGroupsA, call->fmszReadersIsNULL, call->cchReaders); + WLog_LVL(TAG, g_LogLevel, "}"); + + free(mszGroupsA); +} + +static void smartcard_trace_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, + const LocateCardsByATRA_Call* call) +{ + UINT32 index; + char* szEventState; + char* szCurrentState; + + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "LocateCardsByATRA_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + + for (index = 0; index < call->cReaders; index++) + { + char buffer[1024]; + const LPSCARD_READERSTATEA readerState = &call->rgReaderStates[index]; + + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index, + readerState->szReader, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index, + szCurrentState, readerState->dwCurrentState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index, + szEventState, readerState->dwEventState); + + WLog_DBG( + TAG, "\t[%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, readerState->cbAtr, + smartcard_array_dump(readerState->rgbAtr, readerState->cbAtr, buffer, sizeof(buffer))); + + free(szCurrentState); + free(szEventState); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_locate_cards_a_call(SMARTCARD_DEVICE* smartcard, + const LocateCardsA_Call* call) +{ + char buffer[8192]; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "LocateCardsA_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + WLog_LVL(TAG, g_LogLevel, " cBytes=%" PRId32, call->cBytes); + WLog_LVL(TAG, g_LogLevel, " mszCards=%s", + smartcard_msz_dump_a(call->mszCards, call->cBytes, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->cReaders); + // WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->rgReaderStates); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_locate_cards_return(SMARTCARD_DEVICE* smartcard, + const LocateCards_Return* ret) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "LocateCards_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode == SCARD_S_SUCCESS) + { + WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, ret->cReaders); + } + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_reader_icon_return(SMARTCARD_DEVICE* smartcard, + const GetReaderIcon_Return* ret) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetReaderIcon_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode == SCARD_S_SUCCESS) + { + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRId32, ret->cbDataLen); + } + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_transmit_count_return(SMARTCARD_DEVICE* smartcard, + const GetTransmitCount_Return* ret) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + WLog_LVL(TAG, g_LogLevel, " cTransmitCount=%" PRIu32, ret->cTransmitCount); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_read_cache_return(SMARTCARD_DEVICE* smartcard, + const ReadCache_Return* ret) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ReadCache_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode == SCARD_S_SUCCESS) + { + char buffer[1024]; + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRId32, ret->cbDataLen); + WLog_LVL(TAG, g_LogLevel, " cbData: %s", + smartcard_array_dump(ret->pbData, ret->cbDataLen, buffer, sizeof(buffer))); + } + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_locate_cards_w_call(SMARTCARD_DEVICE* smartcard, + const LocateCardsW_Call* call) +{ + char buffer[8192]; + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "LocateCardsW_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + WLog_LVL(TAG, g_LogLevel, " cBytes=%" PRId32, call->cBytes); + WLog_LVL(TAG, g_LogLevel, " sz2=%s", + smartcard_msz_dump_w(call->mszCards, call->cBytes, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " cReaders=%" PRId32, call->cReaders); + // WLog_LVL(TAG, g_LogLevel, " sz2=%s", call->rgReaderStates); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_list_readers_return(SMARTCARD_DEVICE* smartcard, + const ListReaders_Return* ret, BOOL unicode) +{ + char* mszA = NULL; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ListReaders%s_Return {", unicode ? "W" : "A"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode != SCARD_S_SUCCESS) + { + WLog_LVL(TAG, g_LogLevel, "}"); + return; + } + + mszA = smartcard_convert_string_list(ret->msz, ret->cBytes, unicode); + + WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " msz: %s", ret->cBytes, mszA); + WLog_LVL(TAG, g_LogLevel, "}"); + free(mszA); +} + +static void smartcard_trace_get_status_change_return(SMARTCARD_DEVICE* smartcard, + const GetStatusChange_Return* ret, + BOOL unicode) +{ + UINT32 index; + char* szEventState; + char* szCurrentState; + + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetStatusChange%s_Return {", unicode ? "W" : "A"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " cReaders: %" PRIu32 "", ret->cReaders); + + for (index = 0; index < ret->cReaders; index++) + { + char buffer[1024]; + const ReaderState_Return* rgReaderState = &(ret->rgReaderStates[index]); + szCurrentState = SCardGetReaderStateString(rgReaderState->dwCurrentState); + szEventState = SCardGetReaderStateString(rgReaderState->dwEventState); + WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index, + szCurrentState, rgReaderState->dwCurrentState); + WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index, + szEventState, rgReaderState->dwEventState); + WLog_LVL(TAG, g_LogLevel, " [%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, + rgReaderState->cbAtr, + smartcard_array_dump(rgReaderState->rgbAtr, rgReaderState->cbAtr, buffer, + sizeof(buffer))); + free(szCurrentState); + free(szEventState); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_context_and_two_strings_a_call(SMARTCARD_DEVICE* smartcard, + const ContextAndTwoStringA_Call* call) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ContextAndTwoStringW_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + WLog_LVL(TAG, g_LogLevel, " sz1=%s", call->sz1); + WLog_LVL(TAG, g_LogLevel, " sz2=%s", call->sz2); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_context_and_two_strings_w_call(SMARTCARD_DEVICE* smartcard, + const ContextAndTwoStringW_Call* call) +{ + CHAR* sz1 = NULL; + CHAR* sz2 = NULL; + + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ContextAndTwoStringW_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + ConvertFromUnicode(CP_UTF8, 0, call->sz1, -1, &sz1, 0, NULL, NULL); + ConvertFromUnicode(CP_UTF8, 0, call->sz2, -1, &sz2, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, " sz1=%s", sz1); + WLog_LVL(TAG, g_LogLevel, " sz2=%s", sz2); + free(sz1); + free(sz2); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_transmit_count_call(SMARTCARD_DEVICE* smartcard, + const GetTransmitCount_Call* call) +{ + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_write_cache_a_call(SMARTCARD_DEVICE* smartcard, + const WriteCacheA_Call* call) +{ + char buffer[1024]; + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {"); + + WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", call->szLookupName); + + smartcard_log_context(TAG, &call->Common.handles.hContext); + WLog_DBG( + TAG, "..CardIdentifier=%s", + smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter); + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen); + WLog_DBG( + TAG, " pbData=%s", + smartcard_array_dump(call->Common.pbData, call->Common.cbDataLen, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_write_cache_w_call(SMARTCARD_DEVICE* smartcard, + const WriteCacheW_Call* call) +{ + char* tmp = NULL; + char buffer[1024]; + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {"); + + ConvertFromUnicode(CP_UTF8, 0, call->szLookupName, -1, &tmp, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", tmp); + free(tmp); + smartcard_log_context(TAG, &call->Common.handles.hContext); + WLog_DBG( + TAG, "..CardIdentifier=%s", + smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter); + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen); + WLog_DBG( + TAG, " pbData=%s", + smartcard_array_dump(call->Common.pbData, call->Common.cbDataLen, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_read_cache_a_call(SMARTCARD_DEVICE* smartcard, + const ReadCacheA_Call* call) +{ + char buffer[1024]; + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {"); + + WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", call->szLookupName); + smartcard_log_context(TAG, &call->Common.handles.hContext); + WLog_DBG( + TAG, "..CardIdentifier=%s", + smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter); + WLog_LVL(TAG, g_LogLevel, " fPbDataIsNULL=%" PRId32, call->Common.fPbDataIsNULL); + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_read_cache_w_call(SMARTCARD_DEVICE* smartcard, + const ReadCacheW_Call* call) +{ + char* tmp = NULL; + char buffer[1024]; + WINPR_UNUSED(smartcard); + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetTransmitCount_Call {"); + + ConvertFromUnicode(CP_UTF8, 0, call->szLookupName, -1, &tmp, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, " szLookupName=%s", tmp); + free(tmp); + smartcard_log_context(TAG, &call->Common.handles.hContext); + WLog_DBG( + TAG, "..CardIdentifier=%s", + smartcard_array_dump(call->Common.CardIdentifier, sizeof(UUID), buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, " FreshnessCounter=%" PRIu32, call->Common.FreshnessCounter); + WLog_LVL(TAG, g_LogLevel, " fPbDataIsNULL=%" PRId32, call->Common.fPbDataIsNULL); + WLog_LVL(TAG, g_LogLevel, " cbDataLen=%" PRIu32, call->Common.cbDataLen); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_transmit_call(SMARTCARD_DEVICE* smartcard, const Transmit_Call* call) +{ + UINT32 cbExtraBytes; + BYTE* pbExtraBytes; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Transmit_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + if (call->pioSendPci) + { + cbExtraBytes = (UINT32)(call->pioSendPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*)call->pioSendPci)[sizeof(SCARD_IO_REQUEST)]; + WLog_LVL(TAG, g_LogLevel, "pioSendPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "", + call->pioSendPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char buffer[1024]; + WLog_LVL(TAG, g_LogLevel, "pbExtraBytes: %s", + smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer))); + } + } + else + { + WLog_LVL(TAG, g_LogLevel, "pioSendPci: null"); + } + + WLog_LVL(TAG, g_LogLevel, "cbSendLength: %" PRIu32 "", call->cbSendLength); + + if (call->pbSendBuffer) + { + char buffer[1024]; + WLog_DBG( + TAG, "pbSendBuffer: %s", + smartcard_array_dump(call->pbSendBuffer, call->cbSendLength, buffer, sizeof(buffer))); + } + else + { + WLog_LVL(TAG, g_LogLevel, "pbSendBuffer: null"); + } + + if (call->pioRecvPci) + { + cbExtraBytes = (UINT32)(call->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + pbExtraBytes = &((BYTE*)call->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + WLog_LVL(TAG, g_LogLevel, "pioRecvPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "", + call->pioRecvPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char buffer[1024]; + WLog_LVL(TAG, g_LogLevel, "pbExtraBytes: %s", + smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer))); + } + } + else + { + WLog_LVL(TAG, g_LogLevel, "pioRecvPci: null"); + } + + WLog_LVL(TAG, g_LogLevel, "fpbRecvBufferIsNULL: %" PRId32 " cbRecvLength: %" PRIu32 "", + call->fpbRecvBufferIsNULL, call->cbRecvLength); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_locate_cards_by_atr_w_call(SMARTCARD_DEVICE* smartcard, + const LocateCardsByATRW_Call* call) +{ + UINT32 index; + char* szEventState; + char* szCurrentState; + + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "LocateCardsByATRW_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + + for (index = 0; index < call->cReaders; index++) + { + char buffer[1024]; + char* tmp = NULL; + const LPSCARD_READERSTATEW readerState = + (const LPSCARD_READERSTATEW)&call->rgReaderStates[index]; + + ConvertFromUnicode(CP_UTF8, 0, readerState->szReader, -1, &tmp, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index, tmp, + readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index, + szCurrentState, readerState->dwCurrentState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index, + szEventState, readerState->dwEventState); + + WLog_DBG( + TAG, "\t[%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, readerState->cbAtr, + smartcard_array_dump(readerState->rgbAtr, readerState->cbAtr, buffer, sizeof(buffer))); + + free(szCurrentState); + free(szEventState); + free(tmp); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_transmit_return(SMARTCARD_DEVICE* smartcard, const Transmit_Return* ret) +{ + UINT32 cbExtraBytes; + BYTE* pbExtraBytes; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Transmit_Return {"); + WLog_LVL(TAG, g_LogLevel, " 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_LVL(TAG, g_LogLevel, " pioRecvPci: dwProtocol: %" PRIu32 " cbExtraBytes: %" PRIu32 "", + ret->pioRecvPci->dwProtocol, cbExtraBytes); + + if (cbExtraBytes) + { + char buffer[1024]; + WLog_LVL(TAG, g_LogLevel, " pbExtraBytes: %s", + smartcard_array_dump(pbExtraBytes, cbExtraBytes, buffer, sizeof(buffer))); + } + } + else + { + WLog_LVL(TAG, g_LogLevel, " pioRecvPci: null"); + } + + WLog_LVL(TAG, g_LogLevel, " cbRecvLength: %" PRIu32 "", ret->cbRecvLength); + + if (ret->pbRecvBuffer) + { + char buffer[1024]; + WLog_DBG( + TAG, " pbRecvBuffer: %s", + smartcard_array_dump(ret->pbRecvBuffer, ret->cbRecvLength, buffer, sizeof(buffer))); + } + else + { + WLog_LVL(TAG, g_LogLevel, " pbRecvBuffer: null"); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_control_return(SMARTCARD_DEVICE* smartcard, const Control_Return* ret) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Control_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " cbOutBufferSize: %" PRIu32 "", ret->cbOutBufferSize); + + if (ret->pvOutBuffer) + { + char buffer[1024]; + WLog_DBG( + TAG, "pvOutBuffer: %s", + smartcard_array_dump(ret->pvOutBuffer, ret->cbOutBufferSize, buffer, sizeof(buffer))); + } + else + { + WLog_LVL(TAG, g_LogLevel, "pvOutBuffer: null"); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_control_call(SMARTCARD_DEVICE* smartcard, const Control_Call* call) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Control_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, + "dwControlCode: 0x%08" PRIX32 " cbInBufferSize: %" PRIu32 + " fpvOutBufferIsNULL: %" PRId32 " cbOutBufferSize: %" PRIu32 "", + call->dwControlCode, call->cbInBufferSize, call->fpvOutBufferIsNULL, + call->cbOutBufferSize); + + if (call->pvInBuffer) + { + char buffer[1024]; + WLog_DBG( + TAG, "pbInBuffer: %s", + smartcard_array_dump(call->pvInBuffer, call->cbInBufferSize, buffer, sizeof(buffer))); + } + else + { + WLog_LVL(TAG, g_LogLevel, "pvInBuffer: null"); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_set_attrib_call(SMARTCARD_DEVICE* smartcard, const SetAttrib_Call* call) +{ + char buffer[8192]; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetAttrib_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + WLog_LVL(TAG, g_LogLevel, "dwAttrId: 0x%08" PRIX32, call->dwAttrId); + WLog_LVL(TAG, g_LogLevel, "cbAttrLen: 0x%08" PRId32, call->cbAttrLen); + WLog_LVL(TAG, g_LogLevel, "pbAttr: %s", + smartcard_array_dump(call->pbAttr, call->cbAttrLen, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_attrib_return(SMARTCARD_DEVICE* smartcard, + const GetAttrib_Return* ret, DWORD dwAttrId) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetAttrib_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " dwAttrId: %s (0x%08" PRIX32 ") cbAttrLen: 0x%08" PRIX32 "", + SCardGetAttributeString(dwAttrId), dwAttrId, ret->cbAttrLen); + + if (dwAttrId == SCARD_ATTR_VENDOR_NAME) + { + WLog_LVL(TAG, g_LogLevel, " pbAttr: %.*s", ret->cbAttrLen, (char*)ret->pbAttr); + } + else if (dwAttrId == SCARD_ATTR_CURRENT_PROTOCOL_TYPE) + { + union + { + BYTE* pb; + DWORD* pd; + } attr; + attr.pb = ret->pbAttr; + WLog_LVL(TAG, g_LogLevel, " dwProtocolType: %s (0x%08" PRIX32 ")", + SCardGetProtocolString(*attr.pd), *attr.pd); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_get_attrib_call(SMARTCARD_DEVICE* smartcard, const GetAttrib_Call* call) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetAttrib_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, + "dwAttrId: %s (0x%08" PRIX32 ") fpbAttrIsNULL: %" PRId32 " cbAttrLen: 0x%08" PRIX32 "", + SCardGetAttributeString(call->dwAttrId), call->dwAttrId, call->fpbAttrIsNULL, + call->cbAttrLen); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_status_call(SMARTCARD_DEVICE* smartcard, const Status_Call* call, + BOOL unicode) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Status%s_Call {", unicode ? "W" : "A"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, + "fmszReaderNamesIsNULL: %" PRId32 " cchReaderLen: %" PRIu32 " cbAtrLen: %" PRIu32 "", + call->fmszReaderNamesIsNULL, call->cchReaderLen, call->cbAtrLen); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_status_return(SMARTCARD_DEVICE* smartcard, const Status_Return* ret, + BOOL unicode) +{ + char* mszReaderNamesA = NULL; + char buffer[1024]; + DWORD cBytes; + + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + cBytes = ret->cBytes; + if (ret->ReturnCode != SCARD_S_SUCCESS) + cBytes = 0; + if (cBytes == SCARD_AUTOALLOCATE) + cBytes = 0; + mszReaderNamesA = smartcard_convert_string_list(ret->mszReaderNames, cBytes, unicode); + + WLog_LVL(TAG, g_LogLevel, "Status%s_Return {", unicode ? "W" : "A"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " dwState: %s (0x%08" PRIX32 ") dwProtocol: %s (0x%08" PRIX32 ")", + SCardGetCardStateString(ret->dwState), ret->dwState, + SCardGetProtocolString(ret->dwProtocol), ret->dwProtocol); + + WLog_LVL(TAG, g_LogLevel, " cBytes: %" PRIu32 " mszReaderNames: %s", ret->cBytes, + mszReaderNamesA); + + WLog_LVL(TAG, g_LogLevel, " cbAtrLen: %" PRIu32 " pbAtr: %s", ret->cbAtrLen, + smartcard_array_dump(ret->pbAtr, ret->cbAtrLen, buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, "}"); + free(mszReaderNamesA); +} + +static void smartcard_trace_state_return(SMARTCARD_DEVICE* smartcard, const State_Return* ret) +{ + char buffer[1024]; + char* state; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + state = SCardGetReaderStateString(ret->dwState); + WLog_LVL(TAG, g_LogLevel, "Reconnect_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " dwState: %s (0x%08" PRIX32 ")", state, ret->dwState); + WLog_LVL(TAG, g_LogLevel, " dwProtocol: %s (0x%08" PRIX32 ")", + SCardGetProtocolString(ret->dwProtocol), ret->dwProtocol); + WLog_LVL(TAG, g_LogLevel, " cbAtrLen: (0x%08" PRIX32 ")", ret->cbAtrLen); + WLog_LVL(TAG, g_LogLevel, " rgAtr: %s", + smartcard_array_dump(ret->rgAtr, sizeof(ret->rgAtr), buffer, sizeof(buffer))); + WLog_LVL(TAG, g_LogLevel, "}"); + free(state); +} + +static void smartcard_trace_reconnect_return(SMARTCARD_DEVICE* smartcard, + const Reconnect_Return* ret) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Reconnect_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " dwActiveProtocol: %s (0x%08" PRIX32 ")", + SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_connect_a_call(SMARTCARD_DEVICE* smartcard, const ConnectA_Call* call) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "ConnectA_Call {"); + smartcard_log_context(TAG, &call->Common.handles.hContext); + + WLog_LVL(TAG, g_LogLevel, + "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_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_connect_w_call(SMARTCARD_DEVICE* smartcard, const ConnectW_Call* call) +{ + char* szReaderA = NULL; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + ConvertFromUnicode(CP_UTF8, 0, call->szReader, -1, &szReaderA, 0, NULL, NULL); + WLog_LVL(TAG, g_LogLevel, "ConnectW_Call {"); + smartcard_log_context(TAG, &call->Common.handles.hContext); + + WLog_LVL(TAG, g_LogLevel, + "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_LVL(TAG, g_LogLevel, "}"); + free(szReaderA); +} + +static void smartcard_trace_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, + const HCardAndDisposition_Call* call, + const char* name) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "%s_Call {", name); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, "dwDisposition: %s (0x%08" PRIX32 ")", + SCardGetDispositionString(call->dwDisposition), call->dwDisposition); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_establish_context_call(SMARTCARD_DEVICE* smartcard, + const EstablishContext_Call* call) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "EstablishContext_Call {"); + WLog_LVL(TAG, g_LogLevel, "dwScope: %s (0x%08" PRIX32 ")", SCardGetScopeString(call->dwScope), + call->dwScope); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_establish_context_return(SMARTCARD_DEVICE* smartcard, + const EstablishContext_Return* ret) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "EstablishContext_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + smartcard_log_context(TAG, &ret->hContext); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +void smartcard_trace_long_return(SMARTCARD_DEVICE* smartcard, const Long_Return* ret, + const char* name) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "%s_Return {", name); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_connect_return(SMARTCARD_DEVICE* smartcard, const Connect_Return* ret) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Connect_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + smartcard_log_context(TAG, &ret->hContext); + smartcard_log_redir_handle(TAG, &ret->hCard); + + WLog_LVL(TAG, g_LogLevel, " dwActiveProtocol: %s (0x%08" PRIX32 ")", + SCardGetProtocolString(ret->dwActiveProtocol), ret->dwActiveProtocol); + WLog_LVL(TAG, g_LogLevel, "}"); +} + +void smartcard_trace_reconnect_call(SMARTCARD_DEVICE* smartcard, const Reconnect_Call* call) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "Reconnect_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + smartcard_log_redir_handle(TAG, &call->handles.hCard); + + WLog_LVL(TAG, g_LogLevel, + "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_LVL(TAG, g_LogLevel, "}"); +} + +static void smartcard_trace_device_type_id_return(SMARTCARD_DEVICE* smartcard, + const GetDeviceTypeId_Return* ret) +{ + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetDeviceTypeId_Return {"); + WLog_LVL(TAG, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_LVL(TAG, g_LogLevel, " dwDeviceId=%08" PRIx32, ret->dwDeviceId); + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static LONG smartcard_unpack_common_context_and_string_a(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* phContext, + CHAR** pszReaderName) +{ + LONG status; + UINT32 index = 0; + status = smartcard_unpack_redir_scard_context(smartcard, s, phContext, &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, NULL)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, phContext); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_ndr_read_a(s, pszReaderName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_context_and_string_call_a(__FUNCTION__, phContext, *pszReaderName); + return SCARD_S_SUCCESS; +} + +static LONG smartcard_unpack_common_context_and_string_w(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* phContext, + WCHAR** pszReaderName) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, phContext, &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, NULL)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, phContext); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_ndr_read_w(s, pszReaderName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_context_and_string_call_w(__FUNCTION__, phContext, *pszReaderName); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_common_type_header(SMARTCARD_DEVICE* smartcard, wStream* s) +{ + UINT8 version; + UINT32 filler; + UINT8 endianness; + UINT16 commonHeaderLength; + WINPR_UNUSED(smartcard); + + 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) +{ + WINPR_UNUSED(smartcard); + 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; + WINPR_UNUSED(smartcard); + + 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) +{ + WINPR_UNUSED(smartcard); + 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, size_t size, + UINT32 alignment) +{ + size_t pad; + WINPR_UNUSED(smartcard); + pad = size; + size = (size + alignment - 1) & ~(alignment - 1); + pad = size - pad; + + if (pad) + Stream_Seek(s, pad); + + return (LONG)pad; +} + +LONG smartcard_pack_write_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, size_t size, + UINT32 alignment) +{ + size_t pad; + WINPR_UNUSED(smartcard); + 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 }; + WINPR_UNUSED(smartcard); + + 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); + + return hContext; +} + +void smartcard_scard_context_native_to_redir(SMARTCARD_DEVICE* smartcard, + REDIR_SCARDCONTEXT* context, SCARDCONTEXT hContext) +{ + WINPR_UNUSED(smartcard); + 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; + WINPR_UNUSED(smartcard); + + if (handle->cbHandle == 0) + return hCard; + + 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) +{ + WINPR_UNUSED(smartcard); + 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* index, + const char* file, const char* function, int line) +{ + UINT32 pbContextNdrPtr; + WINPR_UNUSED(smartcard); + WINPR_UNUSED(file); + + 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; + } + + if (!smartcard_ndr_pointer_read_(s, index, &pbContextNdrPtr, file, function, line)) + return ERROR_INVALID_DATA; + + 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, + const REDIR_SCARDCONTEXT* context, DWORD* index) +{ + const UINT32 pbContextNdrPtr = 0x00020000 + *index * 4; + WINPR_UNUSED(smartcard); + + if (context->cbContext != 0) + { + Stream_Write_UINT32(s, context->cbContext); /* cbContext (4 bytes) */ + Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */ + *index = *index + 1; + } + else + Stream_Zero(s, 8); + + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_context_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDCONTEXT* context) +{ + UINT32 length; + WINPR_UNUSED(smartcard); + + 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, + const REDIR_SCARDCONTEXT* context) +{ + WINPR_UNUSED(smartcard); + 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* index, + const char* file, const char* function, int line) +{ + WINPR_UNUSED(smartcard); + 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; + } + + if (!smartcard_ndr_pointer_read_(s, index, NULL, file, function, line)) + return ERROR_INVALID_DATA; + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_handle(SMARTCARD_DEVICE* smartcard, wStream* s, + const REDIR_SCARDHANDLE* handle, DWORD* index) +{ + const UINT32 pbContextNdrPtr = 0x00020000 + *index * 4; + WINPR_UNUSED(smartcard); + + if (handle->cbHandle != 0) + { + Stream_Write_UINT32(s, handle->cbHandle); /* cbContext (4 bytes) */ + Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */ + *index = *index + 1; + } + else + Stream_Zero(s, 8); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_handle_ref(SMARTCARD_DEVICE* smartcard, wStream* s, + REDIR_SCARDHANDLE* handle) +{ + UINT32 length; + WINPR_UNUSED(smartcard); + + 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, + const REDIR_SCARDHANDLE* handle) +{ + WINPR_UNUSED(smartcard); + 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) +{ + WINPR_UNUSED(smartcard); + + 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) */ + smartcard_trace_establish_context_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_establish_context_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const EstablishContext_Return* ret) +{ + LONG status; + DWORD index = 0; + + smartcard_trace_establish_context_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + return ret->ReturnCode; + + if ((status = smartcard_pack_redir_scard_context(smartcard, s, &(ret->hContext), &index))) + return status; + + return smartcard_pack_redir_scard_context_ref(smartcard, s, &(ret->hContext)); +} + +LONG smartcard_unpack_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, Context_Call* call, + const char* name) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "", + status); + + smartcard_trace_context_call(smartcard, call, name); + return status; +} + +LONG smartcard_unpack_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Call* call, BOOL unicode) +{ + LONG status; + UINT32 index = 0; + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "ListReaderGroups_Call is too short: %" PRIdz, Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_INT32(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->handles.hContext)); + + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_list_reader_groups_call(smartcard, call, unicode); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ListReaderGroups_Return* ret, BOOL unicode) +{ + LONG status; + DWORD cBytes = ret->cBytes; + DWORD index = 0; + + smartcard_trace_list_reader_groups_return(smartcard, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cBytes = 0; + if (cBytes == SCARD_AUTOALLOCATE) + cBytes = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + + Stream_Write_UINT32(s, cBytes); /* cBytes (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cBytes)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->msz, cBytes, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_list_readers_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Call* call, BOOL unicode) +{ + LONG status; + UINT32 index = 0; + UINT32 mszGroupsNdrPtr; + call->mszGroups = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, &mszGroupsNdrPtr)) + return ERROR_INVALID_DATA; + Stream_Read_INT32(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->handles.hContext)))) + return status; + + if (mszGroupsNdrPtr) + { + status = smartcard_ndr_read(s, &call->mszGroups, call->cBytes, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_list_readers_call(smartcard, call, unicode); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_list_readers_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ListReaders_Return* ret, BOOL unicode) +{ + LONG status; + DWORD index = 0; + UINT32 size = unicode ? sizeof(WCHAR) : sizeof(CHAR); + + size *= ret->cBytes; + + smartcard_trace_list_readers_return(smartcard, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) + size = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, size); /* cBytes (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, size)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->msz, size, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +static LONG smartcard_unpack_connect_common(SMARTCARD_DEVICE* smartcard, wStream* s, + Connect_Common_Call* common, UINT32* index) +{ + LONG status; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(common->handles.hContext), index); + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 8) + { + WLog_WARN(TAG, "Connect_Common is too short: %" PRIuz "", Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + 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 index = 0; + call->szReader = NULL; + + if (!smartcard_ndr_pointer_read(s, &index, NULL)) + return ERROR_INVALID_DATA; + + if ((status = smartcard_unpack_connect_common(smartcard, s, &(call->Common), &index))) + { + WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %" PRId32 "", status); + return status; + } + + status = smartcard_ndr_read_a(s, &call->szReader, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, + &(call->Common.handles.hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "", + status); + + smartcard_trace_connect_a_call(smartcard, call); + return status; +} + +LONG smartcard_unpack_connect_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectW_Call* call) +{ + LONG status; + UINT32 index = 0; + + call->szReader = NULL; + + if (!smartcard_ndr_pointer_read(s, &index, NULL)) + return ERROR_INVALID_DATA; + + if ((status = smartcard_unpack_connect_common(smartcard, s, &(call->Common), &index))) + { + WLog_ERR(TAG, "smartcard_unpack_connect_common failed with error %" PRId32 "", status); + return status; + } + + status = smartcard_ndr_read_w(s, &call->szReader, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + + if ((status = smartcard_unpack_redir_scard_context_ref(smartcard, s, + &(call->Common.handles.hContext)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "", + status); + + smartcard_trace_connect_w_call(smartcard, call); + return status; +} + +LONG smartcard_pack_connect_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Connect_Return* ret) +{ + LONG status; + DWORD index = 0; + + smartcard_trace_connect_return(smartcard, ret); + + status = smartcard_pack_redir_scard_context(smartcard, s, &ret->hContext, &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_pack_redir_scard_handle(smartcard, s, &ret->hCard, &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + + Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */ + status = smartcard_pack_redir_scard_context_ref(smartcard, s, &ret->hContext); + if (status != SCARD_S_SUCCESS) + return status; + return smartcard_pack_redir_scard_handle_ref(smartcard, s, &(ret->hCard)); +} + +LONG smartcard_unpack_reconnect_call(SMARTCARD_DEVICE* smartcard, wStream* s, Reconnect_Call* call) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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->handles.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->handles.hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %" PRId32 "", + status); + + smartcard_trace_reconnect_call(smartcard, call); + return status; +} + +LONG smartcard_pack_reconnect_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Reconnect_Return* ret) +{ + smartcard_trace_reconnect_return(smartcard, ret); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + Stream_Write_UINT32(s, ret->dwActiveProtocol); /* dwActiveProtocol (4 bytes) */ + return ret->ReturnCode; +} + +LONG smartcard_unpack_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, wStream* s, + HCardAndDisposition_Call* call, const char* name) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + smartcard_trace_hcard_and_disposition_call(smartcard, call, name); + return status; +} + +static void smartcard_trace_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, + const GetStatusChangeA_Call* call) +{ + UINT32 index; + char* szEventState; + char* szCurrentState; + LPSCARD_READERSTATEA readerState; + WINPR_UNUSED(smartcard); + + if (!WLog_IsLevelActive(WLog_Get(TAG), g_LogLevel)) + return; + + WLog_LVL(TAG, g_LogLevel, "GetStatusChangeA_Call {"); + smartcard_log_context(TAG, &call->handles.hContext); + + WLog_LVL(TAG, g_LogLevel, "dwTimeOut: 0x%08" PRIX32 " cReaders: %" PRIu32 "", call->dwTimeOut, + call->cReaders); + + for (index = 0; index < call->cReaders; index++) + { + readerState = &call->rgReaderStates[index]; + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: szReader: %s cbAtr: %" PRIu32 "", index, + readerState->szReader, readerState->cbAtr); + szCurrentState = SCardGetReaderStateString(readerState->dwCurrentState); + szEventState = SCardGetReaderStateString(readerState->dwEventState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", index, + szCurrentState, readerState->dwCurrentState); + WLog_LVL(TAG, g_LogLevel, "\t[%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", index, + szEventState, readerState->dwEventState); + free(szCurrentState); + free(szEventState); + } + + WLog_LVL(TAG, g_LogLevel, "}"); +} + +static LONG smartcard_unpack_reader_state_a(wStream* s, LPSCARD_READERSTATEA* ppcReaders, + UINT32 cReaders, UINT32* ptrIndex) +{ + UINT32 index, len; + LONG status = SCARD_E_NO_MEMORY; + LPSCARD_READERSTATEA rgReaderStates; + BOOL* states; + + if (Stream_GetRemainingLength(s) < 4) + return status; + + Stream_Read_UINT32(s, len); + if (len != cReaders) + { + WLog_ERR(TAG, "Count mismatch when reading LPSCARD_READERSTATEA"); + return status; + } + rgReaderStates = (LPSCARD_READERSTATEA)calloc(cReaders, sizeof(SCARD_READERSTATEA)); + states = calloc(cReaders, sizeof(BOOL)); + if (!rgReaderStates || !states) + goto fail; + status = ERROR_INVALID_DATA; + + for (index = 0; index < cReaders; index++) + { + UINT32 ptr = UINT32_MAX; + LPSCARD_READERSTATEA readerState = &rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 52) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %" PRIuz "", + Stream_GetRemainingLength(s)); + goto fail; + } + + if (!smartcard_ndr_pointer_read(s, ptrIndex, &ptr)) + { + if (ptr != 0) + goto fail; + } + /* Ignore NULL length strings */ + states[index] = ptr != 0; + 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, 36); /* rgbAtr [0..36] (36 bytes) */ + } + + for (index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEA readerState = &rgReaderStates[index]; + + /* Ignore empty strings */ + if (!states[index]) + continue; + status = smartcard_ndr_read_a(s, &readerState->szReader, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + goto fail; + } + + *ppcReaders = rgReaderStates; + free(states); + return SCARD_S_SUCCESS; +fail: + if (rgReaderStates) + { + for (index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEA readerState = &rgReaderStates[index]; + free(readerState->szReader); + } + } + free(rgReaderStates); + free(states); + return status; +} + +static LONG smartcard_unpack_reader_state_w(wStream* s, LPSCARD_READERSTATEW* ppcReaders, + UINT32 cReaders, UINT32* ptrIndex) +{ + UINT32 index, len; + LONG status = SCARD_E_NO_MEMORY; + LPSCARD_READERSTATEW rgReaderStates; + BOOL* states; + + if (Stream_GetRemainingLength(s) < 4) + return status; + + Stream_Read_UINT32(s, len); + if (len != cReaders) + { + WLog_ERR(TAG, "Count mismatch when reading LPSCARD_READERSTATEW"); + return status; + } + + rgReaderStates = (LPSCARD_READERSTATEW)calloc(cReaders, sizeof(SCARD_READERSTATEW)); + states = calloc(cReaders, sizeof(BOOL)); + + if (!rgReaderStates || !states) + goto fail; + + status = ERROR_INVALID_DATA; + for (index = 0; index < cReaders; index++) + { + UINT32 ptr = UINT32_MAX; + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + + if (Stream_GetRemainingLength(s) < 52) + { + WLog_WARN(TAG, "GetStatusChangeA_Call is too short: %" PRIuz "", + Stream_GetRemainingLength(s)); + goto fail; + } + + if (!smartcard_ndr_pointer_read(s, ptrIndex, &ptr)) + { + if (ptr != 0) + goto fail; + } + /* Ignore NULL length strings */ + states[index] = ptr != 0; + 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, 36); /* rgbAtr [0..36] (36 bytes) */ + } + + for (index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + + /* Skip NULL pointers */ + if (!states[index]) + continue; + + status = smartcard_ndr_read_w(s, &readerState->szReader, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + goto fail; + } + + *ppcReaders = rgReaderStates; + free(states); + return SCARD_S_SUCCESS; +fail: + if (rgReaderStates) + { + for (index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + free(readerState->szReader); + } + } + free(rgReaderStates); + free(states); + return status; +} + +/******************************************************************************/ +/************************************* End Trace Functions ********************/ +/******************************************************************************/ + +LONG smartcard_unpack_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeA_Call* call) +{ + LONG status; + UINT32 ndrPtr; + UINT32 index = 0; + call->rgReaderStates = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr)) + return ERROR_INVALID_DATA; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + return status; + + if (ndrPtr) + { + status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_get_status_change_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeW_Call* call) +{ + UINT32 ndrPtr; + LONG status; + UINT32 index = 0; + + call->rgReaderStates = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr)) + return ERROR_INVALID_DATA; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + return status; + + if (ndrPtr) + { + status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_get_status_change_w_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_get_status_change_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetStatusChange_Return* ret, BOOL unicode) +{ + LONG status; + DWORD cReaders = ret->cReaders; + UINT32 index = 0; + + smartcard_trace_get_status_change_return(smartcard, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cReaders = 0; + if (cReaders == SCARD_AUTOALLOCATE) + cReaders = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + + Stream_Write_UINT32(s, cReaders); /* cReaders (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cReaders)) + return SCARD_E_NO_MEMORY; + status = smartcard_ndr_write_state(s, ret->rgReaderStates, cReaders, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_state_call(SMARTCARD_DEVICE* smartcard, wStream* s, State_Call* call) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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_INT32(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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + return status; +} + +LONG smartcard_pack_state_return(SMARTCARD_DEVICE* smartcard, wStream* s, const State_Return* ret) +{ + LONG status; + DWORD cbAtrLen = ret->cbAtrLen; + DWORD index = 0; + + smartcard_trace_state_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbAtrLen = 0; + if (cbAtrLen == SCARD_AUTOALLOCATE) + cbAtrLen = 0; + + Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */ + Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Write_UINT32(s, cbAtrLen); /* cbAtrLen (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cbAtrLen)) + return SCARD_E_NO_MEMORY; + status = smartcard_ndr_write(s, ret->rgAtr, cbAtrLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_status_call(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Call* call, + BOOL unicode) +{ + LONG status; + UINT32 index = 0; + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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_INT32(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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + smartcard_trace_status_call(smartcard, call, unicode); + return status; +} + +LONG smartcard_pack_status_return(SMARTCARD_DEVICE* smartcard, wStream* s, const Status_Return* ret, + BOOL unicode) +{ + LONG status; + DWORD index = 0; + DWORD cBytes = ret->cBytes; + + smartcard_trace_status_return(smartcard, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cBytes = 0; + if (cBytes == SCARD_AUTOALLOCATE) + cBytes = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_F_INTERNAL_ERROR; + + Stream_Write_UINT32(s, cBytes); /* cBytes (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cBytes)) + return SCARD_E_NO_MEMORY; + + if (!Stream_EnsureRemainingCapacity(s, 44)) + return SCARD_F_INTERNAL_ERROR; + + Stream_Write_UINT32(s, ret->dwState); /* dwState (4 bytes) */ + Stream_Write_UINT32(s, ret->dwProtocol); /* dwProtocol (4 bytes) */ + Stream_Write(s, ret->pbAtr, sizeof(ret->pbAtr)); /* pbAtr (32 bytes) */ + Stream_Write_UINT32(s, ret->cbAtrLen); /* cbAtrLen (4 bytes) */ + status = smartcard_ndr_write(s, ret->mszReaderNames, cBytes, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_get_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, GetAttrib_Call* call) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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_INT32(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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + smartcard_trace_get_attrib_call(smartcard, call); + return status; +} + +LONG smartcard_pack_get_attrib_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetAttrib_Return* ret, DWORD dwAttrId, + DWORD cbAttrCallLen) +{ + LONG status; + DWORD cbAttrLen; + DWORD index = 0; + smartcard_trace_get_attrib_return(smartcard, ret, dwAttrId); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_F_INTERNAL_ERROR; + + cbAttrLen = ret->cbAttrLen; + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbAttrLen = 0; + if (cbAttrLen == SCARD_AUTOALLOCATE) + cbAttrLen = 0; + if (cbAttrCallLen < cbAttrLen) + cbAttrLen = cbAttrCallLen; + Stream_Write_UINT32(s, cbAttrLen); /* cbAttrLen (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cbAttrLen)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->pbAttr, cbAttrLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_control_call(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Call* call) +{ + LONG status; + UINT32 index = 0; + UINT32 pvInBufferNdrPtr; + + call->pvInBuffer = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, &pvInBufferNdrPtr)) /* pvInBufferNdrPtr (4 bytes) */ + return ERROR_INVALID_DATA; + Stream_Read_INT32(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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + if (pvInBufferNdrPtr) + { + status = smartcard_ndr_read(s, &call->pvInBuffer, call->cbInBufferSize, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_control_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_control_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Control_Return* ret) +{ + LONG status; + DWORD cbDataLen = ret->cbOutBufferSize; + DWORD index = 0; + + smartcard_trace_control_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbDataLen = 0; + if (cbDataLen == SCARD_AUTOALLOCATE) + cbDataLen = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_F_INTERNAL_ERROR; + + Stream_Write_UINT32(s, cbDataLen); /* cbOutBufferSize (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cbDataLen)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->pvOutBuffer, cbDataLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +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; + UINT32 index = 0; + call->pioSendPci = NULL; + call->pioRecvPci = NULL; + call->pbSendBuffer = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, + &pbExtraBytesNdrPtr)) /* pbExtraBytesNdrPtr (4 bytes) */ + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, call->cbSendLength); /* cbSendLength (4 bytes) */ + if (!smartcard_ndr_pointer_read(s, &index, + &pbSendBufferNdrPtr)) /* pbSendBufferNdrPtr (4 bytes) */ + return ERROR_INVALID_DATA; + + if (!smartcard_ndr_pointer_read(s, &index, &pioRecvPciNdrPtr)) /* pioRecvPciNdrPtr (4 bytes) */ + return ERROR_INVALID_DATA; + + Stream_Read_INT32(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->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + 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) + { + // TODO: Use unified pointer reading + 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) + { + status = smartcard_ndr_read(s, &call->pbSendBuffer, call->cbSendLength, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + + 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) */ + if (!smartcard_ndr_pointer_read(s, &index, + &pbExtraBytesNdrPtr)) /* pbExtraBytesNdrPtr (4 bytes) */ + return ERROR_INVALID_DATA; + + if (ioRecvPci.cbExtraBytes && !pbExtraBytesNdrPtr) + { + WLog_WARN( + TAG, + "Transmit_Call ioRecvPci.cbExtraBytes is non-zero but pbExtraBytesNdrPtr is null"); + return STATUS_INVALID_PARAMETER; + } + + if (pbExtraBytesNdrPtr) + { + // TODO: Unify ndr pointer reading + 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); + } + } + + smartcard_trace_transmit_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_transmit_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Transmit_Return* ret) +{ + LONG status; + DWORD index = 0; + LONG error; + UINT32 cbRecvLength = ret->cbRecvLength; + UINT32 cbRecvPci = ret->pioRecvPci ? ret->pioRecvPci->cbPciLength : 0; + + smartcard_trace_transmit_return(smartcard, ret); + + if (!ret->pbRecvBuffer) + cbRecvLength = 0; + + if (!smartcard_ndr_pointer_write(s, &index, cbRecvPci)) + return SCARD_E_NO_MEMORY; + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + Stream_Write_UINT32(s, cbRecvLength); /* cbRecvLength (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cbRecvLength)) + return SCARD_E_NO_MEMORY; + + if (ret->pioRecvPci) + { + UINT32 cbExtraBytes = (UINT32)(ret->pioRecvPci->cbPciLength - sizeof(SCARD_IO_REQUEST)); + BYTE* pbExtraBytes = &((BYTE*)ret->pioRecvPci)[sizeof(SCARD_IO_REQUEST)]; + + 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) */ + if (!smartcard_ndr_pointer_write(s, &index, cbExtraBytes)) + return SCARD_E_NO_MEMORY; + error = smartcard_ndr_write(s, pbExtraBytes, cbExtraBytes, 1, NDR_PTR_SIMPLE); + if (error) + return error; + } + + status = smartcard_ndr_write(s, ret->pbRecvBuffer, ret->cbRecvLength, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRA_Call* call) +{ + LONG status; + UINT32 rgReaderStatesNdrPtr; + UINT32 rgAtrMasksNdrPtr; + UINT32 index = 0; + call->rgReaderStates = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + 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); + if (!smartcard_ndr_pointer_read(s, &index, &rgAtrMasksNdrPtr)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + if (!smartcard_ndr_pointer_read(s, &index, &rgReaderStatesNdrPtr)) + return ERROR_INVALID_DATA; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + return status; + + 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) + { + status = smartcard_ndr_read_atrmask(s, &call->rgAtrMasks, call->cAtrs, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + + if (rgReaderStatesNdrPtr) + { + status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_locate_cards_by_atr_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_context_and_two_strings_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndTwoStringA_Call* call) +{ + LONG status; + UINT32 sz1NdrPtr, sz2NdrPtr; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr)) + return ERROR_INVALID_DATA; + if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + if (sz1NdrPtr) + { + status = smartcard_ndr_read_a(s, &call->sz1, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + if (sz2NdrPtr) + { + status = smartcard_ndr_read_a(s, &call->sz2, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_context_and_two_strings_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_context_and_two_strings_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndTwoStringW_Call* call) +{ + LONG status; + UINT32 sz1NdrPtr, sz2NdrPtr; + UINT32 index = 0; + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr)) + return ERROR_INVALID_DATA; + if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + if (sz1NdrPtr) + { + status = smartcard_ndr_read_w(s, &call->sz1, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + if (sz2NdrPtr) + { + status = smartcard_ndr_read_w(s, &call->sz2, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_context_and_two_strings_w_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_locate_cards_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsA_Call* call) +{ + LONG status; + UINT32 sz1NdrPtr, sz2NdrPtr; + UINT32 index = 0; + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_WARN(TAG, "%s is too short: %" PRIuz "", __FUNCTION__, Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + Stream_Read_UINT32(s, call->cBytes); + if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, call->cReaders); + if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr)) + return ERROR_INVALID_DATA; + + if (sz1NdrPtr) + { + status = + smartcard_ndr_read_fixed_string_a(s, &call->mszCards, call->cBytes, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + if (sz2NdrPtr) + { + status = smartcard_unpack_reader_state_a(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_locate_cards_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_locate_cards_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsW_Call* call) +{ + LONG status; + UINT32 sz1NdrPtr, sz2NdrPtr; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_WARN(TAG, "%s is too short: %" PRIuz "", __FUNCTION__, Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + Stream_Read_UINT32(s, call->cBytes); + if (!smartcard_ndr_pointer_read(s, &index, &sz1NdrPtr)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, call->cReaders); + if (!smartcard_ndr_pointer_read(s, &index, &sz2NdrPtr)) + return ERROR_INVALID_DATA; + + if (sz1NdrPtr) + { + status = + smartcard_ndr_read_fixed_string_w(s, &call->mszCards, call->cBytes, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + if (sz2NdrPtr) + { + status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_locate_cards_w_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_set_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, SetAttrib_Call* call) +{ + LONG status; + UINT32 index = 0; + UINT32 ndrPtr; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 12) + return STATUS_BUFFER_TOO_SMALL; + Stream_Read_UINT32(s, call->dwAttrId); + Stream_Read_UINT32(s, call->cbAttrLen); + + if (!smartcard_ndr_pointer_read(s, &index, &ndrPtr)) + return ERROR_INVALID_DATA; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + return status; + + if ((status = smartcard_unpack_redir_scard_handle_ref(smartcard, s, &(call->handles.hCard)))) + return status; + + if (ndrPtr) + { + // TODO: call->cbAttrLen was larger than the pointer value. + // TODO: Maybe need to refine the checks? + status = smartcard_ndr_read(s, &call->pbAttr, 0, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_set_attrib_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_locate_cards_by_atr_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRW_Call* call) +{ + LONG status; + UINT32 rgReaderStatesNdrPtr; + UINT32 rgAtrMasksNdrPtr; + UINT32 index = 0; + call->rgReaderStates = NULL; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_WARN(TAG, "LocateCardsByATRW_Call is too short: %" PRIuz "", + Stream_GetRemainingLength(s)); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT32(s, call->cAtrs); + if (!smartcard_ndr_pointer_read(s, &index, &rgAtrMasksNdrPtr)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + if (!smartcard_ndr_pointer_read(s, &index, &rgReaderStatesNdrPtr)) + return ERROR_INVALID_DATA; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.hContext)))) + return status; + + if ((rgAtrMasksNdrPtr && !call->cAtrs) || (!rgAtrMasksNdrPtr && call->cAtrs)) + { + WLog_WARN(TAG, + "LocateCardsByATRW_Call rgAtrMasksNdrPtr (0x%08" PRIX32 + ") and cAtrs (0x%08" PRIX32 ") inconsistency", + rgAtrMasksNdrPtr, call->cAtrs); + return STATUS_INVALID_PARAMETER; + } + + if (rgAtrMasksNdrPtr) + { + status = smartcard_ndr_read_atrmask(s, &call->rgAtrMasks, call->cAtrs, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + + if (rgReaderStatesNdrPtr) + { + status = smartcard_unpack_reader_state_w(s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + + smartcard_trace_locate_cards_by_atr_w_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_read_cache_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ReadCacheA_Call* call) +{ + LONG status; + UINT32 mszNdrPtr; + UINT32 contextNdrPtr; + UINT32 index = 0; + + if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->Common.handles.hContext), + &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr)) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 12) + return STATUS_BUFFER_TOO_SMALL; + Stream_Read_UINT32(s, call->Common.FreshnessCounter); + Stream_Read_INT32(s, call->Common.fPbDataIsNULL); + Stream_Read_UINT32(s, call->Common.cbDataLen); + + call->szLookupName = NULL; + if (mszNdrPtr) + { + status = smartcard_ndr_read_a(s, &call->szLookupName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->Common.handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + if (contextNdrPtr) + { + status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_read_cache_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_read_cache_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ReadCacheW_Call* call) +{ + LONG status; + UINT32 mszNdrPtr; + UINT32 contextNdrPtr; + UINT32 index = 0; + + if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->Common.handles.hContext), + &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr)) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 12) + return STATUS_BUFFER_TOO_SMALL; + Stream_Read_UINT32(s, call->Common.FreshnessCounter); + Stream_Read_INT32(s, call->Common.fPbDataIsNULL); + Stream_Read_UINT32(s, call->Common.cbDataLen); + + call->szLookupName = NULL; + if (mszNdrPtr) + { + status = smartcard_ndr_read_w(s, &call->szLookupName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->Common.handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + if (contextNdrPtr) + { + status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_read_cache_w_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_write_cache_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + WriteCacheA_Call* call) +{ + LONG status; + UINT32 mszNdrPtr; + UINT32 contextNdrPtr; + UINT32 pbDataNdrPtr; + UINT32 index = 0; + + if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->Common.handles.hContext), + &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr)) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 8) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, call->Common.FreshnessCounter); + Stream_Read_UINT32(s, call->Common.cbDataLen); + + if (!smartcard_ndr_pointer_read(s, &index, &pbDataNdrPtr)) + return ERROR_INVALID_DATA; + + call->szLookupName = NULL; + if (mszNdrPtr) + { + status = smartcard_ndr_read_a(s, &call->szLookupName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->Common.handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + call->Common.CardIdentifier = NULL; + if (contextNdrPtr) + { + status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier); + if (status != SCARD_S_SUCCESS) + return status; + } + + call->Common.pbData = NULL; + if (pbDataNdrPtr) + { + status = + smartcard_ndr_read(s, &call->Common.pbData, call->Common.cbDataLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_write_cache_a_call(smartcard, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_write_cache_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + WriteCacheW_Call* call) +{ + LONG status; + UINT32 mszNdrPtr; + UINT32 contextNdrPtr; + UINT32 pbDataNdrPtr; + UINT32 index = 0; + + if (!smartcard_ndr_pointer_read(s, &index, &mszNdrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->Common.handles.hContext), + &index); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(s, &index, &contextNdrPtr)) + return ERROR_INVALID_DATA; + + if (Stream_GetRemainingLength(s) < 8) + return STATUS_BUFFER_TOO_SMALL; + Stream_Read_UINT32(s, call->Common.FreshnessCounter); + Stream_Read_UINT32(s, call->Common.cbDataLen); + + if (!smartcard_ndr_pointer_read(s, &index, &pbDataNdrPtr)) + return ERROR_INVALID_DATA; + + call->szLookupName = NULL; + if (mszNdrPtr) + { + status = smartcard_ndr_read_w(s, &call->szLookupName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + } + + status = smartcard_unpack_redir_scard_context_ref(smartcard, s, &call->Common.handles.hContext); + if (status != SCARD_S_SUCCESS) + return status; + + call->Common.CardIdentifier = NULL; + if (contextNdrPtr) + { + status = smartcard_ndr_read_u(s, &call->Common.CardIdentifier); + if (status != SCARD_S_SUCCESS) + return status; + } + + call->Common.pbData = NULL; + if (pbDataNdrPtr) + { + status = + smartcard_ndr_read(s, &call->Common.pbData, call->Common.cbDataLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + } + smartcard_trace_write_cache_w_call(smartcard, call); + return status; +} + +LONG smartcard_unpack_get_transmit_count_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetTransmitCount_Call* call) +{ + LONG status; + UINT32 index = 0; + + status = smartcard_unpack_redir_scard_context(smartcard, s, &(call->handles.hContext), &index); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_handle(smartcard, s, &(call->handles.hCard), &index); + if (status != SCARD_S_SUCCESS) + return status; + + if ((status = + smartcard_unpack_redir_scard_context_ref(smartcard, s, &(call->handles.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->handles.hCard)))) + WLog_ERR(TAG, "smartcard_unpack_redir_scard_handle_ref failed with error %" PRId32 "", + status); + + smartcard_trace_get_transmit_count_call(smartcard, call); + return status; +} + +LONG smartcard_unpack_get_reader_icon_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetReaderIcon_Call* call) +{ + return smartcard_unpack_common_context_and_string_w(smartcard, s, &call->handles.hContext, + &call->szReaderName); +} + +LONG smartcard_unpack_context_and_string_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndStringA_Call* call) +{ + return smartcard_unpack_common_context_and_string_a(smartcard, s, &call->handles.hContext, + &call->sz); +} + +LONG smartcard_unpack_context_and_string_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndStringW_Call* call) +{ + return smartcard_unpack_common_context_and_string_w(smartcard, s, &call->handles.hContext, + &call->sz); +} + +LONG smartcard_unpack_get_device_type_id_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetDeviceTypeId_Call* call) +{ + return smartcard_unpack_common_context_and_string_w(smartcard, s, &call->handles.hContext, + &call->szReaderName); +} + +LONG smartcard_pack_device_type_id_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetDeviceTypeId_Return* ret) +{ + smartcard_trace_device_type_id_return(smartcard, ret); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->dwDeviceId); /* cBytes (4 bytes) */ + + return ret->ReturnCode; +} + +LONG smartcard_pack_locate_cards_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const LocateCards_Return* ret) +{ + LONG status; + DWORD cbDataLen = ret->cReaders; + DWORD index = 0; + + smartcard_trace_locate_cards_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbDataLen = 0; + if (cbDataLen == SCARD_AUTOALLOCATE) + cbDataLen = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */ + if (!smartcard_ndr_pointer_write(s, &index, cbDataLen)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write_state(s, ret->rgReaderStates, cbDataLen, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_pack_get_reader_icon_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetReaderIcon_Return* ret) +{ + LONG status; + DWORD index = 0; + DWORD cbDataLen = ret->cbDataLen; + smartcard_trace_get_reader_icon_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbDataLen = 0; + if (cbDataLen == SCARD_AUTOALLOCATE) + cbDataLen = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */ + if (!smartcard_ndr_pointer_write(s, &index, cbDataLen)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->pbData, cbDataLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_pack_get_transmit_count_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetTransmitCount_Return* ret) +{ + smartcard_trace_get_transmit_count_return(smartcard, ret); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->cTransmitCount); /* cBytes (4 cbDataLen) */ + + return ret->ReturnCode; +} + +LONG smartcard_pack_read_cache_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ReadCache_Return* ret) +{ + LONG status; + DWORD index = 0; + DWORD cbDataLen = ret->cbDataLen; + smartcard_trace_read_cache_return(smartcard, ret); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cbDataLen = 0; + + if (cbDataLen == SCARD_AUTOALLOCATE) + cbDataLen = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, cbDataLen); /* cBytes (4 cbDataLen) */ + if (!smartcard_ndr_pointer_write(s, &index, cbDataLen)) + return SCARD_E_NO_MEMORY; + + status = smartcard_ndr_write(s, ret->pbData, cbDataLen, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} diff --git a/channels/smartcard/client/smartcard_pack.h b/channels/smartcard/client/smartcard_pack.h new file mode 100644 index 0000000..82b1f3b --- /dev/null +++ b/channels/smartcard/client/smartcard_pack.h @@ -0,0 +1,196 @@ +/** + * 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 + * Copyright 2020 Armin Novak + * Copyright 2020 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_SMARTCARD_CLIENT_PACK_H +#define FREERDP_CHANNEL_SMARTCARD_CLIENT_PACK_H + +#include +#include +#include + +#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, size_t size, + UINT32 alignment); +LONG smartcard_unpack_read_size_align(SMARTCARD_DEVICE* smartcard, wStream* s, size_t 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_establish_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, + EstablishContext_Call* call); + +LONG smartcard_pack_establish_context_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const EstablishContext_Return* ret); + +LONG smartcard_unpack_context_call(SMARTCARD_DEVICE* smartcard, wStream* s, Context_Call* call, + const char* name); + +void smartcard_trace_long_return(SMARTCARD_DEVICE* smartcard, const Long_Return* ret, + const char* name); + +LONG smartcard_unpack_list_reader_groups_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaderGroups_Call* call, BOOL unicode); + +LONG smartcard_pack_list_reader_groups_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ListReaderGroups_Return* ret, BOOL unicode); + +LONG smartcard_unpack_list_readers_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ListReaders_Call* call, BOOL unicode); + +LONG smartcard_pack_list_readers_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ListReaders_Return* ret, BOOL unicode); + +LONG smartcard_unpack_context_and_two_strings_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndTwoStringA_Call* call); + +LONG smartcard_unpack_context_and_two_strings_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndTwoStringW_Call* call); + +LONG smartcard_unpack_context_and_string_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndStringA_Call* call); + +LONG smartcard_unpack_context_and_string_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ContextAndStringW_Call* call); + +LONG smartcard_unpack_locate_cards_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsA_Call* call); + +LONG smartcard_pack_locate_cards_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const LocateCards_Return* ret); + +LONG smartcard_unpack_locate_cards_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsW_Call* call); + +LONG smartcard_pack_locate_cards_w_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const LocateCardsW_Call* ret); + +LONG smartcard_unpack_connect_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectA_Call* call); + +LONG smartcard_unpack_connect_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, ConnectW_Call* call); + +LONG smartcard_pack_connect_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Connect_Return* ret); + +LONG smartcard_unpack_reconnect_call(SMARTCARD_DEVICE* smartcard, wStream* s, Reconnect_Call* call); + +LONG smartcard_pack_reconnect_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Reconnect_Return* ret); + +LONG smartcard_unpack_hcard_and_disposition_call(SMARTCARD_DEVICE* smartcard, wStream* s, + HCardAndDisposition_Call* call, const char* name); + +LONG smartcard_unpack_get_status_change_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeA_Call* call); + +LONG smartcard_unpack_get_status_change_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetStatusChangeW_Call* call); + +LONG smartcard_pack_get_status_change_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const 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, const State_Return* ret); + +LONG smartcard_unpack_status_call(SMARTCARD_DEVICE* smartcard, wStream* s, Status_Call* call, + BOOL unicode); + +LONG smartcard_pack_status_return(SMARTCARD_DEVICE* smartcard, wStream* s, const Status_Return* ret, + BOOL unicode); + +LONG smartcard_unpack_get_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetAttrib_Call* call); + +LONG smartcard_pack_get_attrib_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetAttrib_Return* ret, DWORD dwAttrId, + DWORD cbAttrCallLen); + +LONG smartcard_unpack_set_attrib_call(SMARTCARD_DEVICE* smartcard, wStream* s, + SetAttrib_Call* call); + +LONG smartcard_unpack_control_call(SMARTCARD_DEVICE* smartcard, wStream* s, Control_Call* call); + +LONG smartcard_pack_control_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Control_Return* ret); + +LONG smartcard_unpack_transmit_call(SMARTCARD_DEVICE* smartcard, wStream* s, Transmit_Call* call); + +LONG smartcard_pack_transmit_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const Transmit_Return* ret); + +LONG smartcard_unpack_locate_cards_by_atr_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRA_Call* call); + +LONG smartcard_unpack_locate_cards_by_atr_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + LocateCardsByATRW_Call* call); + +LONG smartcard_unpack_read_cache_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ReadCacheA_Call* call); + +LONG smartcard_unpack_read_cache_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + ReadCacheW_Call* call); + +LONG smartcard_pack_read_cache_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const ReadCache_Return* ret); + +LONG smartcard_unpack_write_cache_a_call(SMARTCARD_DEVICE* smartcard, wStream* s, + WriteCacheA_Call* call); + +LONG smartcard_unpack_write_cache_w_call(SMARTCARD_DEVICE* smartcard, wStream* s, + WriteCacheW_Call* call); + +LONG smartcard_unpack_get_transmit_count_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetTransmitCount_Call* call); +LONG smartcard_pack_get_transmit_count_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetTransmitCount_Return* call); + +LONG smartcard_unpack_get_reader_icon_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetReaderIcon_Call* call); +LONG smartcard_pack_get_reader_icon_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetReaderIcon_Return* ret); + +LONG smartcard_unpack_get_device_type_id_call(SMARTCARD_DEVICE* smartcard, wStream* s, + GetDeviceTypeId_Call* call); + +LONG smartcard_pack_device_type_id_return(SMARTCARD_DEVICE* smartcard, wStream* s, + const GetDeviceTypeId_Return* ret); + +#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..71aab99 --- /dev/null +++ b/channels/sshagent/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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() diff --git a/channels/sshagent/ChannelOptions.cmake b/channels/sshagent/ChannelOptions.cmake new file mode 100644 index 0000000..41b5a21 --- /dev/null +++ b/channels/sshagent/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +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 (experimental)" + SPECIFICATIONS "" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_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..aa7e632 --- /dev/null +++ b/channels/sshagent/client/sshagent_main.c @@ -0,0 +1,388 @@ +/** + * 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..550b2b7 --- /dev/null +++ b/channels/sshagent/client/sshagent_main.h @@ -0,0 +1,44 @@ +/** + * 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/telemetry/CMakeLists.txt b/channels/telemetry/CMakeLists.txt new file mode 100644 index 0000000..d2b4f24 --- /dev/null +++ b/channels/telemetry/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("telemetry") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/telemetry/ChannelOptions.cmake b/channels/telemetry/ChannelOptions.cmake new file mode 100644 index 0000000..1b9e391 --- /dev/null +++ b/channels/telemetry/ChannelOptions.cmake @@ -0,0 +1,12 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "telemetry" TYPE "dynamic" + DESCRIPTION "Telemetry Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPET]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/telemetry/server/CMakeLists.txt b/channels/telemetry/server/CMakeLists.txt new file mode 100644 index 0000000..75be8ac --- /dev/null +++ b/channels/telemetry/server/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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("telemetry") + +set(${MODULE_PREFIX}_SRCS + telemetry_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/telemetry/server/telemetry_main.c b/channels/telemetry/server/telemetry_main.c new file mode 100644 index 0000000..7173603 --- /dev/null +++ b/channels/telemetry/server/telemetry_main.c @@ -0,0 +1,443 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Telemetry Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +#define TAG CHANNELS_TAG("telemetry.server") + +typedef enum +{ + TELEMETRY_INITIAL, + TELEMETRY_OPENED, +} eTelemetryChannelState; + +typedef struct +{ + TelemetryServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* telemetry_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eTelemetryChannelState state; + + wStream* buffer; +} telemetry_server; + +static UINT telemetry_server_initialize(TelemetryServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (telemetry->isOpened) + { + WLog_WARN(TAG, "Application error: TELEMETRY channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + telemetry->externalThread = externalThread; + + return error; +} + +static UINT telemetry_server_open_channel(telemetry_server* telemetry) +{ + TelemetryServerContext* context = &telemetry->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(telemetry); + + if (WTSQuerySessionInformationA(telemetry->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + telemetry->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(telemetry->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + telemetry->telemetry_channel = WTSVirtualChannelOpenEx( + telemetry->SessionId, TELEMETRY_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!telemetry->telemetry_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(telemetry->telemetry_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT telemetry_server_recv_rdp_telemetry_pdu(TelemetryServerContext* context, wStream* s) +{ + TELEMETRY_RDP_TELEMETRY_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (Stream_GetRemainingLength(s) < 16) + { + WLog_ERR(TAG, "telemetry_server_recv_rdp_telemetry_pdu: Not enough data!"); + return ERROR_NO_DATA; + } + + Stream_Read_UINT32(s, pdu.PromptForCredentialsMillis); + Stream_Read_UINT32(s, pdu.PromptForCredentialsDoneMillis); + Stream_Read_UINT32(s, pdu.GraphicsChannelOpenedMillis); + Stream_Read_UINT32(s, pdu.FirstGraphicsReceivedMillis); + + IFCALLRET(context->RdpTelemetry, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->RdpTelemetry failed with error %" PRIu32 "", error); + + return error; +} + +static UINT telemetry_process_message(telemetry_server* telemetry) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + BYTE MessageId; + BYTE Length; + wStream* s; + + WINPR_ASSERT(telemetry); + WINPR_ASSERT(telemetry->telemetry_channel); + + s = telemetry->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(telemetry->telemetry_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(telemetry->telemetry_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, MessageId); + Stream_Read_UINT8(s, Length); + + switch (MessageId) + { + case 0x01: + error = telemetry_server_recv_rdp_telemetry_pdu(&telemetry->context, s); + break; + default: + WLog_ERR(TAG, "telemetry_process_message: unknown MessageId %" PRIu8 "", MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT telemetry_server_context_poll_int(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(telemetry); + + switch (telemetry->state) + { + case TELEMETRY_INITIAL: + error = telemetry_server_open_channel(telemetry); + if (error) + WLog_ERR(TAG, "telemetry_server_open_channel failed with error %" PRIu32 "!", + error); + else + telemetry->state = TELEMETRY_OPENED; + break; + case TELEMETRY_OPENED: + error = telemetry_process_message(telemetry); + break; + } + + return error; +} + +static HANDLE telemetry_server_get_channel_handle(telemetry_server* telemetry) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(telemetry); + + if (WTSVirtualChannelQuery(telemetry->telemetry_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI telemetry_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + telemetry_server* telemetry = (telemetry_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(telemetry); + + nCount = 0; + events[nCount++] = telemetry->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (telemetry->state) + { + case TELEMETRY_INITIAL: + error = telemetry_server_context_poll_int(&telemetry->context); + if (error == CHANNEL_RC_OK) + { + events[1] = telemetry_server_get_channel_handle(telemetry); + nCount = 2; + } + break; + case TELEMETRY_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = telemetry_server_context_poll_int(&telemetry->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(telemetry->telemetry_channel); + telemetry->telemetry_channel = NULL; + + if (error && telemetry->context.rdpcontext) + setChannelError(telemetry->context.rdpcontext, error, + "telemetry_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT telemetry_server_open(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread && (telemetry->thread == NULL)) + { + telemetry->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!telemetry->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + telemetry->thread = CreateThread(NULL, 0, telemetry_server_thread_func, telemetry, 0, NULL); + if (!telemetry->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(telemetry->stopEvent); + telemetry->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + telemetry->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT telemetry_server_close(TelemetryServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread && telemetry->thread) + { + SetEvent(telemetry->stopEvent); + + if (WaitForSingleObject(telemetry->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(telemetry->thread); + CloseHandle(telemetry->stopEvent); + telemetry->thread = NULL; + telemetry->stopEvent = NULL; + } + if (telemetry->externalThread) + { + if (telemetry->state != TELEMETRY_INITIAL) + { + WTSVirtualChannelClose(telemetry->telemetry_channel); + telemetry->telemetry_channel = NULL; + telemetry->state = TELEMETRY_INITIAL; + } + } + telemetry->isOpened = FALSE; + + return error; +} + +static UINT telemetry_server_context_poll(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread) + return ERROR_INTERNAL_ERROR; + + return telemetry_server_context_poll_int(context); +} + +static BOOL telemetry_server_context_handle(TelemetryServerContext* context, HANDLE* handle) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + WINPR_ASSERT(handle); + + if (!telemetry->externalThread) + return FALSE; + if (telemetry->state == TELEMETRY_INITIAL) + return FALSE; + + *handle = telemetry_server_get_channel_handle(telemetry); + + return TRUE; +} + +TelemetryServerContext* telemetry_server_context_new(HANDLE vcm) +{ + telemetry_server* telemetry = (telemetry_server*)calloc(1, sizeof(telemetry_server)); + + if (!telemetry) + return NULL; + + telemetry->context.vcm = vcm; + telemetry->context.Initialize = telemetry_server_initialize; + telemetry->context.Open = telemetry_server_open; + telemetry->context.Close = telemetry_server_close; + telemetry->context.Poll = telemetry_server_context_poll; + telemetry->context.ChannelHandle = telemetry_server_context_handle; + + telemetry->buffer = Stream_New(NULL, 4096); + if (!telemetry->buffer) + goto fail; + + return &telemetry->context; +fail: + telemetry_server_context_free(&telemetry->context); + return NULL; +} + +void telemetry_server_context_free(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + if (telemetry) + { + telemetry_server_close(context); + Stream_Free(telemetry->buffer, TRUE); + } + + free(telemetry); +} 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..b5252ea --- /dev/null +++ b/channels/tsmf/ChannelOptions.cmake @@ -0,0 +1,23 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +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 "[DEPRECATED] 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..d7438de --- /dev/null +++ b/channels/tsmf/client/CMakeLists.txt @@ -0,0 +1,114 @@ +# 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") + +message(DEPRECATION "TSMF channel is no longer maintained. Use [MS-RDPEVOR] (/video) instead.") + +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") + +if (WIN32) + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "OPTIONAL") +endif() +if (APPLE) + set(GSTREAMER_1_0_FEATURE_TYPE "OPTIONAL") + + if (IOS) + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "DISABLED") + endif() +endif() +if (ANDROID) + set(GSTREAMER_1_0_FEATURE_TYPE "DISABLED") + set(GSTREAMER_0_10_FEATURE_TYPE "DISABLED") +endif() + +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}) + +if (WITH_GSTREAMER_0_10 AND GSTREAMER_0_10_FOUND) + add_definitions(-DWITH_GSTREAMER_0_10) +endif() +if (WITH_GSTREAMER_1_0 AND GSTREAMER_1_0_FOUND) + add_definitions(-DWITH_GSTREAMER_1_0) +endif() + +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..6e1f003 --- /dev/null +++ b/channels/tsmf/client/alsa/tsmf_alsa.c @@ -0,0 +1,248 @@ +/** + * 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..c14877b --- /dev/null +++ b/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,667 @@ +/** + * 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) + { + size_t required = 6; + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + p = mdecoder->codec_context->extradata; + if (mdecoder->codec_context->extradata_size < required) + return FALSE; + *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))); + required += size + 2; + if (mdecoder->codec_context->extradata_size < required) + return FALSE; + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + required++; + if (mdecoder->codec_context->extradata_size < required) + return FALSE; + *p++ = 1; /* #pps */ + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + required += size + 2; + if (mdecoder->codec_context->extradata_size < required) + return FALSE; + memcpy(p, s, size + 2); + } + else + { + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, + media_type->ExtraDataSize); + if (mdecoder->codec_context->extradata_size < media_type->ExtraDataSize + 8) + return FALSE; + 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; + + WINPR_ASSERT(mdecoder); + WINPR_ASSERT(media_type); + + 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) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + + 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) +{ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) + avcodec_register_all(); +#endif + 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..ae383df --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_X11.c @@ -0,0 +1,506 @@ +/* + * 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, FALSE); +#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_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..51cbc86 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_gstreamer.c @@ -0,0 +1,1072 @@ +/* + * 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); + +static 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; +} + +static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp) +{ + /* + * 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) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + 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, { NULL } + }; + 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; +} + +static 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; +} + +static 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..b6f0b33 --- /dev/null +++ b/channels/tsmf/client/gstreamer/tsmf_platform.h @@ -0,0 +1,85 @@ +/* + * 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* 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..774affb --- /dev/null +++ b/channels/tsmf/client/oss/tsmf_oss.c @@ -0,0 +1,252 @@ +/** + * 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..b2f567e --- /dev/null +++ b/channels/tsmf/client/pulse/tsmf_pulse.c @@ -0,0 +1,422 @@ +/** + * 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..9f0bb32 --- /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)(void*)freerdp_load_channel_addin_entry("tsmf", 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..e7ae68c --- /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..a212aff --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.c @@ -0,0 +1,612 @@ +/** + * 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) +{ + UINT32 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) + { + const size_t nsize = cbFormat - 176; + if (mediatype->ExtraDataSize < nsize) + return FALSE; + if (!Stream_CheckAndLogRequiredLength(TAG, s, nsize)) + return FALSE; + mediatype->ExtraDataSize = nsize; + 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..ab98899 --- /dev/null +++ b/channels/tsmf/client/tsmf_codec.h @@ -0,0 +1,28 @@ +/** + * 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..43d37f2 --- /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..c59b0f6 --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.c @@ -0,0 +1,122 @@ +/** + * 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)(void*)freerdp_load_channel_addin_entry("tsmf", 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..9a16faf --- /dev/null +++ b/channels/tsmf/client/tsmf_decoder.h @@ -0,0 +1,78 @@ +/** + * 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..cfee41c --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.c @@ -0,0 +1,841 @@ +/** + * 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 = 0; + UINT32 v = 0; + UINT32 pos = 0; + UINT32 CapabilityType = 0; + UINT32 cbCapabilityLength = 0; + UINT32 numHostCapabilities = 0; + + WINPR_ASSERT(ifman); + if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4)) + return ERROR_OUTOFMEMORY; + + if (Stream_GetRemainingLength(ifman->input) < ifman->input_size) + return ERROR_INVALID_DATA; + + 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; +} + +static 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..3830f5b --- /dev/null +++ b/channels/tsmf/client/tsmf_ifman.h @@ -0,0 +1,69 @@ +/** + * 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..ec281ea --- /dev/null +++ b/channels/tsmf/client/tsmf_main.c @@ -0,0 +1,624 @@ +/** + * 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; +} + +/** + * 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; + 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 } }; + 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..366215c --- /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..b77a3c6 --- /dev/null +++ b/channels/tsmf/client/tsmf_media.c @@ -0,0 +1,1552 @@ +/** + * 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) +{ + size_t 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, strnlen(buf, sizeof(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; + } + + 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; + if (presentation->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..ade06da --- /dev/null +++ b/channels/tsmf/client/tsmf_media.h @@ -0,0 +1,72 @@ +/** + * 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..7e3823d --- /dev/null +++ b/channels/tsmf/client/tsmf_types.h @@ -0,0 +1,63 @@ +/** + * 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..bc570e0 --- /dev/null +++ b/channels/urbdrc/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("urbdrc") + +include_directories(common) +add_subdirectory(common) + +if(WITH_CLIENT_CHANNELS) + option(WITH_DEBUG_URBDRC "Dump data send/received in URBDRC channel" OFF) + + find_package(libusb-1.0 REQUIRED) + include_directories(${LIBUSB_1_INCLUDE_DIRS}) + + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/urbdrc/ChannelOptions.cmake b/channels/urbdrc/ChannelOptions.cmake new file mode 100644 index 0000000..770ba5e --- /dev/null +++ b/channels/urbdrc/ChannelOptions.cmake @@ -0,0 +1,18 @@ + +if (IOS OR ANDROID) + set(OPTION_DEFAULT OFF) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +else() + set(OPTION_DEFAULT ON) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +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..2d69618 --- /dev/null +++ b/channels/urbdrc/client/CMakeLists.txt @@ -0,0 +1,47 @@ +# 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 + data_transfer.c + data_transfer.h + urbdrc_main.c + urbdrc_main.h + $ + ) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +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..6987961 --- /dev/null +++ b/channels/urbdrc/client/data_transfer.c @@ -0,0 +1,1856 @@ +/** + * 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 "urbdrc_types.h" +#include "data_transfer.h" + +static void usb_process_get_port_status(IUDEVICE* pdev, wStream* out) +{ + int bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB); + + switch (bcdUSB) + { + case USB_v1_0: + Stream_Write_UINT32(out, 0x303); + break; + + case USB_v1_1: + Stream_Write_UINT32(out, 0x103); + break; + + case USB_v2_0: + Stream_Write_UINT32(out, 0x503); + break; + + default: + Stream_Write_UINT32(out, 0x503); + break; + } +} + +static UINT urb_write_completion(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, BOOL noAck, + wStream* out, UINT32 InterfaceId, UINT32 MessageId, + UINT32 RequestId, UINT32 usbd_status, UINT32 OutputBufferSize) +{ + if (!out) + return ERROR_INVALID_PARAMETER; + + if (Stream_Capacity(out) < OutputBufferSize + 36) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_PARAMETER; + } + + Stream_SetPosition(out, 0); + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + + if (OutputBufferSize != 0) + Stream_Write_UINT32(out, URB_COMPLETION); + else + Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); + + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 8); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + Stream_Write_UINT16(out, 8); /** Size */ + Stream_Write_UINT16(out, 0); /* Padding */ + Stream_Write_UINT32(out, usbd_status); /** UsbdStatus */ + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + Stream_Seek(out, OutputBufferSize); + + if (!noAck) + return stream_write_and_free(callback->plugin, callback->channel, out); + else + Stream_Free(out, TRUE); + + return ERROR_SUCCESS; +} + +static wStream* urb_create_iocompletion(UINT32 InterfaceField, UINT32 MessageId, UINT32 RequestId, + UINT32 OutputBufferSize) +{ + const UINT32 InterfaceId = (STREAM_ID_PROXY << 30) | (InterfaceField & 0x3FFFFFFF); + wStream* out = Stream_New(NULL, OutputBufferSize + 28); + + if (!out) + return NULL; + + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + Stream_Write_UINT32(out, IOCONTROL_COMPLETION); /** function id */ + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** Information */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + return out; +} + +static UINT urbdrc_process_register_request_callback(IUDEVICE* pdev, + URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + IUDEVMAN* udevman) +{ + UINT32 NumRequestCompletion = 0; + UINT32 RequestCompletion = 0; + URBDRC_PLUGIN* urbdrc; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + WLog_Print(urbdrc->log, WLOG_DEBUG, "urbdrc_process_register_request_callback"); + + if (Stream_GetRemainingLength(s) >= 8) + { + Stream_Read_UINT32(s, NumRequestCompletion); /** must be 1 */ + /** RequestCompletion: + * unique Request Completion interface for the client to use */ + Stream_Read_UINT32(s, RequestCompletion); + pdev->set_ReqCompletion(pdev, RequestCompletion); + } + else if (Stream_GetRemainingLength(s) >= 4) /** Unregister the device */ + { + Stream_Read_UINT32(s, RequestCompletion); + + if (pdev->get_ReqCompletion(pdev) == RequestCompletion) + pdev->setChannelClosed(pdev); + } + else + return ERROR_INVALID_DATA; + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_cancel_request(IUDEVICE* pdev, wStream* s, IUDEVMAN* udevman) +{ + UINT32 CancelId; + URBDRC_PLUGIN* urbdrc; + + if (!s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)udevman->plugin; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, CancelId); + WLog_Print(urbdrc->log, WLOG_DEBUG, "CANCEL_REQUEST: CancelId=%08" PRIx32 "", CancelId); + + if (pdev->cancel_transfer_request(pdev, CancelId) < 0) + return ERROR_INTERNAL_ERROR; + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_retract_device_request(IUDEVICE* pdev, wStream* s, IUDEVMAN* udevman) +{ + UINT32 Reason; + URBDRC_PLUGIN* urbdrc; + + if (!s || !udevman) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)udevman->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Reason); /** Reason */ + + switch (Reason) + { + case UsbRetractReason_BlockedByPolicy: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "UsbRetractReason_BlockedByPolicy: now it is not support"); + return ERROR_ACCESS_DENIED; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urbdrc_process_retract_device_request: Unknown Reason %" PRIu32 "", Reason); + return ERROR_ACCESS_DENIED; + } + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_io_control(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 MessageId, IUDEVMAN* udevman) +{ + UINT32 InterfaceId; + UINT32 IoControlCode; + UINT32 InputBufferSize; + UINT32 OutputBufferSize; + UINT32 RequestId; + UINT32 usbd_status = USBD_STATUS_SUCCESS; + wStream* out; + int success = 0; + URBDRC_PLUGIN* urbdrc; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, IoControlCode); + Stream_Read_UINT32(s, InputBufferSize); + + if (!Stream_SafeSeek(s, InputBufferSize)) + return ERROR_INVALID_DATA; + if (Stream_GetRemainingLength(s) < 8ULL) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferSize); + Stream_Read_UINT32(s, RequestId); + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, OutputBufferSize + 4); + + if (!out) + return ERROR_OUTOFMEMORY; + + switch (IoControlCode) + { + case IOCTL_INTERNAL_USB_SUBMIT_URB: /** 0x00220003 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_SUBMIT_URB"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_SUBMIT_URB: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_RESET_PORT: /** 0x00220007 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_RESET_PORT"); + break; + + case IOCTL_INTERNAL_USB_GET_PORT_STATUS: /** 0x00220013 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_GET_PORT_STATUS"); + success = pdev->query_device_port_status(pdev, &usbd_status, &OutputBufferSize, + Stream_Pointer(out)); + + if (success) + { + if (!Stream_SafeSeek(out, OutputBufferSize)) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_DATA; + } + + if (pdev->isExist(pdev) == 0) + Stream_Write_UINT32(out, 0); + else + usb_process_get_port_status(pdev, out); + } + + break; + + case IOCTL_INTERNAL_USB_CYCLE_PORT: /** 0x0022001F */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_CYCLE_PORT"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_CYCLE_PORT: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: /** 0x00220027 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, + "ioctl: IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: Unchecked"); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urbdrc_process_io_control: unknown IoControlCode 0x%" PRIX32 "", + IoControlCode); + Stream_Free(out, TRUE); + return ERROR_INVALID_OPERATION; + } + + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static UINT urbdrc_process_internal_io_control(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman) +{ + wStream* out; + UINT32 IoControlCode, InterfaceId, InputBufferSize; + UINT32 OutputBufferSize, RequestId, frames; + + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, IoControlCode); + Stream_Read_UINT32(s, InputBufferSize); + + if (!Stream_SafeSeek(s, InputBufferSize)) + return ERROR_INVALID_DATA; + if (Stream_GetRemainingLength(s) < 8ULL) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, OutputBufferSize); + Stream_Read_UINT32(s, RequestId); + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + // TODO: Implement control code. + /** Fixme: Currently this is a FALSE bustime... */ + frames = GetTickCount(); + out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, 4); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, frames); /** OutputBuffer */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static UINT urbdrc_process_query_device_text(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman) +{ + UINT32 out_size; + UINT32 TextType; + UINT32 LocaleId; + UINT32 InterfaceId; + UINT8 bufferSize = 0xFF; + UINT32 hr; + wStream* out; + BYTE DeviceDescription[0x100] = { 0 }; + + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, TextType); + Stream_Read_UINT32(s, LocaleId); + if (LocaleId > UINT16_MAX) + return ERROR_INVALID_DATA; + + hr = pdev->control_query_device_text(pdev, TextType, (UINT16)LocaleId, &bufferSize, + DeviceDescription); + InterfaceId = ((STREAM_ID_STUB << 30) | pdev->get_UsbDevice(pdev)); + out_size = 16 + bufferSize; + + if (bufferSize != 0) + out_size += 2; + + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + Stream_Write_UINT32(out, bufferSize / 2); /** cchDeviceDescription in WCHAR */ + Stream_Write(out, DeviceDescription, bufferSize); /* '\0' terminated unicode */ + Stream_Write_UINT32(out, hr); /** HResult */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static void func_select_all_interface_for_msconfig(IUDEVICE* pdev, + MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + UINT32 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 UINT urb_select_configuration(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig = NULL; + size_t out_size; + UINT32 InterfaceId, NumInterfaces, usbd_status = 0; + BYTE ConfigurationDescriptorIsValid; + wStream* out; + int MsOutSize = 0; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_configuration: unsupported transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT8(s, ConfigurationDescriptorIsValid); + Stream_Seek(s, 3); /* Padding */ + Stream_Read_UINT32(s, NumInterfaces); + + /** if ConfigurationDescriptorIsValid is zero, then just do nothing.*/ + if (ConfigurationDescriptorIsValid) + { + /* parser data for struct config */ + MsConfig = msusb_msconfig_read(s, NumInterfaces); + + if (!MsConfig) + return ERROR_INVALID_DATA; + + /* select config */ + pdev->select_configuration(pdev, MsConfig->bConfigurationValue); + /* select all interface */ + func_select_all_interface_for_msconfig(pdev, MsConfig); + /* complete configuration setup */ + if (!pdev->complete_msconfig_setup(pdev, MsConfig)) + { + msusb_msconfig_free(MsConfig); + MsConfig = NULL; + } + } + + if (MsConfig) + MsOutSize = MsConfig->MsOutSize; + + if (MsOutSize > 0) + { + if ((size_t)MsOutSize > SIZE_MAX - 36) + return ERROR_INVALID_DATA; + + out_size = 36 + MsOutSize; + } + else + out_size = 44; + + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */ + Stream_Write_UINT32(out, RequestId); /** RequestId */ + + if (MsOutSize > 0) + { + /** CbTsUrbResult */ + Stream_Write_UINT32(out, 8 + MsOutSize); + /** TS_URB_RESULT_HEADER Size*/ + Stream_Write_UINT16(out, 8 + MsOutSize); + } + else + { + Stream_Write_UINT32(out, 16); + Stream_Write_UINT16(out, 16); + } + + /** Padding, MUST be ignored upon receipt */ + Stream_Write_UINT16(out, TS_URB_SELECT_CONFIGURATION); + Stream_Write_UINT32(out, usbd_status); /** UsbdStatus */ + + /** TS_URB_SELECT_CONFIGURATION_RESULT */ + if (MsOutSize > 0) + msusb_msconfig_write(MsConfig, out); + else + { + Stream_Write_UINT32(out, 0); /** ConfigurationHandle */ + Stream_Write_UINT32(out, NumInterfaces); /** NumInterfaces */ + } + + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, 0); /** OutputBufferSize */ + + if (!noAck) + return stream_write_and_free(callback->plugin, callback->channel, out); + else + Stream_Free(out, TRUE); + + return ERROR_SUCCESS; +} + +static UINT urb_select_interface(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + UINT32 out_size, InterfaceId, ConfigurationHandle; + UINT32 OutputBufferSize; + BYTE InterfaceNumber; + wStream* out; + UINT32 interface_size; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_interface: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, ConfigurationHandle); + MsInterface = msusb_msinterface_read(s); + + if ((Stream_GetRemainingLength(s) < 4) || !MsInterface) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferSize); + pdev->select_interface(pdev, MsInterface->InterfaceNumber, MsInterface->AlternateSetting); + /* replace device's MsInterface */ + MsConfig = pdev->get_MsConfig(pdev); + InterfaceNumber = MsInterface->InterfaceNumber; + if (!msusb_msinterface_replace(MsConfig, InterfaceNumber, MsInterface)) + { + msusb_msconfig_free(MsConfig); + return ERROR_BAD_CONFIGURATION; + } + /* complete configuration setup */ + if (!pdev->complete_msconfig_setup(pdev, MsConfig)) + { + msusb_msconfig_free(MsConfig); + return ERROR_BAD_CONFIGURATION; + } + MsInterface = MsConfig->MsInterfaces[InterfaceNumber]; + interface_size = 16 + (MsInterface->NumberOfPipes * 20); + out_size = 36 + interface_size; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */ + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 8 + interface_size); /** CbTsUrbResult */ + /** TS_URB_RESULT_HEADER */ + Stream_Write_UINT16(out, 8 + interface_size); /** Size */ + /** Padding, MUST be ignored upon receipt */ + Stream_Write_UINT16(out, TS_URB_SELECT_INTERFACE); + Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** UsbdStatus */ + /** TS_URB_SELECT_INTERFACE_RESULT */ + msusb_msinterface_write(MsInterface, out); + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, 0); /** OutputBufferSize */ + + if (!noAck) + return stream_write_and_free(callback->plugin, callback->channel, out); + else + Stream_Free(out, TRUE); + + return ERROR_SUCCESS; +} + +static UINT urb_control_transfer(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir, int External) +{ + UINT32 out_size, InterfaceId, EndpointAddress, PipeHandle; + UINT32 TransferFlags, OutputBufferSize, usbd_status, Timeout; + BYTE bmRequestType, Request; + UINT16 Value, Index, length; + BYTE* buffer; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, PipeHandle); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + EndpointAddress = (PipeHandle & 0x000000ff); + Timeout = 2000; + + switch (External) + { + case URB_CONTROL_TRANSFER_EXTERNAL: + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Timeout); /** TransferFlags */ + break; + + case URB_CONTROL_TRANSFER_NONEXTERNAL: + break; + } + + /** SetupPacket 8 bytes */ + if (Stream_GetRemainingLength(s) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, bmRequestType); + Stream_Read_UINT8(s, Request); + Stream_Read_UINT16(s, Value); + Stream_Read_UINT16(s, Index); + Stream_Read_UINT16(s, length); + Stream_Read_UINT32(s, OutputBufferSize); + + if (length != OutputBufferSize) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_control_transfer ERROR: buf != length"); + return ERROR_INVALID_DATA; + } + + out_size = 36 + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + /** Get Buffer Data */ + buffer = Stream_Pointer(out); + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + Stream_Copy(s, out, OutputBufferSize); + + /** process TS_URB_CONTROL_TRANSFER */ + if (!pdev->control_transfer(pdev, RequestId, EndpointAddress, TransferFlags, bmRequestType, + Request, Value, Index, &usbd_status, &OutputBufferSize, buffer, + Timeout)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static void urb_bulk_transfer_cb(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* out, + UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, UINT32 RequestId, + UINT32 NumberOfPackets, UINT32 status, UINT32 StartFrame, + UINT32 ErrorCount, UINT32 OutputBufferSize) +{ + if (!pdev->isChannelClosed(pdev)) + urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, status, + OutputBufferSize); + else + Stream_Free(out, TRUE); +} + +static UINT urb_bulk_or_interrupt_transfer(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + UINT32 EndpointAddress, PipeHandle; + UINT32 TransferFlags, OutputBufferSize; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PipeHandle); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT32(s, OutputBufferSize); + EndpointAddress = (PipeHandle & 0x000000ff); + /** process TS_URB_BULK_OR_INTERRUPT_TRANSFER */ + return pdev->bulk_or_interrupt_transfer( + pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, noAck, + OutputBufferSize, (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : NULL, + urb_bulk_transfer_cb, 10000); +} + +static void urb_isoch_transfer_cb(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* out, + UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, + UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status, + UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize) +{ + if (!noAck) + { + UINT32 packetSize = (status == 0) ? NumberOfPackets * 12 : 0; + Stream_SetPosition(out, 0); + /* fill the send data */ + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + + if (OutputBufferSize == 0) + Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); /** function id */ + else + Stream_Write_UINT32(out, URB_COMPLETION); /** function id */ + + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 20 + packetSize); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + Stream_Write_UINT16(out, 20 + packetSize); /** Size */ + Stream_Write_UINT16(out, 0); /* Padding */ + Stream_Write_UINT32(out, status); /** UsbdStatus */ + Stream_Write_UINT32(out, StartFrame); /** StartFrame */ + + if (status == 0) + { + /** NumberOfPackets */ + Stream_Write_UINT32(out, NumberOfPackets); + Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */ + Stream_Seek(out, packetSize); + } + else + { + Stream_Write_UINT32(out, 0); /** NumberOfPackets */ + Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */ + } + + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + Stream_Seek(out, OutputBufferSize); + + stream_write_and_free(callback->plugin, callback->channel, out); + } +} + +static UINT urb_isoch_transfer(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + UINT32 EndpointAddress; + UINT32 PipeHandle, TransferFlags, StartFrame, NumberOfPackets; + UINT32 ErrorCount, OutputBufferSize; + BYTE* packetDescriptorData; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!pdev || !callback || !udevman) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 20) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PipeHandle); + EndpointAddress = (PipeHandle & 0x000000ff); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT32(s, StartFrame); /** StartFrame */ + Stream_Read_UINT32(s, NumberOfPackets); /** NumberOfPackets */ + Stream_Read_UINT32(s, ErrorCount); /** ErrorCount */ + + if (Stream_GetRemainingLength(s) < NumberOfPackets * 12 + 4) + return ERROR_INVALID_DATA; + + packetDescriptorData = Stream_Pointer(s); + Stream_Seek(s, NumberOfPackets * 12); + Stream_Read_UINT32(s, OutputBufferSize); + return pdev->isoch_transfer( + pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, StartFrame, + ErrorCount, noAck, packetDescriptorData, NumberOfPackets, OutputBufferSize, + (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : NULL, + urb_isoch_transfer_cb, 2000); +} + +static UINT urb_control_descriptor_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, int transferDir) +{ + size_t out_size; + UINT32 InterfaceId, OutputBufferSize, usbd_status; + BYTE bmRequestType, desc_index, desc_type; + UINT16 langId; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT8(s, desc_index); + Stream_Read_UINT8(s, desc_type); + Stream_Read_UINT16(s, langId); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (Stream_GetRemainingLength(s) < OutputBufferSize) + return ERROR_INVALID_DATA; + } + + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_IN: + bmRequestType |= 0x80; + break; + + case USBD_TRANSFER_DIRECTION_OUT: + bmRequestType |= 0x00; + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "get error transferDir"); + OutputBufferSize = 0; + usbd_status = USBD_STATUS_STALL_PID; + break; + } + + /** process get usb device descriptor */ + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, + 0x06, /* REQUEST_GET_DESCRIPTOR */ + (desc_type << 8) | desc_index, langId, &usbd_status, + &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "get_descriptor failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_get_status_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, int transferDir) +{ + size_t out_size; + UINT32 InterfaceId, OutputBufferSize, usbd_status; + UINT16 Index; + BYTE bmRequestType; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_status_request: transfer out not supported"); + return ERROR_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, Index); /** Index */ + Stream_Seek(s, 2); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient | 0x80; + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, 0x00, /* REQUEST_GET_STATUS */ + 0, Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out), + 1000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_vendor_or_class_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_type, + BYTE func_recipient, int transferDir) +{ + UINT32 out_size, InterfaceId, TransferFlags, usbd_status; + UINT32 OutputBufferSize; + BYTE ReqTypeReservedBits, Request, bmRequestType; + UINT16 Value, Index, Padding; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 16) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT8(s, ReqTypeReservedBits); /** ReqTypeReservedBids */ + Stream_Read_UINT8(s, Request); /** Request */ + Stream_Read_UINT16(s, Value); /** value */ + Stream_Read_UINT16(s, Index); /** index */ + Stream_Read_UINT16(s, Padding); /** Padding */ + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (Stream_GetRemainingLength(s) < OutputBufferSize) + return ERROR_INVALID_DATA; + } + + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + /** Get Buffer */ + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + } + + /** vendor or class command */ + bmRequestType = func_type | func_recipient; + + if (TransferFlags & USBD_TRANSFER_DIRECTION) + bmRequestType |= 0x80; + + WLog_Print(urbdrc->log, WLOG_DEBUG, + "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); + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, Request, Value, Index, + &usbd_status, &OutputBufferSize, Stream_Pointer(out), 2000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_os_feature_descriptor_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size; + UINT32 InterfaceId, OutputBufferSize, usbd_status; + BYTE Recipient, InterfaceNumber, Ms_PageIndex; + UINT16 Ms_featureDescIndex; + wStream* out; + int ret; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_INVALID_DATA; + + /* 2.2.9.15 TS_URB_OS_FEATURE_DESCRIPTOR_REQUEST */ + Stream_Read_UINT8(s, Recipient); /** Recipient */ + Recipient = (Recipient & 0x1f); /* Mask out Padding1 */ + Stream_Read_UINT8(s, InterfaceNumber); /** InterfaceNumber */ + Stream_Read_UINT8(s, Ms_PageIndex); /** Ms_PageIndex */ + Stream_Read_UINT16(s, Ms_featureDescIndex); /** Ms_featureDescIndex */ + Stream_Seek(s, 3); /* Padding 2 */ + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + if (Stream_GetRemainingLength(s) < OutputBufferSize) + return ERROR_INVALID_DATA; + + break; + + default: + break; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + break; + + case USBD_TRANSFER_DIRECTION_IN: + break; + } + + WLog_Print(urbdrc->log, WLOG_DEBUG, + "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, Stream_Pointer(out), 1000); + + if (ret < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "os_feature_descriptor_request: error num %d", ret); + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_pipe_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir, int action) +{ + UINT32 out_size, InterfaceId, PipeHandle, EndpointAddress; + UINT32 OutputBufferSize, usbd_status = 0; + wStream* out; + UINT32 ret = USBD_STATUS_REQUEST_FAILED; + int rc; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, PipeHandle); /** PipeHandle */ + Stream_Read_UINT32(s, OutputBufferSize); + EndpointAddress = (PipeHandle & 0x000000ff); + + switch (action) + { + case PIPE_CANCEL: + rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status, + PIPE_CANCEL); + + if (rc < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE SET HALT: error %d", ret); + else + ret = USBD_STATUS_SUCCESS; + + break; + + case PIPE_RESET: + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: PIPE_RESET ep 0x%" PRIx32 "", + EndpointAddress); + rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status, + PIPE_RESET); + + if (rc < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE RESET: error %d", ret); + else + ret = USBD_STATUS_SUCCESS; + + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request action: %d not supported", + action); + ret = USBD_STATUS_INVALID_URB_FUNCTION; + break; + } + + /** send data */ + out_size = 36; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, ret, + 0); +} + +static UINT urb_get_current_frame_number(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + UINT32 out_size, InterfaceId, OutputBufferSize; + UINT32 dummy_frames; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_get_current_frame_number: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, OutputBufferSize); + /** Fixme: Need to fill actual frame number!!*/ + dummy_frames = GetTickCount(); + out_size = 40; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /** interface */ + Stream_Write_UINT32(out, MessageId); /** message id */ + Stream_Write_UINT32(out, URB_COMPLETION_NO_DATA); + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 12); /** CbTsUrbResult */ + /** TsUrbResult TS_URB_RESULT_HEADER */ + Stream_Write_UINT16(out, 12); /** Size */ + /** Padding, MUST be ignored upon receipt */ + Stream_Write_UINT16(out, TS_URB_GET_CURRENT_FRAME_NUMBER); + Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** UsbdStatus */ + Stream_Write_UINT32(out, dummy_frames); /** FrameNumber */ + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, 0); /** OutputBufferSize */ + + if (!noAck) + return stream_write_and_free(callback->plugin, callback->channel, out); + else + Stream_Free(out, TRUE); + + return ERROR_SUCCESS; +} + +/* Unused function for current server */ +static UINT urb_control_get_configuration_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size; + UINT32 InterfaceId, OutputBufferSize, usbd_status; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_configuration_request:" + " not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, 0x80 | 0x00, + 0x08, /* REQUEST_GET_CONFIGURATION */ + 0, 0, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +/* Unused function for current server */ +static UINT urb_control_get_interface_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size; + UINT32 InterfaceId, OutputBufferSize, usbd_status; + UINT16 interface; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_interface_request: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, interface); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + if (!pdev->control_transfer( + pdev, RequestId, 0, 0, 0x80 | 0x01, 0x0A, /* REQUEST_GET_INTERFACE */ + 0, interface, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_feature_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, BYTE command, + int transferDir) +{ + UINT32 InterfaceId, OutputBufferSize, usbd_status; + UINT16 FeatureSelector, Index; + BYTE bmRequestType, bmRequest; + wStream* out; + URBDRC_PLUGIN* urbdrc; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 8) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, FeatureSelector); + Stream_Read_UINT16(s, Index); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + if (Stream_GetRemainingLength(s) < OutputBufferSize) + return ERROR_INVALID_DATA; + + break; + + default: + break; + } + + out = Stream_New(NULL, 36ULL + OutputBufferSize); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + WLog_Print(urbdrc->log, WLOG_ERROR, + "Function urb_control_feature_request: OUT Unchecked"); + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, 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_Print(urbdrc->log, WLOG_ERROR, + "urb_control_feature_request: Error Command 0x%02" PRIx8 "", command); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, bmRequest, FeatureSelector, + Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "feature control transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urbdrc_process_transfer_request(IUDEVICE* pdev, URBDRC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + UINT32 CbTsUrb; + UINT16 Size; + UINT16 URB_Function; + UINT32 RequestId; + UINT error = ERROR_INTERNAL_ERROR; + URBDRC_PLUGIN* urbdrc; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, CbTsUrb); /** CbTsUrb */ + Stream_Read_UINT16(s, Size); /** size */ + Stream_Read_UINT16(s, URB_Function); + Stream_Read_UINT32(s, RequestId); + WLog_Print(urbdrc->log, WLOG_DEBUG, "URB %s[" PRIu16 "]", urb_function_string(URB_Function), + URB_Function); + + switch (URB_Function) + { + case TS_URB_SELECT_CONFIGURATION: /** 0x0000 */ + error = urb_select_configuration(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_SELECT_INTERFACE: /** 0x0001 */ + error = + urb_select_interface(pdev, callback, s, RequestId, MessageId, udevman, transferDir); + break; + + case TS_URB_PIPE_REQUEST: /** 0x0002 */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_CANCEL); + break; + + case TS_URB_TAKE_FRAME_LENGTH_CONTROL: /** 0x0003 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_RELEASE_FRAME_LENGTH_CONTROL: /** 0x0004 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_GET_FRAME_LENGTH: /** 0x0005 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_SET_FRAME_LENGTH: /** 0x0006 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_GET_CURRENT_FRAME_NUMBER: /** 0x0007 */ + error = urb_get_current_frame_number(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_CONTROL_TRANSFER: /** 0x0008 */ + error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir, URB_CONTROL_TRANSFER_NONEXTERNAL); + break; + + case TS_URB_BULK_OR_INTERRUPT_TRANSFER: /** 0x0009 */ + error = urb_bulk_or_interrupt_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_ISOCH_TRANSFER: /** 0x000A */ + error = + urb_isoch_transfer(pdev, callback, s, RequestId, MessageId, udevman, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_DEVICE: /** 0x000B */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_DEVICE: /** 0x000C */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_DEVICE: /** 0x000D */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_INTERFACE: /** 0x000E */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_ENDPOINT: /** 0x000F */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_DEVICE: /** 0x0010 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_INTERFACE: /** 0x0011 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_ENDPOINT: /** 0x0012 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_DEVICE: /** 0x0013 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_INTERFACE: /** 0x0014 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_ENDPOINT: /** 0x0015 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_RESERVED_0X0016: /** 0x0016 */ + break; + + case TS_URB_VENDOR_DEVICE: /** 0x0017 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x00, transferDir); + break; + + case TS_URB_VENDOR_INTERFACE: /** 0x0018 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x01, transferDir); + break; + + case TS_URB_VENDOR_ENDPOINT: /** 0x0019 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x02, transferDir); + break; + + case TS_URB_CLASS_DEVICE: /** 0x001A */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x00, transferDir); + break; + + case TS_URB_CLASS_INTERFACE: /** 0x001B */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x01, transferDir); + break; + + case TS_URB_CLASS_ENDPOINT: /** 0x001C */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x02, transferDir); + break; + + case TS_URB_RESERVE_0X001D: /** 0x001D */ + break; + + case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL: /** 0x001E */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_CLASS_OTHER: /** 0x001F */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x03, transferDir); + break; + + case TS_URB_VENDOR_OTHER: /** 0x0020 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x03, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_OTHER: /** 0x0021 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_OTHER: /** 0x0022 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_OTHER: /** 0x0023 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT: /** 0x0024 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT: /** 0x0025 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST: /** 0x0026 */ + error = urb_control_get_configuration_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_CONTROL_GET_INTERFACE_REQUEST: /** 0x0027 */ + error = urb_control_get_interface_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE: /** 0x0028 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_INTERFACE: /** 0x0029 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST: /** 0x002A */ + error = urb_os_feature_descriptor_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_RESERVE_0X002B: /** 0x002B */ + case TS_URB_RESERVE_0X002C: /** 0x002C */ + case TS_URB_RESERVE_0X002D: /** 0x002D */ + case TS_URB_RESERVE_0X002E: /** 0x002E */ + case TS_URB_RESERVE_0X002F: /** 0x002F */ + break; + + /** USB 2.0 calls start at 0x0030 */ + case TS_URB_SYNC_RESET_PIPE: /** 0x0030 */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_SYNC_CLEAR_STALL: /** 0x0031 */ + urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_CONTROL_TRANSFER_EX: /** 0x0032 */ + error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir, URB_CONTROL_TRANSFER_EXTERNAL); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "URB_Func: %" PRIx16 " is not found!", + URB_Function); + break; + } + + if (error) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "USB transfer request URB Function %08" PRIx32 " failed with %08" PRIx32, + URB_Function, error); + } + + return error; +} + +UINT urbdrc_process_udev_data_transfer(URBDRC_CHANNEL_CALLBACK* callback, URBDRC_PLUGIN* urbdrc, + IUDEVMAN* udevman, wStream* data) +{ + UINT32 InterfaceId; + UINT32 MessageId; + UINT32 FunctionId; + IUDEVICE* pdev; + UINT error = ERROR_INTERNAL_ERROR; + size_t len; + + if (!urbdrc || !data || !callback || !udevman) + goto fail; + + len = Stream_GetRemainingLength(data); + + if (len < 8) + goto fail; + + Stream_Rewind_UINT32(data); + + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, InterfaceId); + + /* Device does not exist, ignore this request. */ + if (pdev == NULL) + { + error = ERROR_SUCCESS; + goto fail; + } + + /* Device has been removed, ignore this request. */ + if (pdev->isChannelClosed(pdev)) + { + error = ERROR_SUCCESS; + goto fail; + } + + /* USB kernel driver detach!! */ + pdev->detach_kernel_driver(pdev); + + switch (FunctionId) + { + case CANCEL_REQUEST: + error = urbdrc_process_cancel_request(pdev, data, udevman); + break; + + case REGISTER_REQUEST_CALLBACK: + error = urbdrc_process_register_request_callback(pdev, callback, data, udevman); + break; + + case IO_CONTROL: + error = urbdrc_process_io_control(pdev, callback, data, MessageId, udevman); + break; + + case INTERNAL_IO_CONTROL: + error = urbdrc_process_internal_io_control(pdev, callback, data, MessageId, udevman); + break; + + case QUERY_DEVICE_TEXT: + error = urbdrc_process_query_device_text(pdev, callback, data, MessageId, udevman); + break; + + case TRANSFER_IN_REQUEST: + error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman, + USBD_TRANSFER_DIRECTION_IN); + break; + + case TRANSFER_OUT_REQUEST: + error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman, + USBD_TRANSFER_DIRECTION_OUT); + break; + + case RETRACT_DEVICE: + error = urbdrc_process_retract_device_request(pdev, data, udevman); + break; + + default: + WLog_Print(urbdrc->log, WLOG_WARN, + "urbdrc_process_udev_data_transfer:" + " unknown FunctionId 0x%" PRIX32 "", + FunctionId); + break; + } + +fail: + if (error) + { + WLog_WARN(TAG, "USB request failed with %08" PRIx32, error); + } + + return error; +} diff --git a/channels/urbdrc/client/data_transfer.h b/channels/urbdrc/client/data_transfer.h new file mode 100644 index 0000000..d63f82e --- /dev/null +++ b/channels/urbdrc/client/data_transfer.h @@ -0,0 +1,36 @@ +/** + * 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 + +#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))) + +UINT urbdrc_process_udev_data_transfer(URBDRC_CHANNEL_CALLBACK* callback, URBDRC_PLUGIN* urbdrc, + IUDEVMAN* udevman, wStream* data); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H */ diff --git a/channels/urbdrc/client/libusb/CMakeLists.txt b/channels/urbdrc/client/libusb/CMakeLists.txt new file mode 100644 index 0000000..c5e9b70 --- /dev/null +++ b/channels/urbdrc/client/libusb/CMakeLists.txt @@ -0,0 +1,46 @@ +# 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) + +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 urbdrc-client) + +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..505c31d --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevice.c @@ -0,0 +1,1796 @@ +/** + * 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 "libusb_udevice.h" +#include "../common/urbdrc_types.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 + +#if LIBUSB_API_VERSION >= 0x01000103 +#define HAVE_STREAM_ID_API 1 +#endif + +typedef struct _ASYNC_TRANSFER_USER_DATA ASYNC_TRANSFER_USER_DATA; + +struct _ASYNC_TRANSFER_USER_DATA +{ + wStream* data; + BOOL noack; + UINT32 MessageId; + UINT32 StartFrame; + UINT32 ErrorCount; + IUDEVICE* idev; + UINT32 OutputBufferSize; + URBDRC_CHANNEL_CALLBACK* callback; + t_isoch_transfer_cb cb; + wArrayList* queue; +#if !defined(HAVE_STREAM_ID_API) + UINT32 streamID; +#endif +}; + +static void request_free(void* value); + +static struct libusb_transfer* list_contains(wArrayList* list, UINT32 streamID) +{ + int x, count; + if (!list) + return NULL; + count = ArrayList_Count(list); + for (x = 0; x < count; x++) + { + struct libusb_transfer* transfer = ArrayList_GetItem(list, x); + +#if defined(HAVE_STREAM_ID_API) + const UINT32 currentID = libusb_transfer_get_stream_id(transfer); +#else + const ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + const UINT32 currentID = user_data->streamID; +#endif + if (currentID == streamID) + return transfer; + } + return NULL; +} + +static UINT32 stream_id_from_buffer(struct libusb_transfer* transfer) +{ + if (!transfer) + return 0; +#if defined(HAVE_STREAM_ID_API) + return libusb_transfer_get_stream_id(transfer); +#else + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + return 0; + return user_data->streamID; +#endif +} + +static void set_stream_id_for_buffer(struct libusb_transfer* transfer, UINT32 streamID) +{ +#if defined(HAVE_STREAM_ID_API) + libusb_transfer_set_stream_id(transfer, streamID); +#else + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + return; + user_data->streamID = streamID; +#endif +} +static BOOL log_libusb_result(wLog* log, DWORD lvl, const char* fmt, int error, ...) +{ + if (error < 0) + { + char buffer[8192] = { 0 }; + va_list ap; + va_start(ap, error); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + WLog_Print(log, lvl, "%s: error %s[%d]", buffer, libusb_error_name(error), error); + return TRUE; + } + return FALSE; +} + +const char* usb_interface_class_to_string(uint8_t class) +{ + switch (class) + { + case LIBUSB_CLASS_PER_INTERFACE: + return "LIBUSB_CLASS_PER_INTERFACE"; + case LIBUSB_CLASS_AUDIO: + return "LIBUSB_CLASS_AUDIO"; + case LIBUSB_CLASS_COMM: + return "LIBUSB_CLASS_COMM"; + case LIBUSB_CLASS_HID: + return "LIBUSB_CLASS_HID"; + case LIBUSB_CLASS_PHYSICAL: + return "LIBUSB_CLASS_PHYSICAL"; + case LIBUSB_CLASS_PRINTER: + return "LIBUSB_CLASS_PRINTER"; + case LIBUSB_CLASS_IMAGE: + return "LIBUSB_CLASS_IMAGE"; + case LIBUSB_CLASS_MASS_STORAGE: + return "LIBUSB_CLASS_MASS_STORAGE"; + case LIBUSB_CLASS_HUB: + return "LIBUSB_CLASS_HUB"; + case LIBUSB_CLASS_DATA: + return "LIBUSB_CLASS_DATA"; + case LIBUSB_CLASS_SMART_CARD: + return "LIBUSB_CLASS_SMART_CARD"; + case LIBUSB_CLASS_CONTENT_SECURITY: + return "LIBUSB_CLASS_CONTENT_SECURITY"; + case LIBUSB_CLASS_VIDEO: + return "LIBUSB_CLASS_VIDEO"; + case LIBUSB_CLASS_PERSONAL_HEALTHCARE: + return "LIBUSB_CLASS_PERSONAL_HEALTHCARE"; + case LIBUSB_CLASS_DIAGNOSTIC_DEVICE: + return "LIBUSB_CLASS_DIAGNOSTIC_DEVICE"; + case LIBUSB_CLASS_WIRELESS: + return "LIBUSB_CLASS_WIRELESS"; + case LIBUSB_CLASS_APPLICATION: + return "LIBUSB_CLASS_APPLICATION"; + case LIBUSB_CLASS_VENDOR_SPEC: + return "LIBUSB_CLASS_VENDOR_SPEC"; + default: + return "UNKNOWN_DEVICE_CLASS"; + } +} + +static ASYNC_TRANSFER_USER_DATA* async_transfer_user_data_new(IUDEVICE* idev, UINT32 MessageId, + size_t offset, size_t BufferSize, + const BYTE* data, size_t packetSize, + BOOL NoAck, t_isoch_transfer_cb cb, + URBDRC_CHANNEL_CALLBACK* callback) +{ + ASYNC_TRANSFER_USER_DATA* user_data = calloc(1, sizeof(ASYNC_TRANSFER_USER_DATA)); + UDEVICE* pdev = (UDEVICE*)idev; + + if (!user_data) + return NULL; + + user_data->data = Stream_New(NULL, offset + BufferSize + packetSize); + + if (!user_data->data) + { + free(user_data); + return NULL; + } + + Stream_Seek(user_data->data, offset); /* Skip header offset */ + if (data) + memcpy(Stream_Pointer(user_data->data), data, BufferSize); + else + user_data->OutputBufferSize = BufferSize; + + user_data->noack = NoAck; + user_data->cb = cb; + user_data->callback = callback; + user_data->idev = idev; + user_data->MessageId = MessageId; + + user_data->queue = pdev->request_queue; + + return user_data; +} + +static void async_transfer_user_data_free(ASYNC_TRANSFER_USER_DATA* user_data) +{ + if (user_data) + { + Stream_Free(user_data->data, TRUE); + free(user_data); + } +} + +static void LIBUSB_CALL func_iso_callback(struct libusb_transfer* transfer) +{ + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + const UINT32 streamID = stream_id_from_buffer(transfer); + wArrayList* list = user_data->queue; + + ArrayList_Lock(list); + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: + { + int i; + UINT32 index = 0; + BYTE* dataStart = Stream_Pointer(user_data->data); + Stream_SetPosition(user_data->data, + 40); /* TS_URB_ISOCH_TRANSFER_RESULT IsoPacket offset */ + + for (i = 0; i < transfer->num_iso_packets; i++) + { + const UINT32 act_len = transfer->iso_packet_desc[i].actual_length; + Stream_Write_UINT32(user_data->data, index); + Stream_Write_UINT32(user_data->data, act_len); + Stream_Write_UINT32(user_data->data, transfer->iso_packet_desc[i].status); + + if (transfer->iso_packet_desc[i].status != USBD_STATUS_SUCCESS) + user_data->ErrorCount++; + else + { + const unsigned char* packetBuffer = + libusb_get_iso_packet_buffer_simple(transfer, i); + BYTE* data = dataStart + index; + + if (data != packetBuffer) + memmove(data, packetBuffer, act_len); + + index += act_len; + } + } + } + /* fallthrough */ + + case LIBUSB_TRANSFER_CANCELLED: + case LIBUSB_TRANSFER_TIMED_OUT: + case LIBUSB_TRANSFER_ERROR: + { + const UINT32 InterfaceId = + ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev)); + + if (list_contains(list, streamID)) + { + if (!user_data->noack) + { + const UINT32 RequestID = streamID & INTERFACE_ID_MASK; + user_data->cb(user_data->idev, user_data->callback, user_data->data, + InterfaceId, user_data->noack, user_data->MessageId, RequestID, + transfer->num_iso_packets, transfer->status, + user_data->StartFrame, user_data->ErrorCount, + user_data->OutputBufferSize); + user_data->data = NULL; + } + ArrayList_Remove(list, transfer); + } + } + break; + default: + break; + } + ArrayList_Unlock(list); +} + +static const LIBUSB_ENDPOINT_DESCEIPTOR* func_get_ep_desc(LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig, + MSUSB_CONFIG_DESCRIPTOR* MsConfig, + UINT32 EndpointAddress) +{ + BYTE alt; + UINT32 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 LIBUSB_CALL func_bulk_transfer_cb(struct libusb_transfer* transfer) +{ + ASYNC_TRANSFER_USER_DATA* user_data; + uint32_t streamID; + wArrayList* list; + + user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + { + WLog_ERR(TAG, "[%s]: Invalid transfer->user_data!"); + return; + } + list = user_data->queue; + ArrayList_Lock(list); + streamID = stream_id_from_buffer(transfer); + + if (list_contains(list, streamID)) + { + const UINT32 InterfaceId = + ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev)); + const UINT32 RequestID = streamID & INTERFACE_ID_MASK; + + user_data->cb(user_data->idev, user_data->callback, user_data->data, InterfaceId, + user_data->noack, user_data->MessageId, RequestID, transfer->num_iso_packets, + transfer->status, user_data->StartFrame, user_data->ErrorCount, + transfer->actual_length); + user_data->data = NULL; + ArrayList_Remove(list, transfer); + } + ArrayList_Unlock(list); +} + +static BOOL func_set_usbd_status(URBDRC_PLUGIN* urbdrc, UDEVICE* pdev, UINT32* status, + int err_result) +{ + if (!urbdrc || !status) + return FALSE; + + switch (err_result) + { + case LIBUSB_SUCCESS: + *status = USBD_STATUS_SUCCESS; + break; + + case LIBUSB_ERROR_IO: + *status = USBD_STATUS_STALL_PID; + 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; + } + + 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 TRUE; +} + +static int func_config_release_all_interface(URBDRC_PLUGIN* urbdrc, + LIBUSB_DEVICE_HANDLE* libusb_handle, + UINT32 NumInterfaces) +{ + UINT32 i; + + for (i = 0; i < NumInterfaces; i++) + { + int ret = libusb_release_interface(libusb_handle, i); + + if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_release_interface", ret)) + return -1; + } + + return 0; +} + +static int func_claim_all_interface(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE_HANDLE* libusb_handle, + int NumInterfaces) +{ + int i, ret; + + for (i = 0; i < NumInterfaces; i++) + { + ret = libusb_claim_interface(libusb_handle, i); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_claim_interface", ret)) + return -1; + } + + return 0; +} + +static LIBUSB_DEVICE* udev_get_libusb_dev(libusb_context* context, uint8_t bus_number, + uint8_t dev_number) +{ + ssize_t i, total_device; + LIBUSB_DEVICE** libusb_list; + LIBUSB_DEVICE* device = NULL; + total_device = libusb_get_device_list(context, &libusb_list); + + for (i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + if ((bus_number == libusb_get_bus_number(dev)) && + (dev_number == libusb_get_device_address(dev))) + { + device = dev; + } + else + { + libusb_unref_device(dev); + } + } + + libusb_free_device_list(libusb_list, 0); + return device; +} + +static LIBUSB_DEVICE_DESCRIPTOR* udev_new_descript(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE* libusb_dev) +{ + int ret; + LIBUSB_DEVICE_DESCRIPTOR* descriptor = + (LIBUSB_DEVICE_DESCRIPTOR*)calloc(1, sizeof(LIBUSB_DEVICE_DESCRIPTOR)); + if (!descriptor) + return NULL; + ret = libusb_get_device_descriptor(libusb_dev, descriptor); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_device_descriptor", ret)) + { + free(descriptor); + return NULL; + } + + return descriptor; +} + +static int libusb_udev_select_interface(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting) +{ + int error = 0, diff = 0; + UDEVICE* pdev = (UDEVICE*)idev; + URBDRC_PLUGIN* urbdrc; + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + + if (!pdev || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + MsConfig = pdev->MsConfig; + + if (MsConfig) + { + MsInterfaces = MsConfig->MsInterfaces; + if (MsInterfaces) + { + WLog_Print(urbdrc->log, WLOG_INFO, + "select Interface(%" PRIu8 ") curr AlternateSetting(%" PRIu8 + ") new AlternateSetting(" PRIu8 ")", + InterfaceNumber, MsInterfaces[InterfaceNumber]->AlternateSetting, + AlternateSetting); + + if (MsInterfaces[InterfaceNumber]->AlternateSetting != AlternateSetting) + { + diff = 1; + } + } + + if (diff) + { + error = libusb_set_interface_alt_setting(pdev->libusb_handle, InterfaceNumber, + AlternateSetting); + + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_interface_alt_setting", 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; + URBDRC_PLUGIN* urbdrc; + UINT32 inum = 0, pnum = 0, MsOutSize = 0; + + if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc || !MsConfig) + return NULL; + + urbdrc = pdev->urbdrc; + LibusbConfig = pdev->LibusbConfig; + + if (LibusbConfig->bNumInterfaces != MsConfig->NumInterfaces) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "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*)calloc(1, 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; + LIBUSB_DEVICE_HANDLE* libusb_handle; + LIBUSB_DEVICE* libusb_dev; + URBDRC_PLUGIN* urbdrc; + LIBUSB_CONFIG_DESCRIPTOR** LibusbConfig; + int ret = 0; + + if (!pdev || !pdev->MsConfig || !pdev->LibusbConfig || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + MsConfig = pdev->MsConfig; + libusb_handle = pdev->libusb_handle; + libusb_dev = pdev->libusb_dev; + LibusbConfig = &pdev->LibusbConfig; + + if (MsConfig->InitCompleted) + { + func_config_release_all_interface(pdev->urbdrc, 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 (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret)) + { + func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + else + { + ret = libusb_get_active_config_descriptor(libusb_dev, LibusbConfig); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret)) + { + func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + } + + func_claim_all_interface(urbdrc, 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; + return error; +} + +static UINT32 libusb_udev_control_query_device_text(IUDEVICE* idev, UINT32 TextType, + UINT16 LocaleId, UINT8* BufferSize, + BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*)idev; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor; + const char strDesc[] = "Generic Usb String"; + char deviceLocation[25] = { 0 }; + BYTE bus_number; + BYTE device_address; + int ret = 0; + size_t i, len; + URBDRC_PLUGIN* urbdrc; + WCHAR* text = (WCHAR*)Buffer; + BYTE slen, locale; + const UINT8 inSize = *BufferSize; + + *BufferSize = 0; + if (!pdev || !pdev->devDescriptor || !pdev->urbdrc) + return ERROR_INVALID_DATA; + + urbdrc = pdev->urbdrc; + devDescriptor = pdev->devDescriptor; + + switch (TextType) + { + case DeviceTextDescription: + { + BYTE data[0x100] = { 0 }; + ret = libusb_get_string_descriptor(pdev->libusb_handle, devDescriptor->iProduct, + LocaleId, data, 0xFF); + /* The returned data in the buffer is: + * 1 byte length of following data + * 1 byte descriptor type, must be 0x03 for strings + * n WCHAR unicode string (of length / 2 characters) including '\0' + */ + slen = data[0]; + locale = data[1]; + + if ((ret <= 0) || (ret <= 4) || (slen <= 4) || (locale != LIBUSB_DT_STRING) || + (ret > UINT8_MAX)) + { + const char* msg = "SHORT_DESCRIPTOR"; + if (ret < 0) + msg = libusb_error_name(ret); + WLog_Print(urbdrc->log, WLOG_DEBUG, + "libusb_get_string_descriptor: " + "%s [%d], iProduct: %" PRIu8 "!", + msg, ret, devDescriptor->iProduct); + + len = MIN(sizeof(strDesc), inSize); + for (i = 0; i < len; i++) + text[i] = (WCHAR)strDesc[i]; + + *BufferSize = (BYTE)(len * 2); + } + else + { + /* ret and slen should be equals, but you never know creativity + * of device manufacturers... + * So also check the string length returned as server side does + * not honor strings with multi '\0' characters well. + */ + const size_t rchar = _wcsnlen((WCHAR*)&data[2], sizeof(data) / 2); + len = MIN((BYTE)ret, slen); + len = MIN(len, inSize); + len = MIN(len, rchar * 2 + sizeof(WCHAR)); + memcpy(Buffer, &data[2], len); + + /* Just as above, the returned WCHAR string should be '\0' + * terminated, but never trust hardware to conform to specs... */ + Buffer[len - 2] = '\0'; + Buffer[len - 1] = '\0'; + *BufferSize = (BYTE)len; + } + } + break; + + case DeviceTextLocationInformation: + bus_number = libusb_get_bus_number(pdev->libusb_dev); + device_address = libusb_get_device_address(pdev->libusb_dev); + sprintf_s(deviceLocation, sizeof(deviceLocation), + "Port_#%04" PRIu8 ".Hub_#%04" PRIu8 "", device_address, bus_number); + + len = strnlen(deviceLocation, + MIN(sizeof(deviceLocation), (inSize > 0) ? inSize - 1U : 0)); + for (i = 0; i < len; i++) + text[i] = (WCHAR)deviceLocation[i]; + text[len++] = '\0'; + *BufferSize = (UINT8)(len * sizeof(WCHAR)); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "Query Text: unknown TextType %" PRIu32 "", + TextType); + return ERROR_INVALID_DATA; + } + + return S_OK; +} + +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] = { 0 }; + int error = 0; + /* + pdev->request_queue->register_request(pdev->request_queue, RequestId, NULL, 0); + */ + error = libusb_control_transfer(pdev->libusb_handle, LIBUSB_ENDPOINT_IN | Recipient, + LIBUSB_REQUEST_GET_DESCRIPTOR, 0x03ee, 0, ms_string_desc, 0x12, + Timeout); + + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error); + + if (error > 0) + { + const BYTE bMS_Vendorcode = ms_string_desc[16]; + /** 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); + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error); + + if (error >= 0) + *BufferSize = error; + } + + if (error < 0) + *UsbdStatus = USBD_STATUS_STALL_PID; + else + *UsbdStatus = USBD_STATUS_SUCCESS; + + return ERROR_SUCCESS; +} + +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 BOOL libusb_udev_detach_kernel_driver(IUDEVICE* idev) +{ + int i, err = 0; + UDEVICE* pdev = (UDEVICE*)idev; + URBDRC_PLUGIN* urbdrc; + + if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc) + return FALSE; + +#ifdef _WIN32 + return TRUE; +#else + urbdrc = pdev->urbdrc; + + 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); + log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_kernel_driver_active", err); + + if (err) + { + err = libusb_detach_kernel_driver(pdev->libusb_handle, i); + log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_detach_kernel_driver", err); + } + } + + pdev->status |= URBDRC_DEVICE_DETACH_KERNEL; + } + + return TRUE; +#endif +} + +static BOOL libusb_udev_attach_kernel_driver(IUDEVICE* idev) +{ + int i, err = 0; + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc) + return FALSE; + + for (i = 0; i < pdev->LibusbConfig->bNumInterfaces && err != LIBUSB_ERROR_NO_DEVICE; i++) + { + err = libusb_release_interface(pdev->libusb_handle, i); + + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_release_interface", err); + +#ifndef _WIN32 + if (err != LIBUSB_ERROR_NO_DEVICE) + { + err = libusb_attach_kernel_driver(pdev->libusb_handle, i); + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_attach_kernel_driver if=%d", + err, i); + } +#endif + } + + return TRUE; +} + +static int libusb_udev_is_composite_device(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return pdev->isCompositeDevice; +} + +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; + IUDEVMAN* udevman; + if (!pdev || !pdev->urbdrc) + return 1; + + udevman = pdev->urbdrc->udevman; + if (udevman) + { + if (udevman->status & URBDRC_DEVICE_CHANNEL_CLOSED) + return 1; + } + + if (pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) + return 1; + + return 0; +} + +static int libusb_udev_is_already_send(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return (pdev->status & URBDRC_DEVICE_ALREADY_SEND) ? 1 : 0; +} + +/* This is called from channel cleanup code. + * Avoid double free, just remove the device and mark the channel closed. */ +static void libusb_udev_mark_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0)) + { + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + const uint8_t busNr = idev->get_bus_number(idev); + const uint8_t devNr = idev->get_dev_number(idev); + + pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr); + } +} + +/* This is called by local events where the device is removed or in an error + * state. Remove the device from redirection and close the channel. */ +static void libusb_udev_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0)) + { + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + const uint8_t busNr = idev->get_bus_number(idev); + const uint8_t devNr = idev->get_dev_number(idev); + IWTSVirtualChannel* channel = NULL; + + if (pdev->channelManager) + channel = IFCALLRESULT(NULL, pdev->channelManager->FindChannelById, + pdev->channelManager, pdev->channelID); + + pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + + if (channel) + channel->Write(channel, 0, NULL, NULL); + + urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr); + } +} + +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_query_device_port_status(IUDEVICE* idev, UINT32* UsbdStatus, + UINT32* BufferSize, BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*)idev; + int success = 0, ret; + URBDRC_PLUGIN* urbdrc; + + if (!pdev || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + + 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 (log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", ret)) + *BufferSize = 0; + else + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "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_isoch_transfer(IUDEVICE* idev, URBDRC_CHANNEL_CALLBACK* callback, + UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress, + UINT32 TransferFlags, UINT32 StartFrame, UINT32 ErrorCount, + BOOL NoAck, const BYTE* packetDescriptorData, + UINT32 NumberOfPackets, UINT32 BufferSize, const BYTE* Buffer, + t_isoch_transfer_cb cb, UINT32 Timeout) +{ + UINT32 iso_packet_size; + UDEVICE* pdev = (UDEVICE*)idev; + ASYNC_TRANSFER_USER_DATA* user_data; + struct libusb_transfer* iso_transfer = NULL; + URBDRC_PLUGIN* urbdrc; + size_t outSize = (NumberOfPackets * 12); + uint32_t streamID = 0x40000000 | RequestId; + + if (!pdev || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + user_data = async_transfer_user_data_new(idev, MessageId, 48, BufferSize, Buffer, + outSize + 1024, NoAck, cb, callback); + + if (!user_data) + return -1; + + user_data->ErrorCount = ErrorCount; + user_data->StartFrame = StartFrame; + + if (!Buffer) + Stream_Seek(user_data->data, (NumberOfPackets * 12)); + + iso_packet_size = BufferSize / NumberOfPackets; + iso_transfer = libusb_alloc_transfer(NumberOfPackets); + + if (iso_transfer == NULL) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "Error: libusb_alloc_transfer."); + async_transfer_user_data_free(user_data); + return -1; + } + + /** process URB_FUNCTION_IOSCH_TRANSFER */ + libusb_fill_iso_transfer(iso_transfer, pdev->libusb_handle, EndpointAddress, + Stream_Pointer(user_data->data), BufferSize, NumberOfPackets, + func_iso_callback, user_data, Timeout); + set_stream_id_for_buffer(iso_transfer, streamID); + libusb_set_iso_packet_lengths(iso_transfer, iso_packet_size); + + if (ArrayList_Add(pdev->request_queue, iso_transfer) < 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Failed to queue iso transfer, streamID %08" PRIx32 " already in use!", + streamID); + request_free(iso_transfer); + return -1; + } + return libusb_submit_transfer(iso_transfer); +} + +static BOOL 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; + + if (!pdev || !pdev->urbdrc) + return FALSE; + + status = libusb_control_transfer(pdev->libusb_handle, bmRequestType, Request, Value, Index, + Buffer, *BufferSize, Timeout); + + if (status >= 0) + *BufferSize = (UINT32)status; + else + log_libusb_result(pdev->urbdrc->log, WLOG_ERROR, "libusb_control_transfer", status); + + if (!func_set_usbd_status(pdev->urbdrc, pdev, UrbdStatus, status)) + return FALSE; + + return TRUE; +} + +static int libusb_udev_bulk_or_interrupt_transfer(IUDEVICE* idev, URBDRC_CHANNEL_CALLBACK* callback, + UINT32 MessageId, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, + BOOL NoAck, UINT32 BufferSize, const BYTE* data, + t_isoch_transfer_cb cb, UINT32 Timeout) +{ + UINT32 transfer_type; + UDEVICE* pdev = (UDEVICE*)idev; + const LIBUSB_ENDPOINT_DESCEIPTOR* ep_desc; + struct libusb_transfer* transfer = NULL; + URBDRC_PLUGIN* urbdrc; + ASYNC_TRANSFER_USER_DATA* user_data; + uint32_t streamID = 0x80000000 | RequestId; + + if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + user_data = + async_transfer_user_data_new(idev, MessageId, 36, BufferSize, data, 0, NoAck, cb, callback); + + if (!user_data) + return -1; + + /* alloc memory for urb transfer */ + transfer = libusb_alloc_transfer(0); + if (!transfer) + { + async_transfer_user_data_free(user_data); + return -1; + } + + ep_desc = func_get_ep_desc(pdev->LibusbConfig, pdev->MsConfig, EndpointAddress); + + if (!ep_desc) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "func_get_ep_desc: endpoint 0x%" PRIx32 " not found", + EndpointAddress); + request_free(transfer); + return -1; + } + + transfer_type = (ep_desc->bmAttributes) & 0x3; + WLog_Print(urbdrc->log, WLOG_DEBUG, + "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 */ + libusb_fill_bulk_transfer(transfer, pdev->libusb_handle, EndpointAddress, + Stream_Pointer(user_data->data), BufferSize, + func_bulk_transfer_cb, user_data, Timeout); + break; + + case INTERRUPT_TRANSFER: + /** Interrupt Transfer */ + libusb_fill_interrupt_transfer(transfer, pdev->libusb_handle, EndpointAddress, + Stream_Pointer(user_data->data), BufferSize, + func_bulk_transfer_cb, user_data, Timeout); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_bulk_or_interrupt_transfer:" + " other transfer type 0x%" PRIX32 "", + transfer_type); + request_free(transfer); + return -1; + } + + set_stream_id_for_buffer(transfer, streamID); + + if (ArrayList_Add(pdev->request_queue, transfer) < 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Failed to queue transfer, streamID %08" PRIx32 " already in use!", streamID); + request_free(transfer); + return -1; + } + return libusb_submit_transfer(transfer); +} + +static int func_cancel_xact_request(URBDRC_PLUGIN* urbdrc, struct libusb_transfer* transfer) +{ + int status; + + if (!urbdrc || !transfer) + return -1; + + status = libusb_cancel_transfer(transfer); + + if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_cancel_transfer", status)) + { + if (status == LIBUSB_ERROR_NOT_FOUND) + return -1; + } + else + return 1; + + return 0; +} + +static void libusb_udev_cancel_all_transfer_request(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + int count, x; + + if (!pdev || !pdev->request_queue || !pdev->urbdrc) + return; + + ArrayList_Lock(pdev->request_queue); + count = ArrayList_Count(pdev->request_queue); + + for (x = 0; x < count; x++) + { + struct libusb_transfer* transfer = ArrayList_GetItem(pdev->request_queue, x); + func_cancel_xact_request(pdev->urbdrc, transfer); + } + + ArrayList_Unlock(pdev->request_queue); +} + +static int libusb_udev_cancel_transfer_request(IUDEVICE* idev, UINT32 RequestId) +{ + int rc = -1; + UDEVICE* pdev = (UDEVICE*)idev; + struct libusb_transfer* transfer; + uint32_t cancelID1 = 0x40000000 | RequestId; + uint32_t cancelID2 = 0x80000000 | RequestId; + + if (!idev || !pdev->urbdrc || !pdev->request_queue) + return -1; + + ArrayList_Lock(pdev->request_queue); + transfer = list_contains(pdev->request_queue, cancelID1); + if (!transfer) + transfer = list_contains(pdev->request_queue, cancelID2); + + if (transfer) + { + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pdev->urbdrc; + + rc = func_cancel_xact_request(urbdrc, transfer); + } + ArrayList_Unlock(pdev->request_queue); + return rc; +} + +BASIC_STATE_FUNC_DEFINED(channelManager, IWTSVirtualChannelManager*) +BASIC_STATE_FUNC_DEFINED(channelID, UINT32) +BASIC_STATE_FUNC_DEFINED(ReqCompletion, UINT32) +BASIC_STATE_FUNC_DEFINED(bus_number, BYTE) +BASIC_STATE_FUNC_DEFINED(dev_number, BYTE) +BASIC_STATE_FUNC_DEFINED(port_number, int) +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 UINT32 udev_get_UsbDevice(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev) + return 0; + + return pdev->UsbDevice; +} + +static void udev_set_UsbDevice(IUDEVICE* idev, UINT32 val) +{ + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev) + return; + + pdev->UsbDevice = val; +} + +static void udev_free(IUDEVICE* idev) +{ + int rc; + UDEVICE* udev = (UDEVICE*)idev; + URBDRC_PLUGIN* urbdrc; + + if (!idev || !udev->urbdrc) + return; + + urbdrc = udev->urbdrc; + + libusb_udev_cancel_all_transfer_request(&udev->iface); + if (udev->libusb_handle) + { + rc = libusb_reset_device(udev->libusb_handle); + + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_reset_device", rc); + } + + /* release all interface and attach kernel driver */ + udev->iface.attach_kernel_driver(idev); + ArrayList_Free(udev->request_queue); + /* free the config descriptor that send from windows */ + msusb_msconfig_free(udev->MsConfig); + libusb_unref_device(udev->libusb_dev); + libusb_close(udev->libusb_handle); + libusb_close(udev->hub_handle); + free(udev->devDescriptor); + free(idev); +} + +static void udev_load_interface(UDEVICE* pdev) +{ + /* load interface */ + /* Basic */ + BASIC_STATE_FUNC_REGISTER(channelManager, pdev); + BASIC_STATE_FUNC_REGISTER(channelID, 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(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.isExist = libusb_udev_is_exist; + pdev->iface.isAlreadySend = libusb_udev_is_already_send; + pdev->iface.isChannelClosed = libusb_udev_is_channel_closed; + pdev->iface.setAlreadySend = libusb_udev_set_already_send; + pdev->iface.setChannelClosed = libusb_udev_channel_closed; + pdev->iface.markChannelClosed = libusb_udev_mark_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.query_device_port_status = libusb_udev_query_device_port_status; + pdev->iface.free = udev_free; +} + +static int udev_get_device_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev, + UINT16 bus_number, UINT16 dev_number) +{ + int error; + ssize_t i, total_device; + uint8_t port_numbers[16]; + LIBUSB_DEVICE** libusb_list; + total_device = libusb_get_device_list(ctx, &libusb_list); + /* Look for device. */ + error = -1; + + for (i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + + if ((bus_number != libusb_get_bus_number(dev)) || + (dev_number != libusb_get_device_address(dev))) + continue; + + error = libusb_open(dev, &pdev->libusb_handle); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error)) + break; + + /* get port number */ + error = libusb_get_port_numbers(dev, port_numbers, sizeof(port_numbers)); + + if (error < 1) + { + /* Prevent open hub, treat as error. */ + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_port_numbers", error); + break; + } + + pdev->port_number = port_numbers[(error - 1)]; + error = 0; + WLog_Print(urbdrc->log, WLOG_DEBUG, " Port: %d", pdev->port_number); + /* gen device path */ + sprintf(pdev->path, "%" PRIu16 "-%" PRIu16 "", bus_number, pdev->port_number); + + WLog_Print(urbdrc->log, WLOG_DEBUG, " DevPath: %s", pdev->path); + break; + } + libusb_free_device_list(libusb_list, 1); + + if (error < 0) + return -1; + return 0; +} + +static int udev_get_hub_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev, + UINT16 bus_number, UINT16 dev_number) +{ + int error; + ssize_t i, total_device; + LIBUSB_DEVICE** libusb_list; + LIBUSB_DEVICE_HANDLE* handle; + total_device = libusb_get_device_list(ctx, &libusb_list); + + /* Look for device hub. */ + error = -1; + + for (i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + + if ((bus_number != libusb_get_bus_number(dev)) || + (1 != libusb_get_device_address(dev))) /* Root hub allways first on bus. */ + continue; + + WLog_Print(urbdrc->log, WLOG_DEBUG, " Open hub: %" PRIu16 "", bus_number); + error = libusb_open(dev, &handle); + + if (!log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error)) + pdev->hub_handle = handle; + + break; + } + + libusb_free_device_list(libusb_list, 1); + + if (error < 0) + return -1; + + return 0; +} + +static void request_free(void* value) +{ + ASYNC_TRANSFER_USER_DATA* user_data; + struct libusb_transfer* transfer = (struct libusb_transfer*)value; + if (!transfer) + return; + + user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + async_transfer_user_data_free(user_data); + transfer->user_data = NULL; + libusb_free_transfer(transfer); +} + +static IUDEVICE* udev_init(URBDRC_PLUGIN* urbdrc, libusb_context* context, LIBUSB_DEVICE* device, + BYTE bus_number, BYTE dev_number) +{ + UDEVICE* pdev; + int status = LIBUSB_ERROR_OTHER; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor; + LIBUSB_CONFIG_DESCRIPTOR* config_temp; + LIBUSB_INTERFACE_DESCRIPTOR interface_temp; + pdev = (PUDEVICE)calloc(1, sizeof(UDEVICE)); + + if (!pdev) + return NULL; + + pdev->urbdrc = urbdrc; + udev_load_interface(pdev); + + if (device) + pdev->libusb_dev = device; + else + pdev->libusb_dev = udev_get_libusb_dev(context, bus_number, dev_number); + + if (pdev->libusb_dev == NULL) + goto fail; + + if (urbdrc->listener_callback) + udev_set_channelManager(&pdev->iface, urbdrc->listener_callback->channel_mgr); + + /* Get DEVICE handle */ + status = udev_get_device_handle(urbdrc, context, pdev, bus_number, dev_number); + if (status != LIBUSB_SUCCESS) + { + struct libusb_device_descriptor desc; + const uint8_t port = libusb_get_port_number(pdev->libusb_dev); + libusb_get_device_descriptor(pdev->libusb_dev, &desc); + + log_libusb_result(urbdrc->log, WLOG_ERROR, + "libusb_open [b=0x%02X,p=0x%02X,a=0x%02X,VID=0x%04X,PID=0x%04X]", status, + bus_number, port, dev_number, desc.idVendor, desc.idProduct); + goto fail; + } + + /* Get HUB handle */ + status = udev_get_hub_handle(urbdrc, context, pdev, bus_number, dev_number); + + if (status < 0) + pdev->hub_handle = NULL; + + pdev->devDescriptor = udev_new_descript(urbdrc, pdev->libusb_dev); + + if (!pdev->devDescriptor) + goto fail; + + status = libusb_get_active_config_descriptor(pdev->libusb_dev, &pdev->LibusbConfig); + + if (status == LIBUSB_ERROR_NOT_FOUND) + status = libusb_get_config_descriptor(pdev->libusb_dev, 0, &pdev->LibusbConfig); + + if (status < 0) + goto fail; + + config_temp = pdev->LibusbConfig; + /* get the first interface and first altsetting */ + interface_temp = config_temp->interface[0].altsetting[0]; + WLog_Print(urbdrc->log, WLOG_DEBUG, + "Registered Device: Vid: 0x%04" PRIX16 " Pid: 0x%04" PRIX16 "" + " InterfaceClass = %s", + pdev->devDescriptor->idVendor, pdev->devDescriptor->idProduct, + usb_interface_class_to_string(interface_temp.bInterfaceClass)); + /* Check composite device */ + devDescriptor = pdev->devDescriptor; + + if ((devDescriptor->bNumConfigurations == 1) && (config_temp->bNumInterfaces > 1) && + (devDescriptor->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE)) + { + pdev->isCompositeDevice = 1; + } + else if ((devDescriptor->bDeviceClass == 0xef) && + (devDescriptor->bDeviceSubClass == LIBUSB_CLASS_COMM) && + (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->bus_number = bus_number; + pdev->dev_number = dev_number; + pdev->request_queue = ArrayList_New(TRUE); + + if (!pdev->request_queue) + goto fail; + + ArrayList_Object(pdev->request_queue)->fnObjectFree = request_free; + + /* set config of windows */ + pdev->MsConfig = msusb_msconfig_new(); + + if (!pdev->MsConfig) + goto fail; + + // deb_config_msg(pdev->libusb_dev, config_temp, devDescriptor->bNumConfigurations); + return &pdev->iface; +fail: + pdev->iface.free(&pdev->iface); + return NULL; +} + +size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct, + IUDEVICE*** devArray) +{ + LIBUSB_DEVICE** libusb_list; + UDEVICE** array; + ssize_t i, total_device; + size_t num = 0; + + if (!urbdrc || !devArray) + return 0; + + WLog_Print(urbdrc->log, WLOG_INFO, "VID: 0x%04" PRIX16 ", PID: 0x%04" PRIX16 "", idVendor, + idProduct); + total_device = libusb_get_device_list(ctx, &libusb_list); + array = (UDEVICE**)calloc(total_device, sizeof(UDEVICE*)); + + if (!array) + goto fail; + + for (i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + LIBUSB_DEVICE_DESCRIPTOR* descriptor = udev_new_descript(urbdrc, dev); + + if ((descriptor->idVendor == idVendor) && (descriptor->idProduct == idProduct)) + { + array[num] = (PUDEVICE)udev_init(urbdrc, ctx, dev, libusb_get_bus_number(dev), + libusb_get_device_address(dev)); + + if (array[num] != NULL) + num++; + } + else + { + libusb_unref_device(dev); + } + + free(descriptor); + } + +fail: + libusb_free_device_list(libusb_list, 0); + *devArray = (IUDEVICE**)array; + return num; +} + +IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* context, BYTE bus_number, + BYTE dev_number) +{ + WLog_Print(urbdrc->log, WLOG_DEBUG, "bus:%d dev:%d", bus_number, dev_number); + return udev_init(urbdrc, context, NULL, 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..a5f836b --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevice.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_LIBUSB_UDEVICE_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H + +#include +#include + +#include "urbdrc_types.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 */ + IWTSVirtualChannelManager* channelManager; + UINT32 channelID; + UINT16 status; + BYTE bus_number; + BYTE 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; + + wArrayList* request_queue; + + URBDRC_PLUGIN* urbdrc; +}; +typedef UDEVICE* PUDEVICE; + +size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct, + IUDEVICE*** devArray); +IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, BYTE bus_number, + BYTE dev_number); +const char* usb_interface_class_to_string(uint8_t class); + +#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..5f1e9e0 --- /dev/null +++ b/channels/urbdrc/client/libusb/libusb_udevman.c @@ -0,0 +1,975 @@ +/** + * 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 + +#include "urbdrc_types.h" +#include "urbdrc_main.h" + +#include "libusb_udevice.h" + +#include + +#if !defined(LIBUSB_HOTPLUG_NO_FLAGS) +#define LIBUSB_HOTPLUG_NO_FLAGS 0 +#endif + +#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 _VID_PID_PAIR VID_PID_PAIR; + +struct _VID_PID_PAIR +{ + UINT16 vid; + UINT16 pid; +}; + +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 */ + + LPSTR devices_vid_pid; + LPSTR devices_addr; + wArrayList* hotplug_vid_pids; + UINT16 flags; + UINT32 device_num; + UINT32 next_device_id; + UINT32 channel_id; + + HANDLE devman_loading; + libusb_context* context; + HANDLE thread; + BOOL running; +}; +typedef UDEVMAN* PUDEVMAN; + +static BOOL poll_libusb_events(UDEVMAN* udevman); + +static void udevman_rewind(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + udevman->idev = udevman->head; +} + +static BOOL udevman_has_next(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!udevman || !udevman->idev) + return FALSE; + else + return TRUE; +} + +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, BYTE bus_number, BYTE dev_number) +{ + IUDEVICE* dev = NULL; + + if (!idevman) + return NULL; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + IUDEVICE* pdev = idevman->get_next(idevman); + + if ((pdev->get_bus_number(pdev) == bus_number) && + (pdev->get_dev_number(pdev) == dev_number)) + { + dev = pdev; + break; + } + } + + idevman->loading_unlock(idevman); + return dev; +} + +static size_t udevman_register_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number, + UINT16 idVendor, UINT16 idProduct, UINT32 flag) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + IUDEVICE* pdev = NULL; + IUDEVICE** devArray; + URBDRC_PLUGIN* urbdrc; + size_t i, num, addnum = 0; + + if (!idevman || !idevman->plugin) + return 0; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + pdev = (IUDEVICE*)udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + + if (pdev != NULL) + return 0; + + if (flag & UDEVMAN_FLAG_ADD_BY_ADDR) + { + UINT32 id; + IUDEVICE* tdev = udev_new_by_addr(urbdrc, udevman->context, bus_number, dev_number); + + if (tdev == NULL) + return 0; + + id = idevman->get_next_device_id(idevman); + tdev->set_UsbDevice(tdev, id); + idevman->loading_lock(idevman); + + if (udevman->head == NULL) + { + /* linked list is empty */ + udevman->head = tdev; + udevman->tail = tdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, tdev); + tdev->set_p_prev(tdev, udevman->tail); + udevman->tail = tdev; + } + + 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(urbdrc, udevman->context, idVendor, idProduct, &devArray); + + if (num == 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Could not find or redirect any usb devices by id %04x:%04x", idVendor, idProduct); + } + + for (i = 0; i < num; i++) + { + UINT32 id; + IUDEVICE* tdev = devArray[i]; + + if (udevman_get_udevice_by_addr(idevman, tdev->get_bus_number(tdev), + tdev->get_dev_number(tdev)) != NULL) + { + tdev->free(tdev); + devArray[i] = NULL; + continue; + } + + id = idevman->get_next_device_id(idevman); + tdev->set_UsbDevice(tdev, id); + idevman->loading_lock(idevman); + + if (udevman->head == NULL) + { + /* linked list is empty */ + udevman->head = tdev; + udevman->tail = tdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, tdev); + tdev->set_p_prev(tdev, udevman->tail); + udevman->tail = tdev; + } + + udevman->device_num += 1; + idevman->loading_unlock(idevman); + addnum++; + } + + free(devArray); + return addnum; + } + else + { + WLog_Print(urbdrc->log, WLOG_ERROR, "udevman_register_udevice: Invalid flag=%08 " PRIx32, + flag); + return 0; + } + + return 1; +} + +static BOOL udevman_unregister_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + UDEVICE* pdev; + UDEVICE* dev = (UDEVICE*)udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + + if (!dev || !idevman) + return FALSE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + 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) + { + dev->iface.free(&dev->iface); + return TRUE; /* unregistration successful */ + } + + /* if we reach this point, the device wasn't found */ + return FALSE; +} + +static BOOL udevman_unregister_all_udevices(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!idevman) + return FALSE; + + if (!udevman->head) + return TRUE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + UDEVICE* dev = (UDEVICE*)idevman->get_next(idevman); + + if (!dev) + continue; + + /* set previous device to point to next device */ + if (dev->prev != NULL) + { + /* unregistered device is not the head */ + UDEVICE* 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 */ + UDEVICE* pdev = (UDEVICE*)dev->next; + pdev->prev = dev->prev; + } + else + { + /* unregistered device is the tail, update tail */ + udevman->tail = (IUDEVICE*)dev->prev; + } + + dev->iface.free(&dev->iface); + udevman->device_num--; + } + + idevman->loading_unlock(idevman); + + return TRUE; +} + +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(IUDEVMAN* idevman, UINT32 UsbDevice) +{ + UDEVICE* pdev; + URBDRC_PLUGIN* urbdrc; + + if (!idevman || !idevman->plugin) + return NULL; + + /* Mask highest 2 bits, must be ignored */ + UsbDevice = UsbDevice & INTERFACE_ID_MASK; + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + 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); + WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to deviceId=%08" PRIx32, + UsbDevice); + return NULL; +} + +static IUDEVICE* udevman_get_udevice_by_ChannelID(IUDEVMAN* idevman, UINT32 channelID) +{ + UDEVICE* pdev; + URBDRC_PLUGIN* urbdrc; + + if (!idevman || !idevman->plugin) + return NULL; + + /* Mask highest 2 bits, must be ignored */ + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*)idevman->get_next(idevman); + + if (pdev->channelID == channelID) + { + idevman->loading_unlock(idevman); + return (IUDEVICE*)pdev; + } + } + + idevman->loading_unlock(idevman); + WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to channelID=%08" PRIx32, + channelID); + return NULL; +} + +static void udevman_loading_lock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + WaitForSingleObject(udevman->devman_loading, INFINITE); +} + +static void udevman_loading_unlock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + ReleaseMutex(udevman->devman_loading); +} + +BASIC_STATE_FUNC_DEFINED(device_num, UINT32) + +static UINT32 udevman_get_next_device_id(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + return udevman->next_device_id++; +} + +static void udevman_set_next_device_id(IUDEVMAN* idevman, UINT32 _t) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + udevman->next_device_id = _t; +} + +static void udevman_free(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!udevman) + return; + + udevman->running = FALSE; + if (udevman->thread) + { + WaitForSingleObject(udevman->thread, INFINITE); + CloseHandle(udevman->thread); + } + + udevman_unregister_all_udevices(idevman); + + if (udevman->devman_loading) + CloseHandle(udevman->devman_loading); + + libusb_exit(udevman->context); + + ArrayList_Free(udevman->hotplug_vid_pids); + free(udevman); +} + +static BOOL filter_by_class(uint8_t bDeviceClass, uint8_t bDeviceSubClass) +{ + switch (bDeviceClass) + { + case LIBUSB_CLASS_AUDIO: + case LIBUSB_CLASS_HID: + case LIBUSB_CLASS_MASS_STORAGE: + case LIBUSB_CLASS_HUB: + case LIBUSB_CLASS_SMART_CARD: + return TRUE; + default: + break; + } + + switch (bDeviceSubClass) + { + default: + break; + } + + return FALSE; +} + +static BOOL append(char* dst, size_t length, const char* src) +{ + return winpr_str_append(src, dst, length, NULL); +} + +static BOOL device_is_filtered(struct libusb_device* dev, + const struct libusb_device_descriptor* desc, + libusb_hotplug_event event) +{ + char buffer[8192] = { 0 }; + char* what; + BOOL filtered = FALSE; + append(buffer, sizeof(buffer), usb_interface_class_to_string(desc->bDeviceClass)); + if (filter_by_class(desc->bDeviceClass, desc->bDeviceSubClass)) + filtered = TRUE; + + switch (desc->bDeviceClass) + { + case LIBUSB_CLASS_PER_INTERFACE: + { + struct libusb_config_descriptor* config = NULL; + int rc = libusb_get_active_config_descriptor(dev, &config); + if (rc == LIBUSB_SUCCESS) + { + uint8_t x; + + for (x = 0; x < config->bNumInterfaces; x++) + { + int y; + const struct libusb_interface* ifc = &config->interface[x]; + for (y = 0; y < ifc->num_altsetting; y++) + { + const struct libusb_interface_descriptor* const alt = &ifc->altsetting[y]; + if (filter_by_class(alt->bInterfaceClass, alt->bInterfaceSubClass)) + filtered = TRUE; + + append(buffer, sizeof(buffer), "|"); + append(buffer, sizeof(buffer), + usb_interface_class_to_string(alt->bInterfaceClass)); + } + } + } + libusb_free_config_descriptor(config); + } + break; + default: + break; + } + + if (filtered) + what = "Filtered"; + else + { + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + what = "Hotplug remove"; + break; + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + what = "Hotplug add"; + break; + default: + what = "Hotplug unknown"; + break; + } + } + + WLog_DBG(TAG, "%s device VID=0x%04X,PID=0x%04X class %s", what, desc->idVendor, desc->idProduct, + buffer); + return filtered; +} + +static int hotplug_callback(struct libusb_context* ctx, struct libusb_device* dev, + libusb_hotplug_event event, void* user_data) +{ + VID_PID_PAIR pair; + struct libusb_device_descriptor desc; + UDEVMAN* udevman = (UDEVMAN*)user_data; + const uint8_t bus = libusb_get_bus_number(dev); + const uint8_t addr = libusb_get_device_address(dev); + int rc = libusb_get_device_descriptor(dev, &desc); + + WINPR_UNUSED(ctx); + + if (rc != LIBUSB_SUCCESS) + return rc; + + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + pair.vid = desc.idVendor; + pair.pid = desc.idProduct; + if ((ArrayList_Contains(udevman->hotplug_vid_pids, &pair)) || + (udevman->iface.isAutoAdd(&udevman->iface) && + !device_is_filtered(dev, &desc, event))) + { + add_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor, + desc.idProduct); + } + break; + + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + del_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor, + desc.idProduct); + break; + + default: + break; + } + + return 0; +} + +static BOOL udevman_initialize(IUDEVMAN* idevman, UINT32 channelId) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!udevman) + return FALSE; + + idevman->status &= ~URBDRC_DEVICE_CHANNEL_CLOSED; + idevman->controlChannelId = channelId; + return TRUE; +} + +static BOOL udevman_vid_pid_pair_equals(const void* objA, const void* objB) +{ + const VID_PID_PAIR* a = objA; + const VID_PID_PAIR* b = objB; + + return (a->vid == b->vid) && (a->pid == b->pid); +} + +static BOOL udevman_parse_device_id_addr(const char** str, UINT16* id1, UINT16* id2, UINT16 max, + char split_sign, char delimiter) +{ + char* mid; + char* end; + unsigned long rc; + + rc = strtoul(*str, &mid, 16); + + if ((mid == *str) || (*mid != split_sign) || (rc > max)) + return FALSE; + + *id1 = (UINT16)rc; + rc = strtoul(++mid, &end, 16); + + if ((end == mid) || (rc > max)) + return FALSE; + + *id2 = (UINT16)rc; + + *str += end - *str; + if (*end == '\0') + return TRUE; + if (*end == delimiter) + { + (*str)++; + return TRUE; + } + + return FALSE; +} + +static BOOL urbdrc_udevman_register_devices(UDEVMAN* udevman, const char* devices, BOOL add_by_addr) +{ + const char* pos = devices; + VID_PID_PAIR* idpair; + UINT16 id1, id2; + + while (*pos != '\0') + { + if (!udevman_parse_device_id_addr(&pos, &id1, &id2, (add_by_addr) ? UINT8_MAX : UINT16_MAX, + ':', '#')) + { + WLog_ERR(TAG, "Invalid device argument: \"%s\"", devices); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (add_by_addr) + { + add_device(&udevman->iface, DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV, (UINT8)id1, + (UINT8)id2, 0, 0); + } + else + { + idpair = calloc(1, sizeof(VID_PID_PAIR)); + if (!idpair) + return CHANNEL_RC_NO_MEMORY; + idpair->vid = id1; + idpair->pid = id2; + if (ArrayList_Add(udevman->hotplug_vid_pids, idpair) == -1) + { + free(idpair); + return CHANNEL_RC_NO_MEMORY; + } + + add_device(&udevman->iface, DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT, 0, 0, id1, + id2); + } + } + + return CHANNEL_RC_OK; +} + +static UINT urbdrc_udevman_parse_addin_args(UDEVMAN* udevman, ADDIN_ARGV* args) +{ + int status; + LPSTR devices = NULL; + COMMAND_LINE_ARGUMENT_A* arg; + 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_OPTIONAL, "", NULL, BoolValueFalse, -1, NULL, + "FLAG_ADD_BY_VID_PID" }, + { "addr", COMMAND_LINE_VALUE_OPTIONAL, "", 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 } + }; + + status = CommandLineParseArgumentsA(args->argc, args->argv, urbdrc_udevman_args, + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON, + udevman, NULL, NULL); + + if (status != CHANNEL_RC_OK) + return status; + + arg = urbdrc_udevman_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dbg") + { + WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE); + } + CommandLineSwitchCase(arg, "dev") + { + devices = arg->Value; + } + CommandLineSwitchCase(arg, "id") + { + if (arg->Value) + udevman->devices_vid_pid = arg->Value; + else + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + } + CommandLineSwitchCase(arg, "addr") + { + if (arg->Value) + udevman->devices_addr = arg->Value; + else + 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); + + if (devices) + { + if (udevman->flags & UDEVMAN_FLAG_ADD_BY_VID_PID) + udevman->devices_vid_pid = devices; + else if (udevman->flags & UDEVMAN_FLAG_ADD_BY_ADDR) + udevman->devices_addr = devices; + } + + return CHANNEL_RC_OK; +} + +static UINT udevman_listener_created_callback(IUDEVMAN* iudevman) +{ + UINT status; + UDEVMAN* udevman = (UDEVMAN*)iudevman; + + if (udevman->devices_vid_pid) + { + status = urbdrc_udevman_register_devices(udevman, udevman->devices_vid_pid, FALSE); + if (status != CHANNEL_RC_OK) + return status; + } + + if (udevman->devices_addr) + return urbdrc_udevman_register_devices(udevman, udevman->devices_addr, TRUE); + + return CHANNEL_RC_OK; +} + +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_ChannelID = udevman_get_udevice_by_ChannelID; + /* Extension */ + udevman->iface.isAutoAdd = udevman_is_auto_add; + /* Basic state */ + BASIC_STATE_FUNC_REGISTER(device_num, udevman); + BASIC_STATE_FUNC_REGISTER(next_device_id, udevman); + + /* control semaphore or mutex lock */ + udevman->iface.loading_lock = udevman_loading_lock; + udevman->iface.loading_unlock = udevman_loading_unlock; + udevman->iface.initialize = udevman_initialize; + udevman->iface.listener_created_callback = udevman_listener_created_callback; +} + +static BOOL poll_libusb_events(UDEVMAN* udevman) +{ + int rc = LIBUSB_SUCCESS; + struct timeval tv = { 0, 500 }; + if (libusb_try_lock_events(udevman->context) == 0) + { + if (libusb_event_handling_ok(udevman->context)) + { + rc = libusb_handle_events_locked(udevman->context, &tv); + if (rc != LIBUSB_SUCCESS) + WLog_WARN(TAG, "libusb_handle_events_locked %d", rc); + } + libusb_unlock_events(udevman->context); + } + else + { + libusb_lock_event_waiters(udevman->context); + if (libusb_event_handler_active(udevman->context)) + { + rc = libusb_wait_for_event(udevman->context, &tv); + if (rc < LIBUSB_SUCCESS) + WLog_WARN(TAG, "libusb_wait_for_event %d", rc); + } + libusb_unlock_event_waiters(udevman->context); + } + + return rc > 0; +} + +static DWORD WINAPI poll_thread(LPVOID lpThreadParameter) +{ + libusb_hotplug_callback_handle handle; + UDEVMAN* udevman = (UDEVMAN*)lpThreadParameter; + BOOL hasHotplug = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); + + if (hasHotplug) + { + int rc = libusb_hotplug_register_callback( + udevman->context, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_NO_FLAGS, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, udevman, &handle); + + if (rc != LIBUSB_SUCCESS) + udevman->running = FALSE; + } + else + WLog_WARN(TAG, "Platform does not support libusb hotplug. USB devices plugged in later " + "will not be detected."); + + while (udevman->running) + { + poll_libusb_events(udevman); + } + + if (hasHotplug) + libusb_hotplug_deregister_callback(udevman->context, handle); + + /* Process remaining usb events */ + while (poll_libusb_events(udevman)) + ; + + ExitThread(0); + return 0; +} + +#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 +UINT freerdp_urbdrc_client_subsystem_entry(PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints) +{ + UINT rc; + UINT status; + UDEVMAN* udevman; + ADDIN_ARGV* args = pEntryPoints->args; + udevman = (PUDEVMAN)calloc(1, sizeof(UDEVMAN)); + + if (!udevman) + goto fail; + + udevman->hotplug_vid_pids = ArrayList_New(TRUE); + if (!udevman->hotplug_vid_pids) + goto fail; + ArrayList_Object(udevman->hotplug_vid_pids)->fnObjectFree = free; + ArrayList_Object(udevman->hotplug_vid_pids)->fnObjectEquals = udevman_vid_pid_pair_equals; + + udevman->next_device_id = BASE_USBDEVICE_NUM; + udevman->iface.plugin = pEntryPoints->plugin; + rc = libusb_init(&udevman->context); + + if (rc != LIBUSB_SUCCESS) + goto fail; + +#ifdef _WIN32 +#if LIBUSB_API_VERSION >= 0x01000106 + /* Prefer usbDK backend on windows. Not supported on other platforms. */ + rc = libusb_set_option(udevman->context, LIBUSB_OPTION_USE_USBDK); + switch (rc) + { + case LIBUSB_SUCCESS: + break; + case LIBUSB_ERROR_NOT_FOUND: + case LIBUSB_ERROR_NOT_SUPPORTED: + WLog_WARN(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc); + break; + default: + WLog_ERR(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc); + goto fail; + } +#endif +#endif + + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + udevman->devman_loading = CreateMutexA(NULL, FALSE, "devman_loading"); + + if (!udevman->devman_loading) + goto fail; + + /* load usb device service management */ + udevman_load_interface(udevman); + status = urbdrc_udevman_parse_addin_args(udevman, args); + + if (status != CHANNEL_RC_OK) + goto fail; + + udevman->running = TRUE; + udevman->thread = CreateThread(NULL, 0, poll_thread, udevman, 0, NULL); + + if (!udevman->thread) + goto fail; + + if (!pEntryPoints->pRegisterUDEVMAN(pEntryPoints->plugin, (IUDEVMAN*)udevman)) + goto fail; + + WLog_DBG(TAG, "UDEVMAN device registered."); + return 0; +fail: + udevman_free(&udevman->iface); + return ERROR_INTERNAL_ERROR; +} diff --git a/channels/urbdrc/client/urbdrc_main.c b/channels/urbdrc/client/urbdrc_main.c new file mode 100644 index 0000000..1683634 --- /dev/null +++ b/channels/urbdrc/client/urbdrc_main.c @@ -0,0 +1,1017 @@ +/** + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" +#include "data_transfer.h" + +#include + +static BOOL Stream_Write_UTF16_String_From_Utf8(wStream* s, const char* utf8, size_t len) +{ + BOOL ret; + WCHAR* utf16; + int rc; + + if (len > INT_MAX) + return FALSE; + + rc = ConvertToUnicode(CP_UTF8, 0, utf8, (int)len, &utf16, 0); + + if (rc < 0) + return FALSE; + + ret = Stream_Write_UTF16_String(s, utf16, (size_t)rc); + free(utf16); + return ret; +} + +static IWTSVirtualChannel* get_channel(IUDEVMAN* idevman) +{ + IWTSVirtualChannelManager* channel_mgr; + URBDRC_PLUGIN* urbdrc; + + if (!idevman) + return NULL; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return NULL; + + channel_mgr = urbdrc->listener_callback->channel_mgr; + + if (!channel_mgr) + return NULL; + + return channel_mgr->FindChannelById(channel_mgr, idevman->controlChannelId); +} + +static int func_container_id_generate(IUDEVICE* pdev, char* strContainerId) +{ + char *p, *path; + UINT8 containerId[17] = { 0 }; + 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; + + 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, size_t len) +{ + char instanceId[17] = { 0 }; + sprintf_s(instanceId, sizeof(instanceId), "\\%s", pdev->getPath(pdev)); + /* format */ + sprintf_s(strInstanceId, len, + "%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; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_capability_request(URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 MessageId) +{ + UINT32 InterfaceId; + UINT32 Version; + UINT32 out_size; + wStream* out; + + if (!callback || !s) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(s) < 4) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Version); + + if (Version > RIM_CAPABILITY_VERSION_01) + Version = RIM_CAPABILITY_VERSION_01; + + InterfaceId = ((STREAM_ID_NONE << 30) | CAPABILITIES_NEGOTIATOR); + out_size = 16; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /* interface id */ + Stream_Write_UINT32(out, MessageId); /* message id */ + Stream_Write_UINT32(out, Version); /* usb protocol version */ + Stream_Write_UINT32(out, 0x00000000); /* HRESULT */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_create(URBDRC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 MessageId) +{ + UINT32 InterfaceId; + UINT32 out_size; + UINT32 MajorVersion; + UINT32 MinorVersion; + UINT32 Capabilities; + wStream* out; + URBDRC_PLUGIN* urbdrc; + + if (!callback || !s || !callback->plugin) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (Stream_GetRemainingLength(s) < 12) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, MajorVersion); + Stream_Read_UINT32(s, MinorVersion); + Stream_Read_UINT32(s, Capabilities); + + /* Version check, we only support version 1.0 */ + if ((MajorVersion != 1) || (MinorVersion != 0)) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "server supports USB channel version %" PRIu32 ".%" PRIu32); + WLog_Print(urbdrc->log, WLOG_WARN, "we only support channel version 1.0"); + MajorVersion = 1; + MinorVersion = 0; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_CHANNEL_NOTIFICATION); + out_size = 24; + out = Stream_New(NULL, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /* interface id */ + Stream_Write_UINT32(out, MessageId); /* message id */ + Stream_Write_UINT32(out, CHANNEL_CREATED); /* function id */ + Stream_Write_UINT32(out, MajorVersion); + Stream_Write_UINT32(out, MinorVersion); + Stream_Write_UINT32(out, Capabilities); /* capabilities version */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static UINT urdbrc_send_virtual_channel_add(IWTSPlugin* plugin, IWTSVirtualChannel* channel, + UINT32 MessageId) +{ + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + wStream* out = Stream_New(NULL, 12); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /* interface */ + Stream_Write_UINT32(out, MessageId); /* message id */ + Stream_Write_UINT32(out, ADD_VIRTUAL_CHANNEL); /* function id */ + return stream_write_and_free(plugin, channel, out); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urdbrc_send_usb_device_add(URBDRC_CHANNEL_CALLBACK* callback, IUDEVICE* pdev) +{ + wStream* out; + UINT32 InterfaceId; + char HardwareIds[2][DEVICE_HARDWARE_ID_SIZE] = { { 0 } }; + char CompatibilityIds[3][DEVICE_COMPATIBILITY_ID_SIZE] = { { 0 } }; + char strContainerId[DEVICE_CONTAINER_STR_SIZE] = { 0 }; + char strInstanceId[DEVICE_INSTANCE_STR_SIZE] = { 0 }; + const char* composite_str = "USB\\COMPOSITE"; + const size_t composite_len = 13; + size_t size; + size_t CompatibilityIdLen[3]; + size_t HardwareIdsLen[2]; + size_t ContainerIdLen, InstanceIdLen; + size_t cchCompatIds; + UINT32 bcdUSB; + InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + /* USB kernel driver detach!! */ + pdev->detach_kernel_driver(pdev); + { + const UINT16 idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR); + const UINT16 idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT); + const UINT16 bcdDevice = (UINT16)pdev->query_device_descriptor(pdev, BCD_DEVICE); + sprintf_s(HardwareIds[1], DEVICE_HARDWARE_ID_SIZE, + "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "", idVendor, idProduct); + sprintf_s(HardwareIds[0], DEVICE_HARDWARE_ID_SIZE, + "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "&REV_%04" PRIX16 "", idVendor, idProduct, + bcdDevice); + } + { + const UINT8 bDeviceClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_CLASS); + const UINT8 bDeviceSubClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_SUBCLASS); + const UINT8 bDeviceProtocol = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_PROTOCOL); + + if (!(pdev->isCompositeDevice(pdev))) + { + sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\Class_%02" PRIX8 "", + bDeviceClass); + sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "", bDeviceClass, + bDeviceSubClass); + sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "&Prot_%02" PRIX8 "", + bDeviceClass, bDeviceSubClass, bDeviceProtocol); + } + else + { + sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\DevClass_00"); + sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\DevClass_00&SubClass_00"); + sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\DevClass_00&SubClass_00&Prot_00"); + } + } + func_instance_id_generate(pdev, strInstanceId, DEVICE_INSTANCE_STR_SIZE); + func_container_id_generate(pdev, strContainerId); + CompatibilityIdLen[0] = strnlen(CompatibilityIds[0], sizeof(CompatibilityIds[0])); + CompatibilityIdLen[1] = strnlen(CompatibilityIds[1], sizeof(CompatibilityIds[1])); + CompatibilityIdLen[2] = strnlen(CompatibilityIds[2], sizeof(CompatibilityIds[2])); + HardwareIdsLen[0] = strnlen(HardwareIds[0], sizeof(HardwareIds[0])); + HardwareIdsLen[1] = strnlen(HardwareIds[1], sizeof(HardwareIds[1])); + cchCompatIds = + CompatibilityIdLen[0] + 1 + CompatibilityIdLen[1] + 1 + CompatibilityIdLen[2] + 2; + InstanceIdLen = strnlen(strInstanceId, sizeof(strInstanceId)); + ContainerIdLen = strnlen(strContainerId, sizeof(strContainerId)); + + if (pdev->isCompositeDevice(pdev)) + cchCompatIds += composite_len + 1; + + size = 24; + size += (InstanceIdLen + 1) * 2 + (HardwareIdsLen[0] + 1) * 2 + 4 + + (HardwareIdsLen[1] + 1) * 2 + 2 + 4 + (cchCompatIds)*2 + (ContainerIdLen + 1) * 2 + 4 + + 28; + out = Stream_New(NULL, size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, InterfaceId); /* interface */ + Stream_Write_UINT32(out, 0); + Stream_Write_UINT32(out, ADD_DEVICE); /* function id */ + Stream_Write_UINT32(out, 0x00000001); /* NumUsbDevice */ + Stream_Write_UINT32(out, pdev->get_UsbDevice(pdev)); /* UsbDevice */ + Stream_Write_UINT32(out, (UINT32)InstanceIdLen + 1); /* cchDeviceInstanceId */ + Stream_Write_UTF16_String_From_Utf8(out, strInstanceId, InstanceIdLen); + Stream_Write_UINT16(out, 0); + Stream_Write_UINT32(out, HardwareIdsLen[0] + HardwareIdsLen[1] + 3); /* cchHwIds */ + /* HardwareIds 1 */ + Stream_Write_UTF16_String_From_Utf8(out, HardwareIds[0], HardwareIdsLen[0]); + Stream_Write_UINT16(out, 0); + Stream_Write_UTF16_String_From_Utf8(out, HardwareIds[1], HardwareIdsLen[1]); + Stream_Write_UINT16(out, 0); + Stream_Write_UINT16(out, 0); /* add "\0" */ + Stream_Write_UINT32(out, (UINT32)cchCompatIds); /* cchCompatIds */ + /* CompatibilityIds */ + Stream_Write_UTF16_String_From_Utf8(out, CompatibilityIds[0], CompatibilityIdLen[0]); + Stream_Write_UINT16(out, 0); + Stream_Write_UTF16_String_From_Utf8(out, CompatibilityIds[1], CompatibilityIdLen[1]); + Stream_Write_UINT16(out, 0); + Stream_Write_UTF16_String_From_Utf8(out, CompatibilityIds[2], CompatibilityIdLen[2]); + Stream_Write_UINT16(out, 0); + + if (pdev->isCompositeDevice(pdev)) + { + Stream_Write_UTF16_String_From_Utf8(out, composite_str, composite_len); + Stream_Write_UINT16(out, 0); + } + + Stream_Write_UINT16(out, 0x0000); /* add "\0" */ + Stream_Write_UINT32(out, (UINT32)ContainerIdLen + 1); /* cchContainerId */ + /* ContainerId */ + Stream_Write_UTF16_String_From_Utf8(out, strContainerId, ContainerIdLen); + Stream_Write_UINT16(out, 0); + /* USB_DEVICE_CAPABILITIES 28 bytes */ + Stream_Write_UINT32(out, 0x0000001c); /* CbSize */ + Stream_Write_UINT32(out, 2); /* UsbBusInterfaceVersion, 0 ,1 or 2 */ // TODO: Get from libusb + Stream_Write_UINT32(out, 0x600); /* USBDI_Version, 0x500 or 0x600 */ // TODO: Get from libusb + /* Supported_USB_Version, 0x110,0x110 or 0x200(usb2.0) */ + bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB); + Stream_Write_UINT32(out, bcdUSB); + Stream_Write_UINT32(out, 0x00000000); /* HcdCapabilities, MUST always be zero */ + + if (bcdUSB < 0x200) + Stream_Write_UINT32(out, 0x00000000); /* DeviceIsHighSpeed */ + else + Stream_Write_UINT32(out, 0x00000001); /* DeviceIsHighSpeed */ + + Stream_Write_UINT32(out, 0x50); /* NoAckIsochWriteJitterBufferSizeInMs, >=10 or <=512 */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_exchange_capabilities(URBDRC_CHANNEL_CALLBACK* callback, wStream* data) +{ + UINT32 MessageId; + UINT32 FunctionId; + UINT32 InterfaceId; + UINT error = CHANNEL_RC_OK; + + if (!data) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(data) < 8) + return ERROR_INVALID_DATA; + + Stream_Rewind_UINT32(data); + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = urbdrc_process_capability_request(callback, data, MessageId); + break; + + case RIMCALL_RELEASE: + break; + + default: + error = ERROR_NOT_FOUND; + break; + } + + return error; +} + +static BOOL urbdrc_announce_devices(IUDEVMAN* udevman) +{ + UINT error = ERROR_SUCCESS; + + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + IUDEVICE* pdev = udevman->get_next(udevman); + + if (!pdev->isAlreadySend(pdev)) + { + const UINT32 deviceId = pdev->get_UsbDevice(pdev); + UINT error = + urdbrc_send_virtual_channel_add(udevman->plugin, get_channel(udevman), deviceId); + + if (error != ERROR_SUCCESS) + break; + } + } + + udevman->loading_unlock(udevman); + + return error == ERROR_SUCCESS; +} + +static UINT urbdrc_device_control_channel(URBDRC_CHANNEL_CALLBACK* callback, wStream* s) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + IUDEVMAN* udevman = urbdrc->udevman; + IWTSVirtualChannel* channel = callback->channel; + IUDEVICE* pdev = NULL; + BOOL found = FALSE; + UINT error = ERROR_INTERNAL_ERROR; + UINT32 channelId = callback->channel_mgr->GetChannelId(channel); + + switch (urbdrc->vchannel_status) + { + case INIT_CHANNEL_IN: + /* Control channel was established */ + error = ERROR_SUCCESS; + udevman->initialize(udevman, channelId); + + if (!urbdrc_announce_devices(udevman)) + goto fail; + + urbdrc->vchannel_status = INIT_CHANNEL_OUT; + break; + + case INIT_CHANNEL_OUT: + /* A new device channel was created, add the channel + * to the device */ + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (!pdev->isAlreadySend(pdev)) + { + const UINT32 channelID = callback->channel_mgr->GetChannelId(channel); + found = TRUE; + pdev->setAlreadySend(pdev); + pdev->set_channelManager(pdev, callback->channel_mgr); + pdev->set_channelID(pdev, channelID); + break; + } + } + + udevman->loading_unlock(udevman); + error = ERROR_SUCCESS; + + if (found && pdev->isAlreadySend(pdev)) + error = urdbrc_send_usb_device_add(callback, pdev); + + break; + + default: + WLog_Print(urbdrc->log, WLOG_ERROR, "vchannel_status unknown value %" PRIu32 "", + urbdrc->vchannel_status); + break; + } + +fail: + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_notification(URBDRC_CHANNEL_CALLBACK* callback, wStream* data) +{ + UINT32 MessageId; + UINT32 FunctionId; + UINT32 InterfaceId; + UINT error = CHANNEL_RC_OK; + URBDRC_PLUGIN* urbdrc; + + if (!callback || !data) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (Stream_GetRemainingLength(data) < 8) + return ERROR_INVALID_DATA; + + Stream_Rewind(data, 4); + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + WLog_Print(urbdrc->log, WLOG_TRACE, "%s [%" PRIu32 "]", + call_to_string(FALSE, InterfaceId, FunctionId), FunctionId); + + switch (FunctionId) + { + case CHANNEL_CREATED: + error = urbdrc_process_channel_create(callback, data, MessageId); + break; + + case RIMCALL_RELEASE: + error = urbdrc_device_control_channel(callback, data); + break; + + default: + WLog_Print(urbdrc->log, WLOG_TRACE, "%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 InterfaceId; + UINT error = ERROR_INTERNAL_ERROR; + + if (callback == NULL) + return ERROR_INVALID_PARAMETER; + + if (callback->plugin == NULL) + return error; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (urbdrc->udevman == NULL) + return error; + + udevman = (IUDEVMAN*)urbdrc->udevman; + + if (Stream_GetRemainingLength(data) < 12) + return ERROR_INVALID_DATA; + + urbdrc_dump_message(urbdrc->log, FALSE, FALSE, data); + Stream_Read_UINT32(data, InterfaceId); + + /* Need to check InterfaceId and mask values */ + switch (InterfaceId) + { + case CAPABILITIES_NEGOTIATOR | (STREAM_ID_NONE << 30): + error = urbdrc_exchange_capabilities(callback, data); + break; + + case SERVER_CHANNEL_NOTIFICATION | (STREAM_ID_PROXY << 30): + error = urbdrc_process_channel_notification(callback, data); + break; + + default: + error = urbdrc_process_udev_data_transfer(callback, urbdrc, udevman, data); + error = ERROR_SUCCESS; /* Ignore errors, the device may have been unplugged. */ + break; + } + + return error; +} + +/** + * 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; + if (callback) + { + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + if (urbdrc) + { + IUDEVMAN* udevman = urbdrc->udevman; + if (udevman && callback->channel_mgr) + { + UINT32 control = callback->channel_mgr->GetChannelId(callback->channel); + if (udevman->controlChannelId == control) + udevman->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + else + { /* Need to notify the local backend the device is gone */ + IUDEVICE* pdev = udevman->get_udevice_by_ChannelID(udevman, control); + if (pdev) + pdev->markChannelClosed(pdev); + } + } + } + } + free(callback); + 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; + + if (!ppCallback) + return ERROR_INVALID_PARAMETER; + + 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) +{ + UINT status; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + IUDEVMAN* udevman; + char channelName[sizeof(URBDRC_CHANNEL_NAME)] = { URBDRC_CHANNEL_NAME }; + + if (!urbdrc || !urbdrc->udevman) + return ERROR_INVALID_PARAMETER; + + if (urbdrc->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", URBDRC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + udevman = urbdrc->udevman; + 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; + + /* [MS-RDPEUSB] 2.1 Transport defines the channel name in uppercase letters */ + CharUpperA(channelName); + status = pChannelMgr->CreateListener(pChannelMgr, channelName, 0, + &urbdrc->listener_callback->iface, &urbdrc->listener); + if (status != CHANNEL_RC_OK) + return status; + + status = CHANNEL_RC_OK; + if (udevman->listener_created_callback) + status = udevman->listener_created_callback(udevman); + + urbdrc->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * 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; + + if (!urbdrc) + return ERROR_INVALID_DATA; + if (urbdrc->listener_callback) + { + IWTSVirtualChannelManager* mgr = urbdrc->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, urbdrc->listener); + } + udevman = urbdrc->udevman; + + if (udevman) + { + udevman->free(udevman); + udevman = NULL; + } + + free(urbdrc->subsystem); + free(urbdrc->listener_callback); + free(urbdrc); + return CHANNEL_RC_OK; +} + +static BOOL urbdrc_register_udevman_addin(IWTSPlugin* pPlugin, IUDEVMAN* udevman) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + + if (urbdrc->udevman) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "existing device, abort."); + return FALSE; + } + + DEBUG_DVC("device registered."); + urbdrc->udevman = udevman; + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_load_udevman_addin(IWTSPlugin* pPlugin, LPCSTR name, ADDIN_ARGV* args) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + PFREERDP_URBDRC_DEVICE_ENTRY entry; + FREERDP_URBDRC_SERVICE_ENTRY_POINTS entryPoints; + entry = (PFREERDP_URBDRC_DEVICE_ENTRY)freerdp_load_channel_addin_entry(URBDRC_CHANNEL_NAME, + 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_Print(urbdrc->log, WLOG_ERROR, "%s entry returns error.", name); + return ERROR_INVALID_OPERATION; + } + + return CHANNEL_RC_OK; +} + +static BOOL urbdrc_set_subsystem(URBDRC_PLUGIN* urbdrc, const char* subsystem) +{ + free(urbdrc->subsystem); + urbdrc->subsystem = _strdup(subsystem); + return (urbdrc->subsystem != NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_addin_args(URBDRC_PLUGIN* urbdrc, const ADDIN_ARGV* args) +{ + int status; + 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 } + }; + + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + COMMAND_LINE_ARGUMENT_A* arg; + 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(urbdrc->log, 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; +} + +BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor, + UINT16 idProduct) +{ + size_t success = 0; + URBDRC_PLUGIN* urbdrc; + UINT32 mask, regflags = 0; + + if (!idevman) + return FALSE; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return FALSE; + + mask = (DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT); + if ((flags & mask) == mask) + regflags |= UDEVMAN_FLAG_ADD_BY_VID_PID; + mask = (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV); + if ((flags & mask) == mask) + regflags |= UDEVMAN_FLAG_ADD_BY_ADDR; + + success = idevman->register_udevice(idevman, busnum, devnum, idVendor, idProduct, regflags); + + if ((success > 0) && (flags & DEVICE_ADD_FLAG_REGISTER)) + { + if (!urbdrc_announce_devices(idevman)) + return FALSE; + } + + return TRUE; +} + +BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor, + UINT16 idProduct) +{ + IUDEVICE* pdev = NULL; + URBDRC_PLUGIN* urbdrc; + + if (!idevman) + return FALSE; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return FALSE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + BOOL match = TRUE; + IUDEVICE* dev = idevman->get_next(idevman); + + if ((flags & (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR | + DEVICE_ADD_FLAG_PRODUCT)) == 0) + match = FALSE; + if (flags & DEVICE_ADD_FLAG_BUS) + { + if (dev->get_bus_number(dev) != busnum) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_DEV) + { + if (dev->get_dev_number(dev) != devnum) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_VENDOR) + { + int vid = dev->query_device_descriptor(dev, ID_VENDOR); + if (vid != idVendor) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_PRODUCT) + { + int pid = dev->query_device_descriptor(dev, ID_PRODUCT); + if (pid != idProduct) + match = FALSE; + } + + if (match) + { + pdev = dev; + break; + } + } + + if (pdev) + pdev->setChannelClosed(pdev); + + idevman->loading_unlock(idevman); + return TRUE; +} +#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_CHANNEL_NAME); + 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.Terminated = urbdrc_plugin_terminated; + urbdrc->vchannel_status = INIT_CHANNEL_IN; + status = + pEntryPoints->RegisterPlugin(pEntryPoints, URBDRC_CHANNEL_NAME, (IWTSPlugin*)urbdrc); + + if (status != CHANNEL_RC_OK) + goto fail; + + urbdrc->log = WLog_Get(TAG); + + if (!urbdrc->log) + goto fail; + } + + status = urbdrc_process_addin_args(urbdrc, args); + + if (status != CHANNEL_RC_OK) + goto fail; + + if (!urbdrc->subsystem && !urbdrc_set_subsystem(urbdrc, "libusb")) + goto fail; + + return urbdrc_load_udevman_addin((IWTSPlugin*)urbdrc, urbdrc->subsystem, args); +fail: + urbdrc_plugin_terminated(&urbdrc->iface); + return status; +} + +UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, wStream* out) +{ + UINT rc; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)plugin; + + if (!out) + return ERROR_INVALID_PARAMETER; + + if (!channel || !out || !urbdrc) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_PARAMETER; + } + + if (!channel->Write) + { + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + urbdrc_dump_message(urbdrc->log, TRUE, TRUE, out); + rc = channel->Write(channel, Stream_GetPosition(out), Stream_Buffer(out), NULL); + Stream_Free(out, TRUE); + return rc; +} diff --git a/channels/urbdrc/client/urbdrc_main.h b/channels/urbdrc/client/urbdrc_main.h new file mode 100644 index 0000000..8335714 --- /dev/null +++ b/channels/urbdrc/client/urbdrc_main.h @@ -0,0 +1,247 @@ +/** + * 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 +#include + +#define DEVICE_HARDWARE_ID_SIZE 32 +#define DEVICE_COMPATIBILITY_ID_SIZE 36 +#define DEVICE_INSTANCE_STR_SIZE 37 +#define DEVICE_CONTAINER_STR_SIZE 39 + +#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 + +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; + UINT32 vchannel_status; + char* subsystem; + + wLog* log; + IWTSListener* listener; + BOOL initialized; +}; + +typedef BOOL (*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; + IWTSVirtualChannel* channel; + wStream* s; +}; + +typedef void (*t_isoch_transfer_cb)(IUDEVICE* idev, URBDRC_CHANNEL_CALLBACK* callback, wStream* out, + UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, + UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status, + UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize); + +struct _IUDEVICE +{ + /* Transfer */ + int (*isoch_transfer)(IUDEVICE* idev, URBDRC_CHANNEL_CALLBACK* callback, UINT32 MessageId, + UINT32 RequestId, UINT32 EndpointAddress, UINT32 TransferFlags, + UINT32 StartFrame, UINT32 ErrorCount, BOOL NoAck, + const BYTE* packetDescriptorData, UINT32 NumberOfPackets, + UINT32 BufferSize, const BYTE* Buffer, t_isoch_transfer_cb cb, + UINT32 Timeout); + + BOOL(*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, URBDRC_CHANNEL_CALLBACK* callback, + UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress, + UINT32 TransferFlags, BOOL NoAck, UINT32 BufferSize, + const BYTE* data, t_isoch_transfer_cb cb, 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); + + UINT32(*control_query_device_text) + (IUDEVICE* idev, UINT32 TextType, UINT16 LocaleId, UINT8* 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); + + BOOL (*detach_kernel_driver)(IUDEVICE* idev); + + BOOL (*attach_kernel_driver)(IUDEVICE* idev); + + int (*query_device_port_status)(IUDEVICE* idev, UINT32* UsbdStatus, UINT32* BufferSize, + BYTE* Buffer); + + MSUSB_CONFIG_DESCRIPTOR* (*complete_msconfig_setup)(IUDEVICE* idev, + MSUSB_CONFIG_DESCRIPTOR* MsConfig); + /* Basic state */ + int (*isCompositeDevice)(IUDEVICE* idev); + + int (*isExist)(IUDEVICE* idev); + int (*isAlreadySend)(IUDEVICE* idev); + int (*isChannelClosed)(IUDEVICE* idev); + + void (*setAlreadySend)(IUDEVICE* idev); + void (*setChannelClosed)(IUDEVICE* idev); + void (*markChannelClosed)(IUDEVICE* idev); + char* (*getPath)(IUDEVICE* idev); + + void (*free)(IUDEVICE* idev); + + BASIC_DEV_STATE_DEFINED(channelManager, IWTSVirtualChannelManager*); + BASIC_DEV_STATE_DEFINED(channelID, UINT32); + BASIC_DEV_STATE_DEFINED(UsbDevice, UINT32); + BASIC_DEV_STATE_DEFINED(ReqCompletion, UINT32); + BASIC_DEV_STATE_DEFINED(bus_number, BYTE); + BASIC_DEV_STATE_DEFINED(dev_number, BYTE); + BASIC_DEV_STATE_DEFINED(port_number, int); + 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*); +}; + +struct _IUDEVMAN +{ + /* Standard */ + void (*free)(IUDEVMAN* idevman); + + /* Manage devices */ + void (*rewind)(IUDEVMAN* idevman); + BOOL (*has_next)(IUDEVMAN* idevman); + BOOL (*unregister_udevice)(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number); + size_t (*register_udevice)(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number, UINT16 idVendor, + UINT16 idProduct, UINT32 flag); + IUDEVICE* (*get_next)(IUDEVMAN* idevman); + IUDEVICE* (*get_udevice_by_UsbDevice)(IUDEVMAN* idevman, UINT32 UsbDevice); + IUDEVICE* (*get_udevice_by_ChannelID)(IUDEVMAN* idevman, UINT32 channelID); + + /* Extension */ + int (*isAutoAdd)(IUDEVMAN* idevman); + + /* Basic state */ + BASIC_DEVMAN_STATE_DEFINED(device_num, UINT32); + BASIC_DEVMAN_STATE_DEFINED(next_device_id, UINT32); + + /* control semaphore or mutex lock */ + void (*loading_lock)(IUDEVMAN* idevman); + void (*loading_unlock)(IUDEVMAN* idevman); + BOOL (*initialize)(IUDEVMAN* idevman, UINT32 channelId); + UINT (*listener_created_callback)(IUDEVMAN* idevman); + + IWTSPlugin* plugin; + UINT32 controlChannelId; + UINT32 status; +}; + +#define DEVICE_ADD_FLAG_BUS 0x01 +#define DEVICE_ADD_FLAG_DEV 0x02 +#define DEVICE_ADD_FLAG_VENDOR 0x04 +#define DEVICE_ADD_FLAG_PRODUCT 0x08 +#define DEVICE_ADD_FLAG_REGISTER 0x10 + +#define DEVICE_ADD_FLAG_ALL \ + (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR | \ + DEVICE_ADD_FLAG_PRODUCT | DEVICE_ADD_FLAG_REGISTER) + +FREERDP_API BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, + UINT16 idVendor, UINT16 idProduct); +FREERDP_API BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, + UINT16 idVendor, UINT16 idProduct); + +UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, wStream* s); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H */ diff --git a/channels/urbdrc/common/CMakeLists.txt b/channels/urbdrc/common/CMakeLists.txt new file mode 100644 index 0000000..0e7b448 --- /dev/null +++ b/channels/urbdrc/common/CMakeLists.txt @@ -0,0 +1,26 @@ +# 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. + +set(SRCS + urbdrc_types.h + urbdrc_helpers.h + urbdrc_helpers.c + msusb.h + msusb.c) + +add_library(urbdrc-common OBJECT ${SRCS}) diff --git a/channels/urbdrc/common/msusb.c b/channels/urbdrc/common/msusb.c new file mode 100644 index 0000000..bb517ce --- /dev/null +++ b/channels/urbdrc/common/msusb.c @@ -0,0 +1,402 @@ +/** + * 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 + +#define TAG FREERDP_TAG("utils") + +static MSUSB_PIPE_DESCRIPTOR* msusb_mspipe_new() +{ + return (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR)); +} + +static void msusb_mspipes_free(MSUSB_PIPE_DESCRIPTOR** MsPipes, UINT32 NumberOfPipes) +{ + UINT32 pnum = 0; + + if (MsPipes) + { + for (pnum = 0; pnum < NumberOfPipes && MsPipes[pnum]; pnum++) + free(MsPipes[pnum]); + + free(MsPipes); + } +} + +BOOL msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, + MSUSB_PIPE_DESCRIPTOR** NewMsPipes, UINT32 NewNumberOfPipes) +{ + if (!MsInterface || !NewMsPipes) + return FALSE; + + /* free orignal MsPipes */ + msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes); + /* And replace it */ + MsInterface->MsPipes = NewMsPipes; + MsInterface->NumberOfPipes = NewNumberOfPipes; + return TRUE; +} + +static MSUSB_PIPE_DESCRIPTOR** msusb_mspipes_read(wStream* s, UINT32 NumberOfPipes) +{ + UINT32 pnum; + MSUSB_PIPE_DESCRIPTOR** MsPipes; + + if (Stream_GetRemainingCapacity(s) / 12 < NumberOfPipes) + return NULL; + + MsPipes = (MSUSB_PIPE_DESCRIPTOR**)calloc(NumberOfPipes, sizeof(MSUSB_PIPE_DESCRIPTOR*)); + + if (!MsPipes) + return NULL; + + for (pnum = 0; pnum < NumberOfPipes; pnum++) + { + MSUSB_PIPE_DESCRIPTOR* MsPipe = msusb_mspipe_new(); + + if (!MsPipe) + goto out_error; + + Stream_Read_UINT16(s, MsPipe->MaximumPacketSize); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, MsPipe->MaximumTransferSize); + Stream_Read_UINT32(s, MsPipe->PipeFlags); + /* Already set to zero by memset + MsPipe->PipeHandle = 0; + MsPipe->bEndpointAddress = 0; + MsPipe->bInterval = 0; + MsPipe->PipeType = 0; + MsPipe->InitCompleted = 0; + */ + MsPipes[pnum] = MsPipe; + } + + return MsPipes; +out_error: + + for (pnum = 0; pnum < NumberOfPipes; pnum++) + free(MsPipes[pnum]); + + free(MsPipes); + return NULL; +} + +static MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_new() +{ + return (MSUSB_INTERFACE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_INTERFACE_DESCRIPTOR)); +} + +static void msusb_msinterface_free(MSUSB_INTERFACE_DESCRIPTOR* MsInterface) +{ + if (MsInterface) + { + msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes); + MsInterface->MsPipes = NULL; + free(MsInterface); + } +} + +static void msusb_msinterface_free_list(MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces, + UINT32 NumInterfaces) +{ + UINT32 inum = 0; + + if (MsInterfaces) + { + for (inum = 0; inum < NumInterfaces; inum++) + { + msusb_msinterface_free(MsInterfaces[inum]); + } + + free(MsInterfaces); + } +} + +BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig, BYTE InterfaceNumber, + MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface) +{ + if (!MsConfig || !MsConfig->MsInterfaces) + return FALSE; + + msusb_msinterface_free(MsConfig->MsInterfaces[InterfaceNumber]); + MsConfig->MsInterfaces[InterfaceNumber] = NewMsInterface; + return TRUE; +} + +MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* s) +{ + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + + if (Stream_GetRemainingCapacity(s) < 12) + return NULL; + + MsInterface = msusb_msinterface_new(); + + if (!MsInterface) + return NULL; + + Stream_Read_UINT16(s, MsInterface->Length); + Stream_Read_UINT16(s, MsInterface->NumberOfPipesExpected); + Stream_Read_UINT8(s, MsInterface->InterfaceNumber); + Stream_Read_UINT8(s, MsInterface->AlternateSetting); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, MsInterface->NumberOfPipes); + MsInterface->InterfaceHandle = 0; + MsInterface->bInterfaceClass = 0; + MsInterface->bInterfaceSubClass = 0; + MsInterface->bInterfaceProtocol = 0; + MsInterface->InitCompleted = 0; + MsInterface->MsPipes = NULL; + + if (MsInterface->NumberOfPipes > 0) + { + MsInterface->MsPipes = msusb_mspipes_read(s, MsInterface->NumberOfPipes); + + if (!MsInterface->MsPipes) + goto out_error; + } + + return MsInterface; +out_error: + msusb_msinterface_free(MsInterface); + return NULL; +} + +BOOL msusb_msinterface_write(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, wStream* out) +{ + MSUSB_PIPE_DESCRIPTOR** MsPipes; + MSUSB_PIPE_DESCRIPTOR* MsPipe; + UINT32 pnum = 0; + + if (!MsInterface) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(out, 16 + MsInterface->NumberOfPipes * 20)) + return FALSE; + + /* Length */ + Stream_Write_UINT16(out, MsInterface->Length); + /* InterfaceNumber */ + Stream_Write_UINT8(out, MsInterface->InterfaceNumber); + /* AlternateSetting */ + Stream_Write_UINT8(out, MsInterface->AlternateSetting); + /* bInterfaceClass */ + Stream_Write_UINT8(out, MsInterface->bInterfaceClass); + /* bInterfaceSubClass */ + Stream_Write_UINT8(out, MsInterface->bInterfaceSubClass); + /* bInterfaceProtocol */ + Stream_Write_UINT8(out, MsInterface->bInterfaceProtocol); + /* Padding */ + Stream_Write_UINT8(out, 0); + /* InterfaceHandle */ + Stream_Write_UINT32(out, MsInterface->InterfaceHandle); + /* NumberOfPipes */ + Stream_Write_UINT32(out, MsInterface->NumberOfPipes); + /* Pipes */ + MsPipes = MsInterface->MsPipes; + + for (pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++) + { + MsPipe = MsPipes[pnum]; + /* MaximumPacketSize */ + Stream_Write_UINT16(out, MsPipe->MaximumPacketSize); + /* EndpointAddress */ + Stream_Write_UINT8(out, MsPipe->bEndpointAddress); + /* Interval */ + Stream_Write_UINT8(out, MsPipe->bInterval); + /* PipeType */ + Stream_Write_UINT32(out, MsPipe->PipeType); + /* PipeHandle */ + Stream_Write_UINT32(out, MsPipe->PipeHandle); + /* MaximumTransferSize */ + Stream_Write_UINT32(out, MsPipe->MaximumTransferSize); + /* PipeFlags */ + Stream_Write_UINT32(out, MsPipe->PipeFlags); + } + + return TRUE; +} + +static MSUSB_INTERFACE_DESCRIPTOR** msusb_msinterface_read_list(wStream* s, UINT32 NumInterfaces) +{ + UINT32 inum; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + MsInterfaces = + (MSUSB_INTERFACE_DESCRIPTOR**)calloc(NumInterfaces, sizeof(MSUSB_INTERFACE_DESCRIPTOR*)); + + if (!MsInterfaces) + return NULL; + + for (inum = 0; inum < NumInterfaces; inum++) + { + MsInterfaces[inum] = msusb_msinterface_read(s); + + if (!MsInterfaces[inum]) + goto fail; + } + + return MsInterfaces; +fail: + + for (inum = 0; inum < NumInterfaces; inum++) + msusb_msinterface_free(MsInterfaces[inum]); + + free(MsInterfaces); + return NULL; +} + +BOOL msusb_msconfig_write(MSUSB_CONFIG_DESCRIPTOR* MsConfg, wStream* out) +{ + UINT32 inum = 0; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + + if (!MsConfg) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(out, 8)) + return FALSE; + + /* ConfigurationHandle*/ + Stream_Write_UINT32(out, MsConfg->ConfigurationHandle); + /* NumInterfaces*/ + Stream_Write_UINT32(out, MsConfg->NumInterfaces); + /* Interfaces */ + MsInterfaces = MsConfg->MsInterfaces; + + for (inum = 0; inum < MsConfg->NumInterfaces; inum++) + { + MsInterface = MsInterfaces[inum]; + + if (!msusb_msinterface_write(MsInterface, out)) + return FALSE; + } + + return TRUE; +} + +MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void) +{ + return (MSUSB_CONFIG_DESCRIPTOR*)calloc(1, sizeof(MSUSB_CONFIG_DESCRIPTOR)); +} + +void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + if (MsConfig) + { + msusb_msinterface_free_list(MsConfig->MsInterfaces, MsConfig->NumInterfaces); + MsConfig->MsInterfaces = NULL; + free(MsConfig); + } +} + +MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + BYTE lenConfiguration, typeConfiguration; + + if (Stream_GetRemainingCapacity(s) < 6ULL + NumInterfaces * 2ULL) + return NULL; + + MsConfig = msusb_msconfig_new(); + + if (!MsConfig) + goto fail; + + MsConfig->MsInterfaces = msusb_msinterface_read_list(s, NumInterfaces); + + if (!MsConfig->MsInterfaces) + goto fail; + + Stream_Read_UINT8(s, lenConfiguration); + Stream_Read_UINT8(s, typeConfiguration); + + if (lenConfiguration != 0x9 || typeConfiguration != 0x2) + { + WLog_ERR(TAG, "len and type must be 0x9 and 0x2 , but it is 0x%" PRIx8 " and 0x%" PRIx8 "", + lenConfiguration, typeConfiguration); + goto fail; + } + + Stream_Read_UINT16(s, MsConfig->wTotalLength); + Stream_Seek(s, 1); + Stream_Read_UINT8(s, MsConfig->bConfigurationValue); + MsConfig->NumInterfaces = NumInterfaces; + return MsConfig; +fail: + msusb_msconfig_free(MsConfig); + return NULL; +} + +void msusb_msconfig_dump(MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface; + MSUSB_PIPE_DESCRIPTOR** MsPipes; + MSUSB_PIPE_DESCRIPTOR* MsPipe; + UINT32 inum = 0, pnum = 0; + WLog_INFO(TAG, "=================MsConfig:========================"); + WLog_INFO(TAG, "wTotalLength:%" PRIu16 "", MsConfig->wTotalLength); + WLog_INFO(TAG, "bConfigurationValue:%" PRIu8 "", MsConfig->bConfigurationValue); + WLog_INFO(TAG, "ConfigurationHandle:0x%08" PRIx32 "", MsConfig->ConfigurationHandle); + WLog_INFO(TAG, "InitCompleted:%d", MsConfig->InitCompleted); + WLog_INFO(TAG, "MsOutSize:%d", MsConfig->MsOutSize); + WLog_INFO(TAG, "NumInterfaces:%" PRIu32 "", MsConfig->NumInterfaces); + MsInterfaces = MsConfig->MsInterfaces; + + for (inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MsInterface = MsInterfaces[inum]; + WLog_INFO(TAG, " Interface: %" PRIu8 "", MsInterface->InterfaceNumber); + WLog_INFO(TAG, " Length: %" PRIu16 "", MsInterface->Length); + WLog_INFO(TAG, " NumberOfPipesExpected: %" PRIu16 "", + MsInterface->NumberOfPipesExpected); + WLog_INFO(TAG, " AlternateSetting: %" PRIu8 "", MsInterface->AlternateSetting); + WLog_INFO(TAG, " NumberOfPipes: %" PRIu32 "", MsInterface->NumberOfPipes); + WLog_INFO(TAG, " InterfaceHandle: 0x%08" PRIx32 "", MsInterface->InterfaceHandle); + WLog_INFO(TAG, " bInterfaceClass: 0x%02" PRIx8 "", MsInterface->bInterfaceClass); + WLog_INFO(TAG, " bInterfaceSubClass: 0x%02" PRIx8 "", MsInterface->bInterfaceSubClass); + WLog_INFO(TAG, " bInterfaceProtocol: 0x%02" PRIx8 "", MsInterface->bInterfaceProtocol); + WLog_INFO(TAG, " InitCompleted: %d", MsInterface->InitCompleted); + MsPipes = MsInterface->MsPipes; + + for (pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++) + { + MsPipe = MsPipes[pnum]; + WLog_INFO(TAG, " Pipe: %d", pnum); + WLog_INFO(TAG, " MaximumPacketSize: 0x%04" PRIx16 "", MsPipe->MaximumPacketSize); + WLog_INFO(TAG, " MaximumTransferSize: 0x%08" PRIx32 "", + MsPipe->MaximumTransferSize); + WLog_INFO(TAG, " PipeFlags: 0x%08" PRIx32 "", MsPipe->PipeFlags); + WLog_INFO(TAG, " PipeHandle: 0x%08" PRIx32 "", MsPipe->PipeHandle); + WLog_INFO(TAG, " bEndpointAddress: 0x%02" PRIx8 "", MsPipe->bEndpointAddress); + WLog_INFO(TAG, " bInterval: %" PRIu8 "", MsPipe->bInterval); + WLog_INFO(TAG, " PipeType: 0x%02" PRIx8 "", MsPipe->PipeType); + WLog_INFO(TAG, " InitCompleted: %d", MsPipe->InitCompleted); + } + } + + WLog_INFO(TAG, "=================================================="); +} diff --git a/channels/urbdrc/common/msusb.h b/channels/urbdrc/common/msusb.h new file mode 100644 index 0000000..89f1a2b --- /dev/null +++ b/channels/urbdrc/common/msusb.h @@ -0,0 +1,97 @@ +/** + * 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_UTILS_MSCONFIG_H +#define FREERDP_UTILS_MSCONFIG_H + +#include +#include + +typedef struct _MSUSB_INTERFACE_DESCRIPTOR MSUSB_INTERFACE_DESCRIPTOR; +typedef struct _MSUSB_PIPE_DESCRIPTOR MSUSB_PIPE_DESCRIPTOR; +typedef struct _MSUSB_CONFIG_DESCRIPTOR MSUSB_CONFIG_DESCRIPTOR; + +struct _MSUSB_PIPE_DESCRIPTOR +{ + UINT16 MaximumPacketSize; + UINT32 MaximumTransferSize; + UINT32 PipeFlags; + UINT32 PipeHandle; + BYTE bEndpointAddress; + BYTE bInterval; + BYTE PipeType; + int InitCompleted; +}; + +struct _MSUSB_INTERFACE_DESCRIPTOR +{ + UINT16 Length; + UINT16 NumberOfPipesExpected; + BYTE InterfaceNumber; + BYTE AlternateSetting; + UINT32 NumberOfPipes; + UINT32 InterfaceHandle; + BYTE bInterfaceClass; + BYTE bInterfaceSubClass; + BYTE bInterfaceProtocol; + MSUSB_PIPE_DESCRIPTOR** MsPipes; + int InitCompleted; +}; + +struct _MSUSB_CONFIG_DESCRIPTOR +{ + UINT16 wTotalLength; + BYTE bConfigurationValue; + UINT32 ConfigurationHandle; + UINT32 NumInterfaces; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + int InitCompleted; + int MsOutSize; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* MSUSB_PIPE exported functions */ + FREERDP_API BOOL msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, + MSUSB_PIPE_DESCRIPTOR** NewMsPipes, + UINT32 NewNumberOfPipes); + + /* MSUSB_INTERFACE exported functions */ + FREERDP_API BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig, + BYTE InterfaceNumber, + MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface); + FREERDP_API MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* out); + FREERDP_API BOOL msusb_msinterface_write(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, wStream* out); + + /* MSUSB_CONFIG exported functions */ + FREERDP_API MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void); + FREERDP_API void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig); + FREERDP_API MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces); + FREERDP_API BOOL msusb_msconfig_write(MSUSB_CONFIG_DESCRIPTOR* MsConfg, wStream* out); + FREERDP_API void msusb_msconfig_dump(MSUSB_CONFIG_DESCRIPTOR* MsConfg); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_UTILS_MSCONFIG_H */ diff --git a/channels/urbdrc/common/urbdrc_helpers.c b/channels/urbdrc/common/urbdrc_helpers.c new file mode 100644 index 0000000..53c3b89 --- /dev/null +++ b/channels/urbdrc/common/urbdrc_helpers.c @@ -0,0 +1,421 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server USB redirection channel - helper functions + * + * 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 "urbdrc_helpers.h" +#include "urbdrc_types.h" +#include + +const char* mask_to_string(UINT32 mask) +{ + switch (mask) + { + case STREAM_ID_NONE: + return "STREAM_ID_NONE"; + + case STREAM_ID_PROXY: + return "STREAM_ID_PROXY"; + + case STREAM_ID_STUB: + return "STREAM_ID_STUB"; + + default: + return "UNKNOWN"; + } +} +const char* interface_to_string(UINT32 id) +{ + switch (id) + { + case CAPABILITIES_NEGOTIATOR: + return "CAPABILITIES_NEGOTIATOR"; + + case SERVER_CHANNEL_NOTIFICATION: + return "SERVER_CHANNEL_NOTIFICATION"; + + case CLIENT_CHANNEL_NOTIFICATION: + return "CLIENT_CHANNEL_NOTIFICATION"; + + default: + return "DEVICE_MESSAGE"; + } +} + +static const char* call_to_string_none(BOOL client, UINT32 interfaceId, UINT32 functionId) +{ + WINPR_UNUSED(interfaceId); + + if (client) + return "RIM_EXCHANGE_CAPABILITY_RESPONSE [none |client]"; + else + { + switch (functionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + return "RIM_EXCHANGE_CAPABILITY_REQUEST [none |server]"; + + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [none |server]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [none |server]"; + + default: + return "UNKNOWN [none |server]"; + } + } +} + +static const char* call_to_string_proxy_server(UINT32 functionId) +{ + switch (functionId) + { + case QUERY_DEVICE_TEXT: + return "QUERY_DEVICE_TEXT [proxy|server]"; + + case INTERNAL_IO_CONTROL: + return "INTERNAL_IO_CONTROL [proxy|server]"; + + case IO_CONTROL: + return "IO_CONTROL [proxy|server]"; + + case REGISTER_REQUEST_CALLBACK: + return "REGISTER_REQUEST_CALLBACK [proxy|server]"; + + case CANCEL_REQUEST: + return "CANCEL_REQUEST [proxy|server]"; + + case RETRACT_DEVICE: + return "RETRACT_DEVICE [proxy|server]"; + + case TRANSFER_IN_REQUEST: + return "TRANSFER_IN_REQUEST [proxy|server]"; + + default: + return "UNKNOWN [proxy|server]"; + } +} + +static const char* call_to_string_proxy_client(UINT32 functionId) +{ + switch (functionId) + { + case URB_COMPLETION_NO_DATA: + return "URB_COMPLETION_NO_DATA [proxy|client]"; + + case URB_COMPLETION: + return "URB_COMPLETION [proxy|client]"; + + case IOCONTROL_COMPLETION: + return "IOCONTROL_COMPLETION [proxy|client]"; + + case TRANSFER_OUT_REQUEST: + return "TRANSFER_OUT_REQUEST [proxy|client]"; + + default: + return "UNKNOWN [proxy|client]"; + } +} + +static const char* call_to_string_proxy(BOOL client, UINT32 interfaceId, UINT32 functionId) +{ + switch (interfaceId & INTERFACE_ID_MASK) + { + case CLIENT_DEVICE_SINK: + switch (functionId) + { + case ADD_VIRTUAL_CHANNEL: + return "ADD_VIRTUAL_CHANNEL [proxy|sink ]"; + + case ADD_DEVICE: + return "ADD_DEVICE [proxy|sink ]"; + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|sink ]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|sink ]"; + default: + return "UNKNOWN [proxy|sink ]"; + } + + case SERVER_CHANNEL_NOTIFICATION: + switch (functionId) + { + case CHANNEL_CREATED: + return "CHANNEL_CREATED [proxy|server]"; + + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|server]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|server]"; + + default: + return "UNKNOWN [proxy|server]"; + } + + case CLIENT_CHANNEL_NOTIFICATION: + switch (functionId) + { + case CHANNEL_CREATED: + return "CHANNEL_CREATED [proxy|client]"; + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|client]"; + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|client]"; + default: + return "UNKNOWN [proxy|client]"; + } + + default: + if (client) + return call_to_string_proxy_client(functionId); + else + return call_to_string_proxy_server(functionId); + } +} + +static const char* call_to_string_stub(BOOL client, UINT32 interfaceId, UINT32 functionId) +{ + return "QUERY_DEVICE_TEXT_RSP [stub |client]"; +} + +const char* call_to_string(BOOL client, UINT32 interface, UINT32 functionId) +{ + const UINT32 mask = (interface & STREAM_ID_MASK) >> 30; + const UINT32 interfaceId = interface & INTERFACE_ID_MASK; + + switch (mask) + { + case STREAM_ID_NONE: + return call_to_string_none(client, interfaceId, functionId); + + case STREAM_ID_PROXY: + return call_to_string_proxy(client, interfaceId, functionId); + + case STREAM_ID_STUB: + return call_to_string_stub(client, interfaceId, functionId); + + default: + return "UNKNOWN[mask]"; + } +} + +const char* urb_function_string(UINT16 urb) +{ + switch (urb) + { + case TS_URB_SELECT_CONFIGURATION: + return "TS_URB_SELECT_CONFIGURATION"; + + case TS_URB_SELECT_INTERFACE: + return "TS_URB_SELECT_INTERFACE"; + + case TS_URB_PIPE_REQUEST: + return "TS_URB_PIPE_REQUEST"; + + case TS_URB_TAKE_FRAME_LENGTH_CONTROL: + return "TS_URB_TAKE_FRAME_LENGTH_CONTROL"; + + case TS_URB_RELEASE_FRAME_LENGTH_CONTROL: + return "TS_URB_RELEASE_FRAME_LENGTH_CONTROL"; + + case TS_URB_GET_FRAME_LENGTH: + return "TS_URB_GET_FRAME_LENGTH"; + + case TS_URB_SET_FRAME_LENGTH: + return "TS_URB_SET_FRAME_LENGTH"; + + case TS_URB_GET_CURRENT_FRAME_NUMBER: + return "TS_URB_GET_CURRENT_FRAME_NUMBER"; + + case TS_URB_CONTROL_TRANSFER: + return "TS_URB_CONTROL_TRANSFER"; + + case TS_URB_BULK_OR_INTERRUPT_TRANSFER: + return "TS_URB_BULK_OR_INTERRUPT_TRANSFER"; + + case TS_URB_ISOCH_TRANSFER: + return "TS_URB_ISOCH_TRANSFER"; + + case TS_URB_GET_DESCRIPTOR_FROM_DEVICE: + return "TS_URB_GET_DESCRIPTOR_FROM_DEVICE"; + + case TS_URB_SET_DESCRIPTOR_TO_DEVICE: + return "TS_URB_SET_DESCRIPTOR_TO_DEVICE"; + + case TS_URB_SET_FEATURE_TO_DEVICE: + return "TS_URB_SET_FEATURE_TO_DEVICE"; + + case TS_URB_SET_FEATURE_TO_INTERFACE: + return "TS_URB_SET_FEATURE_TO_INTERFACE"; + + case TS_URB_SET_FEATURE_TO_ENDPOINT: + return "TS_URB_SET_FEATURE_TO_ENDPOINT"; + + case TS_URB_CLEAR_FEATURE_TO_DEVICE: + return "TS_URB_CLEAR_FEATURE_TO_DEVICE"; + + case TS_URB_CLEAR_FEATURE_TO_INTERFACE: + return "TS_URB_CLEAR_FEATURE_TO_INTERFACE"; + + case TS_URB_CLEAR_FEATURE_TO_ENDPOINT: + return "TS_URB_CLEAR_FEATURE_TO_ENDPOINT"; + + case TS_URB_GET_STATUS_FROM_DEVICE: + return "TS_URB_GET_STATUS_FROM_DEVICE"; + + case TS_URB_GET_STATUS_FROM_INTERFACE: + return "TS_URB_GET_STATUS_FROM_INTERFACE"; + + case TS_URB_GET_STATUS_FROM_ENDPOINT: + return "TS_URB_GET_STATUS_FROM_ENDPOINT"; + + case TS_URB_RESERVED_0X0016: + return "TS_URB_RESERVED_0X0016"; + + case TS_URB_VENDOR_DEVICE: + return "TS_URB_VENDOR_DEVICE"; + + case TS_URB_VENDOR_INTERFACE: + return "TS_URB_VENDOR_INTERFACE"; + + case TS_URB_VENDOR_ENDPOINT: + return "TS_URB_VENDOR_ENDPOINT"; + + case TS_URB_CLASS_DEVICE: + return "TS_URB_CLASS_DEVICE"; + + case TS_URB_CLASS_INTERFACE: + return "TS_URB_CLASS_INTERFACE"; + + case TS_URB_CLASS_ENDPOINT: + return "TS_URB_CLASS_ENDPOINT"; + + case TS_URB_RESERVE_0X001D: + return "TS_URB_RESERVE_0X001D"; + + case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL: + return "TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL"; + + case TS_URB_CLASS_OTHER: + return "TS_URB_CLASS_OTHER"; + + case TS_URB_VENDOR_OTHER: + return "TS_URB_VENDOR_OTHER"; + + case TS_URB_GET_STATUS_FROM_OTHER: + return "TS_URB_GET_STATUS_FROM_OTHER"; + + case TS_URB_CLEAR_FEATURE_TO_OTHER: + return "TS_URB_CLEAR_FEATURE_TO_OTHER"; + + case TS_URB_SET_FEATURE_TO_OTHER: + return "TS_URB_SET_FEATURE_TO_OTHER"; + + case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT: + return "TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT"; + + case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT: + return "TS_URB_SET_DESCRIPTOR_TO_ENDPOINT"; + + case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST: + return "TS_URB_CONTROL_GET_CONFIGURATION_REQUEST"; + + case TS_URB_CONTROL_GET_INTERFACE_REQUEST: + return "TS_URB_CONTROL_GET_INTERFACE_REQUEST"; + + case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE: + return "TS_URB_GET_DESCRIPTOR_FROM_INTERFACE"; + + case TS_URB_SET_DESCRIPTOR_TO_INTERFACE: + return "TS_URB_SET_DESCRIPTOR_TO_INTERFACE"; + + case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST: + return "TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST"; + + case TS_URB_RESERVE_0X002B: + return "TS_URB_RESERVE_0X002B"; + + case TS_URB_RESERVE_0X002C: + return "TS_URB_RESERVE_0X002C"; + + case TS_URB_RESERVE_0X002D: + return "TS_URB_RESERVE_0X002D"; + + case TS_URB_RESERVE_0X002E: + return "TS_URB_RESERVE_0X002E"; + + case TS_URB_RESERVE_0X002F: + return "TS_URB_RESERVE_0X002F"; + + case TS_URB_SYNC_RESET_PIPE: + return "TS_URB_SYNC_RESET_PIPE"; + + case TS_URB_SYNC_CLEAR_STALL: + return "TS_URB_SYNC_CLEAR_STALL"; + + case TS_URB_CONTROL_TRANSFER_EX: + return "TS_URB_CONTROL_TRANSFER_EX"; + + default: + return "UNKNOWN"; + } +} + +void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s) +{ + const char* type = write ? "WRITE" : "READ"; + UINT32 InterfaceId, MessageId, FunctionId; + size_t length, pos; + + pos = Stream_GetPosition(s); + if (write) + { + length = pos; + Stream_SetPosition(s, 0); + } + else + length = Stream_GetRemainingLength(s); + + if (length < 12) + return; + + Stream_Read_UINT32(s, InterfaceId); + Stream_Read_UINT32(s, MessageId); + Stream_Read_UINT32(s, FunctionId); + Stream_SetPosition(s, pos); + + WLog_Print(log, WLOG_DEBUG, + "[%-5s] %s [%08" PRIx32 "] InterfaceId=%08" PRIx32 ", MessageId=%08" PRIx32 + ", FunctionId=%08" PRIx32 ", length=%" PRIuz, + type, call_to_string(client, InterfaceId, FunctionId), FunctionId, InterfaceId, + MessageId, FunctionId, length); +#if defined(WITH_DEBUG_URBDRC) + if (write) + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC sent: ---"); + else + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC received:"); + winpr_HexLogDump(log, WLOG_TRACE, Stream_Buffer(s), length); + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC end -----"); +#endif +} diff --git a/channels/urbdrc/common/urbdrc_helpers.h b/channels/urbdrc/common/urbdrc_helpers.h new file mode 100644 index 0000000..e9e25af --- /dev/null +++ b/channels/urbdrc/common/urbdrc_helpers.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server USB redirection channel - helper functions + * + * 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. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_HELPERS_H +#define FREERDP_CHANNEL_URBDRC_HELPERS_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + + const char* urb_function_string(UINT16 urb); + const char* mask_to_string(UINT32 mask); + const char* interface_to_string(UINT32 id); + const char* call_to_string(BOOL client, UINT32 interface, UINT32 functionId); + + void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_URBDRC_HELPERS_H */ diff --git a/channels/urbdrc/common/urbdrc_types.h b/channels/urbdrc/common/urbdrc_types.h new file mode 100644 index 0000000..c120715 --- /dev/null +++ b/channels/urbdrc/common/urbdrc_types.h @@ -0,0 +1,308 @@ +/** + * 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 + +#define RIM_CAPABILITY_VERSION_01 0x00000001 + +#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 RIMCALL_QUERYINTERFACE 0x00000002 +#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 0x0UL +#define STREAM_ID_PROXY 0x1UL +#define STREAM_ID_STUB 0x2UL +#define STREAM_ID_MASK 0xC0000000 +#define INTERFACE_ID_MASK 0x3FFFFFFF + +#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 TS_URB_SELECT_CONFIGURATION 0x0000 +#define TS_URB_SELECT_INTERFACE 0x0001 +#define TS_URB_PIPE_REQUEST 0x0002 +#define TS_URB_TAKE_FRAME_LENGTH_CONTROL 0x0003 +#define TS_URB_RELEASE_FRAME_LENGTH_CONTROL 0x0004 +#define TS_URB_GET_FRAME_LENGTH 0x0005 +#define TS_URB_SET_FRAME_LENGTH 0x0006 +#define TS_URB_GET_CURRENT_FRAME_NUMBER 0x0007 +#define TS_URB_CONTROL_TRANSFER 0x0008 +#define TS_URB_BULK_OR_INTERRUPT_TRANSFER 0x0009 +#define TS_URB_ISOCH_TRANSFER 0x000A +#define TS_URB_GET_DESCRIPTOR_FROM_DEVICE 0x000B +#define TS_URB_SET_DESCRIPTOR_TO_DEVICE 0x000C +#define TS_URB_SET_FEATURE_TO_DEVICE 0x000D +#define TS_URB_SET_FEATURE_TO_INTERFACE 0x000E +#define TS_URB_SET_FEATURE_TO_ENDPOINT 0x000F +#define TS_URB_CLEAR_FEATURE_TO_DEVICE 0x0010 +#define TS_URB_CLEAR_FEATURE_TO_INTERFACE 0x0011 +#define TS_URB_CLEAR_FEATURE_TO_ENDPOINT 0x0012 +#define TS_URB_GET_STATUS_FROM_DEVICE 0x0013 +#define TS_URB_GET_STATUS_FROM_INTERFACE 0x0014 +#define TS_URB_GET_STATUS_FROM_ENDPOINT 0x0015 +#define TS_URB_RESERVED_0X0016 0x0016 +#define TS_URB_VENDOR_DEVICE 0x0017 +#define TS_URB_VENDOR_INTERFACE 0x0018 +#define TS_URB_VENDOR_ENDPOINT 0x0019 +#define TS_URB_CLASS_DEVICE 0x001A +#define TS_URB_CLASS_INTERFACE 0x001B +#define TS_URB_CLASS_ENDPOINT 0x001C +#define TS_URB_RESERVE_0X001D 0x001D +#define TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL 0x001E +#define TS_URB_CLASS_OTHER 0x001F +#define TS_URB_VENDOR_OTHER 0x0020 +#define TS_URB_GET_STATUS_FROM_OTHER 0x0021 +#define TS_URB_CLEAR_FEATURE_TO_OTHER 0x0022 +#define TS_URB_SET_FEATURE_TO_OTHER 0x0023 +#define TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT 0x0024 +#define TS_URB_SET_DESCRIPTOR_TO_ENDPOINT 0x0025 +#define TS_URB_CONTROL_GET_CONFIGURATION_REQUEST 0x0026 +#define TS_URB_CONTROL_GET_INTERFACE_REQUEST 0x0027 +#define TS_URB_GET_DESCRIPTOR_FROM_INTERFACE 0x0028 +#define TS_URB_SET_DESCRIPTOR_TO_INTERFACE 0x0029 +#define TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST 0x002A +#define TS_URB_RESERVE_0X002B 0x002B +#define TS_URB_RESERVE_0X002C 0x002C +#define TS_URB_RESERVE_0X002D 0x002D +#define TS_URB_RESERVE_0X002E 0x002E +#define TS_URB_RESERVE_0X002F 0x002F +// USB 2.0 calls start at 0x0030 +#define TS_URB_SYNC_RESET_PIPE 0x0030 +#define TS_URB_SYNC_CLEAR_STALL 0x0031 +#define TS_URB_CONTROL_TRANSFER_EX 0x0032 + +#define USBD_STATUS_SUCCESS 0x0 +#define USBD_STATUS_PENDING 0x40000000 +#define USBD_STATUS_CANCELED 0xC0010000 + +#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200 +#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_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 + +#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 100644 index 0000000..a8031fc --- /dev/null +++ b/channels/video/client/video_main.c @@ -0,0 +1,1190 @@ +/** + * 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; + BOOL initialized; +}; +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; +} + +static 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) +{ + size_t s; + VideoClientContextPriv* priv = video->priv; + PresentationContext* ret; + s = width * height * 4ULL; + if (s > INT32_MAX) + return NULL; + + 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, s); + 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 ret; +} + +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 * 4ULL); + + 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 * 4ULL); + 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); + if (!Stream_CheckAndLogRequiredLength(TAG, s, data.cbSample)) + return ERROR_INVALID_DATA; + 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; + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + 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; + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + 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; + + if (video->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", VIDEO_CONTROL_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + 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, + &callback->iface, &(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, + &callback->iface, &(video->dataListener)); + + if (status == CHANNEL_RC_OK) + video->dataListener->pInterface = video->wtsPlugin.pInterface; + + video->initialized = status == CHANNEL_RC_OK; + 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->control_callback) + { + IWTSVirtualChannelManager* mgr = video->control_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, video->controlListener); + } + if (video->data_callback) + { + IWTSVirtualChannelManager* mgr = video->data_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, video->dataListener); + } + + 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..1814566 --- /dev/null +++ b/channels/video/client/video_main.h @@ -0,0 +1,33 @@ +/** + * 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..bcb3446 --- /dev/null +++ b/ci/cmake-preloads/config-android.txt @@ -0,0 +1,7 @@ +message("PRELOADING android cache") +set(CMAKE_TOOLCHAIN_FILE "$ANDROID_NDK/build/cmake/android.toolchain.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..08145bb --- /dev/null +++ b/ci/cmake-preloads/config-ios.txt @@ -0,0 +1,8 @@ +message("PRELOADING iOS cache") +set (CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/ios.toolchain.cmake" CACHE PATH "cmake 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 (CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "iOS platform to build") +set (CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "iOS minimum target") +set (WITH_SANITIZE_ADDRESS ON CACHE BOOL "build with address sanitizer") +set (WITH_CLIENT OFF CACHE BOOL "disable iOS client") diff --git a/ci/cmake-preloads/config-linux-all.txt b/ci/cmake-preloads/config-linux-all.txt new file mode 100644 index 0000000..4ba743e --- /dev/null +++ b/ci/cmake-preloads/config-linux-all.txt @@ -0,0 +1,53 @@ +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_WAYLAND ON CACHE BOOL "wayland") +set (WITH_GSSAPI ON CACHE BOOL "Kerberos support") +set (WITH_PCSC ON CACHE BOOL "PCSC") +set (WITH_JPEG ON CACHE BOOL "jpeg") +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_RDPGFX 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) +set (WITH_PROXY_MODULES OFF CACHE BOOL "compile proxy modules") diff --git a/ci/cmake-preloads/config-macosx.txt b/ci/cmake-preloads/config-macosx.txt new file mode 100644 index 0000000..a004dd9 --- /dev/null +++ b/ci/cmake-preloads/config-macosx.txt @@ -0,0 +1,8 @@ +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 (CHANNEL_URBDRC OFF CACHE BOOL "USB redirection") +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..fcc78ae --- /dev/null +++ b/ci/cmake-preloads/config-windows.txt @@ -0,0 +1,6 @@ +message("PRELOADING windows cache") +set (CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set (WITH_SERVER "ON" CACHE BOOL "Build server binaries") +set (CHANNEL_URBDRC OFF CACHE BOOL "USB redirection") +set (BUILD_TESTING ON CACHE BOOL "build testing") +set (WITH_SANITIZE_ADDRESS ON) diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..7c1ea95 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,12 @@ +/* +!/Android +!/common +!/iOS +!/Mac +!/Sample +!/Windows +!/X11 +!/Wayland +!/CMakeLists.txt +!*.in +Wayland/wlfreerdp.1 diff --git a/client/Android/BuildFlags.java.in b/client/Android/BuildFlags.java.in new file mode 100644 index 0000000..9b15e47 --- /dev/null +++ b/client/Android/BuildFlags.java.in @@ -0,0 +1,6 @@ +package com.freerdp.freerdpcore.utils; + +public class BuildFlags +{ + private final static boolean USE_OPENSSL_DEFAULT_NAMES = @USE_OPENSSL_DEFAULT_NAMES@; +} diff --git a/client/Android/CMakeLists.txt b/client/Android/CMakeLists.txt new file mode 100644 index 0000000..b3473a9 --- /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..96f7184 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/build.gradle @@ -0,0 +1,52 @@ +plugins { + id 'com.gladed.androidgitversion' version '0.4.4' +} + +androidGitVersion { + abis = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'mips':5, 'mips64':6, 'x86':8, 'x86_64':9 ] + prefix '' +} + +println 'Version Name: ' + androidGitVersion.name() +println 'Version Code: ' + androidGitVersion.code() + +apply plugin: 'com.android.application' +android { + compileSdkVersion = rootProject.ext.compileApi + buildToolsVersion = rootProject.ext.toolsVersion + + defaultConfig { + applicationId "com.freerdp.afreerdp" + minSdkVersion rootProject.ext.minApi + targetSdkVersion rootProject.ext.targetApi + vectorDrawables.useSupportLibrary = true + versionName = androidGitVersion.name() + versionCode = androidGitVersion.code() + } + + 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 { + implementation 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..8c46bc9 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..7d44959 --- /dev/null +++ b/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java @@ -0,0 +1,5 @@ +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..81ae37c --- /dev/null +++ b/client/Android/Studio/build.gradle @@ -0,0 +1,52 @@ +// 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='nokeyfile' +} +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='' +} + +ext { + compileApi = properties.get('COMPILE_API', 31) + targetApi = properties.get('TARGET_API', 31) + minApi = properties.get('MIN_API', 23) + toolsVersion = properties.get('TOOLS_VERSION', '31.0.0') + + println '----------------- Project configuration -------------------' + println 'RELEASE_STORE_FILE: ' + RELEASE_STORE_FILE + println 'RELEASE_KEY_ALIAS: ' + RELEASE_KEY_ALIAS + println 'compile API: ' + compileApi + println 'target API: ' + targetApi + println 'min API: ' + minApi + println 'tools version: ' + toolsVersion + println '-----------------------------------------------------------' +} + +buildscript { + repositories { + mavenCentral() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + } +} + +allprojects { + repositories { + mavenCentral() + google() + } +} diff --git a/client/Android/Studio/freeRDPCore/build.gradle b/client/Android/Studio/freeRDPCore/build.gradle new file mode 100644 index 0000000..e2224c2 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion = rootProject.ext.compileApi + buildToolsVersion = rootProject.ext.toolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minApi + targetSdkVersion rootProject.ext.targetApi + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + debug { + jniDebuggable true + renderscriptDebuggable true + } + } +} + +dependencies { + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:support-vector-drawable:28.0.0' +} 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..3461333 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..e71a86e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java @@ -0,0 +1,211 @@ +/* + 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..ea3d663 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java @@ -0,0 +1,58 @@ +/* + 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..d1330ca --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java @@ -0,0 +1,30 @@ +/* + 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..1e1431c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java @@ -0,0 +1,129 @@ +/* + 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..171f279 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java @@ -0,0 +1,1063 @@ +/* + 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..3e68776 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java @@ -0,0 +1,85 @@ +/* + 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..874d4e9 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java @@ -0,0 +1,255 @@ +/* + 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..d15aaf7 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java @@ -0,0 +1,84 @@ +/* + 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..3367b54 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java @@ -0,0 +1,70 @@ +/* + 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..8fdfa7a --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java @@ -0,0 +1,121 @@ +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 androidx.core.text.TextUtilsCompat; +import androidx.appcompat.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..2506e71 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java @@ -0,0 +1,300 @@ +/* + 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 androidx.appcompat.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..89ac4d4 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java @@ -0,0 +1,743 @@ +/* + 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..8772c9e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java @@ -0,0 +1,77 @@ +/* + 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 androidx.appcompat.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..f8cd21c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java @@ -0,0 +1,399 @@ +/* + 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 androidx.appcompat.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.equals(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..ad1d572 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java @@ -0,0 +1,1349 @@ +/* + * 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..a2d25f1 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java @@ -0,0 +1,1433 @@ +/* + 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 androidx.appcompat.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, true); + LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey, false); + } + + @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..fd4e02b --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java @@ -0,0 +1,411 @@ +/* + 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.Color; +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.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.utils.DoubleGestureDetector; +import com.freerdp.freerdpcore.utils.GestureDetector; + +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); + canvas.drawColor(Color.BLACK); + 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..2121c6e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java @@ -0,0 +1,160 @@ +/* + 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..6b8b96c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java @@ -0,0 +1,385 @@ +/* + 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..d3ed7fe --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java @@ -0,0 +1,617 @@ +/* + 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..420e540 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java @@ -0,0 +1,422 @@ +/* + 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..d5f657c --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java @@ -0,0 +1,134 @@ +/* + 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..b483aac --- /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..0f4a921 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java @@ -0,0 +1,656 @@ +/* + 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 androidx.collection.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; +import java.util.List; +import java.util.Objects; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LibFreeRDP +{ + private static final String TAG = "LibFreeRDP"; + private static EventListener listener; + private static boolean mHasH264 = false; + + private static final LongSparseArray mInstanceState = new LongSparseArray<>(); + + private static boolean tryLoad(String[] libraries) + { + boolean success = false; + 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); + success = true; + } + catch (UnsatisfiedLinkError e) + { + Log.e(TAG, "Failed to load library " + lib + ": " + e.toString()); + success = false; + break; + } + } + + return success; + } + + private static boolean tryLoad(String library) + { + return tryLoad(new String[] { library }); + } + static + { + try + { + System.loadLibrary("freerdp-android"); + String version = freerdp_get_jni_version(); + Pattern pattern = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+).*"); + Matcher matcher = pattern.matcher(version); + if (!matcher.matches() || (matcher.groupCount() < 3)) + throw new RuntimeException("APK broken: native library version " + version + + " does not meet requirements!"); + int major = Integer.parseInt(Objects.requireNonNull(matcher.group(1))); + int minor = Integer.parseInt(Objects.requireNonNull(matcher.group(2))); + int patch = Integer.parseInt(Objects.requireNonNull(matcher.group(3))); + + if (major > 2) + mHasH264 = freerdp_has_h264(); + else if (minor > 5) + mHasH264 = freerdp_has_h264(); + else if ((minor == 5) && (patch >= 1)) + mHasH264 = freerdp_has_h264(); + else + throw new RuntimeException("APK broken: native library version " + version + + " does not meet requirements!"); + Log.i(TAG, "Successfully loaded native library. H264 is " + + (mHasH264 ? "supported" : "not available")); + } + catch (UnsatisfiedLinkError e) + { + Log.e(TAG, "Failed to load library: " + e.toString()); + throw e; + } + } + + public static boolean hasH264Support() + { + return mHasH264; + } + + private static native boolean freerdp_has_h264(); + + 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, + boolean down); + + private static native boolean freerdp_send_clipboard_data(long inst, String data); + + private static native String freerdp_get_last_error_string(long inst); + + 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, boolean down) + { + return freerdp_send_unicodekey_event(inst, keycode, down); + } + + 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..2cd2751 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java @@ -0,0 +1,131 @@ +/* + 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..4dd5139 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java @@ -0,0 +1,121 @@ +/* + 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..9436210 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java @@ -0,0 +1,77 @@ +/* + 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 androidx.appcompat.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..d321d7b --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java @@ -0,0 +1,112 @@ +package com.freerdp.freerdpcore.utils; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.LayoutRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.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..6c6de6a --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java @@ -0,0 +1,135 @@ +/* + 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..72c8cf0 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java @@ -0,0 +1,96 @@ +/* + 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..cb26ddb --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java @@ -0,0 +1,100 @@ +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..2e8dfc8 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java @@ -0,0 +1,349 @@ +/* + 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..2e971b5 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java @@ -0,0 +1,620 @@ +/* + * 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..a383d91 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java @@ -0,0 +1,101 @@ +/* + 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..0b4f643 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java @@ -0,0 +1,39 @@ +/* + 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..f1456b2 --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java @@ -0,0 +1,725 @@ +/* + 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..11f1d3e --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java @@ -0,0 +1,64 @@ +/* + 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..413050a --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java @@ -0,0 +1,111 @@ +/* + 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..659732d --- /dev/null +++ b/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java @@ -0,0 +1,208 @@ +/* + 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/Clipboard.h b/client/Mac/Clipboard.h new file mode 100644 index 0000000..5883d3b --- /dev/null +++ b/client/Mac/Clipboard.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * 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. + */ + +#import "mfreerdp.h" +#import "mf_client.h" + +#import "freerdp/freerdp.h" +#import "freerdp/channels/channels.h" +#import "freerdp/client/cliprdr.h" + +int mac_cliprdr_send_client_format_list(CliprdrClientContext* cliprdr); + +void mac_cliprdr_init(mfContext* mfc, CliprdrClientContext* cliprdr); +void mac_cliprdr_uninit(mfContext* mfc, CliprdrClientContext* cliprdr); diff --git a/client/Mac/Clipboard.m b/client/Mac/Clipboard.m new file mode 100644 index 0000000..a57725f --- /dev/null +++ b/client/Mac/Clipboard.m @@ -0,0 +1,433 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * 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. + */ + +#import "Clipboard.h" + +int mac_cliprdr_send_client_format_list(CliprdrClientContext *cliprdr) +{ + UINT32 index; + UINT32 formatId; + UINT32 numFormats; + UINT32 *pFormatIds; + const char *formatName; + CLIPRDR_FORMAT *formats; + CLIPRDR_FORMAT_LIST formatList = { 0 }; + mfContext *mfc = (mfContext *)cliprdr->custom; + + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + + pFormatIds = NULL; + numFormats = ClipboardGetFormatIds(mfc->clipboard, &pFormatIds); + + formats = (CLIPRDR_FORMAT *)calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + return -1; + + for (index = 0; index < numFormats; index++) + { + formatId = pFormatIds[index]; + formatName = ClipboardGetFormatName(mfc->clipboard, formatId); + + formats[index].formatId = formatId; + formats[index].formatName = NULL; + + if ((formatId > CF_MAX) && formatName) + formats[index].formatName = _strdup(formatName); + } + + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = numFormats; + formatList.formats = formats; + formatList.msgType = CB_FORMAT_LIST; + + mfc->cliprdr->ClientFormatList(mfc->cliprdr, &formatList); + + for (index = 0; index < numFormats; index++) + { + free(formats[index].formatName); + } + + free(pFormatIds); + free(formats); + + return 1; +} + +static int mac_cliprdr_send_client_format_list_response(CliprdrClientContext *cliprdr, 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; + + cliprdr->ClientFormatListResponse(cliprdr, &formatListResponse); + + return 1; +} + +static int mac_cliprdr_send_client_format_data_request(CliprdrClientContext *cliprdr, + UINT32 formatId) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + mfContext *mfc = (mfContext *)cliprdr->custom; + + ZeroMemory(&formatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); + + formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.msgFlags = 0; + + formatDataRequest.requestedFormatId = formatId; + mfc->requestedFormatId = formatId; + ResetEvent(mfc->clipboardRequestEvent); + + cliprdr->ClientFormatDataRequest(cliprdr, &formatDataRequest); + + return 1; +} + +static int mac_cliprdr_send_client_capabilities(CliprdrClientContext *cliprdr) +{ + 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; + + cliprdr->ClientCapabilities(cliprdr, &capabilities); + + return 1; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_monitor_ready(CliprdrClientContext *cliprdr, + const CLIPRDR_MONITOR_READY *monitorReady) +{ + mfContext *mfc = (mfContext *)cliprdr->custom; + + mfc->clipboardSync = TRUE; + mac_cliprdr_send_client_capabilities(cliprdr); + mac_cliprdr_send_client_format_list(cliprdr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_capabilities(CliprdrClientContext *cliprdr, + const CLIPRDR_CAPABILITIES *capabilities) +{ + UINT32 index; + CLIPRDR_CAPABILITY_SET *capabilitySet; + mfContext *mfc = (mfContext *)cliprdr->custom; + + 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; + + mfc->clipboardCapabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_format_list(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_LIST *formatList) +{ + UINT32 index; + CLIPRDR_FORMAT *format; + mfContext *mfc = (mfContext *)cliprdr->custom; + + if (mfc->serverFormats) + { + for (index = 0; index < mfc->numServerFormats; index++) + { + free(mfc->serverFormats[index].formatName); + } + + free(mfc->serverFormats); + mfc->serverFormats = NULL; + mfc->numServerFormats = 0; + } + + if (formatList->numFormats < 1) + return CHANNEL_RC_OK; + + mfc->numServerFormats = formatList->numFormats; + mfc->serverFormats = (CLIPRDR_FORMAT *)calloc(mfc->numServerFormats, sizeof(CLIPRDR_FORMAT)); + + if (!mfc->serverFormats) + return CHANNEL_RC_NO_MEMORY; + + for (index = 0; index < mfc->numServerFormats; index++) + { + mfc->serverFormats[index].formatId = formatList->formats[index].formatId; + mfc->serverFormats[index].formatName = NULL; + + if (formatList->formats[index].formatName) + mfc->serverFormats[index].formatName = _strdup(formatList->formats[index].formatName); + } + + mac_cliprdr_send_client_format_list_response(cliprdr, TRUE); + + for (index = 0; index < mfc->numServerFormats; index++) + { + format = &(mfc->serverFormats[index]); + + if (format->formatId == CF_UNICODETEXT) + { + mac_cliprdr_send_client_format_data_request(cliprdr, CF_UNICODETEXT); + break; + } + else if (format->formatId == CF_OEMTEXT) + { + mac_cliprdr_send_client_format_data_request(cliprdr, CF_OEMTEXT); + break; + } + else if (format->formatId == CF_TEXT) + { + mac_cliprdr_send_client_format_data_request(cliprdr, CF_TEXT); + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_list_response(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_lock_clipboard_data(CliprdrClientContext *cliprdr, + const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_unlock_clipboard_data(CliprdrClientContext *cliprdr, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_data_request(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) +{ + BYTE *data; + UINT32 size; + UINT32 formatId; + CLIPRDR_FORMAT_DATA_RESPONSE response; + mfContext *mfc = (mfContext *)cliprdr->custom; + + ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); + + formatId = formatDataRequest->requestedFormatId; + data = (BYTE *)ClipboardGetData(mfc->clipboard, formatId, &size); + + response.msgFlags = CB_RESPONSE_OK; + response.dataLen = size; + response.requestedFormatData = data; + + if (!data) + { + response.msgFlags = CB_RESPONSE_FAIL; + response.dataLen = 0; + response.requestedFormatData = NULL; + } + + cliprdr->ClientFormatDataResponse(cliprdr, &response); + + free(data); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_data_response(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) +{ + BYTE *data; + UINT32 size; + UINT32 index; + UINT32 formatId; + CLIPRDR_FORMAT *format = NULL; + mfContext *mfc = (mfContext *)cliprdr->custom; + MRDPView *view = (MRDPView *)mfc->view; + + if (formatDataResponse->msgFlags & CB_RESPONSE_FAIL) + { + SetEvent(mfc->clipboardRequestEvent); + return ERROR_INTERNAL_ERROR; + } + + for (index = 0; index < mfc->numServerFormats; index++) + { + if (mfc->requestedFormatId == mfc->serverFormats[index].formatId) + format = &(mfc->serverFormats[index]); + } + + if (!format) + { + SetEvent(mfc->clipboardRequestEvent); + return ERROR_INTERNAL_ERROR; + } + + if (format->formatName) + formatId = ClipboardRegisterFormat(mfc->clipboard, format->formatName); + else + formatId = format->formatId; + + size = formatDataResponse->dataLen; + + ClipboardSetData(mfc->clipboard, formatId, formatDataResponse->requestedFormatData, size); + + SetEvent(mfc->clipboardRequestEvent); + + if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) || (formatId == CF_UNICODETEXT)) + { + formatId = ClipboardRegisterFormat(mfc->clipboard, "UTF8_STRING"); + + data = (void *)ClipboardGetData(mfc->clipboard, formatId, &size); + + if (size > 1) + size--; /* we need the size without the null terminator */ + + NSString *str = [[NSString alloc] initWithBytes:(void *)data + length:size + encoding:NSUTF8StringEncoding]; + free(data); + + NSArray *types = [[NSArray alloc] initWithObjects:NSStringPboardType, nil]; + [view->pasteboard_wr declareTypes:types owner:view]; + [view->pasteboard_wr setString:str forType:NSStringPboardType]; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_file_contents_request(CliprdrClientContext *cliprdr, + const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_file_contents_response( + CliprdrClientContext *cliprdr, const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse) +{ + return CHANNEL_RC_OK; +} + +void mac_cliprdr_init(mfContext *mfc, CliprdrClientContext *cliprdr) +{ + cliprdr->custom = (void *)mfc; + mfc->cliprdr = cliprdr; + + mfc->clipboard = ClipboardCreate(); + mfc->clipboardRequestEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + cliprdr->MonitorReady = mac_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = mac_cliprdr_server_capabilities; + cliprdr->ServerFormatList = mac_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = mac_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = mac_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = mac_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = mac_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = mac_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = mac_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = mac_cliprdr_server_file_contents_response; +} + +void mac_cliprdr_uninit(mfContext *mfc, CliprdrClientContext *cliprdr) +{ + cliprdr->custom = NULL; + mfc->cliprdr = NULL; + + ClipboardDestroy(mfc->clipboard); + CloseHandle(mfc->clipboardRequestEvent); +} diff --git a/client/Mac/Credits.rtf b/client/Mac/Credits.rtf new file mode 100644 index 0000000..41b9f40 --- /dev/null +++ b/client/Mac/Credits.rtf @@ -0,0 +1,21 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww9600\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Jay sorg\ + Marc-Andre Moreau\ + Vic Lee\ + Otvaio Salvador \ + Laxmikant Rashinkar\ + and others\ +\ + +\b Human Interface Design: +\b0 \ + Laxmikant Rashinkar\ + Jay Sorg\ +} \ No newline at end of file diff --git a/client/Mac/Info.plist b/client/Mac/Info.plist new file mode 100644 index 0000000..fd111db --- /dev/null +++ b/client/Mac/Info.plist @@ -0,0 +1,30 @@ + + + + + NSCameraUsageDescription + This application requires camera access to redirect it to the remote host + NSMicrophoneUsageDescription + This application requires microphone access to redirect it to the remote host + CFBundleDevelopmentRegion + English + CFBundleIconFile + + CFBundleIdentifier + FreeRDP.Mac + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/client/Mac/Keyboard.h b/client/Mac/Keyboard.h new file mode 100644 index 0000000..27efcdf --- /dev/null +++ b/client/Mac/Keyboard.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * 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. + */ + +enum APPLE_KEYBOARD_TYPE +{ + APPLE_KEYBOARD_TYPE_ANSI, + APPLE_KEYBOARD_TYPE_ISO, + APPLE_KEYBOARD_TYPE_JIS +}; + +enum APPLE_KEYBOARD_TYPE mac_detect_keyboard_type(void); diff --git a/client/Mac/Keyboard.m b/client/Mac/Keyboard.m new file mode 100644 index 0000000..9bb9cd4 --- /dev/null +++ b/client/Mac/Keyboard.m @@ -0,0 +1,241 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * 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. + */ + +#import "Keyboard.h" + +#include + +#include +#include + +struct _APPLE_KEYBOARD_DESC +{ + uint32_t ProductId; + enum APPLE_KEYBOARD_TYPE Type; +}; +typedef struct _APPLE_KEYBOARD_DESC APPLE_KEYBOARD_DESC; + +/* VendorID: 0x05AC (Apple, Inc.) */ + +static const APPLE_KEYBOARD_DESC APPLE_KEYBOARDS[] = { + { 0x200, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x201, APPLE_KEYBOARD_TYPE_ANSI }, /* USB Keyboard [Alps or Logitech, M2452] */ + { 0x202, APPLE_KEYBOARD_TYPE_ANSI }, /* Keyboard [ALPS] */ + { 0x203, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x204, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x205, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x206, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x207, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x208, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x209, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x20A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x20B, APPLE_KEYBOARD_TYPE_ANSI }, /* Pro Keyboard [Mitsumi, A1048/US layout] */ + { 0x20C, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x20D, APPLE_KEYBOARD_TYPE_ANSI }, /* Pro Keyboard [Mitsumi, A1048/JIS layout] */ + { 0x20E, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x20F, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x210, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x211, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x212, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x213, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x214, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x215, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x216, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x217, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x218, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x219, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x21A, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x21B, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x21C, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x21D, APPLE_KEYBOARD_TYPE_ANSI }, /* Aluminum Mini Keyboard (ANSI) */ + { 0x21E, APPLE_KEYBOARD_TYPE_ISO }, /* Aluminum Mini Keyboard (ISO) */ + { 0x21F, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Mini Keyboard (JIS) */ + { 0x220, APPLE_KEYBOARD_TYPE_ANSI }, /* Aluminum Keyboard (ANSI) */ + { 0x221, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Keyboard (JIS) */ + { 0x222, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Keyboard (JIS) */ + { 0x223, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x224, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x225, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x226, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x227, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x228, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x229, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (MacBook Pro) (ANSI) */ + { 0x22A, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Pro) (ISO) */ + { 0x22B, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (MacBook Pro) (JIS) */ + { 0x22C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x230, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (ANSI) */ + { 0x231, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (ISO) */ + { 0x232, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (JIS) */ + { 0x233, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x234, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x235, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x236, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x237, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x238, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x239, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23F, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x240, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x241, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x242, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x243, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x244, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x245, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x246, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x247, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x248, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x249, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24A, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Air) (ISO) */ + { 0x24B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24D, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Air) (ISO) */ + { 0x24E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x250, APPLE_KEYBOARD_TYPE_ISO }, /* Aluminium Keyboard (ISO) */ + { 0x251, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x252, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x253, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x254, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x255, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x256, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x257, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x258, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x259, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x260, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x261, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x262, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x263, APPLE_KEYBOARD_TYPE_ANSI }, /* Apple Internal Keyboard / Trackpad (MacBook Retina) */ + { 0x264, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x265, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x266, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x267, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x268, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x269, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x26A, APPLE_KEYBOARD_TYPE_ANSI } +}; + +static enum APPLE_KEYBOARD_TYPE mac_identify_keyboard_type(uint32_t vendorID, uint32_t productID) +{ + enum APPLE_KEYBOARD_TYPE type = APPLE_KEYBOARD_TYPE_ANSI; + + if (vendorID != 0x05AC) /* Apple, Inc. */ + return type; + + if ((productID < 0x200) || (productID > 0x26A)) + return type; + + type = APPLE_KEYBOARDS[productID - 0x200].Type; + return type; +} + +enum APPLE_KEYBOARD_TYPE mac_detect_keyboard_type(void) +{ + CFSetRef deviceCFSetRef = NULL; + IOHIDDeviceRef inIOHIDDeviceRef = NULL; + IOHIDManagerRef tIOHIDManagerRef = NULL; + IOHIDDeviceRef *tIOHIDDeviceRefs = nil; + enum APPLE_KEYBOARD_TYPE type = APPLE_KEYBOARD_TYPE_ANSI; + tIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + if (!tIOHIDManagerRef) + return type; + + IOHIDManagerSetDeviceMatching(tIOHIDManagerRef, NULL); + IOReturn tIOReturn = IOHIDManagerOpen(tIOHIDManagerRef, kIOHIDOptionsTypeNone); + + if (noErr != tIOReturn) + return type; + + deviceCFSetRef = IOHIDManagerCopyDevices(tIOHIDManagerRef); + + if (!deviceCFSetRef) + return type; + + CFIndex deviceIndex, deviceCount = CFSetGetCount(deviceCFSetRef); + tIOHIDDeviceRefs = malloc(sizeof(IOHIDDeviceRef) * deviceCount); + + if (!tIOHIDDeviceRefs) + return type; + + CFSetGetValues(deviceCFSetRef, (const void **)tIOHIDDeviceRefs); + CFRelease(deviceCFSetRef); + deviceCFSetRef = NULL; + + for (deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++) + { + CFTypeRef tCFTypeRef; + uint32_t vendorID = 0; + uint32_t productID = 0; + uint32_t countryCode = 0; + enum APPLE_KEYBOARD_TYPE ltype; + + if (!tIOHIDDeviceRefs[deviceIndex]) + continue; + + inIOHIDDeviceRef = tIOHIDDeviceRefs[deviceIndex]; + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &vendorID); + + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &productID); + + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &countryCode); + + ltype = mac_identify_keyboard_type(vendorID, productID); + + if (ltype != APPLE_KEYBOARD_TYPE_ANSI) + { + type = ltype; + break; + } + } + + free(tIOHIDDeviceRefs); + + if (deviceCFSetRef) + { + CFRelease(deviceCFSetRef); + deviceCFSetRef = NULL; + } + + if (tIOHIDManagerRef) + CFRelease(tIOHIDManagerRef); + + return type; +} diff --git a/client/Mac/MRDPCursor.h b/client/Mac/MRDPCursor.h new file mode 100644 index 0000000..6b16d79 --- /dev/null +++ b/client/Mac/MRDPCursor.h @@ -0,0 +1,34 @@ +/** + * 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 + +#include "freerdp/graphics.h" + +@interface MRDPCursor : NSObject +{ + @public + rdpPointer *pointer; + BYTE *cursor_data; + NSBitmapImageRep *bmiRep; + NSCursor *nsCursor; + NSImage *nsImage; +} + +@end diff --git a/client/Mac/MRDPCursor.m b/client/Mac/MRDPCursor.m new file mode 100644 index 0000000..6df6267 --- /dev/null +++ b/client/Mac/MRDPCursor.m @@ -0,0 +1,24 @@ +/** + * 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 "MRDPCursor.h" + +@implementation MRDPCursor + +@end diff --git a/client/Mac/MRDPView.h b/client/Mac/MRDPView.h new file mode 100644 index 0000000..ea531c8 --- /dev/null +++ b/client/Mac/MRDPView.h @@ -0,0 +1,91 @@ +#ifndef FREERDP_CLIENT_MAC_MRDPVIEW_H +#define FREERDP_CLIENT_MAC_MRDPVIEW_H + +/** + * 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 + +#import "mfreerdp.h" +#import "mf_client.h" +#import "Keyboard.h" + +#import + +@interface MRDPView : NSView +{ + mfContext *mfc; + NSBitmapImageRep *bmiRep; + NSMutableArray *cursors; + NSMutableArray *windows; + NSTimer *pasteboard_timer; + NSCursor *currentCursor; + NSRect prevWinPosition; + freerdp *instance; + rdpContext *context; + CGContextRef bitmap_context; + char *pixel_data; + int argc; + char **argv; + DWORD kbdModFlags; + BOOL initialized; + NSPoint savedDragLocation; + BOOL firstCreateWindow; + BOOL isMoveSizeInProgress; + BOOL skipResizeOnce; + BOOL saveInitialDragLoc; + BOOL skipMoveWindowOnce; + @public + NSPasteboard *pasteboard_rd; + NSPasteboard *pasteboard_wr; + int pasteboard_changecount; + int pasteboard_format; + int is_connected; +} + +- (int)rdpStart:(rdpContext *)rdp_context; +- (void)setCursor:(NSCursor *)cursor; +- (void)setScrollOffset:(int)xOffset y:(int)yOffset w:(int)width h:(int)height; + +- (void)onPasteboardTimerFired:(NSTimer *)timer; +- (void)pause; +- (void)resume; +- (void)releaseResources; + +@property(assign) int is_connected; + +@end + +BOOL mac_pre_connect(freerdp *instance); +BOOL mac_post_connect(freerdp *instance); +void mac_post_disconnect(freerdp *instance); +BOOL mac_authenticate(freerdp *instance, char **username, char **password, char **domain); +BOOL mac_gw_authenticate(freerdp *instance, char **username, char **password, char **domain); + +DWORD mac_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, const char *issuer, + const char *fingerprint, DWORD flags); +DWORD mac_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, + const char *old_subject, const char *old_issuer, + const char *old_fingerprint, DWORD flags); + +int mac_logon_error_info(freerdp *instance, UINT32 data, UINT32 type); +#endif /* FREERDP_CLIENT_MAC_MRDPVIEW_H */ diff --git a/client/Mac/MRDPView.m b/client/Mac/MRDPView.m new file mode 100644 index 0000000..bb3aaeb --- /dev/null +++ b/client/Mac/MRDPView.m @@ -0,0 +1,1423 @@ +/** + * 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. + */ + +#include + +#include "mf_client.h" +#import "mfreerdp.h" +#import "MRDPView.h" +#import "MRDPCursor.h" +#import "Clipboard.h" +#import "PasswordDialog.h" +#import "CertificateDialog.h" + +#include +#include +#include +#include + +#include + +#import "freerdp/freerdp.h" +#import "freerdp/types.h" +#import "freerdp/channels/channels.h" +#import "freerdp/gdi/gdi.h" +#import "freerdp/gdi/dc.h" +#import "freerdp/gdi/region.h" +#import "freerdp/graphics.h" +#import "freerdp/client/file.h" +#import "freerdp/client/cmdline.h" +#import "freerdp/log.h" + +#import + +#define TAG CLIENT_TAG("mac") + +static BOOL mf_Pointer_New(rdpContext *context, rdpPointer *pointer); +static void mf_Pointer_Free(rdpContext *context, rdpPointer *pointer); +static BOOL mf_Pointer_Set(rdpContext *context, const rdpPointer *pointer); +static BOOL mf_Pointer_SetNull(rdpContext *context); +static BOOL mf_Pointer_SetDefault(rdpContext *context); +static BOOL mf_Pointer_SetPosition(rdpContext *context, UINT32 x, UINT32 y); + +static BOOL mac_begin_paint(rdpContext *context); +static BOOL mac_end_paint(rdpContext *context); +static BOOL mac_desktop_resize(rdpContext *context); + +static void input_activity_cb(freerdp *instance); + +static DWORD WINAPI mac_client_thread(void *param); + +@implementation MRDPView + +@synthesize is_connected; + +- (int)rdpStart:(rdpContext *)rdp_context +{ + rdpSettings *settings; + EmbedWindowEventArgs e; + [self initializeView]; + context = rdp_context; + mfc = (mfContext *)rdp_context; + instance = context->instance; + settings = context->settings; + EventArgsInit(&e, "mfreerdp"); + e.embed = TRUE; + e.handle = (void *)self; + PubSub_OnEmbedWindow(context->pubSub, context, &e); + NSScreen *screen = [[NSScreen screens] objectAtIndex:0]; + NSRect screenFrame = [screen frame]; + + if (instance->settings->Fullscreen) + { + instance->settings->DesktopWidth = screenFrame.size.width; + instance->settings->DesktopHeight = screenFrame.size.height; + [self enterFullScreenMode:[NSScreen mainScreen] withOptions:nil]; + } + else + { + [self exitFullScreenModeWithOptions:nil]; + } + + mfc->client_height = instance->settings->DesktopHeight; + mfc->client_width = instance->settings->DesktopWidth; + + if (!(mfc->thread = + CreateThread(NULL, 0, mac_client_thread, (void *)context, 0, &mfc->mainThreadId))) + { + WLog_ERR(TAG, "failed to create client thread"); + return -1; + } + + return 0; +} + +static DWORD WINAPI mac_client_input_thread(LPVOID param) +{ + int status; + wMessage message; + wMessageQueue *queue; + rdpContext *context = (rdpContext *)param; + status = 1; + queue = freerdp_get_message_queue(context->instance, FREERDP_INPUT_MESSAGE_QUEUE); + + while (MessageQueue_Wait(queue)) + { + while (MessageQueue_Peek(queue, &message, TRUE)) + { + status = freerdp_message_queue_process_message(context->instance, + FREERDP_INPUT_MESSAGE_QUEUE, &message); + + if (!status) + break; + } + + if (!status) + break; + } + + ExitThread(0); + return 0; +} + +DWORD WINAPI mac_client_thread(void *param) +{ + @autoreleasepool + { + int status; + DWORD rc; + HANDLE events[16]; + HANDLE inputEvent; + HANDLE inputThread = NULL; + DWORD nCount; + DWORD nCountTmp; + DWORD nCountBase; + rdpContext *context = (rdpContext *)param; + mfContext *mfc = (mfContext *)context; + freerdp *instance = context->instance; + MRDPView *view = mfc->view; + rdpSettings *settings = context->settings; + status = freerdp_connect(context->instance); + + if (!status) + { + [view setIs_connected:0]; + return 0; + } + + [view setIs_connected:1]; + nCount = 0; + events[nCount++] = mfc->stopEvent; + + if (settings->AsyncInput) + { + if (!(inputThread = CreateThread(NULL, 0, mac_client_input_thread, context, 0, NULL))) + { + WLog_ERR(TAG, "failed to create async input thread"); + goto disconnect; + } + } + else + { + if (!(inputEvent = freerdp_get_message_queue_event_handle(instance, + FREERDP_INPUT_MESSAGE_QUEUE))) + { + WLog_ERR(TAG, "failed to get input event handle"); + goto disconnect; + } + + events[nCount++] = inputEvent; + } + + nCountBase = nCount; + + while (!freerdp_shall_disconnect(instance)) + { + nCount = nCountBase; + { + if (!(nCountTmp = freerdp_get_event_handles(context, &events[nCount], 16 - nCount))) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + nCount += nCountTmp; + } + rc = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (rc >= (WAIT_OBJECT_0 + nCount)) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed (0x%08X)", rc); + break; + } + + if (rc == WAIT_OBJECT_0) + { + /* stop event triggered */ + break; + } + + if (!settings->AsyncInput) + { + if (WaitForSingleObject(inputEvent, 0) == WAIT_OBJECT_0) + { + input_activity_cb(instance); + } + } + + { + if (!freerdp_check_event_handles(context)) + { + WLog_ERR(TAG, "freerdp_check_event_handles failed"); + break; + } + } + } + + disconnect: + [view setIs_connected:0]; + freerdp_disconnect(instance); + + if (settings->AsyncInput && inputThread) + { + wMessageQueue *inputQueue = + freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + + if (inputQueue) + { + MessageQueue_PostQuit(inputQueue, 0); + WaitForSingleObject(inputThread, INFINITE); + } + + CloseHandle(inputThread); + } + + ExitThread(0); + return 0; + } +} + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) + { + // Initialization code here. + } + + return self; +} + +- (void)viewDidLoad +{ + [self initializeView]; +} + +- (void)initializeView +{ + if (!initialized) + { + cursors = [[NSMutableArray alloc] initWithCapacity:10]; + // setup a mouse tracking area + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] + initWithRect:[self visibleRect] + options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | + NSTrackingCursorUpdate | NSTrackingEnabledDuringMouseDrag | + NSTrackingActiveWhenFirstResponder + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + // Set the default cursor + currentCursor = [NSCursor arrowCursor]; + initialized = YES; + } +} + +- (void)setCursor:(NSCursor *)cursor +{ + self->currentCursor = cursor; + dispatch_async(dispatch_get_main_queue(), ^{ + [[self window] invalidateCursorRectsForView:self]; + }); +} + +- (void)resetCursorRects +{ + [self addCursorRect:[self visibleRect] cursor:currentCursor]; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)mouseMoved:(NSEvent *)event +{ + [super mouseMoved:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_scale_mouse_event(context, instance->input, PTR_FLAGS_MOVE, x, y); +} + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, instance->input, 0, x, y, TRUE); +} + +- (void)mouseUp:(NSEvent *)event +{ + [super mouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, instance->input, 0, x, y, FALSE); +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, instance->input, 1, x, y, TRUE); +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, instance->input, 1, x, y, FALSE); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + int pressed = [event buttonNumber]; + mf_press_mouse_button(context, instance->input, pressed, x, y, TRUE); +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + int pressed = [event buttonNumber]; + mf_press_mouse_button(context, instance->input, pressed, x, y, FALSE); +} + +- (void)scrollWheel:(NSEvent *)event +{ + UINT16 flags; + [super scrollWheel:event]; + + if (!self.is_connected) + return; + + float dx = [event deltaX]; + float dy = [event deltaY]; + /* 1 event = 120 units */ + UINT16 units = 0; + + if (fabsf(dy) > FLT_EPSILON) + { + flags = PTR_FLAGS_WHEEL; + units = fabsf(dy) * 120; + + if (dy < 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + else if (fabsf(dx) > FLT_EPSILON) + { + flags = PTR_FLAGS_HWHEEL; + units = fabsf(dx) * 120; + + if (dx > 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + else + return; + + /* Wheel rotation steps: + * + * positive: 0 ... 0xFF -> slow ... fast + * negative: 0 ... 0xFF -> fast ... slow + */ + UINT16 step = units; + if (step > 0xFF) + step = 0xFF; + + /* Negative rotation, so count down steps from top + * 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + step = 0x100 - step; + + mf_scale_mouse_event(context, instance->input, flags | step, 0, 0); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + // send mouse motion event to RDP server + mf_scale_mouse_event(context, instance->input, PTR_FLAGS_MOVE, x, y); +} + +DWORD fixKeyCode(DWORD keyCode, unichar keyChar, enum APPLE_KEYBOARD_TYPE type) +{ + /** + * In 99% of cases, the given key code is truly keyboard independent. + * This function handles the remaining 1% of edge cases. + * + * Hungarian Keyboard: This is 'QWERTZ' and not 'QWERTY'. + * The '0' key is on the left of the '1' key, where '~' is on a US keyboard. + * A special 'i' letter key with acute is found on the right of the left shift key. + * On the hungarian keyboard, the 'i' key is at the left of the 'Y' key + * Some international keyboards have a corresponding key which would be at + * the left of the 'Z' key when using a QWERTY layout. + * + * The Apple Hungarian keyboard sends inverted key codes for the '0' and 'i' keys. + * When using the US keyboard layout, key codes are left as-is (inverted). + * When using the Hungarian keyboard layout, key codes are swapped (non-inverted). + * This means that when using the Hungarian keyboard layout with a US keyboard, + * the keys corresponding to '0' and 'i' will effectively be inverted. + * + * To fix the '0' and 'i' key inversion, we use the corresponding output character + * provided by OS X and check for a character to key code mismatch: for instance, + * when the output character is '0' for the key code corresponding to the 'i' key. + */ +#if 0 + switch (keyChar) + { + case '0': + case 0x00A7: /* section sign */ + if (keyCode == APPLE_VK_ISO_Section) + keyCode = APPLE_VK_ANSI_Grave; + + break; + + case 0x00ED: /* latin small letter i with acute */ + case 0x00CD: /* latin capital letter i with acute */ + if (keyCode == APPLE_VK_ANSI_Grave) + keyCode = APPLE_VK_ISO_Section; + + break; + } + +#endif + + /* Perform keycode correction for all ISO keyboards */ + + if (type == APPLE_KEYBOARD_TYPE_ISO) + { + if (keyCode == APPLE_VK_ANSI_Grave) + keyCode = APPLE_VK_ISO_Section; + else if (keyCode == APPLE_VK_ISO_Section) + keyCode = APPLE_VK_ANSI_Grave; + } + + return keyCode; +} + +- (void)keyDown:(NSEvent *)event +{ + DWORD keyCode; + DWORD keyFlags; + DWORD vkcode; + DWORD scancode; + unichar keyChar; + NSString *characters; + + if (!is_connected) + return; + + keyFlags = KBD_FLAGS_DOWN; + keyCode = [event keyCode]; + characters = [event charactersIgnoringModifiers]; + + if ([characters length] > 0) + { + keyChar = [characters characterAtIndex:0]; + keyCode = fixKeyCode(keyCode, keyChar, mfc->appleKeyboardType); + } + + vkcode = GetVirtualKeyCodeFromKeycode(keyCode + 8, KEYCODE_TYPE_APPLE); + scancode = GetVirtualScanCodeFromVirtualKeyCode(vkcode, 4); + keyFlags |= (scancode & KBDEXT) ? KBDEXT : 0; + scancode &= 0xFF; + vkcode &= 0xFF; +#if 0 + WLog_ERR(TAG, + "keyDown: keyCode: 0x%04X scancode: 0x%04X vkcode: 0x%04X keyFlags: %d name: %s", + keyCode, scancode, vkcode, keyFlags, GetVirtualKeyName(vkcode)); +#endif + sync_keyboard_state(instance); + freerdp_input_send_keyboard_event(instance->input, keyFlags, scancode); +} + +- (void)keyUp:(NSEvent *)event +{ + DWORD keyCode; + DWORD keyFlags; + DWORD vkcode; + DWORD scancode; + unichar keyChar; + NSString *characters; + + if (!is_connected) + return; + + keyFlags = KBD_FLAGS_RELEASE; + keyCode = [event keyCode]; + characters = [event charactersIgnoringModifiers]; + + if ([characters length] > 0) + { + keyChar = [characters characterAtIndex:0]; + keyCode = fixKeyCode(keyCode, keyChar, mfc->appleKeyboardType); + } + + vkcode = GetVirtualKeyCodeFromKeycode(keyCode + 8, KEYCODE_TYPE_APPLE); + scancode = GetVirtualScanCodeFromVirtualKeyCode(vkcode, 4); + keyFlags |= (scancode & KBDEXT) ? KBDEXT : 0; + scancode &= 0xFF; + vkcode &= 0xFF; +#if 0 + WLog_DBG(TAG, + "keyUp: key: 0x%04X scancode: 0x%04X vkcode: 0x%04X keyFlags: %d name: %s", + keyCode, scancode, vkcode, keyFlags, GetVirtualKeyName(vkcode)); +#endif + freerdp_input_send_keyboard_event(instance->input, keyFlags, scancode); +} + +- (void)flagsChanged:(NSEvent *)event +{ + int key; + DWORD keyFlags; + DWORD vkcode; + DWORD scancode; + DWORD modFlags; + + if (!is_connected) + return; + + keyFlags = 0; + key = [event keyCode] + 8; + modFlags = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; + vkcode = GetVirtualKeyCodeFromKeycode(key, KEYCODE_TYPE_APPLE); + scancode = GetVirtualScanCodeFromVirtualKeyCode(vkcode, 4); + keyFlags |= (scancode & KBDEXT) ? KBDEXT : 0; + scancode &= 0xFF; + vkcode &= 0xFF; +#if 0 + WLog_DBG(TAG, + "flagsChanged: key: 0x%04X scancode: 0x%04X vkcode: 0x%04X extended: %d name: %s modFlags: 0x%04X", + key - 8, scancode, vkcode, keyFlags, GetVirtualKeyName(vkcode), modFlags); + + if (modFlags & NSAlphaShiftKeyMask) + WLog_DBG(TAG, "NSAlphaShiftKeyMask"); + + if (modFlags & NSShiftKeyMask) + WLog_DBG(TAG, "NSShiftKeyMask"); + + if (modFlags & NSControlKeyMask) + WLog_DBG(TAG, "NSControlKeyMask"); + + if (modFlags & NSAlternateKeyMask) + WLog_DBG(TAG, "NSAlternateKeyMask"); + + if (modFlags & NSCommandKeyMask) + WLog_DBG(TAG, "NSCommandKeyMask"); + + if (modFlags & NSNumericPadKeyMask) + WLog_DBG(TAG, "NSNumericPadKeyMask"); + + if (modFlags & NSHelpKeyMask) + WLog_DBG(TAG, "NSHelpKeyMask"); + +#endif + + if ((modFlags & NSAlphaShiftKeyMask) && !(kbdModFlags & NSAlphaShiftKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSAlphaShiftKeyMask) && (kbdModFlags & NSAlphaShiftKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSShiftKeyMask) && !(kbdModFlags & NSShiftKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSShiftKeyMask) && (kbdModFlags & NSShiftKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSControlKeyMask) && !(kbdModFlags & NSControlKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSControlKeyMask) && (kbdModFlags & NSControlKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSAlternateKeyMask) && !(kbdModFlags & NSAlternateKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSAlternateKeyMask) && (kbdModFlags & NSAlternateKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSCommandKeyMask) && !(kbdModFlags & NSCommandKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSCommandKeyMask) && (kbdModFlags & NSCommandKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSNumericPadKeyMask) && !(kbdModFlags & NSNumericPadKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSNumericPadKeyMask) && (kbdModFlags & NSNumericPadKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + if ((modFlags & NSHelpKeyMask) && !(kbdModFlags & NSHelpKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_DOWN, scancode); + else if (!(modFlags & NSHelpKeyMask) && (kbdModFlags & NSHelpKeyMask)) + freerdp_input_send_keyboard_event(instance->input, keyFlags | KBD_FLAGS_RELEASE, scancode); + + kbdModFlags = modFlags; +} + +- (void)releaseResources +{ + int i; + + for (i = 0; i < argc; i++) + free(argv[i]); + + if (!is_connected) + return; + + free(pixel_data); +} + +- (void)drawRect:(NSRect)rect +{ + if (!context) + return; + + if (self->bitmap_context) + { + CGContextRef cgContext = [[NSGraphicsContext currentContext] graphicsPort]; + CGImageRef cgImage = CGBitmapContextCreateImage(self->bitmap_context); + CGContextSaveGState(cgContext); + CGContextClipToRect( + cgContext, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); + CGContextDrawImage(cgContext, + CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), + cgImage); + CGContextRestoreGState(cgContext); + CGImageRelease(cgImage); + } + else + { + /* Fill the screen with black */ + [[NSColor blackColor] set]; + NSRectFill([self bounds]); + } +} + +- (void)onPasteboardTimerFired:(NSTimer *)timer +{ + const BYTE *data; + UINT32 size; + UINT32 formatId; + BOOL formatMatch; + int changeCount; + NSData *formatData; + const char *formatType; + NSPasteboardItem *item; + changeCount = (int)[pasteboard_rd changeCount]; + + if (changeCount == pasteboard_changecount) + return; + + pasteboard_changecount = changeCount; + NSArray *items = [pasteboard_rd pasteboardItems]; + + if ([items count] < 1) + return; + + item = [items objectAtIndex:0]; + /** + * System-Declared Uniform Type Identifiers: + * https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html + */ + formatMatch = FALSE; + + for (NSString *type in [item types]) + { + formatType = [type UTF8String]; + + if (strcmp(formatType, "public.utf8-plain-text") == 0) + { + formatData = [item dataForType:type]; + formatId = ClipboardRegisterFormat(mfc->clipboard, "UTF8_STRING"); + size = (UINT32)[formatData length]; + data = [formatData bytes]; + /* size is the string length without the terminating NULL terminator */ + ClipboardSetData(mfc->clipboard, formatId, data, size + 1); + formatMatch = TRUE; + break; + } + } + + if (!formatMatch) + ClipboardEmpty(mfc->clipboard); + + if (mfc->clipboardSync) + mac_cliprdr_send_client_format_list(mfc->cliprdr); +} + +- (void)pause +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self->pasteboard_timer invalidate]; + }); + NSArray *trackingAreas = self.trackingAreas; + + for (NSTrackingArea *ta in trackingAreas) + { + [self removeTrackingArea:ta]; + } +} + +- (void)resume +{ + if (!self.is_connected) + return; + + dispatch_async(dispatch_get_main_queue(), ^{ + self->pasteboard_timer = + [NSTimer scheduledTimerWithTimeInterval:0.5 + target:self + selector:@selector(onPasteboardTimerFired:) + userInfo:nil + repeats:YES]; + + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] + initWithRect:[self visibleRect] + options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | + NSTrackingCursorUpdate | NSTrackingEnabledDuringMouseDrag | + NSTrackingActiveWhenFirstResponder + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + [trackingArea release]; + }); +} + +- (void)setScrollOffset:(int)xOffset y:(int)yOffset w:(int)width h:(int)height +{ + mfc->yCurrentScroll = yOffset; + mfc->xCurrentScroll = xOffset; + mfc->client_height = height; + mfc->client_width = width; +} + +void mac_OnChannelConnectedEventHandler(void *context, ChannelConnectedEventArgs *e) +{ + mfContext *mfc = (mfContext *)context; + rdpSettings *settings = mfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_init(mfc->context.gdi, (RdpgfxClientContext *)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + mac_cliprdr_init(mfc, (CliprdrClientContext *)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } +} + +void mac_OnChannelDisconnectedEventHandler(void *context, ChannelDisconnectedEventArgs *e) +{ + mfContext *mfc = (mfContext *)context; + rdpSettings *settings = mfc->context.settings; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + if (settings->SoftwareGdi) + gdi_graphics_pipeline_uninit(mfc->context.gdi, (RdpgfxClientContext *)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + mac_cliprdr_uninit(mfc, (CliprdrClientContext *)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } +} + +BOOL mac_pre_connect(freerdp *instance) +{ + rdpSettings *settings; + instance->update->BeginPaint = mac_begin_paint; + instance->update->EndPaint = mac_end_paint; + instance->update->DesktopResize = mac_desktop_resize; + settings = instance->settings; + + if (!settings->ServerHostname) + { + WLog_ERR(TAG, "error: server hostname was not specified with /v:[:port]"); + return FALSE; + } + + settings->OsMajorType = OSMAJORTYPE_MACINTOSH; + settings->OsMinorType = OSMINORTYPE_MACINTOSH; + PubSub_SubscribeChannelConnected(instance->context->pubSub, mac_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + mac_OnChannelDisconnectedEventHandler); + + if (!freerdp_client_load_addins(instance->context->channels, instance->settings)) + return FALSE; + + return TRUE; +} + +BOOL mac_post_connect(freerdp *instance) +{ + rdpGdi *gdi; + rdpSettings *settings; + rdpPointer rdp_pointer; + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + ZeroMemory(&rdp_pointer, sizeof(rdpPointer)); + rdp_pointer.size = sizeof(rdpPointer); + rdp_pointer.New = mf_Pointer_New; + rdp_pointer.Free = mf_Pointer_Free; + rdp_pointer.Set = mf_Pointer_Set; + rdp_pointer.SetNull = mf_Pointer_SetNull; + rdp_pointer.SetDefault = mf_Pointer_SetDefault; + rdp_pointer.SetPosition = mf_Pointer_SetPosition; + settings = instance->settings; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRX32)) + return FALSE; + + gdi = instance->context->gdi; + view->bitmap_context = mac_create_bitmap_context(instance->context); + graphics_register_pointer(instance->context->graphics, &rdp_pointer); + /* setup pasteboard (aka clipboard) for copy operations (write only) */ + view->pasteboard_wr = [NSPasteboard generalPasteboard]; + /* setup pasteboard for read operations */ + dispatch_async(dispatch_get_main_queue(), ^{ + view->pasteboard_rd = [NSPasteboard generalPasteboard]; + view->pasteboard_changecount = -1; + }); + [view resume]; + mfc->appleKeyboardType = mac_detect_keyboard_type(); + return TRUE; +} + +void mac_post_disconnect(freerdp *instance) +{ + mfContext *mfc; + MRDPView *view; + if (!instance || !instance->context) + return; + + mfc = (mfContext *)instance->context; + view = (MRDPView *)mfc->view; + + [view pause]; + + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + mac_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + mac_OnChannelDisconnectedEventHandler); + gdi_free(instance); +} + +static BOOL mac_authenticate_int(NSString *title, freerdp *instance, char **username, + char **password, char **domain) +{ + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + PasswordDialog *dialog = [PasswordDialog new]; + dialog.serverHostname = title; + + if (*username) + dialog.username = [NSString stringWithCString:*username encoding:NSUTF8StringEncoding]; + + if (*password) + dialog.password = [NSString stringWithCString:*password encoding:NSUTF8StringEncoding]; + + if (*domain) + dialog.domain = [NSString stringWithCString:*domain encoding:NSUTF8StringEncoding]; + + dispatch_sync(dispatch_get_main_queue(), ^{ + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + }); + BOOL ok = dialog.modalCode; + + if (ok) + { + size_t ulen, plen, dlen; + const char *submittedUsername = [dialog.username cStringUsingEncoding:NSUTF8StringEncoding]; + ulen = (strlen(submittedUsername) + 1) * sizeof(char); + *username = malloc(ulen); + + if (!(*username)) + return FALSE; + + sprintf_s(*username, ulen, "%s", submittedUsername); + const char *submittedPassword = [dialog.password cStringUsingEncoding:NSUTF8StringEncoding]; + plen = (strlen(submittedPassword) + 1) * sizeof(char); + *password = malloc(plen); + + if (!(*password)) + return FALSE; + + sprintf_s(*password, plen, "%s", submittedPassword); + const char *submittedDomain = [dialog.domain cStringUsingEncoding:NSUTF8StringEncoding]; + dlen = (strlen(submittedDomain) + 1) * sizeof(char); + *domain = malloc(dlen); + + if (!(*domain)) + return FALSE; + + sprintf_s(*domain, dlen, "%s", submittedDomain); + } + + return ok; +} + +BOOL mac_authenticate(freerdp *instance, char **username, char **password, char **domain) +{ + NSString *title = + [NSString stringWithFormat:@"%@:%u", + [NSString stringWithCString:instance->settings->ServerHostname + encoding:NSUTF8StringEncoding], + instance -> settings -> ServerPort]; + return mac_authenticate_int(title, instance, username, password, domain); +} + +BOOL mac_gw_authenticate(freerdp *instance, char **username, char **password, char **domain) +{ + NSString *title = + [NSString stringWithFormat:@"%@:%u", + [NSString stringWithCString:instance->settings->GatewayHostname + encoding:NSUTF8StringEncoding], + instance -> settings -> GatewayPort]; + return mac_authenticate_int(title, instance, username, password, domain); +} + +DWORD mac_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, const char *issuer, + const char *fingerprint, DWORD flags) +{ + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + CertificateDialog *dialog = [CertificateDialog new]; + const char *type = "RDP-Server"; + char hostname[8192]; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + sprintf_s(hostname, sizeof(hostname), "%s %s:%" PRIu16, type, host, port); + dialog.serverHostname = [NSString stringWithCString:hostname]; + dialog.commonName = [NSString stringWithCString:common_name encoding:NSUTF8StringEncoding]; + dialog.subject = [NSString stringWithCString:subject encoding:NSUTF8StringEncoding]; + dialog.issuer = [NSString stringWithCString:issuer encoding:NSUTF8StringEncoding]; + dialog.fingerprint = [NSString stringWithCString:fingerprint encoding:NSUTF8StringEncoding]; + + if (flags & VERIFY_CERT_FLAG_MISMATCH) + dialog.hostMismatch = TRUE; + + if (flags & VERIFY_CERT_FLAG_CHANGED) + dialog.changed = TRUE; + + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + return dialog.result; +} + +DWORD mac_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, + const char *old_subject, const char *old_issuer, + const char *old_fingerprint, DWORD flags) +{ + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + CertificateDialog *dialog = [CertificateDialog new]; + const char *type = "RDP-Server"; + char hostname[8192]; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + sprintf_s(hostname, sizeof(hostname), "%s %s:%" PRIu16, type, host, port); + dialog.serverHostname = [NSString stringWithCString:hostname]; + dialog.commonName = [NSString stringWithCString:common_name encoding:NSUTF8StringEncoding]; + dialog.subject = [NSString stringWithCString:subject encoding:NSUTF8StringEncoding]; + dialog.issuer = [NSString stringWithCString:issuer encoding:NSUTF8StringEncoding]; + dialog.fingerprint = [NSString stringWithCString:fingerprint encoding:NSUTF8StringEncoding]; + + if (flags & VERIFY_CERT_FLAG_MISMATCH) + dialog.hostMismatch = TRUE; + + if (flags & VERIFY_CERT_FLAG_CHANGED) + dialog.changed = TRUE; + + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + return dialog.result; +} + +int mac_logon_error_info(freerdp *instance, UINT32 data, UINT32 type) +{ + const char *str_data = freerdp_get_logon_error_info_data(data); + const char *str_type = freerdp_get_logon_error_info_type(type); + // TODO: Error message dialog + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +BOOL mf_Pointer_New(rdpContext *context, rdpPointer *pointer) +{ + rdpGdi *gdi; + NSRect rect; + NSImage *image; + NSPoint hotSpot; + NSCursor *cursor; + BYTE *cursor_data; + NSMutableArray *ma; + NSBitmapImageRep *bmiRep; + MRDPCursor *mrdpCursor = [[MRDPCursor alloc] init]; + mfContext *mfc = (mfContext *)context; + MRDPView *view; + UINT32 format; + + if (!mfc || !context || !pointer) + return FALSE; + + view = (MRDPView *)mfc->view; + gdi = context->gdi; + + if (!gdi || !view) + return FALSE; + + rect.size.width = pointer->width; + rect.size.height = pointer->height; + rect.origin.x = pointer->xPos; + rect.origin.y = pointer->yPos; + cursor_data = (BYTE *)malloc(rect.size.width * rect.size.height * 4); + + if (!cursor_data) + return FALSE; + + mrdpCursor->cursor_data = cursor_data; + format = PIXEL_FORMAT_RGBA32; + + if (!freerdp_image_copy_from_pointer_data(cursor_data, format, 0, 0, 0, pointer->width, + pointer->height, pointer->xorMaskData, + pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, NULL)) + { + free(cursor_data); + mrdpCursor->cursor_data = NULL; + return FALSE; + } + + /* store cursor bitmap image in representation - required by NSImage */ + bmiRep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:(unsigned char **)&cursor_data + pixelsWide:rect.size.width + pixelsHigh:rect.size.height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:0 + bytesPerRow:rect.size.width * GetBytesPerPixel(format) + bitsPerPixel:0]; + mrdpCursor->bmiRep = bmiRep; + /* create an image using above representation */ + image = [[NSImage alloc] initWithSize:[bmiRep size]]; + [image addRepresentation:bmiRep]; + [image setFlipped:NO]; + mrdpCursor->nsImage = image; + /* need hotspot to create cursor */ + hotSpot.x = pointer->xPos; + hotSpot.y = pointer->yPos; + cursor = [[NSCursor alloc] initWithImage:image hotSpot:hotSpot]; + mrdpCursor->nsCursor = cursor; + mrdpCursor->pointer = pointer; + /* save cursor for later use in mf_Pointer_Set() */ + ma = view->cursors; + [ma addObject:mrdpCursor]; + return TRUE; +} + +void mf_Pointer_Free(rdpContext *context, rdpPointer *pointer) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + NSMutableArray *ma = view->cursors; + + for (MRDPCursor *cursor in ma) + { + if (cursor->pointer == pointer) + { + cursor->nsImage = nil; + cursor->nsCursor = nil; + cursor->bmiRep = nil; + free(cursor->cursor_data); + [ma removeObject:cursor]; + return; + } + } +} + +BOOL mf_Pointer_Set(rdpContext *context, const rdpPointer *pointer) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + NSMutableArray *ma = view->cursors; + + for (MRDPCursor *cursor in ma) + { + if (cursor->pointer == pointer) + { + [view setCursor:cursor->nsCursor]; + return TRUE; + } + } + + NSLog(@"Cursor not found"); + return TRUE; +} + +BOOL mf_Pointer_SetNull(rdpContext *context) +{ + return TRUE; +} + +BOOL mf_Pointer_SetDefault(rdpContext *context) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + [view setCursor:[NSCursor arrowCursor]]; + return TRUE; +} + +static BOOL mf_Pointer_SetPosition(rdpContext *context, UINT32 x, UINT32 y) +{ + mfContext *mfc = (mfContext *)context; + + if (!mfc) + return FALSE; + + /* TODO: Set pointer position */ + return TRUE; +} + +CGContextRef mac_create_bitmap_context(rdpContext *context) +{ + CGContextRef bitmap_context; + rdpGdi *gdi = context->gdi; + UINT32 bpp = GetBytesPerPixel(gdi->dstFormat); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + if (bpp == 2) + { + bitmap_context = CGBitmapContextCreate( + gdi->primary_buffer, gdi->width, gdi->height, 5, gdi->stride, colorSpace, + kCGBitmapByteOrder16Little | kCGImageAlphaNoneSkipFirst); + } + else + { + bitmap_context = CGBitmapContextCreate( + gdi->primary_buffer, gdi->width, gdi->height, 8, gdi->stride, colorSpace, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst); + } + + CGColorSpaceRelease(colorSpace); + return bitmap_context; +} + +BOOL mac_begin_paint(rdpContext *context) +{ + rdpGdi *gdi = context->gdi; + + if (!gdi) + return FALSE; + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +BOOL mac_end_paint(rdpContext *context) +{ + rdpGdi *gdi; + HGDI_RGN invalid; + NSRect newDrawRect; + int ww, wh, dw, dh; + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + gdi = context->gdi; + + if (!gdi) + return FALSE; + + ww = mfc->client_width; + wh = mfc->client_height; + dw = mfc->context.settings->DesktopWidth; + dh = mfc->context.settings->DesktopHeight; + + if ((!context) || (!context->gdi)) + return FALSE; + + if (context->gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + invalid = gdi->primary->hdc->hwnd->invalid; + newDrawRect.origin.x = invalid->x; + newDrawRect.origin.y = invalid->y; + newDrawRect.size.width = invalid->w; + newDrawRect.size.height = invalid->h; + + if (mfc->context.settings->SmartSizing && (ww != dw || wh != dh)) + { + newDrawRect.origin.y = newDrawRect.origin.y * wh / dh - 1; + newDrawRect.size.height = newDrawRect.size.height * wh / dh + 1; + newDrawRect.origin.x = newDrawRect.origin.x * ww / dw - 1; + newDrawRect.size.width = newDrawRect.size.width * ww / dw + 1; + } + else + { + newDrawRect.origin.y = newDrawRect.origin.y - 1; + newDrawRect.size.height = newDrawRect.size.height + 1; + newDrawRect.origin.x = newDrawRect.origin.x - 1; + newDrawRect.size.width = newDrawRect.size.width + 1; + } + + windows_to_apple_cords(mfc->view, &newDrawRect); + dispatch_sync(dispatch_get_main_queue(), ^{ + [view setNeedsDisplayInRect:newDrawRect]; + }); + gdi->primary->hdc->hwnd->ninvalid = 0; + return TRUE; +} + +BOOL mac_desktop_resize(rdpContext *context) +{ + ResizeWindowEventArgs e; + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + rdpSettings *settings = context->settings; + + if (!context->gdi) + return TRUE; + + /** + * TODO: Fix resizing race condition. We should probably implement a message to be + * put on the update message queue to be able to properly flush pending updates, + * resize, and then continue with post-resizing graphical updates. + */ + CGContextRef old_context = view->bitmap_context; + view->bitmap_context = NULL; + CGContextRelease(old_context); + mfc->width = settings->DesktopWidth; + mfc->height = settings->DesktopHeight; + + if (!gdi_resize(context->gdi, mfc->width, mfc->height)) + return FALSE; + + view->bitmap_context = mac_create_bitmap_context(context); + + if (!view->bitmap_context) + return FALSE; + + mfc->client_width = mfc->width; + mfc->client_height = mfc->height; + [view setFrameSize:NSMakeSize(mfc->width, mfc->height)]; + EventArgsInit(&e, "mfreerdp"); + e.width = settings->DesktopWidth; + e.height = settings->DesktopHeight; + PubSub_OnResizeWindow(context->pubSub, context, &e); + return TRUE; +} + +void input_activity_cb(freerdp *instance) +{ + int status; + wMessage message; + wMessageQueue *queue; + status = 1; + queue = freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + + if (queue) + { + while (MessageQueue_Peek(queue, &message, TRUE)) + { + status = freerdp_message_queue_process_message(instance, FREERDP_INPUT_MESSAGE_QUEUE, + &message); + + if (!status) + break; + } + } + else + { + WLog_ERR(TAG, "input_activity_cb: No queue!"); + } +} + +/** + * given a rect with 0,0 at the top left (windows cords) + * convert it to a rect with 0,0 at the bottom left (apple cords) + * + * Note: the formula works for conversions in both directions. + * + */ + +void windows_to_apple_cords(MRDPView *view, NSRect *r) +{ + dispatch_sync(dispatch_get_main_queue(), ^{ + r->origin.y = [view frame].size.height - (r->origin.y + r->size.height); + }); +} + +void sync_keyboard_state(freerdp *instance) +{ + mfContext *context = (mfContext *)instance->context; + UINT32 flags = 0; + CGEventFlags currentFlags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState); + + if (context->kbdFlags != currentFlags) + { + if (currentFlags & kCGEventFlagMaskAlphaShift) + flags |= KBD_SYNC_CAPS_LOCK; + + if (currentFlags & kCGEventFlagMaskNumericPad) + flags |= KBD_SYNC_NUM_LOCK; + + freerdp_input_send_synchronize_event(instance->input, flags); + context->kbdFlags = currentFlags; + } +} + +@end diff --git a/client/Mac/ModuleOptions.cmake b/client/Mac/ModuleOptions.cmake new file mode 100644 index 0000000..3902d2b --- /dev/null +++ b/client/Mac/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_CLIENT_NAME "mfreerdp") +set(FREERDP_CLIENT_PLATFORM "MacOSX") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/client/Mac/PasswordDialog.h b/client/Mac/PasswordDialog.h new file mode 100644 index 0000000..eb24c5c --- /dev/null +++ b/client/Mac/PasswordDialog.h @@ -0,0 +1,49 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * 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. + */ + +#import + +@interface PasswordDialog : NSWindowController +{ + @public + NSTextField *usernameText; + NSTextField *passwordText; + NSTextField *messageLabel; + NSString *serverHostname; + NSString *username; + NSString *password; + NSString *domain; + BOOL modalCode; +} +@property(retain) IBOutlet NSTextField *usernameText; +@property(retain) IBOutlet NSTextField *passwordText; +@property(retain) IBOutlet NSTextField *messageLabel; + +- (IBAction)onOK:(NSObject *)sender; +- (IBAction)onCancel:(NSObject *)sender; + +@property(retain) NSString *serverHostname; +@property(retain) NSString *username; +@property(retain) NSString *password; +@property(retain) NSString *domain; +@property(readonly) BOOL modalCode; + +- (BOOL)runModal:(NSWindow *)mainWindow; + +@end diff --git a/client/Mac/PasswordDialog.m b/client/Mac/PasswordDialog.m new file mode 100644 index 0000000..f4c520b --- /dev/null +++ b/client/Mac/PasswordDialog.m @@ -0,0 +1,134 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * 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. + */ + +#import "PasswordDialog.h" +#import + +#import + +@interface PasswordDialog () + +@property BOOL modalCode; + +@end + +@implementation PasswordDialog + +@synthesize usernameText; +@synthesize passwordText; +@synthesize messageLabel; +@synthesize serverHostname; +@synthesize username; +@synthesize password; +@synthesize domain; +@synthesize modalCode; + +- (id)init +{ + return [self initWithWindowNibName:@"PasswordDialog"]; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + // Implement this method to handle any initialization after your window controller's window has + // been loaded from its nib file. + [self.window setTitle:self.serverHostname]; + [self.messageLabel + setStringValue:[NSString stringWithFormat:@"Authenticate to %@", self.serverHostname]]; + NSMutableString *domainUser = [[NSMutableString alloc] initWithString:@""]; + + if (self.domain != nil && + [[self.domain stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] + length] > 0) + { + [domainUser appendFormat:@"%@\\", self.domain]; + } + + if (self.username != nil) + { + [domainUser appendString:self.username]; + [self.window makeFirstResponder:self.passwordText]; + } + + [self.usernameText setStringValue:domainUser]; +} + +- (IBAction)onOK:(NSObject *)sender +{ + char *submittedUser = NULL; + char *submittedDomain = NULL; + + if (freerdp_parse_username( + [self.usernameText.stringValue cStringUsingEncoding:NSUTF8StringEncoding], + &submittedUser, &submittedDomain)) + { + self.username = [NSString stringWithCString:submittedUser encoding:NSUTF8StringEncoding]; + self.domain = [NSString stringWithCString:submittedDomain encoding:NSUTF8StringEncoding]; + } + else + { + self.username = self.usernameText.stringValue; + } + + self.password = self.passwordText.stringValue; + [NSApp stopModalWithCode:TRUE]; +} + +- (IBAction)onCancel:(NSObject *)sender +{ + [NSApp stopModalWithCode:FALSE]; +} + +- (BOOL)runModal:(NSWindow *)mainWindow +{ + if ([mainWindow respondsToSelector:@selector(beginSheet:completionHandler:)]) + { + [mainWindow beginSheet:self.window completionHandler:nil]; + self.modalCode = [NSApp runModalForWindow:self.window]; + [mainWindow endSheet:self.window]; + } + else + { + [NSApp beginSheet:self.window + modalForWindow:mainWindow + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + self.modalCode = [NSApp runModalForWindow:self.window]; + [NSApp endSheet:self.window]; + } + + [self.window orderOut:nil]; + return self.modalCode; +} + +- (void)dealloc +{ + [usernameText release]; + [passwordText release]; + [messageLabel release]; + [serverHostname release]; + [username release]; + [password release]; + [domain release]; + [super dealloc]; +} + +@end diff --git a/client/Mac/PasswordDialog.xib b/client/Mac/PasswordDialog.xib new file mode 100644 index 0000000..3911c14 --- /dev/null +++ b/client/Mac/PasswordDialog.xib @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/Mac/cli/AppDelegate.h b/client/Mac/cli/AppDelegate.h new file mode 100644 index 0000000..64b2611 --- /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..e6e625b --- /dev/null +++ b/client/Mac/cli/AppDelegate.m @@ -0,0 +1,307 @@ +// +// 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) + { + 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]; + } + else + { + [NSApp terminate:self]; + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification +{ + [mrdpView resume]; +} + +- (void)applicationWillResignActive:(NSNotification *)notification +{ + [mrdpView pause]; +} + +- (void)applicationWillTerminate:(NSNotification *)notification +{ + NSLog(@"Stopping...\n"); + freerdp_client_stop(context); + [mrdpView releaseResources]; + _singleDelegate = nil; + NSLog(@"Stopped.\n"); + [NSApp terminate:self]; +} + +- (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); + 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) +{ + rdpContext *context = (rdpContext *)ctx; + NSLog(@"ConnectionResult event result:%d\n", e->result); + + if (_singleDelegate) + { + if (e->result != 0) + { + NSString *message = nil; + DWORD code = freerdp_get_last_error(context); + switch (code) + { + case FREERDP_ERROR_AUTHENTICATION_FAILED: + message = [NSString + stringWithFormat:@"%@", @"Authentication failure, check credentials."]; + break; + default: + break; + } + + // 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]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + 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; + dispatch_async(dispatch_get_main_queue(), ^{ + 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..b481ab5 --- /dev/null +++ b/client/Mac/cli/CMakeLists.txt @@ -0,0 +1,114 @@ + +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) + +string(TIMESTAMP VERSION_YEAR "%Y") +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 ${FREERDP_VERSION}") +set(MACOSX_BUNDLE_BUNDLE_NAME "MacFreeRDP") +set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${FREERDP_VERSION}) +set(MACOSX_BUNDLE_BUNDLE_VERSION ${FREERDP_VERSION}) +set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013-${VERSION_YEAR}. 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..198a144 --- /dev/null +++ b/client/Mac/cli/Info.plist @@ -0,0 +1,38 @@ + + + + + NSCameraUsageDescription + This application requires camera access to redirect it to the remote host + NSMicrophoneUsageDescription + This application requires microphone access to redirect it to the remote host + 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..f647699 --- /dev/null +++ b/client/Mac/cli/MainMenu.xib @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..7e5e478 --- /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, const char *argv[]) +{ + return NSApplicationMain(argc, 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..0f1916e --- /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, const char *argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/client/Mac/mf_client.h b/client/Mac/mf_client.h new file mode 100644 index 0000000..c3b3c85 --- /dev/null +++ b/client/Mac/mf_client.h @@ -0,0 +1,49 @@ +/** + * 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_press_mouse_button(void* context, rdpInput* intput, int button, int x, + int y, BOOL down); + FREERDP_API void mf_scale_mouse_event(void* context, rdpInput* input, UINT16 flags, UINT16 x, + UINT16 y); + FREERDP_API void mf_scale_mouse_event_ex(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..a46d3d1 --- /dev/null +++ b/client/Mac/mf_client.m @@ -0,0 +1,216 @@ +/** + * 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(void) +{ + freerdp_handle_signals(); + return TRUE; +} + +static void mfreerdp_client_global_uninit(void) +{ +} + +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; + + freerdp_abort_connect(context->instance); + 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->PostDisconnect = mac_post_disconnect; + context->instance->Authenticate = mac_authenticate; + context->instance->GatewayAuthenticate = mac_gw_authenticate; + context->instance->VerifyCertificateEx = mac_verify_certificate_ex; + context->instance->VerifyChangedCertificateEx = mac_verify_changed_certificate_ex; + context->instance->LogonErrorInfo = mac_logon_error_info; + context->instance->settings = instance->settings; + settings = context->settings; + settings->AsyncInput = TRUE; + return TRUE; +} + +static void mfreerdp_client_free(freerdp *instance, rdpContext *context) +{ + mfContext *mfc; + + if (!instance || !context) + return; + + mfc = (mfContext *)instance->context; + CloseHandle(mfc->stopEvent); +} + +static void mf_scale_mouse_coordinates(mfContext *mfc, UINT16 *px, UINT16 *py) +{ + UINT16 x = *px; + UINT16 y = *py; + UINT32 ww = mfc->client_width; + UINT32 wh = mfc->client_height; + UINT32 dw = mfc->context.settings->DesktopWidth; + UINT32 dh = mfc->context.settings->DesktopHeight; + + if (!mfc->context.settings->SmartSizing || ((ww == dw) && (wh == dh))) + { + y = y + mfc->yCurrentScroll; + x = x + mfc->xCurrentScroll; + + y -= (dh - wh); + x -= (dw - ww); + } + else + { + y = y * dh / wh + mfc->yCurrentScroll; + x = x * dw / ww + mfc->xCurrentScroll; + } + + *px = x; + *py = 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; + // Convert to windows coordinates + y = [view frame].size.height - y; + + if ((flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) == 0) + mf_scale_mouse_coordinates(mfc, &x, &y); + freerdp_input_send_mouse_event(input, flags, x, y); +} + +void mf_scale_mouse_event_ex(void *context, rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + // Convert to windows coordinates + y = [view frame].size.height - y; + + mf_scale_mouse_coordinates(mfc, &x, &y); + freerdp_input_send_extended_mouse_event(input, flags, x, y); +} + +void mf_press_mouse_button(void *context, rdpInput *input, int button, int x, int y, BOOL down) +{ + UINT16 flags = 0; + UINT16 xflags = 0; + + if (down) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (button) + { + case 0: + mf_scale_mouse_event(context, input, flags | PTR_FLAGS_BUTTON1, x, y); + break; + + case 1: + mf_scale_mouse_event(context, input, flags | PTR_FLAGS_BUTTON2, x, y); + break; + + case 2: + mf_scale_mouse_event(context, input, flags | PTR_FLAGS_BUTTON3, x, y); + break; + + case 3: + mf_scale_mouse_event_ex(context, input, xflags | PTR_XFLAGS_BUTTON1, x, y); + break; + + case 4: + mf_scale_mouse_event_ex(context, input, xflags | PTR_XFLAGS_BUTTON2, x, y); + break; + + default: + break; + } +} + +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..be39031 --- /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..a8e9a6e --- /dev/null +++ b/client/Sample/CMakeLists.txt @@ -0,0 +1,51 @@ +# 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 + tf_channels.c + tf_channels.h + tf_freerdp.h + tf_freerdp.c) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${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") +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) diff --git a/client/Sample/ModuleOptions.cmake b/client/Sample/ModuleOptions.cmake new file mode 100644 index 0000000..d4d5a9e --- /dev/null +++ b/client/Sample/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_CLIENT_NAME "sfreerdp") +set(FREERDP_CLIENT_PLATFORM "Sample") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/client/Sample/tf_channels.c b/client/Sample/tf_channels.c new file mode 100644 index 0000000..7119a1c --- /dev/null +++ b/client/Sample/tf_channels.c @@ -0,0 +1,115 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client Channels + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include "tf_channels.h" +#include "tf_freerdp.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +tf_encomsp_participant_created(EncomspClientContext* context, + const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(participantCreated); + return CHANNEL_RC_OK; +} + +static void tf_encomsp_init(tfContext* tf, EncomspClientContext* encomsp) +{ + tf->encomsp = encomsp; + encomsp->custom = (void*)tf; + encomsp->ParticipantCreated = tf_encomsp_participant_created; +} + +static void tf_encomsp_uninit(tfContext* tf, EncomspClientContext* encomsp) +{ + if (encomsp) + { + encomsp->custom = NULL; + encomsp->ParticipantCreated = NULL; + } + + if (tf) + tf->encomsp = NULL; +} + +void tf_OnChannelConnectedEventHandler(void* context, ChannelConnectedEventArgs* e) +{ + tfContext* tf = (tfContext*)context; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + tf->rdpei = (RdpeiClientContext*)e->pInterface; + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + gdi_graphics_pipeline_init(tf->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) + { + tf_encomsp_init(tf, (EncomspClientContext*)e->pInterface); + } +} + +void tf_OnChannelDisconnectedEventHandler(void* context, ChannelDisconnectedEventArgs* e) +{ + tfContext* tf = (tfContext*)context; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + tf->rdpei = NULL; + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + gdi_graphics_pipeline_uninit(tf->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) + { + tf_encomsp_uninit(tf, (EncomspClientContext*)e->pInterface); + } +} diff --git a/client/Sample/tf_channels.h b/client/Sample/tf_channels.h new file mode 100644 index 0000000..b1c0b86 --- /dev/null +++ b/client/Sample/tf_channels.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client Channels + * + * Copyright 2018 Armin Novak + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_SAMPLE_CHANNELS_H +#define FREERDP_CLIENT_SAMPLE_CHANNELS_H + +#include +#include + +int tf_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int tf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void tf_OnChannelConnectedEventHandler(void* context, ChannelConnectedEventArgs* e); +void tf_OnChannelDisconnectedEventHandler(void* context, ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_SAMPLE_CHANNELS_H */ diff --git a/client/Sample/tf_freerdp.c b/client/Sample/tf_freerdp.c new file mode 100644 index 0000000..e9b9fe8 --- /dev/null +++ b/client/Sample/tf_freerdp.c @@ -0,0 +1,359 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Test UI + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016,2018 Armin Novak + * Copyright 2016,2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tf_channels.h" +#include "tf_freerdp.h" + +#define TAG CLIENT_TAG("sample") + +/* This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. */ +static BOOL tf_begin_paint(rdpContext* context) +{ + rdpGdi* gdi = context->gdi; + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +/* This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +static BOOL tf_end_paint(rdpContext* context) +{ + rdpGdi* gdi = context->gdi; + + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + return TRUE; +} + +/* This function is called to output a System BEEP */ +static BOOL tf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + /* TODO: Implement */ + WINPR_UNUSED(context); + WINPR_UNUSED(play_sound); + return TRUE; +} + +/* This function is called to update the keyboard indocator LED */ +static BOOL tf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + /* TODO: Set local keyboard indicator LED status */ + WINPR_UNUSED(context); + WINPR_UNUSED(led_flags); + return TRUE; +} + +/* This function is called to set the IME state */ +static BOOL tf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} + +/* Called before a connection is established. + * Set all configuration options to support and load channels here. */ +static BOOL tf_pre_connect(freerdp* instance) +{ + rdpSettings* settings; + settings = instance->settings; + /* Optional OS identifier sent to server */ + settings->OsMajorType = OSMAJORTYPE_UNIX; + settings->OsMinorType = OSMINORTYPE_NATIVE_XSERVER; + /* settings->OrderSupport is initialized at this point. + * Only override it if you plan to implement custom order + * callbacks or deactiveate certain features. */ + /* Register the channel listeners. + * They are required to set up / tear down channels if they are loaded. */ + PubSub_SubscribeChannelConnected(instance->context->pubSub, tf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + tf_OnChannelDisconnectedEventHandler); + + /* Load all required plugins / channels / libraries specified by current + * settings. */ + if (!freerdp_client_load_addins(instance->context->channels, instance->settings)) + return FALSE; + + /* TODO: Any code your client requires */ + return TRUE; +} + +/* Called after a RDP connection was successfully established. + * Settings might have changed during negociation of client / server feature + * support. + * + * Set up local framebuffers and paing callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +static BOOL tf_post_connect(freerdp* instance) +{ + if (!gdi_init(instance, PIXEL_FORMAT_XRGB32)) + return FALSE; + + instance->update->BeginPaint = tf_begin_paint; + instance->update->EndPaint = tf_end_paint; + instance->update->PlaySound = tf_play_sound; + instance->update->SetKeyboardIndicators = tf_keyboard_set_indicators; + instance->update->SetKeyboardImeStatus = tf_keyboard_set_ime_status; + return TRUE; +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +static void tf_post_disconnect(freerdp* instance) +{ + tfContext* context; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (tfContext*)instance->context; + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + tf_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + tf_OnChannelDisconnectedEventHandler); + gdi_free(instance); + /* TODO : Clean up custom stuff */ + WINPR_UNUSED(context); +} + +/* RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. */ +static DWORD WINAPI tf_client_thread_proc(LPVOID arg) +{ + freerdp* instance = (freerdp*)arg; + DWORD nCount; + DWORD status; + DWORD result = 0; + HANDLE handles[64]; + BOOL rc = freerdp_connect(instance); + + if (instance->settings->AuthenticationOnly) + { + result = freerdp_get_last_error(instance->context); + freerdp_abort_connect(instance); + WLog_ERR(TAG, "Authentication only, exit status 0x%08" PRIx32 "", result); + goto disconnect; + } + + if (!rc) + { + result = freerdp_get_last_error(instance->context); + WLog_ERR(TAG, "connection failure 0x%08" PRIx32, result); + return result; + } + + while (!freerdp_shall_disconnect(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; + } + } + +disconnect: + freerdp_disconnect(instance); + return result; +} + +/* Optional global initializer. + * Here we just register a signal handler to print out stack traces + * if available. */ +static BOOL tf_client_global_init(void) +{ + if (freerdp_handle_signals() != 0) + return FALSE; + + return TRUE; +} + +/* Optional global tear down */ +static void tf_client_global_uninit(void) +{ +} + +static int tf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + tfContext* tf; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + tf = (tfContext*)instance->context; + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + WINPR_UNUSED(tf); + + return 1; +} + +static BOOL tf_client_new(freerdp* instance, rdpContext* context) +{ + tfContext* tf = (tfContext*)context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = tf_pre_connect; + instance->PostConnect = tf_post_connect; + instance->PostDisconnect = tf_post_disconnect; + instance->Authenticate = client_cli_authenticate; + instance->GatewayAuthenticate = client_cli_gw_authenticate; + instance->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->LogonErrorInfo = tf_logon_error_info; + /* TODO: Client display set up */ + WINPR_UNUSED(tf); + return TRUE; +} + +static void tf_client_free(freerdp* instance, rdpContext* context) +{ + tfContext* tf = (tfContext*)instance->context; + + if (!context) + return; + + /* TODO: Client display tear down */ + WINPR_UNUSED(tf); +} + +static int tf_client_start(rdpContext* context) +{ + /* TODO: Start client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int tf_client_stop(rdpContext* context) +{ + /* TODO: Stop client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = tf_client_global_init; + pEntryPoints->GlobalUninit = tf_client_global_uninit; + pEntryPoints->ContextSize = sizeof(tfContext); + pEntryPoints->ClientNew = tf_client_new; + pEntryPoints->ClientFree = tf_client_free; + pEntryPoints->ClientStart = tf_client_start; + pEntryPoints->ClientStop = tf_client_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + DWORD status; + 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); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(context->settings, status, argc, + argv); + goto fail; + } + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = tf_client_thread_proc(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} diff --git a/client/Sample/tf_freerdp.h b/client/Sample/tf_freerdp.h new file mode 100644 index 0000000..19e0cee --- /dev/null +++ b/client/Sample/tf_freerdp.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client + * + * 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_CLIENT_SAMPLE_H +#define FREERDP_CLIENT_SAMPLE_H + +#include +#include +#include +#include +#include +#include + +struct tf_context +{ + rdpContext context; + + /* Channels */ + RdpeiClientContext* rdpei; + RdpgfxClientContext* gfx; + EncomspClientContext* encomsp; +}; +typedef struct tf_context tfContext; + +#endif /* FREERDP_CLIENT_SAMPLE_H */ diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt new file mode 100644 index 0000000..a9e19e3 --- /dev/null +++ b/client/Wayland/CMakeLists.txt @@ -0,0 +1,50 @@ +# 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_disp.c + wlf_disp.h + wlf_pointer.c + wlf_pointer.h + wlf_input.c + wlf_input.h + wlf_cliprdr.c + wlf_cliprdr.h + wlf_channels.c + wlf_channels.h + ) + +list (APPEND ${MODULE_PREFIX}_LIBS freerdp-client freerdp uwac) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +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..9c49584 --- /dev/null +++ b/client/Wayland/wlf_channels.c @@ -0,0 +1,156 @@ +/** + * 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 "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlfreerdp.h" + +BOOL encomsp_toggle_control(EncomspClientContext* encomsp, BOOL control) +{ + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu; + + if (!encomsp) + return FALSE; + + pdu.ParticipantId = 0; + pdu.Flags = ENCOMSP_REQUEST_VIEW; + + if (control) + pdu.Flags |= ENCOMSP_REQUEST_INTERACT; + + encomsp->ChangeParticipantControlLevel(encomsp, &pdu); + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_encomsp_participant_created(EncomspClientContext* context, + const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + wlfContext* wlf; + rdpSettings* settings; + BOOL request; + + if (!context || !context->custom || !participantCreated) + return ERROR_INVALID_PARAMETER; + + wlf = (wlfContext*)context->custom; + settings = wlf->context.settings; + + if (!settings) + return ERROR_INVALID_PARAMETER; + + request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl); + if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) && + !(participantCreated->Flags & ENCOMSP_MAY_INTERACT)) + { + if (!encomsp_toggle_control(context, TRUE)) + return ERROR_INTERNAL_ERROR; + } + + 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; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + wlf->rdpei = (RdpeiClientContext*)e->pInterface; + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + 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) + { + wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + wlf_encomsp_init(wlf, (EncomspClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_init(wlf->disp, (DispClientContext*)e->pInterface); + } +} + +void wlf_OnChannelDisconnectedEventHandler(void* context, ChannelDisconnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*)context; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + wlf->rdpei = NULL; + } + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + 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) + { + wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + wlf_encomsp_uninit(wlf, (EncomspClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_uninit(wlf->disp, (DispClientContext*)e->pInterface); + } +} diff --git a/client/Wayland/wlf_channels.h b/client/Wayland/wlf_channels.h new file mode 100644 index 0000000..c56be6a --- /dev/null +++ b/client/Wayland/wlf_channels.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +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_cliprdr.c b/client/Wayland/wlf_cliprdr.c new file mode 100644 index 0000000..0f0195d --- /dev/null +++ b/client/Wayland/wlf_cliprdr.c @@ -0,0 +1,921 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wlf_cliprdr.h" + +#define TAG CLIENT_TAG("wayland.cliprdr") + +#define MAX_CLIPBOARD_FORMATS 255 + +static const char* mime_text[] = { "text/plain", "text/plain;charset=utf-8", + "UTF8_STRING", "COMPOUND_TEXT", + "TEXT", "STRING" }; + +static const char* mime_image[] = { + "image/png", "image/bmp", "image/x-bmp", "image/x-MS-bmp", + "image/x-icon", "image/x-ico", "image/x-win-bitmap", "image/vmd.microsoft.icon", + "application/ico", "image/ico", "image/icon", "image/jpeg", + "image/tiff" +}; + +static const char* mime_html[] = { "text/html" }; + +struct wlf_clipboard +{ + wlfContext* wfc; + rdpChannels* channels; + CliprdrClientContext* context; + wLog* log; + + UwacSeat* seat; + wClipboard* system; + wClipboardDelegate* delegate; + + size_t numClientFormats; + CLIPRDR_FORMAT* clientFormats; + + size_t numServerFormats; + CLIPRDR_FORMAT* serverFormats; + + BOOL sync; + + /* File clipping */ + BOOL streams_supported; + BOOL file_formats_registered; + + /* Server response stuff */ + FILE* responseFile; + UINT32 responseFormat; + const char* responseMime; + CRITICAL_SECTION lock; +}; + +static BOOL wlf_mime_is_text(const char* mime) +{ + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_text); x++) + { + if (strcmp(mime, mime_text[x]) == 0) + return TRUE; + } + + return FALSE; +} + +static BOOL wlf_mime_is_image(const char* mime) +{ + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_image); x++) + { + if (strcmp(mime, mime_image[x]) == 0) + return TRUE; + } + + return FALSE; +} + +static BOOL wlf_mime_is_html(const char* mime) +{ + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_html); x++) + { + if (strcmp(mime, mime_html[x]) == 0) + return TRUE; + } + + return FALSE; +} + +static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard) +{ + if (clipboard && clipboard->serverFormats) + { + size_t j; + + for (j = 0; j < clipboard->numServerFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[j]; + free(format->formatName); + } + + free(clipboard->serverFormats); + clipboard->serverFormats = NULL; + clipboard->numServerFormats = 0; + } + + if (clipboard) + UwacClipboardOfferDestroy(clipboard->seat); +} + +static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard) +{ + if (clipboard && clipboard->numClientFormats) + { + size_t j; + + for (j = 0; j < clipboard->numClientFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->clientFormats[j]; + free(format->formatName); + } + + free(clipboard->clientFormats); + clipboard->clientFormats = NULL; + clipboard->numClientFormats = 0; + } + + if (clipboard) + UwacClipboardOfferDestroy(clipboard->seat); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard) +{ + CLIPRDR_FORMAT_LIST formatList = { 0 }; + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = (UINT32)clipboard->numClientFormats; + formatList.formats = clipboard->clientFormats; + formatList.msgType = CB_FORMAT_LIST; + return clipboard->context->ClientFormatList(clipboard->context, &formatList); +} + +static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId) +{ + size_t x; + CLIPRDR_FORMAT* format; + const char* name = ClipboardGetFormatName(clipboard->system, formatId); + + for (x = 0; x < clipboard->numClientFormats; x++) + { + format = &clipboard->clientFormats[x]; + + if (format->formatId == formatId) + return; + } + + format = realloc(clipboard->clientFormats, + (clipboard->numClientFormats + 1) * sizeof(CLIPRDR_FORMAT)); + + if (!format) + return; + + clipboard->clientFormats = format; + format = &clipboard->clientFormats[clipboard->numClientFormats++]; + format->formatId = formatId; + format->formatName = NULL; + + if (name && (formatId >= CF_MAX)) + format->formatName = _strdup(name); +} + +static void wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime) +{ + if (wlf_mime_is_html(mime)) + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, "HTML Format"); + wfl_cliprdr_add_client_format_id(clipboard, formatId); + } + else if (wlf_mime_is_text(mime)) + { + wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT); + wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT); + wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT); + } + else if (wlf_mime_is_image(mime)) + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, "image/bmp"); + wfl_cliprdr_add_client_format_id(clipboard, formatId); + wfl_cliprdr_add_client_format_id(clipboard, CF_DIB); + } + + wlf_cliprdr_send_client_format_list(clipboard); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, UINT32 formatId) +{ + CLIPRDR_FORMAT_DATA_REQUEST request = { 0 }; + request.requestedFormatId = formatId; + return clipboard->context->ClientFormatDataRequest(clipboard->context, &request); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard, const BYTE* data, size_t size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 }; + + if (size > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + response.dataLen = (UINT32)size; + response.requestedFormatData = data; + return clipboard->context->ClientFormatDataResponse(clipboard->context, &response); +} + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event) +{ + if (!clipboard || !event) + return FALSE; + + if (!clipboard->context) + return TRUE; + + switch (event->type) + { + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + clipboard->seat = event->seat; + return TRUE; + + case UWAC_EVENT_CLIPBOARD_OFFER: + WLog_Print(clipboard->log, WLOG_DEBUG, "client announces mime %s", event->mime); + wlf_cliprdr_add_client_format(clipboard, event->mime); + return TRUE; + + case UWAC_EVENT_CLIPBOARD_SELECT: + WLog_Print(clipboard->log, WLOG_DEBUG, "client announces new data"); + wlf_cliprdr_free_client_formats(clipboard); + return TRUE; + + default: + return FALSE; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard) +{ + 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 | CB_HUGE_FILE_SUPPORT_ENABLED; + + return clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status) +{ + 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 wlf_cliprdr_monitor_ready(CliprdrClientContext* context, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + wfClipboard* clipboard = (wfClipboard*)context->custom; + UINT ret; + WINPR_UNUSED(monitorReady); + + if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK) + return ret; + + if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK) + return ret; + + clipboard->sync = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + UINT32 i; + const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets; + wfClipboard* clipboard = (wfClipboard*)context->custom; + clipboard->streams_supported = FALSE; + + for (i = 0; i < capabilities->cCapabilitiesSets; i++) + { + const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr; + + if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) + { + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = + (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps; + + if (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED) + { + clipboard->streams_supported = TRUE; + } + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd) +{ + wfClipboard* clipboard = (wfClipboard*)context; + size_t x; + WINPR_UNUSED(seat); + + EnterCriticalSection(&clipboard->lock); + clipboard->responseMime = NULL; + + for (x = 0; x < ARRAYSIZE(mime_html); x++) + { + const char* mime_cur = mime_html[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = ClipboardGetFormatId(clipboard->system, "HTML Format"); + break; + } + } + + for (x = 0; x < ARRAYSIZE(mime_text); x++) + { + const char* mime_cur = mime_text[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = CF_UNICODETEXT; + break; + } + } + + for (x = 0; x < ARRAYSIZE(mime_image); x++) + { + const char* mime_cur = mime_image[x]; + + if (strcmp(mime_cur, mime) == 0) + { + clipboard->responseMime = mime_cur; + clipboard->responseFormat = CF_DIB; + break; + } + } + + if (clipboard->responseMime != NULL) + { + if (clipboard->responseFile != NULL) + fclose(clipboard->responseFile); + clipboard->responseFile = fdopen(fd, "w"); + + if (clipboard->responseFile) + wlf_cliprdr_send_data_request(clipboard, clipboard->responseFormat); + else + WLog_Print(clipboard->log, WLOG_ERROR, + "failed to open clipboard file descriptor for MIME %s", + clipboard->responseMime); + } + LeaveCriticalSection(&clipboard->lock); +} + +static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context) +{ + WINPR_UNUSED(seat); + WINPR_UNUSED(context); +} + +/** + * Called when the clipboard changes server side. + * + * Clear the local clipboard offer and replace it with a new one + * that announces the formats we get listed here. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + UINT32 i; + wfClipboard* clipboard; + BOOL html = FALSE; + BOOL text = FALSE; + BOOL image = FALSE; + + if (!context || !context->custom) + return ERROR_INVALID_PARAMETER; + + clipboard = (wfClipboard*)context->custom; + wlf_cliprdr_free_server_formats(clipboard); + + if (!(clipboard->serverFormats = + (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT)))) + { + WLog_Print(clipboard->log, WLOG_ERROR, + "failed to allocate %" PRIuz " CLIPRDR_FORMAT structs", + clipboard->numServerFormats); + return CHANNEL_RC_NO_MEMORY; + } + + clipboard->numServerFormats = formatList->numFormats; + + if (!clipboard->seat) + { + WLog_Print(clipboard->log, WLOG_ERROR, + "clipboard->seat=NULL, check your client implementation"); + return ERROR_INTERNAL_ERROR; + } + + for (i = 0; i < formatList->numFormats; i++) + { + const CLIPRDR_FORMAT* format = &formatList->formats[i]; + CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i]; + srvFormat->formatId = format->formatId; + + if (format->formatName) + { + srvFormat->formatName = _strdup(format->formatName); + + if (!srvFormat->formatName) + { + wlf_cliprdr_free_server_formats(clipboard); + return CHANNEL_RC_NO_MEMORY; + } + } + + if (format->formatName) + { + if (strcmp(format->formatName, "HTML Format") == 0) + { + text = TRUE; + html = TRUE; + } + } + else + { + switch (format->formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + text = TRUE; + break; + + case CF_DIB: + image = TRUE; + break; + + default: + break; + } + } + } + + if (html) + { + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_html); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_html[x]); + } + + if (text) + { + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_text); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_text[x]); + } + + if (image) + { + size_t x; + + for (x = 0; x < ARRAYSIZE(mime_image); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_image[x]); + } + + UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data, + wlf_cliprdr_cancel_data); + return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_list_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + // wfClipboard* clipboard = (wfClipboard*) context->custom; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + int cnv; + UINT rc = CHANNEL_RC_OK; + BYTE* data; + LPWSTR cdata; + size_t size; + const char* mime; + UINT32 formatId = formatDataRequest->requestedFormatId; + wfClipboard* clipboard = (wfClipboard*)context->custom; + + switch (formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + mime = "text/plain;charset=utf-8"; + break; + + case CF_DIB: + case CF_DIBV5: + mime = "image/bmp"; + break; + + default: + if (formatId == ClipboardGetFormatId(clipboard->system, "HTML Format")) + mime = "text/html"; + else if (formatId == ClipboardGetFormatId(clipboard->system, "image/bmp")) + mime = "image/bmp"; + else + mime = ClipboardGetFormatName(clipboard->system, formatId); + + break; + } + + data = UwacClipboardDataGet(clipboard->seat, mime, &size); + + if (!data) + return ERROR_INTERNAL_ERROR; + + switch (formatId) + { + case CF_UNICODETEXT: + if (size > INT_MAX) + rc = ERROR_INTERNAL_ERROR; + else + { + cdata = NULL; + cnv = ConvertToUnicode(CP_UTF8, 0, (LPCSTR)data, (int)size, &cdata, 0); + free(data); + data = NULL; + + if (cnv < 0) + rc = ERROR_INTERNAL_ERROR; + else + { + size = (size_t)cnv; + data = (BYTE*)cdata; + size *= sizeof(WCHAR); + } + } + + break; + + default: + // TODO: Image conversions + break; + } + + if (rc != CHANNEL_RC_OK) + return rc; + + rc = wlf_cliprdr_send_data_response(clipboard, data, size); + free(data); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + int cnv; + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 size = formatDataResponse->dataLen; + LPSTR cdata = NULL; + LPCSTR data = (LPCSTR)formatDataResponse->requestedFormatData; + const WCHAR* wdata = (const WCHAR*)formatDataResponse->requestedFormatData; + wfClipboard* clipboard = (wfClipboard*)context->custom; + + EnterCriticalSection(&clipboard->lock); + + if (size > INT_MAX * sizeof(WCHAR)) + return ERROR_INTERNAL_ERROR; + + switch (clipboard->responseFormat) + { + case CF_UNICODETEXT: + cnv = ConvertFromUnicode(CP_UTF8, 0, wdata, (int)(size / sizeof(WCHAR)), &cdata, 0, + NULL, NULL); + + if (cnv < 0) + return ERROR_INTERNAL_ERROR; + + size = (size_t)cnv; + data = cdata; + break; + + default: + // TODO: Image conversions + break; + } + + if (clipboard->responseFile) + { + fwrite(data, 1, size, clipboard->responseFile); + fclose(clipboard->responseFile); + clipboard->responseFile = NULL; + } + rc = CHANNEL_RC_OK; + free(cdata); + + LeaveCriticalSection(&clipboard->lock); + return rc; +} + +static UINT +wlf_cliprdr_server_file_size_request(wfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileSizeRequest request = { 0 }; + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + + if (fileContentsRequest->cbRequested != sizeof(UINT64)) + { + WLog_Print(clipboard->log, WLOG_WARN, + "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes", + fileContentsRequest->cbRequested); + } + + return clipboard->delegate->ClientRequestFileSize(clipboard->delegate, &request); +} + +static UINT +wlf_cliprdr_server_file_range_request(wfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileRangeRequest request = { 0 }; + 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 +wlf_cliprdr_send_file_contents_failure(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + return context->ClientFileContentsResponse(context, &response); +} + +static UINT +wlf_cliprdr_server_file_contents_request(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + UINT error = NO_ERROR; + wfClipboard* 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_Print(clipboard->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); + return wlf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) + error = wlf_cliprdr_server_file_size_request(clipboard, fileContentsRequest); + + if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) + error = wlf_cliprdr_server_file_range_request(clipboard, fileContentsRequest); + + if (error) + { + WLog_Print(clipboard->log, WLOG_ERROR, + "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", error); + return wlf_cliprdr_send_file_contents_failure(context, fileContentsRequest); + } + + return CHANNEL_RC_OK; +} + +static UINT wlf_cliprdr_clipboard_file_size_success(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, + UINT64 fileSize) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + wfClipboard* clipboard = delegate->custom; + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.cbRequested = sizeof(UINT64); + response.requestedData = (BYTE*)&fileSize; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_size_failure(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request, + UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + wfClipboard* clipboard = delegate->custom; + WINPR_UNUSED(errorCode); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_range_success(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, + const BYTE* data, UINT32 size) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + wfClipboard* clipboard = delegate->custom; + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.cbRequested = size; + response.requestedData = (const BYTE*)data; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static UINT wlf_cliprdr_clipboard_file_range_failure(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request, + UINT errorCode) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + wfClipboard* clipboard = delegate->custom; + WINPR_UNUSED(errorCode); + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +wfClipboard* wlf_clipboard_new(wlfContext* wfc) +{ + rdpChannels* channels; + wfClipboard* clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard)); + + if (!clipboard) + goto fail; + + InitializeCriticalSection(&clipboard->lock); + clipboard->wfc = wfc; + channels = wfc->context.channels; + clipboard->log = WLog_Get(TAG); + clipboard->channels = channels; + clipboard->system = ClipboardCreate(); + if (!clipboard->system) + goto fail; + + clipboard->delegate = ClipboardGetDelegate(clipboard->system); + if (!clipboard->delegate) + goto fail; + + clipboard->delegate->custom = clipboard; + /* TODO: set up a filesystem base path for local URI */ + /* clipboard->delegate->basePath = "file:///tmp/foo/bar/gaga"; */ + clipboard->delegate->ClipboardFileSizeSuccess = wlf_cliprdr_clipboard_file_size_success; + clipboard->delegate->ClipboardFileSizeFailure = wlf_cliprdr_clipboard_file_size_failure; + clipboard->delegate->ClipboardFileRangeSuccess = wlf_cliprdr_clipboard_file_range_success; + clipboard->delegate->ClipboardFileRangeFailure = wlf_cliprdr_clipboard_file_range_failure; + return clipboard; + +fail: + wlf_clipboard_free(clipboard); + return NULL; +} + +void wlf_clipboard_free(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + wlf_cliprdr_free_server_formats(clipboard); + wlf_cliprdr_free_client_formats(clipboard); + ClipboardDestroy(clipboard->system); + + EnterCriticalSection(&clipboard->lock); + if (clipboard->responseFile) + fclose(clipboard->responseFile); + LeaveCriticalSection(&clipboard->lock); + DeleteCriticalSection(&clipboard->lock); + free(clipboard); +} + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + if (!cliprdr || !clipboard) + return FALSE; + + clipboard->context = cliprdr; + cliprdr->custom = (void*)clipboard; + cliprdr->MonitorReady = wlf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = wlf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response; + cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = wlf_cliprdr_server_file_contents_request; + return TRUE; +} + +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + if (cliprdr) + cliprdr->custom = NULL; + + if (clipboard) + clipboard->context = NULL; + + return TRUE; +} diff --git a/client/Wayland/wlf_cliprdr.h b/client/Wayland/wlf_cliprdr.h new file mode 100644 index 0000000..a113140 --- /dev/null +++ b/client/Wayland/wlf_cliprdr.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * Copyright 2018 Armin Novak + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CLIPRDR_H +#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H + +#include "wlfreerdp.h" + +#include + +wfClipboard* wlf_clipboard_new(wlfContext* wlc); +void wlf_clipboard_free(wfClipboard* clipboard); + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr); +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr); + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event); + +#endif /* FREERDP_CLIENT_WAYLAND_CLIPRDR_H */ diff --git a/client/Wayland/wlf_disp.c b/client/Wayland/wlf_disp.c new file mode 100644 index 0000000..51a5f9e --- /dev/null +++ b/client/Wayland/wlf_disp.c @@ -0,0 +1,401 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control 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. + */ + +#include + +#include "wlf_disp.h" + +#define TAG CLIENT_TAG("wayland.disp") + +#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ + +struct _wlfDispContext +{ + wlfContext* wlc; + DispClientContext* disp; + BOOL haveXRandr; + int eventBase, errorBase; + int lastSentWidth, lastSentHeight; + UINT64 lastSentDate; + int targetWidth, targetHeight; + BOOL activated; + BOOL waitingResize; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; +}; + +static UINT wlf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, size_t nmonitors); + +static BOOL wlf_disp_settings_changed(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = wlfDisp->wlc->context.settings; + + if (wlfDisp->lastSentWidth != wlfDisp->targetWidth) + return TRUE; + + if (wlfDisp->lastSentHeight != wlfDisp->targetHeight) + return TRUE; + + if (wlfDisp->lastSentDesktopOrientation != settings->DesktopOrientation) + return TRUE; + + if (wlfDisp->lastSentDesktopScaleFactor != settings->DesktopScaleFactor) + return TRUE; + + if (wlfDisp->lastSentDeviceScaleFactor != settings->DeviceScaleFactor) + return TRUE; + + if (wlfDisp->fullscreen != wlfDisp->wlc->fullscreen) + return TRUE; + + return FALSE; +} + +static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = wlfDisp->wlc->context.settings; + wlfDisp->lastSentWidth = wlfDisp->targetWidth; + wlfDisp->lastSentHeight = wlfDisp->targetHeight; + wlfDisp->lastSentDesktopOrientation = settings->DesktopOrientation; + wlfDisp->lastSentDesktopScaleFactor = settings->DesktopScaleFactor; + wlfDisp->lastSentDeviceScaleFactor = settings->DeviceScaleFactor; + wlfDisp->fullscreen = wlfDisp->wlc->fullscreen; + return TRUE; +} + +static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout; + wlfContext* wlc; + rdpSettings* settings; + + if (!wlfDisp || !wlfDisp->wlc) + return FALSE; + + wlc = wlfDisp->wlc; + settings = wlc->context.settings; + + if (!settings) + return FALSE; + + if (!wlfDisp->activated || !wlfDisp->disp) + return TRUE; + + if (GetTickCount64() - wlfDisp->lastSentDate < RESIZE_MIN_DELAY) + return TRUE; + + wlfDisp->lastSentDate = GetTickCount64(); + + if (!wlf_disp_settings_changed(wlfDisp)) + return TRUE; + + /* TODO: Multimonitor support for wayland + if (wlc->fullscreen && (settings->MonitorCount > 0)) + { + if (wlf_disp_sendLayout(wlfDisp->disp, settings->MonitorDefArray, + settings->MonitorCount) != CHANNEL_RC_OK) + return FALSE; + } + else + */ + { + wlfDisp->waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = wlfDisp->targetWidth; + layout.Height = wlfDisp->targetHeight; + layout.Orientation = settings->DesktopOrientation; + layout.DesktopScaleFactor = settings->DesktopScaleFactor; + layout.DeviceScaleFactor = settings->DeviceScaleFactor; + layout.PhysicalWidth = wlfDisp->targetWidth; + layout.PhysicalHeight = wlfDisp->targetHeight; + + if (IFCALLRESULT(CHANNEL_RC_OK, wlfDisp->disp->SendMonitorLayout, wlfDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + return wlf_update_last_sent(wlfDisp); +} + +static BOOL wlf_disp_set_window_resizable(wlfDispContext* wlfDisp) +{ +#if 0 // TODO +#endif + return TRUE; +} + +static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings) +{ + wlfContext* wlc; + + if (!context) + return FALSE; + + wlc = (wlfContext*)context; + + if (!(wlc->disp)) + return FALSE; + + if (!wlc->context.settings) + return FALSE; + + *ppwlc = wlc; + *ppwlfDisp = wlc->disp; + *ppSettings = wlc->context.settings; + return TRUE; +} + +static void wlf_disp_OnActivated(void* context, ActivatedEventArgs* e) +{ + wlfContext* wlc; + wlfDispContext* wlfDisp; + rdpSettings* settings; + + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !settings->Fullscreen) + { + wlf_disp_set_window_resizable(wlfDisp); + + if (e->firstActivation) + return; + + wlf_disp_sendResize(wlfDisp); + } +} + +static void wlf_disp_OnGraphicsReset(void* context, GraphicsResetEventArgs* e) +{ + wlfContext* wlc; + wlfDispContext* wlfDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !settings->Fullscreen) + { + wlf_disp_set_window_resizable(wlfDisp); + wlf_disp_sendResize(wlfDisp); + } +} + +static void wlf_disp_OnTimer(void* context, TimerEventArgs* e) +{ + wlfContext* wlc; + wlfDispContext* wlfDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + if (!wlfDisp->activated || settings->Fullscreen) + return; + + wlf_disp_sendResize(wlfDisp); +} + +wlfDispContext* wlf_disp_new(wlfContext* wlc) +{ + wlfDispContext* ret; + + if (!wlc || !wlc->context.settings || !wlc->context.pubSub) + return NULL; + + ret = calloc(1, sizeof(wlfDispContext)); + + if (!ret) + return NULL; + + ret->wlc = wlc; + ret->lastSentWidth = ret->targetWidth = wlc->context.settings->DesktopWidth; + ret->lastSentHeight = ret->targetHeight = wlc->context.settings->DesktopHeight; + PubSub_SubscribeActivated(wlc->context.pubSub, wlf_disp_OnActivated); + PubSub_SubscribeGraphicsReset(wlc->context.pubSub, wlf_disp_OnGraphicsReset); + PubSub_SubscribeTimer(wlc->context.pubSub, wlf_disp_OnTimer); + return ret; +} + +void wlf_disp_free(wlfDispContext* disp) +{ + if (!disp) + return; + + if (disp->wlc) + { + PubSub_UnsubscribeActivated(disp->wlc->context.pubSub, wlf_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(disp->wlc->context.pubSub, wlf_disp_OnGraphicsReset); + PubSub_UnsubscribeTimer(disp->wlc->context.pubSub, wlf_disp_OnTimer); + } + + free(disp); +} + +UINT wlf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, size_t nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts; + size_t i; + wlfDispContext* wlfDisp = (wlfDispContext*)disp->custom; + rdpSettings* settings = wlfDisp->wlc->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 wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height) +{ + if (!disp) + return FALSE; + + disp->targetWidth = width; + disp->targetHeight = height; + return wlf_disp_sendResize(disp); +} + +static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + wlfDispContext* wlfDisp = (wlfDispContext*)disp->custom; + rdpSettings* settings = wlfDisp->wlc->context.settings; + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + wlfDisp->activated = TRUE; + + if (settings->Fullscreen) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return wlf_disp_set_window_resizable(wlfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + rdpSettings* settings; + + if (!wlfDisp || !wlfDisp->wlc || !disp) + return FALSE; + + settings = wlfDisp->wlc->context.settings; + + if (!settings) + return FALSE; + + wlfDisp->disp = disp; + disp->custom = (void*)wlfDisp; + + if (settings->DynamicResolutionUpdate) + { + disp->DisplayControlCaps = wlf_DisplayControlCaps; + } + + return TRUE; +} + +BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + if (!wlfDisp || !disp) + return FALSE; + + wlfDisp->disp = NULL; + return TRUE; +} + +int wlf_list_monitors(wlfContext* wlc) +{ + uint32_t i, nmonitors = UwacDisplayGetNbOutputs(wlc->display); + + for (i = 0; i < nmonitors; i++) + { + const UwacOutput* monitor = UwacDisplayGetOutput(wlc->display, i); + UwacSize resolution; + UwacPosition pos; + + if (!monitor) + continue; + UwacOutputGetPosition(monitor, &pos); + UwacOutputGetResolution(monitor, &resolution); + + printf(" %s [%d] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, resolution.width, + resolution.height, pos.x, pos.y); + } + + return 0; +} diff --git a/client/Wayland/wlf_disp.h b/client/Wayland/wlf_disp.h new file mode 100644 index 0000000..36fa27c --- /dev/null +++ b/client/Wayland/wlf_disp.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FREERDP_CLIENT_WAYLAND_DISP_H +#define FREERDP_CLIENT_WAYLAND_DISP_H + +#include +#include + +#include "wlfreerdp.h" + +FREERDP_API BOOL wlf_disp_init(wlfDispContext* xfDisp, DispClientContext* disp); +FREERDP_API BOOL wlf_disp_uninit(wlfDispContext* xfDisp, DispClientContext* disp); + +wlfDispContext* wlf_disp_new(wlfContext* wlc); +void wlf_disp_free(wlfDispContext* disp); +BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height); +void wlf_disp_resized(wlfDispContext* disp); + +int wlf_list_monitors(wlfContext* wlc); + +#endif /* FREERDP_CLIENT_WAYLAND_DISP_H */ diff --git a/client/Wayland/wlf_input.c b/client/Wayland/wlf_input.c new file mode 100644 index 0000000..22041ea --- /dev/null +++ b/client/Wayland/wlf_input.c @@ -0,0 +1,401 @@ +/** + * 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 +#include + +#include "wlfreerdp.h" +#include "wlf_input.h" + +#define TAG CLIENT_TAG("wayland.input") + +#define MAX_CONTACTS 20 + +typedef struct touch_contact +{ + int id; + double pos_x; + double pos_y; + BOOL emulate_mouse; +} touchContact; + +static touchContact contacts[MAX_CONTACTS]; + +BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev) +{ + uint32_t x, y; + + if (!instance || !ev || !instance->input) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_input_send_mouse_event(instance->input, PTR_FLAGS_MOVE, x, y); +} + +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev) +{ + uint32_t x, y; + + if (!instance || !ev || !instance->input) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_input_send_mouse_event(instance->input, PTR_FLAGS_MOVE, x, y); +} + +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev) +{ + rdpInput* input; + UINT16 flags = 0; + UINT16 xflags = 0; + uint32_t x, y; + + if (!instance || !ev || !instance->input) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + input = instance->input; + + if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (ev->button) + { + case BTN_LEFT: + flags |= PTR_FLAGS_BUTTON1; + break; + + case BTN_RIGHT: + flags |= PTR_FLAGS_BUTTON2; + break; + + case BTN_MIDDLE: + flags |= PTR_FLAGS_BUTTON3; + break; + + case BTN_SIDE: + xflags |= PTR_XFLAGS_BUTTON1; + break; + + case BTN_EXTRA: + xflags |= PTR_XFLAGS_BUTTON2; + break; + + default: + return TRUE; + } + + if ((flags & ~PTR_FLAGS_DOWN) != 0) + return freerdp_input_send_mouse_event(input, flags, x, y); + + if ((xflags & ~PTR_XFLAGS_DOWN) != 0) + return freerdp_input_send_extended_mouse_event(input, xflags, x, y); + + return FALSE; +} + +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev) +{ + rdpInput* input; + UINT16 flags = 0; + int32_t direction; + uint32_t x, y; + uint32_t i; + + if (!instance || !ev || !instance->input) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + input = instance->input; + + direction = ev->value; + switch (ev->axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + flags |= PTR_FLAGS_WHEEL; + if (direction > 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + flags |= PTR_FLAGS_HWHEEL; + if (direction < 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + default: + return FALSE; + } + + /* Wheel rotation steps: + * + * positive: 0 ... 0xFF -> slow ... fast + * negative: 0 ... 0xFF -> fast ... slow + */ + for (i = 0; i < abs(direction); i++) + { + uint32_t cflags = flags | 0x78; + /* Convert negative values to 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + cflags = (flags & 0xFF00) | (0x100 - (cflags & 0xFF)); + if (!freerdp_input_send_mouse_event(input, cflags, (UINT16)x, (UINT16)y)) + return FALSE; + } + + return TRUE; +} + +BOOL wlf_handle_key(freerdp* instance, const 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, const UwacKeyboardEnterLeaveEvent* ev) +{ + if (!instance || !ev || !instance->input) + return FALSE; + + ((wlfContext*)instance->context)->focusing = TRUE; + return TRUE; +} + +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev) +{ + rdpInput* input; + uint32_t syncFlags; + + if (!instance || !ev || !instance->input) + return FALSE; + + input = instance->input; + syncFlags = 0; + + if (ev->modifiers & UWAC_MOD_CAPS_MASK) + syncFlags |= KBD_SYNC_CAPS_LOCK; + if (ev->modifiers & UWAC_MOD_NUM_MASK) + syncFlags |= KBD_SYNC_NUM_LOCK; + + if (!((wlfContext*)instance->context)->focusing) + return TRUE; + + ((wlfContext*)instance->context)->focusing = FALSE; + return freerdp_input_send_focus_in_event(input, syncFlags) && + freerdp_input_send_mouse_event(input, PTR_FLAGS_MOVE, 0, 0); +} + +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev) +{ + uint32_t x, y; + int i; + int touchId; + int contactId; + + if (!instance || !ev || !instance->context) + return FALSE; + + touchId = ev->id; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == touchId) + { + contacts[i].id = 0; + x = contacts[i].pos_x; + y = contacts[i].pos_y; + break; + } + } + + if (i == MAX_CONTACTS) + return FALSE; + + WLog_DBG(TAG, "%s called | event_id: %u | x: %u / y: %u", __FUNCTION__, touchId, x, y); + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + RdpeiClientContext* rdpei = ((wlfContext*)instance->context)->rdpei; + + if (contacts[i].emulate_mouse == TRUE) + { + UINT16 flags = 0; + flags |= PTR_FLAGS_BUTTON1; + + if ((flags & ~PTR_FLAGS_DOWN) != 0) + return freerdp_input_send_mouse_event(instance->input, flags, x, y); + + return TRUE; + } + + if (!rdpei) + return FALSE; + + rdpei->TouchEnd(rdpei, touchId, x, y, &contactId); + + return TRUE; +} + +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev) +{ + uint32_t x, y; + int i; + int touchId; + int contactId; + wlfContext* context; + + if (!instance || !ev || !instance->context) + return FALSE; + + x = ev->x; + y = ev->y; + touchId = ev->id; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == 0) + { + contacts[i].id = touchId; + contacts[i].pos_x = x; + contacts[i].pos_y = y; + contacts[i].emulate_mouse = FALSE; + break; + } + } + + if (i == MAX_CONTACTS) + return FALSE; + + WLog_DBG(TAG, "%s called | event_id: %u | x: %u / y: %u", __FUNCTION__, touchId, x, y); + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + context = (wlfContext*)instance->context; + RdpeiClientContext* rdpei = ((wlfContext*)instance->context)->rdpei; + + // Emulate mouse click if touch is not possible, like in login screen + if (!rdpei) + { + contacts[i].emulate_mouse = TRUE; + + UINT16 flags = 0; + flags |= PTR_FLAGS_DOWN; + flags |= PTR_FLAGS_BUTTON1; + + if ((flags & ~PTR_FLAGS_DOWN) != 0) + return freerdp_input_send_mouse_event(instance->input, flags, x, y); + + return FALSE; + } + + rdpei->TouchBegin(rdpei, touchId, x, y, &contactId); + + return TRUE; +} + +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev) +{ + uint32_t x, y; + int i; + int touchId; + int contactId; + + if (!instance || !ev || !instance->context) + return FALSE; + + x = ev->x; + y = ev->y; + touchId = ev->id; + + for (i = 0; i < MAX_CONTACTS; i++) + { + if (contacts[i].id == touchId) + { + if (contacts[i].pos_x == x && contacts[i].pos_y == y) + { + return TRUE; + } + contacts[i].pos_x = x; + contacts[i].pos_y = y; + break; + } + } + + if (i == MAX_CONTACTS) + return FALSE; + + WLog_DBG(TAG, "%s called | event_id: %u | x: %u / y: %u", __FUNCTION__, touchId, x, y); + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + RdpeiClientContext* rdpei = ((wlfContext*)instance->context)->rdpei; + + if (contacts[i].emulate_mouse == TRUE) + { + return TRUE; + } + + if (!rdpei) + return FALSE; + + rdpei->TouchUpdate(rdpei, touchId, x, y, &contactId); + + return TRUE; +} diff --git a/client/Wayland/wlf_input.h b/client/Wayland/wlf_input.h new file mode 100644 index 0000000..2382dd8 --- /dev/null +++ b/client/Wayland/wlf_input.h @@ -0,0 +1,41 @@ +/** + * 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, const UwacPointerEnterLeaveEvent* ev); +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev); +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev); +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev); +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev); +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev); +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev); + +BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev); +BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev); +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev); + +#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */ diff --git a/client/Wayland/wlf_pointer.c b/client/Wayland/wlf_pointer.c new file mode 100644 index 0000000..decde7f --- /dev/null +++ b/client/Wayland/wlf_pointer.c @@ -0,0 +1,170 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * 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 "wlf_pointer.h" +#include "wlfreerdp.h" + +#define TAG CLIENT_TAG("wayland.pointer") + +struct wlf_pointer +{ + rdpPointer pointer; + size_t size; + void* data; +}; +typedef struct wlf_pointer wlfPointer; + +static BOOL wlf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + + if (!ptr) + return FALSE; + + ptr->size = pointer->width * pointer->height * 4ULL; + ptr->data = _aligned_malloc(ptr->size, 16); + + if (!ptr->data) + return FALSE; + + if (!freerdp_image_copy_from_pointer_data( + ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + { + _aligned_free(ptr->data); + return FALSE; + } + + return TRUE; +} + +static void wlf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + WINPR_UNUSED(context); + + if (ptr) + _aligned_free(ptr->data); +} + +static BOOL wlf_Pointer_Set(rdpContext* context, const rdpPointer* pointer) +{ + wlfContext* wlf = (wlfContext*)context; + wlfPointer* ptr = (wlfPointer*)pointer; + void* data; + UINT32 w, h, x, y; + size_t size; + UwacReturnCode rc; + BOOL res = FALSE; + RECTANGLE_16 area; + + if (!wlf || !wlf->seat) + return FALSE; + + x = pointer->xPos; + y = pointer->yPos; + w = pointer->width; + h = pointer->height; + + if (!wlf_scale_coordinates(context, &x, &y, FALSE) || + !wlf_scale_coordinates(context, &w, &h, FALSE)) + return FALSE; + + size = w * h * 4ULL; + data = malloc(size); + + if (!data) + return FALSE; + + area.top = 0; + area.left = 0; + area.right = (UINT16)pointer->width; + area.bottom = (UINT16)pointer->height; + + if (!wlf_copy_image(ptr->data, pointer->width * 4, pointer->width, pointer->height, data, w * 4, + w, h, &area, context->settings->SmartSizing)) + goto fail; + + rc = UwacSeatSetMouseCursor(wlf->seat, data, size, w, h, x, y); + + if (rc == UWAC_SUCCESS) + res = TRUE; + +fail: + free(data); + return res; +} + +static BOOL wlf_Pointer_SetNull(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, NULL, 0, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetDefault(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, NULL, 1, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + // TODO + WLog_WARN(TAG, "%s not implemented", __FUNCTION__); + return TRUE; +} + +BOOL wlf_register_pointer(rdpGraphics* graphics) +{ + rdpPointer* pointer = NULL; + + if (!(pointer = (rdpPointer*)calloc(1, sizeof(rdpPointer)))) + return FALSE; + + pointer->size = sizeof(wlfPointer); + pointer->New = wlf_Pointer_New; + pointer->Free = wlf_Pointer_Free; + pointer->Set = wlf_Pointer_Set; + pointer->SetNull = wlf_Pointer_SetNull; + pointer->SetDefault = wlf_Pointer_SetDefault; + pointer->SetPosition = wlf_Pointer_SetPosition; + graphics_register_pointer(graphics, pointer); + free(pointer); + return TRUE; +} diff --git a/client/Wayland/wlf_pointer.h b/client/Wayland/wlf_pointer.h new file mode 100644 index 0000000..8ae82e1 --- /dev/null +++ b/client/Wayland/wlf_pointer.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2019 Armin Novak + * Copyright 2019 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_POINTER_H +#define FREERDP_CLIENT_WAYLAND_POINTER_H + +#include + +BOOL wlf_register_pointer(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_WAYLAND_POINTER_H */ diff --git a/client/Wayland/wlfreerdp.1.in b/client/Wayland/wlfreerdp.1.in new file mode 100644 index 0000000..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..c7a9fca --- /dev/null +++ b/client/Wayland/wlfreerdp.c @@ -0,0 +1,752 @@ +/** + * 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 + +#include "wlfreerdp.h" +#include "wlf_input.h" +#include "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlf_channels.h" +#include "wlf_pointer.h" + +#define TAG CLIENT_TAG("wayland") + +static BOOL wl_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_update_buffer(wlfContext* context_w, INT32 ix, INT32 iy, INT32 iw, INT32 ih) +{ + BOOL res = FALSE; + rdpGdi* gdi; + char* data; + UINT32 x, y, w, h; + UwacSize geometry; + size_t stride; + UwacReturnCode rc; + RECTANGLE_16 area; + + if (!context_w) + return FALSE; + + if ((ix < 0) || (iy < 0) || (iw < 0) || (ih < 0)) + return FALSE; + + EnterCriticalSection(&context_w->critical); + x = (UINT32)ix; + y = (UINT32)iy; + w = (UINT32)iw; + h = (UINT32)ih; + rc = UwacWindowGetDrawingBufferGeometry(context_w->window, &geometry, &stride); + data = UwacWindowGetDrawingBuffer(context_w->window); + + if (!data || (rc != UWAC_SUCCESS)) + goto fail; + + gdi = context_w->context.gdi; + + if (!gdi) + goto fail; + + /* Ignore output if the surface size does not match. */ + if (((INT64)x > geometry.width) || ((INT64)y > geometry.height)) + { + res = TRUE; + goto fail; + } + + area.left = x; + area.top = y; + area.right = x + w; + area.bottom = y + h; + + if (!wlf_copy_image(gdi->primary_buffer, gdi->stride, gdi->width, gdi->height, data, stride, + geometry.width, geometry.height, &area, + context_w->context.settings->SmartSizing)) + goto fail; + + if (!wlf_scale_coordinates(&context_w->context, &x, &y, FALSE)) + goto fail; + + if (!wlf_scale_coordinates(&context_w->context, &w, &h, FALSE)) + goto fail; + + if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS) + goto fail; + + if (UwacWindowSubmitBuffer(context_w->window, false) != UWAC_SUCCESS) + goto fail; + + res = TRUE; +fail: + LeaveCriticalSection(&context_w->critical); + return res; +} + +static BOOL wl_end_paint(rdpContext* context) +{ + rdpGdi* gdi; + wlfContext* context_w; + INT32 x, y; + INT32 w, h; + + 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; + return wl_update_buffer(context_w, x, y, w, h); +} + +static BOOL wl_refresh_display(wlfContext* context) +{ + rdpGdi* gdi; + + if (!context || !context->context.gdi) + return FALSE; + + gdi = context->context.gdi; + return wl_update_buffer(context, 0, 0, gdi->width, gdi->height); +} + +static BOOL wl_resize_display(rdpContext* context) +{ + wlfContext* wlc = (wlfContext*)context; + rdpGdi* gdi = context->gdi; + rdpSettings* settings = context->settings; + + if (!gdi_resize(gdi, settings->DesktopWidth, settings->DesktopHeight)) + return FALSE; + + return wl_refresh_display(wlc); +} + +static BOOL wl_pre_connect(freerdp* instance) +{ + rdpSettings* settings; + wlfContext* context; + const 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; + 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, 0); + + 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; + rdpSettings* settings; + char* title = "FreeRDP"; + char* app_id = "wlfreerdp"; + UINT32 w, h; + + if (!instance || !instance->context) + return FALSE; + + context = (wlfContext*)instance->context; + settings = instance->context->settings; + + if (settings->WindowTitle) + title = settings->WindowTitle; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + gdi = instance->context->gdi; + + if (!gdi || (gdi->width < 0) || (gdi->height < 0)) + return FALSE; + + if (!wlf_register_pointer(instance->context->graphics)) + return FALSE; + + w = (UINT32)gdi->width; + h = (UINT32)gdi->height; + + if (settings->SmartSizing && !context->fullscreen) + { + if (settings->SmartSizingWidth > 0) + w = settings->SmartSizingWidth; + + if (settings->SmartSizingHeight > 0) + h = settings->SmartSizingHeight; + } + + context->window = window = UwacCreateWindowShm(context->display, w, h, WL_SHM_FORMAT_XRGB8888); + + if (!window) + return FALSE; + + UwacWindowSetFullscreenState(window, NULL, instance->context->settings->Fullscreen); + UwacWindowSetTitle(window, title); + UwacWindowSetAppId(window, app_id); + UwacWindowSetOpaqueRegion(context->window, 0, 0, w, h); + instance->update->BeginPaint = wl_begin_paint; + instance->update->EndPaint = wl_end_paint; + instance->update->DesktopResize = wl_resize_display; + freerdp_keyboard_init_ex(instance->context->settings->KeyboardLayout, + instance->context->settings->KeyboardRemappingList); + + if (!(context->disp = wlf_disp_new(context))) + return FALSE; + + context->clipboard = wlf_clipboard_new(context); + + if (!context->clipboard) + return FALSE; + + return wl_refresh_display(context); +} + +static void wl_post_disconnect(freerdp* instance) +{ + wlfContext* context; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (wlfContext*)instance->context; + gdi_free(instance); + wlf_clipboard_free(context->clipboard); + wlf_disp_free(context->disp); + + if (context->window) + UwacDestroyWindow(&context->window); +} + +static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display) +{ + BOOL rc; + UwacEvent event; + wlfContext* context; + + if (UwacDisplayDispatch(display, 1) < 0) + return FALSE; + + context = (wlfContext*)instance->context; + + while (UwacHasEvent(display)) + { + if (UwacNextEvent(display, &event) != UWAC_SUCCESS) + return FALSE; + + /*printf("UWAC event type %d\n", event.type);*/ + switch (event.type) + { + case UWAC_EVENT_NEW_SEAT: + context->seat = event.seat_new.seat; + break; + + case UWAC_EVENT_REMOVED_SEAT: + context->seat = NULL; + break; + + case UWAC_EVENT_FRAME_DONE: + { + UwacReturnCode r; + EnterCriticalSection(&context->critical); + r = UwacWindowSubmitBuffer(context->window, false); + LeaveCriticalSection(&context->critical); + if (r != UWAC_SUCCESS) + return FALSE; + } + break; + + case UWAC_EVENT_POINTER_ENTER: + if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_MOTION: + if (!wlf_handle_pointer_motion(instance, &event.mouse_motion)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_BUTTONS: + if (!wlf_handle_pointer_buttons(instance, &event.mouse_button)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_AXIS: + break; + + case UWAC_EVENT_POINTER_AXIS_DISCRETE: + 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_TOUCH_UP: + if (!wlf_handle_touch_up(instance, &event.touchUp)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_DOWN: + if (!wlf_handle_touch_down(instance, &event.touchDown)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_MOTION: + if (!wlf_handle_touch_motion(instance, &event.touchMotion)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_ENTER: + if (instance->context->settings->GrabKeyboard) + UwacSeatInhibitShortcuts(event.keyboard_enter_leave.seat, true); + + if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_MODIFIERS: + if (!wlf_keyboard_modifiers(instance, &event.keyboard_modifiers)) + return FALSE; + + break; + + case UWAC_EVENT_CONFIGURE: + if (!wlf_disp_handle_configure(context->disp, event.configure.width, + event.configure.height)) + return FALSE; + + if (!wl_refresh_display(context)) + return FALSE; + + break; + + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + case UWAC_EVENT_CLIPBOARD_OFFER: + case UWAC_EVENT_CLIPBOARD_SELECT: + if (!wlf_cliprdr_handle_event(context->clipboard, &event.clipboard)) + return FALSE; + + break; + + case UWAC_EVENT_CLOSE: + context->closed = TRUE; + + break; + + default: + break; + } + } + + return TRUE; +} + +static BOOL handle_window_events(freerdp* instance) +{ + rdpSettings* settings; + + if (!instance || !instance->settings) + return FALSE; + + settings = instance->settings; + + if (!settings->AsyncInput) + { + } + + 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)) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to connect"); + 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) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + WLog_Print(context->log, WLOG_ERROR, "%s: WaitForMultipleObjects failed", __FUNCTION__); + break; + } + + if (!handle_uwac_events(instance, context->display)) + { + WLog_Print(context->log, WLOG_ERROR, "error handling UWAC events"); + break; + } + + if (context->closed) + { + WLog_Print(context->log, WLOG_INFO, "Closed from Wayland"); + break; + } + + if (freerdp_check_event_handles(instance->context) != TRUE) + { + if (client_auto_reconnect_ex(instance, handle_window_events)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + status = 42; + } + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_Print(context->log, WLOG_ERROR, "Failed to check FreeRDP file descriptor"); + + break; + } + } + + 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_Print(wlf->log, WLOG_INFO, "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->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->PresentGatewayMessage = client_cli_present_gateway_message; + instance->LogonErrorInfo = wlf_logon_error_info; + wfl->log = WLog_Get(TAG); + wfl->display = UwacOpenDisplay(NULL, &status); + + if (!wfl->display || (status != UWAC_SUCCESS) || !wfl->log) + return FALSE; + + wfl->displayHandle = CreateFileDescriptorEvent(NULL, FALSE, FALSE, + UwacDisplayGetFd(wfl->display), WINPR_FD_READ); + + if (!wfl->displayHandle) + return FALSE; + + InitializeCriticalSection(&wfl->critical); + + 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); + DeleteCriticalSection(&wlf->critical); +} + +static int wfl_client_start(rdpContext* context) +{ + WINPR_UNUSED(context); + return 0; +} + +static int wfl_client_stop(rdpContext* context) +{ + WINPR_UNUSED(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; + int status; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* context; + rdpSettings* settings; + wlfContext* wlc; + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + if (!context) + goto fail; + wlc = (wlfContext*)context; + settings = context->settings; + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE); + if (status) + { + BOOL list = settings->ListMonitors; + + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (list) + wlf_list_monitors(wlc); + + goto fail; + } + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = wlfreerdp_run(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} + +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale) +{ + BOOL rc = FALSE; + + if (!src || !dst || !area) + return FALSE; + + if (scale) + { + return freerdp_image_scale(dst, PIXEL_FORMAT_BGRA32, dstStride, 0, 0, dstWidth, dstHeight, + src, PIXEL_FORMAT_BGRA32, srcStride, 0, 0, srcWidth, srcHeight); + } + else + { + size_t i; + const size_t baseSrcOffset = area->top * srcStride + area->left * 4; + const size_t baseDstOffset = area->top * dstStride + area->left * 4; + const size_t width = MIN((size_t)area->right - area->left, dstWidth - area->left); + const size_t height = MIN((size_t)area->bottom - area->top, dstHeight - area->top); + const BYTE* psrc = (const BYTE*)src; + BYTE* pdst = (BYTE*)dst; + + for (i = 0; i < height; i++) + { + const size_t srcOffset = i * srcStride + baseSrcOffset; + const size_t dstOffset = i * dstStride + baseDstOffset; + memcpy(&pdst[dstOffset], &psrc[srcOffset], width * 4); + } + + rc = TRUE; + } + + return rc; +} + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP) +{ + wlfContext* wlf = (wlfContext*)context; + rdpGdi* gdi; + UwacSize geometry; + double sx, sy; + + if (!context || !px || !py || !context->gdi) + return FALSE; + + if (!context->settings->SmartSizing) + return TRUE; + + gdi = context->gdi; + + if (UwacWindowGetDrawingBufferGeometry(wlf->window, &geometry, NULL) != UWAC_SUCCESS) + return FALSE; + + sx = geometry.width / (double)gdi->width; + sy = geometry.height / (double)gdi->height; + + if (!fromLocalToRDP) + { + *px *= sx; + *py *= sy; + } + else + { + *px /= sx; + *py /= sy; + } + + return TRUE; +} diff --git a/client/Wayland/wlfreerdp.h b/client/Wayland/wlfreerdp.h new file mode 100644 index 0000000..d84f91c --- /dev/null +++ b/client/Wayland/wlfreerdp.h @@ -0,0 +1,63 @@ +/** + * 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 + +typedef struct wlf_context wlfContext; +typedef struct wlf_clipboard wfClipboard; +typedef struct _wlfDispContext wlfDispContext; + +struct wlf_context +{ + rdpContext context; + + UwacDisplay* display; + HANDLE displayHandle; + UwacWindow* window; + UwacSeat* seat; + + BOOL fullscreen; + BOOL closed; + BOOL focusing; + + /* Channels */ + RdpeiClientContext* rdpei; + RdpgfxClientContext* gfx; + EncomspClientContext* encomsp; + wfClipboard* clipboard; + wlfDispContext* disp; + wLog* log; + CRITICAL_SECTION critical; +}; + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP); +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale); + +#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */ diff --git a/client/Windows/CMakeLists.txt b/client/Windows/CMakeLists.txt new file mode 100644 index 0000000..6274571 --- /dev/null +++ b/client/Windows/CMakeLists.txt @@ -0,0 +1,98 @@ +# 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) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} msimg32) +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..e325f84 --- /dev/null +++ b/client/Windows/cli/wfreerdp.c @@ -0,0 +1,144 @@ +/** + * 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; + LPWSTR cmd; + char** argv = NULL; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 }; + int ret = 1; + int argc = 0, i; + LPWSTR* args = NULL; + + WINPR_UNUSED(hInstance); + WINPR_UNUSED(hPrevInstance); + WINPR_UNUSED(lpCmdLine); + WINPR_UNUSED(nCmdShow); + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + return -1; + + cmd = GetCommandLineW(); + + if (!cmd) + goto out; + + args = CommandLineToArgvW(cmd, &argc); + + if (!args || (argc <= 0)) + goto out; + + argv = calloc((size_t)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); + if (size <= 0) + goto out; + argv[i] = calloc((size_t)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) + { + ret = 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 = (int)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..35991fc --- /dev/null +++ b/client/Windows/resource.h @@ -0,0 +1,12 @@ + +#define IDI_ICON1 101 +#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/close.bmp b/client/Windows/resource/close.bmp new file mode 100644 index 0000000..bb17b28 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..a59b2e3 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..0442c02 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..2e37e36 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..485e170 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..e16b252 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..26117b0 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..c2479ba 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..fc1b0d3 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..5a7f007 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..3afd52d --- /dev/null +++ b/client/Windows/wf_channels.c @@ -0,0 +1,85 @@ +/** + * 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..5e4ca52 --- /dev/null +++ b/client/Windows/wf_channels.h @@ -0,0 +1,34 @@ +/** + * 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..5ee0594 --- /dev/null +++ b/client/Windows/wf_client.c @@ -0,0 +1,1164 @@ +/** + * 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 BOOL wf_create_console(void) +{ +#if defined(WITH_WIN_CONSOLE) + if (!AttachConsole(ATTACH_PARENT_PROCESS)) + return FALSE; + + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + clearerr(stdout); + clearerr(stderr); + fflush(stdout); + fflush(stderr); + + freopen("CONIN$", "r", stdin); + clearerr(stdin); + + WLog_INFO(TAG, "Debug console created."); + + return TRUE; +#else + return FALSE; +#endif +} + +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; + + if (wfc->xScrollVisible) + { + updateRect.left -= MIN(updateRect.left, wfc->xCurrentScroll); + updateRect.right -= MIN(updateRect.right, wfc->xCurrentScroll); + } + if (wfc->yScrollVisible) + { + updateRect.top -= MIN(updateRect.top, wfc->yCurrentScroll); + updateRect.bottom -= MIN(updateRect.bottom, wfc->yCurrentScroll); + } + + 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) +{ + UINT32 rc; + 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; + wfc->fullscreen = settings->Fullscreen; + wfc->fullscreen_toggle = settings->ToggleFullscreen; + 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; + + rc = freerdp_keyboard_init(freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout)); + freerdp_set_param_uint32(settings, FreeRDP_KeyboardLayout, rc); + 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; + MENUITEMINFO item_info; + + if (wfc->fullscreen && !wfc->fullscreen_toggle) + { + return; + } + + hMenu = GetSystemMenu(wfc->hwnd, FALSE); + 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 WCHAR* wf_window_get_title(rdpSettings* settings) +{ + BOOL port; + WCHAR* windowTitle = NULL; + size_t size; + char* name; + WCHAR prefix[] = L"FreeRDP:"; + + if (!settings) + return NULL; + + name = settings->ServerHostname; + + if (settings->WindowTitle) + { + ConvertToUnicode(CP_UTF8, 0, settings->WindowTitle, -1, &windowTitle, 0); + return windowTitle; + } + + port = (settings->ServerPort != 3389); + size = strlen(name) + 16 + wcslen(prefix); + windowTitle = calloc(size, sizeof(WCHAR)); + + if (!windowTitle) + return NULL; + + if (!port) + _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S", prefix, name); + else + _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S:%u", prefix, name, settings->ServerPort); + + return windowTitle; +} + +static BOOL wf_post_connect(freerdp* instance) +{ + rdpGdi* gdi; + DWORD dwStyle; + rdpCache* cache; + wfContext* wfc; + rdpContext* context; + 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); + } + + wfc->window_title = wf_window_get_title(settings); + + if (!wfc->window_title) + return FALSE; + + 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, wfc->window_title, 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); + } + + wfc->floatbar = wf_floatbar_new(wfc, wfc->hInstance, settings->Floatbar); + return TRUE; +} + +static void wf_post_disconnect(freerdp* instance) +{ + wfContext* wfc; + + if (!instance || !instance->context || !instance->settings) + return; + + wfc = (wfContext*)instance->context; + free(wfc->window_title); +} + +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) +{ + wfContext* wfc; + BOOL fSave; + DWORD status; + DWORD dwFlags; + char UserName[CREDUI_MAX_USERNAME_LENGTH + 1] = { 0 }; + char Password[CREDUI_MAX_PASSWORD_LENGTH + 1] = { 0 }; + char User[CREDUI_MAX_USERNAME_LENGTH + 1] = { 0 }; + char Domain[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = { 0 }; + + if (!instance || !instance->context) + return FALSE; + wfc = (wfContext*)instance->context; + + fSave = FALSE; + dwFlags = CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_EXCLUDE_CERTIFICATES; + + if (username && *username) + strncpy(UserName, *username, CREDUI_MAX_USERNAME_LENGTH); + if (wfc->isConsole) + status = CredUICmdLinePromptForCredentialsA( + title, NULL, 0, UserName, CREDUI_MAX_USERNAME_LENGTH + 1, Password, + CREDUI_MAX_PASSWORD_LENGTH + 1, &fSave, dwFlags); + else + 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; + } + + 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 WCHAR* wf_format_text(const WCHAR* fmt, ...) +{ + int rc; + size_t size = 1024; + WCHAR* buffer = calloc(size, sizeof(WCHAR)); + if (!buffer) + return NULL; + + do + { + WCHAR* tmp; + va_list ap; + va_start(ap, fmt); + rc = vswprintf_s(buffer, size, fmt, ap); + va_end(ap); + if (rc <= 0) + goto fail; + + if ((size_t)rc < size) + return buffer; + + size = (size_t)rc + 1; + tmp = realloc(buffer, size * sizeof(WCHAR)); + if (!tmp) + goto fail; + + buffer = tmp; + } while (TRUE); + +fail: + free(buffer); + return NULL; +} + +static DWORD wf_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, DWORD flags) +{ + WCHAR* buffer; + WCHAR* caption; + int what = IDCANCEL; + + buffer = wf_format_text( + L"Certificate details:\n" + L"\tCommonName: %S\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S\n" + L"\tHostMismatch: %S\n" + L"\n" + L"The above X.509 certificate could not be verified, possibly because you do not have " + L"the CA certificate in your certificate store, or the certificate has expired. " + L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n" + L"\n" + L"YES\tAccept permanently\n" + L"NO\tAccept for this session only\n" + L"CANCEL\tAbort connection\n", + common_name, subject, issuer, fingerprint, + flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No"); + caption = wf_format_text(L"Verify certificate for %S:%hu", host, port); + + if (!buffer || !caption) + goto fail; + + what = MessageBoxW(NULL, buffer, caption, MB_YESNOCANCEL); +fail: + free(buffer); + free(caption); + + /* return 1 to accept and store a certificate, 2 to accept + * a certificate only for this session, 0 otherwise */ + switch (what) + { + case IDYES: + return 1; + case IDNO: + return 2; + default: + return 0; + } +} + +static DWORD wf_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* new_fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint, DWORD flags) +{ + WCHAR* buffer; + WCHAR* caption; + int what = IDCANCEL; + + buffer = wf_format_text( + L"New Certificate details:\n" + L"\tCommonName: %S\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S\n" + L"\tHostMismatch: %S\n" + L"\n" + L"Old Certificate details:\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S" + L"The above X.509 certificate could not be verified, possibly because you do not have " + L"the CA certificate in your certificate store, or the certificate has expired. " + L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n" + L"\n" + L"YES\tAccept permanently\n" + L"NO\tAccept for this session only\n" + L"CANCEL\tAbort connection\n", + common_name, subject, issuer, new_fingerprint, + flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No", old_subject, old_issuer, old_fingerprint); + caption = wf_format_text(L"Verify certificate change for %S:%hu", host, port); + + if (!buffer || !caption) + goto fail; + + what = MessageBoxW(NULL, buffer, caption, MB_YESNOCANCEL); +fail: + free(buffer); + free(caption); + + /* return 1 to accept and store a certificate, 2 to accept + * a certificate only for this session, 0 otherwise */ + switch (what) + { + case IDYES: + return 1; + case IDNO: + return 2; + default: + return 0; + } +} + +static BOOL wf_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, const WCHAR* message) +{ + if (!isDisplayMandatory && !isConsentMandatory) + return TRUE; + + /* special handling for consent messages (show modal dialog) */ + if (type == GATEWAY_MESSAGE_CONSENT && isConsentMandatory) + { + int mbRes; + WCHAR* msg; + + msg = wf_format_text(L"%.*s\n\nI understand and agree to the terms of this policy", length, + message); + mbRes = MessageBoxW(NULL, msg, L"Consent Message", MB_YESNO); + free(msg); + + if (mbRes != IDYES) + return FALSE; + } + else + return client_cli_present_gateway_message(instance, type, isDisplayMandatory, + isConsentMandatory, length, message); + + return TRUE; +} + +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; +} + +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; + + WSAStartup(0x101, &wsaData); + + 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) +{ + wfContext* wfc = (wfContext*)context; + if (!wfc) + return FALSE; + + // AttachConsole and stdin do not work well. + // Use GUI input dialogs instead of command line ones. + wfc->isConsole = wf_create_console(); + + 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; + if (wfc->isConsole) + { + instance->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->PresentGatewayMessage = client_cli_present_gateway_message; + } + else + { + instance->VerifyCertificateEx = wf_verify_certificate_ex; + instance->VerifyChangedCertificateEx = wf_verify_changed_certificate_ex; + instance->PresentGatewayMessage = wf_present_gateway_message; + } + + 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..29f194a --- /dev/null +++ b/client/Windows/wf_client.h @@ -0,0 +1,151 @@ +/** + * 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 fullscreen_toggle; + int fullscreen; + int percentscreen; + WCHAR* window_title; + 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; + + wfFloatBar* floatbar; + + RailClientContext* rail; + wHashTable* railWindows; + BOOL isConsole; + }; + + /** + * 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..bc4bde6 --- /dev/null +++ b/client/Windows/wf_cliprdr.c @@ -0,0 +1,2552 @@ +/** + * 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; + ULONG 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; + ULONG m_nNumFormats; + ULONG 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, const void* streamid, + ULONG index, UINT32 flag, DWORD positionhigh, + DWORD positionlow, ULONG request); + +static void CliprdrDataObject_Delete(CliprdrDataObject* instance); + +static CliprdrEnumFORMATETC* CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC* pFormatEtc); +static void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC* instance); + +static void CliprdrStream_Delete(CliprdrStream* instance); + +static BOOL try_open_clipboard(HWND hwnd) +{ + size_t x; + for (x = 0; x < 10; x++) + { + if (OpenClipboard(hwnd)) + return TRUE; + Sleep(10); + } + return FALSE; +} + +/** + * 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(ULONG 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 LONG cliprdr_lookup_format(CliprdrDataObject* instance, FORMATETC* pFormatEtc) +{ + ULONG 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 (LONG)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) +{ + ULONG i; + LONG 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, ULONG 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) + { + ULONG 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(ULONG nFormats, FORMATETC* pFormatEtc) +{ + ULONG 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 = 1; + 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); +} + +static 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 = try_open_clipboard(clipboard->hwnd); + + if (!clipboardOpen) + { + *pcFormatsOut = 0; + return TRUE; /* Other app holding clipboard */ + } + + 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 = 0; + UINT32 index; + UINT32 numFormats = 0; + UINT32 formatId = 0; + char formatName[1024]; + CLIPRDR_FORMAT* formats = NULL; + CLIPRDR_FORMAT_LIST formatList = { 0 }; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + ZeroMemory(&formatList, sizeof(CLIPRDR_FORMAT_LIST)); + + /* Ignore if other app is holding clipboard */ + if (try_open_clipboard(clipboard->hwnd)) + { + 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; + formatList.msgType = CB_FORMAT_LIST; + 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; +} + +UINT cliprdr_send_request_filecontents(wfClipboard* clipboard, const void* streamid, ULONG index, + UINT32 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 (!try_open_clipboard(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 (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj)) + { + 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) + { + OleUninitialize(); + 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, + const 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, + const 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, + const 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 (!try_open_clipboard(clipboard->hwnd)) + return CHANNEL_RC_OK; /* Ignore, other app holding clipboard */ + + 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, + const 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, + const 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, + const 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, + const 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 + { + /* Ignore if other app is holding the clipboard */ + if (try_open_clipboard(clipboard->hwnd)) + { + 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, + const 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, + const 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; + UINT32 cbRequested; + + if (!context || !fileContentsRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard*)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + cbRequested = fileContentsRequest->cbRequested; + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + cbRequested = sizeof(UINT64); + + pData = (BYTE*)calloc(1, 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 = 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, cbRequested, (PULONG)&uSize); + } + } + else + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + *((UINT32*)&pData[0]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeLow; + *((UINT32*)&pData[4]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeHigh; + uSize = cbRequested; + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + BOOL bRet; + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + bRet = wf_cliprdr_get_file_contents( + clipboard->file_names[fileContentsRequest->listIndex], pData, + fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, 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, NULL))) + goto error; + + if (!(clipboard->req_fevent = CreateEvent(NULL, TRUE, FALSE, NULL))) + 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->hwnd) + PostMessage(clipboard->hwnd, WM_QUIT, 0, 0); + + if (clipboard->thread) + { + WaitForSingleObject(clipboard->thread, INFINITE); + CloseHandle(clipboard->thread); + } + + if (clipboard->response_data_event) + CloseHandle(clipboard->response_data_event); + + if (clipboard->req_fevent) + CloseHandle(clipboard->req_fevent); + + 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..d1ded4c --- /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->fullscreen_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; + /* 9bit twos complement, delta already negative */ + delta = 0x100 + 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); + break; + + // 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..f879f87 --- /dev/null +++ b/client/Windows/wf_event.h @@ -0,0 +1,43 @@ +/** + * 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..512c66b --- /dev/null +++ b/client/Windows/wf_floatbar.c @@ -0,0 +1,745 @@ +/** + * 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" +#pragma comment(lib, "Msimg32.lib") + +#define TAG CLIENT_TAG("windows.floatbar") + +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 576 +#define BACKGROUND_H 27 +#define BUTTON_OFFSET 5 +#define BUTTON_Y 2 +#define BUTTON_WIDTH 23 +#define BUTTON_HEIGHT 21 +#define BUTTON_SPACING 1 + +#define LOCK_X (BACKGROUND_H + BUTTON_OFFSET) +#define CLOSE_X ((BACKGROUND_W - (BACKGROUND_H + BUTTON_OFFSET)) - BUTTON_WIDTH) +#define RESTORE_X (CLOSE_X - (BUTTON_WIDTH + BUTTON_SPACING)) +#define MINIMIZE_X (RESTORE_X - (BUTTON_WIDTH + BUTTON_SPACING)) +#define TEXT_X (BACKGROUND_H + ((BUTTON_WIDTH + BUTTON_SPACING) * 3) + 5) + +struct _Button +{ + wfFloatBar* 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 +{ + HINSTANCE root_window; + DWORD flags; + HWND parent; + HWND hwnd; + RECT rect; + LONG width; + LONG height; + LONG offset; + wfContext* wfc; + Button* buttons[BTN_MAX]; + BOOL shown; + BOOL locked; + HDC hdcmem; + RECT textRect; + UINT_PTR animating; +}; + +static BOOL floatbar_kill_timers(wfFloatBar* floatbar) +{ + size_t x; + UINT_PTR timers[] = { TIMER_HIDE, TIMER_ANIMAT_HIDE, TIMER_ANIMAT_SHOW }; + + if (!floatbar) + return FALSE; + + for (x = 0; x < ARRAYSIZE(timers); x++) + KillTimer(floatbar->hwnd, timers[x]); + + floatbar->animating = 0; + return TRUE; +} + +static BOOL floatbar_animation(wfFloatBar* const floatbar, const BOOL show) +{ + UINT_PTR timer = show ? TIMER_ANIMAT_SHOW : TIMER_ANIMAT_HIDE; + + if (!floatbar) + return FALSE; + + if (floatbar->shown == show) + return TRUE; + + if (floatbar->animating == timer) + return TRUE; + + floatbar->animating = timer; + + if (SetTimer(floatbar->hwnd, timer, USER_TIMER_MINIMUM, NULL) == NULL) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "SetTimer failed with %08" PRIx32, err); + return FALSE; + } + + return TRUE; +} + +static BOOL floatbar_trigger_hide(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + if (!floatbar->locked && floatbar->shown) + { + if (SetTimer(floatbar->hwnd, TIMER_HIDE, 3000, NULL) == NULL) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "SetTimer failed with %08" PRIx32, err); + return FALSE; + } + } + + return TRUE; +} + +static BOOL floatbar_hide(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + floatbar->offset = floatbar->height - 2; + + if (!MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, floatbar->width, + floatbar->height, TRUE)) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "MoveWindow failed with %08" PRIx32, err); + return FALSE; + } + + floatbar->shown = FALSE; + + if (!floatbar_trigger_hide(floatbar)) + return FALSE; + + return TRUE; +} + +static BOOL floatbar_show(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + floatbar->offset = 0; + + if (!MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, floatbar->width, + floatbar->height, TRUE)) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "MoveWindow failed with %08" PRIx32, err); + return FALSE; + } + + floatbar->shown = TRUE; + + if (!floatbar_trigger_hide(floatbar)) + return FALSE; + + return TRUE; +} + +static BOOL button_set_locked(Button* button, BOOL locked) +{ + if (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; + } + + InvalidateRect(button->floatbar->hwnd, NULL, FALSE); + UpdateWindow(button->floatbar->hwnd); + return TRUE; +} + +static BOOL update_locked_state(wfFloatBar* floatbar) +{ + Button* button; + + if (!floatbar) + return FALSE; + + button = floatbar->buttons[3]; + + if (!button_set_locked(button, floatbar->locked)) + return FALSE; + + return TRUE; +} + +static int button_hit(Button* const button) +{ + wfFloatBar* const floatbar = button->floatbar; + + switch (button->type) + { + case BUTTON_LOCKPIN: + floatbar->locked = !floatbar->locked; + update_locked_state(floatbar); + 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(const Button* const button, const HDC hdc) +{ + if (button != NULL) + { + wfFloatBar* floatbar = button->floatbar; + BLENDFUNCTION bf; + SelectObject(floatbar->hdcmem, button->active ? button->bmp_act : button->bmp); + bf.BlendOp = AC_SRC_OVER; + bf.BlendFlags = 0; + bf.SourceConstantAlpha = 255; + bf.AlphaFormat = AC_SRC_ALPHA; + AlphaBlend(hdc, button->x, button->y, button->w, button->h, floatbar->hdcmem, 0, 0, + button->w, button->h, bf); + } + + return 0; +} + +static Button* floatbar_create_button(wfFloatBar* const floatbar, const int type, const int resid, + const int resid_act, const int x, const int y, const int h, + const int w) +{ + Button* button = (Button*)calloc(1, 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->root_window, MAKEINTRESOURCE(resid), IMAGE_BITMAP, 0, + 0, LR_DEFAULTCOLOR); + button->bmp_act = (HBITMAP)LoadImage(floatbar->root_window, MAKEINTRESOURCE(resid_act), + IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_create_lock_button(wfFloatBar* const floatbar, const int unlock_resid, + const int unlock_resid_act, const int lock_resid, + const int lock_resid_act, const int x, const int y, + const int h, const int w) +{ + 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, 0, 0, LR_DEFAULTCOLOR); + button->locked_bmp_act = + (HBITMAP)LoadImage(floatbar->wfc->hInstance, MAKEINTRESOURCE(lock_resid_act), IMAGE_BITMAP, + 0, 0, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_get_button(const wfFloatBar* const floatbar, const int x, const int y) +{ + int i; + + if ((y > BUTTON_Y) && (y < BUTTON_Y + BUTTON_HEIGHT)) + { + for (i = 0; i < BTN_MAX; i++) + { + if ((floatbar->buttons[i] != NULL) && (x > floatbar->buttons[i]->x) && + (x < floatbar->buttons[i]->x + floatbar->buttons[i]->w)) + { + return floatbar->buttons[i]; + } + } + } + + return NULL; +} + +static BOOL floatbar_paint(wfFloatBar* const floatbar, const HDC hdc) +{ + int i; + HPEN hpen; + HGDIOBJECT orig; + /* paint background */ + GRADIENT_RECT gradientRect = { 0, 1 }; + COLORREF rgbTop = RGB(117, 154, 198); + COLORREF rgbBottom = RGB(6, 55, 120); + const int top = 0; + int left = 0; + int bottom = BACKGROUND_H - 1; + int right = BACKGROUND_W - 1; + const int angleOffset = BACKGROUND_H - 1; + TRIVERTEX triVertext[2] = { left, + top, + GetRValue(rgbTop) << 8, + GetGValue(rgbTop) << 8, + GetBValue(rgbTop) << 8, + 0x0000, + right, + bottom, + GetRValue(rgbBottom) << 8, + GetGValue(rgbBottom) << 8, + GetBValue(rgbBottom) << 8, + 0x0000 }; + + if (!floatbar) + return FALSE; + + GradientFill(hdc, triVertext, 2, &gradientRect, 1, GRADIENT_FILL_RECT_V); + /* paint shadow */ + hpen = CreatePen(PS_SOLID, 1, RGB(71, 71, 71)); + orig = SelectObject(hdc, hpen); + MoveToEx(hdc, left, top, NULL); + LineTo(hdc, left + angleOffset, bottom); + LineTo(hdc, right - angleOffset, bottom); + LineTo(hdc, right + 1, top - 1); + DeleteObject(hpen); + hpen = CreatePen(PS_SOLID, 1, RGB(107, 141, 184)); + SelectObject(hdc, hpen); + left += 1; + bottom -= 1; + right -= 1; + MoveToEx(hdc, left, top, NULL); + LineTo(hdc, left + (angleOffset - 1), bottom); + LineTo(hdc, right - (angleOffset - 1), bottom); + LineTo(hdc, right + 1, top - 1); + DeleteObject(hpen); + SelectObject(hdc, orig); + DrawText(hdc, floatbar->wfc->window_title, wcslen(floatbar->wfc->window_title), + &floatbar->textRect, + DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); + + /* paint buttons */ + + for (i = 0; i < BTN_MAX; i++) + button_paint(floatbar->buttons[i], hdc); + + return TRUE; +} + +static LRESULT CALLBACK floatbar_proc(const HWND hWnd, const UINT Msg, const WPARAM wParam, + const LPARAM lParam) +{ + static int dragging = FALSE; + static int lbtn_dwn = FALSE; + static int btn_dwn_x = 0; + static wfFloatBar* floatbar; + static TRACKMOUSEEVENT tme; + PAINTSTRUCT ps; + Button* button; + HDC hdc; + int pos_x; + int pos_y; + NONCLIENTMETRICS ncm; + int xScreen = GetSystemMetrics(SM_CXSCREEN); + + switch (Msg) + { + case WM_CREATE: + floatbar = ((wfFloatBar*)((CREATESTRUCT*)lParam)->lpCreateParams); + floatbar->hwnd = 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; + // Use caption font, white, draw transparent + GetClientRect(hWnd, &floatbar->textRect); + InflateRect(&floatbar->textRect, -TEXT_X, 0); + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(255, 255, 255)); + ncm.cbSize = sizeof(NONCLIENTMETRICS); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0); + SelectObject(hdc, CreateFontIndirect(&ncm.lfCaptionFont)); + floatbar_trigger_hide(floatbar); + 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: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + + if (!floatbar->locked) + 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, 0, floatbar->width, floatbar->height, TRUE); + } + else + { + int i; + + for (i = 0; i < BTN_MAX; i++) + { + if (floatbar->buttons[i] != NULL) + { + 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++) + { + if (floatbar->buttons[i] != NULL) + { + floatbar->buttons[i]->active = FALSE; + } + } + + InvalidateRect(hWnd, NULL, FALSE); + UpdateWindow(hWnd); + floatbar_trigger_hide(floatbar); + break; + } + + case WM_TIMER: + switch (wParam) + { + case TIMER_HIDE: + floatbar_animation(floatbar, FALSE); + break; + + case TIMER_ANIMAT_SHOW: + { + floatbar->offset--; + MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, + floatbar->width, floatbar->height, TRUE); + + if (floatbar->offset <= 0) + floatbar_show(floatbar); + + break; + } + + case TIMER_ANIMAT_HIDE: + { + floatbar->offset++; + MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, + floatbar->width, floatbar->height, TRUE); + + if (floatbar->offset >= floatbar->height - 2) + floatbar_hide(floatbar); + + break; + } + + default: + break; + } + + break; + + case WM_DESTROY: + DeleteDC(floatbar->hdcmem); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static BOOL floatbar_window_create(wfFloatBar* floatbar) +{ + WNDCLASSEX wnd_cls; + HWND barWnd; + HRGN hRgn; + POINT pt[4]; + RECT rect; + LONG x; + + if (!floatbar) + return FALSE; + + if (!GetWindowRect(floatbar->parent, &rect)) + return FALSE; + + x = (rect.right - rect.left - 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(floatbar->root_window, IDC_ARROW); + wnd_cls.hbrBackground = NULL; + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = L"floatbar"; + wnd_cls.hInstance = floatbar->root_window; + wnd_cls.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + barWnd = CreateWindowEx(WS_EX_TOPMOST, L"floatbar", L"floatbar", WS_CHILD, x, 0, BACKGROUND_W, + BACKGROUND_H, floatbar->parent, NULL, floatbar->root_window, floatbar); + + if (barWnd == NULL) + return FALSE; + + pt[0].x = 0; + pt[0].y = 0; + pt[1].x = BACKGROUND_W; + pt[1].y = 0; + pt[2].x = BACKGROUND_W - BACKGROUND_H; + pt[2].y = BACKGROUND_H; + pt[3].x = BACKGROUND_H; + pt[3].y = BACKGROUND_H; + hRgn = CreatePolygonRgn(pt, 4, ALTERNATE); + SetWindowRgn(barWnd, hRgn, TRUE); + return TRUE; +} + +void wf_floatbar_free(wfFloatBar* floatbar) +{ + if (!floatbar) + return; + + free(floatbar); +} + +wfFloatBar* wf_floatbar_new(wfContext* wfc, HINSTANCE window, DWORD flags) +{ + wfFloatBar* floatbar; + + /* Floatbar not enabled */ + if ((flags & 0x0001) == 0) + return NULL; + + if (!wfc) + return NULL; + + // TODO: Disable for remote app + floatbar = (wfFloatBar*)calloc(1, sizeof(wfFloatBar)); + + if (!floatbar) + return NULL; + + floatbar->root_window = window; + floatbar->flags = flags; + floatbar->wfc = wfc; + floatbar->locked = (flags & 0x0002) != 0; + floatbar->shown = (flags & 0x0006) != 0; /* If it is loked or shown show it */ + floatbar->hwnd = NULL; + floatbar->parent = wfc->hwnd; + floatbar->hdcmem = NULL; + + if (wfc->fullscreen_toggle) + { + 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); + } + else + { + floatbar->buttons[0] = NULL; + floatbar->buttons[1] = NULL; + } + + 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); + + if (!floatbar_window_create(floatbar)) + goto fail; + + if (!update_locked_state(floatbar)) + goto fail; + + if (!wf_floatbar_toggle_fullscreen(floatbar, wfc->context.settings->Fullscreen)) + goto fail; + + return floatbar; +fail: + wf_floatbar_free(floatbar); + return NULL; +} + +BOOL wf_floatbar_toggle_fullscreen(wfFloatBar* floatbar, BOOL fullscreen) +{ + BOOL show_fs, show_wn; + + if (!floatbar) + return FALSE; + + show_fs = (floatbar->flags & 0x0010) != 0; + show_wn = (floatbar->flags & 0x0020) != 0; + + if ((show_fs && fullscreen) || (show_wn && !fullscreen)) + { + ShowWindow(floatbar->hwnd, SW_SHOWNORMAL); + Sleep(10); + + if (floatbar->shown) + floatbar_show(floatbar); + else + floatbar_hide(floatbar); + } + else + { + ShowWindow(floatbar->hwnd, SW_HIDE); + } + + return TRUE; +} diff --git a/client/Windows/wf_floatbar.h b/client/Windows/wf_floatbar.h new file mode 100644 index 0000000..2636aba --- /dev/null +++ b/client/Windows/wf_floatbar.h @@ -0,0 +1,33 @@ +/** + * 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 + +#include + +typedef struct _FloatBar wfFloatBar; +typedef struct wf_context wfContext; + +wfFloatBar* wf_floatbar_new(wfContext* wfc, HINSTANCE window, DWORD flags); +void wf_floatbar_free(wfFloatBar* floatbar); + +BOOL wf_floatbar_toggle_fullscreen(wfFloatBar* floatbar, BOOL fullscreen); + +#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..329f43a --- /dev/null +++ b/client/Windows/wf_gdi.c @@ -0,0 +1,827 @@ +/** + * 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) + { + if (!wfc->client_height) + wfc->client_height = settings->DesktopHeight; + + if (!wfc->client_width) + wfc->client_width = settings->DesktopWidth; + + 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*/); + } + 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; + } + + wf_floatbar_toggle_fullscreen(wfc->floatbar, wfc->fullscreen); + 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..a093e1a --- /dev/null +++ b/client/Windows/wf_gdi.h @@ -0,0 +1,38 @@ +/** + * 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..8a146f3 --- /dev/null +++ b/client/Windows/wf_graphics.c @@ -0,0 +1,370 @@ +/** + * 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, + gdi->dstFormat, 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..241575f --- /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..85fbc83 --- /dev/null +++ b/client/Windows/wf_rail.c @@ -0,0 +1,1073 @@ +/** + * 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)) + +struct wf_rail_window +{ + wfContext* wfc; + + HWND hWnd; + + DWORD dwStyle; + DWORD dwExStyle; + + int x; + int y; + int width; + int height; + char* title; +}; + +/* 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 } +}; + +static 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); + } + } +} + +static 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); + } + } +} + +static void PrintRailWindowState(const WINDOW_ORDER_INFO* orderInfo, + const 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(const WINDOW_ORDER_INFO* orderInfo, const 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, const WINDOW_ORDER_INFO* orderInfo, + const 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 */ + ); + + if (!railWindow->hWnd) + { + free(titleW); + free(railWindow->title); + free(railWindow); + WLog_ERR(TAG, "CreateWindowExW failed with error %" PRIu32 "", GetLastError()); + return FALSE; + } + + 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, const 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, const WINDOW_ORDER_INFO* orderInfo, + const 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, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + WLog_DBG(TAG, "RailWindowCachedIcon"); + return TRUE; +} + +static void wf_rail_notify_icon_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_ICON) + { + const ICON_INFO* iconInfo = &(notifyIconState->icon); + PrintRailIconInfo(orderInfo, iconInfo); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) + { + } +} + +static BOOL wf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const 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, const WINDOW_ORDER_INFO* orderInfo, + const 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, const 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, const WINDOW_ORDER_INFO* orderInfo, + const 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, const 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, + const 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, + const RAIL_SYSPARAM_ORDER* sysparam) +{ + return CHANNEL_RC_OK; +} + +static UINT wf_rail_server_start_cmd(RailClientContext* context) +{ + UINT status; + RAIL_EXEC_ORDER exec = { 0 }; + RAIL_SYSPARAM_ORDER sysparam = { 0 }; + RAIL_CLIENT_STATUS_ORDER clientStatus = { 0 }; + wfContext* wfc = (wfContext*)context->custom; + rdpSettings* settings = wfc->context.settings; + clientStatus.flags = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; + + if (settings->AutoReconnectionEnabled) + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT; + + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED; + status = context->ClientInformation(context, &clientStatus); + + if (status != CHANNEL_RC_OK) + return status; + + if (settings->RemoteAppLanguageBarSupported) + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ + status = context->ClientLanguageBarInfo(context, &langBarInfo); + + /* We want the language bar, but the server might not support it. */ + switch (status) + { + case CHANNEL_RC_OK: + case ERROR_BAD_CONFIGURATION: + break; + default: + return status; + } + } + + 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; + status = context->ClientSystemParam(context, &sysparam); + + if (status != CHANNEL_RC_OK) + return status; + + exec.RemoteApplicationProgram = settings->RemoteApplicationProgram; + exec.RemoteApplicationWorkingDir = settings->ShellWorkingDirectory; + exec.RemoteApplicationArguments = settings->RemoteApplicationCmdLine; + return context->ClientExecute(context, &exec); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake(RailClientContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + return wf_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake_ex(RailClientContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + return wf_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_local_move_size(RailClientContext* context, + const 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, + const 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, + const 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, + const 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..2b73821 --- /dev/null +++ b/client/Windows/wf_rail.h @@ -0,0 +1,33 @@ +/** + * 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 + +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..e5bfc9c 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..869652c --- /dev/null +++ b/client/X11/CMakeLists.txt @@ -0,0 +1,249 @@ +# 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_input.c + xf_input.h + xf_event.c + xf_event.h + xf_floatbar.c + xf_floatbar.h + xf_input.c + xf_input.h + xf_channels.c + xf_channels.h + xf_cliprdr.c + xf_cliprdr.h + xf_monitor.c + xf_monitor.h + xf_disp.c + xf_disp.h + xf_graphics.c + xf_graphics.h + xf_keyboard.c + xf_keyboard.h + xf_video.c + xf_video.h + xf_window.c + xf_window.h + xf_client.c + xf_client.h) + +if (CHANNEL_TSMF_CLIENT) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + xf_tsmf.c + xf_tsmf.h) +endif() + +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..8db4d39 --- /dev/null +++ b/client/X11/cli/xfreerdp.c @@ -0,0 +1,85 @@ +/** + * 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 rc = 1; + 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); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (settings->ListMonitors) + xf_list_monitors(xfc); + + goto out; + } + + if (freerdp_client_start(context) != 0) + goto out; + + thread = freerdp_client_get_thread(context); + + WaitForSingleObject(thread, INFINITE); + GetExitCodeThread(thread, &dwExitCode); + rc = xf_exit_code_from_disconnect_reason(dwExitCode); + + freerdp_client_stop(context); + +out: + freerdp_client_context_free(context); + + return rc; +} diff --git a/client/X11/generate_argument_docbook.c b/client/X11/generate_argument_docbook.c new file mode 100644 index 0000000..4fd07b8 --- /dev/null +++ b/client/X11/generate_argument_docbook.c @@ -0,0 +1,271 @@ +#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; + LPSTR tmp2 = 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 (ds) + { + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + } + + 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; + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + + 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; + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + + 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; + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + + 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; + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + + 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; + tmp2 = (LPSTR)realloc(tmp, ds * sizeof(CHAR)); + if (!tmp2) + free(tmp); + tmp = tmp2; + + 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 && + (!arg->Default || arg->Default == BoolValueTrue)) + 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..8090b9b --- /dev/null +++ b/client/X11/xf_channels.c @@ -0,0 +1,137 @@ +/** + * 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" +#if defined(CHANNEL_TSMF_CLIENT) +#include "xf_tsmf.h" +#endif +#include "xf_rail.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_video.h" + +void xf_OnChannelConnectedEventHandler(void* context, ChannelConnectedEventArgs* e) +{ + xfContext* xfc = (xfContext*)context; + + if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + xfc->rdpei = (RdpeiClientContext*)e->pInterface; + } +#if defined(CHANNEL_TSMF_CLIENT) + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_init(xfc, (TsmfClientContext*)e->pInterface); + } +#endif + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + xf_graphics_pipeline_init(xfc, (RdpgfxClientContext*)e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + xf_rail_init(xfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + xf_cliprdr_init(xfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, 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) + { + 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); + } +#if defined(CHANNEL_TSMF_CLIENT) + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_uninit(xfc, (TsmfClientContext*)e->pInterface); + } +#endif + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + xf_graphics_pipeline_uninit(xfc, (RdpgfxClientContext*)e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + xf_rail_uninit(xfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + xf_cliprdr_uninit(xfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, 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..c12d823 --- /dev/null +++ b/client/X11/xf_channels.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 + +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..bd3eb0d --- /dev/null +++ b/client/X11/xf_client.c @@ -0,0 +1,2096 @@ +/** + * 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 +#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" +#if defined(CHANNEL_TSMF_CLIENT) +#include "xf_tsmf.h" +#endif +#include "xf_event.h" +#include "xf_input.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_video.h" +#include "xf_monitor.h" +#include "xf_graphics.h" +#include "xf_keyboard.h" +#include "xf_input.h" +#include "xf_channels.h" +#include "xfreerdp.h" + +#include +#define TAG CLIENT_TAG("x11") + +#define MIN_PIXEL_DIFF 0.001 + +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; + const char* filter; + 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); + /* avoid blurry filter when scaling factor is 2x, 3x, etc + * useful when the client has high-dpi monitor */ + filter = FilterBilinear; + if (fabs(xScalingFactor - yScalingFactor) < MIN_PIXEL_DIFF) + { + const double inverseX = 1.0 / xScalingFactor; + const double inverseRoundedX = round(inverseX); + const double absInverse = fabs(inverseX - inverseRoundedX); + + if (absInverse < MIN_PIXEL_DIFF) + filter = FilterNearest; + } + XRenderSetPictureFilter(xfc->display, primaryPicture, filter, 0, 0); + transform.matrix[0][0] = XDoubleToFixed(xScalingFactor); + transform.matrix[0][1] = XDoubleToFixed(0.0); + transform.matrix[0][2] = XDoubleToFixed(0.0); + transform.matrix[1][0] = XDoubleToFixed(0.0); + transform.matrix[1][1] = XDoubleToFixed(yScalingFactor); + transform.matrix[1][2] = XDoubleToFixed(0.0); + transform.matrix[2][0] = XDoubleToFixed(0.0); + transform.matrix[2][1] = XDoubleToFixed(0.0); + transform.matrix[2][2] = XDoubleToFixed(1.0); + /* calculate and fix up scaled coordinates */ + x2 = x + w; + y2 = y + h; + x = 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 != 0) || (xfc->offset_y != 0) || + (xfc->scaledWidth != (INT64)settings->DesktopWidth) || + (xfc->scaledHeight != (INT64)settings->DesktopHeight)) + { + return TRUE; + } + + return FALSE; +} +#endif /* WITH_XRENDER defined */ + +void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file, + int line) +{ + if (!xfc) + { + WLog_DBG(TAG, "[%s] called from [%s] xfc=%p", __FUNCTION__, fkt, xfc); + return; + } + + 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, settings->DesktopWidth, + settings->DesktopHeight); + } + + 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); + 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); + } + else + { + if (gdi->primary->hdc->hwnd->ninvalid < 1) + return TRUE; + + xf_lock_x11(xfc); + + 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); + } + } + else + { + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + xf_lock_x11(xfc); + xf_rail_paint(xfc, x, y, x + w, y + h); + xf_unlock_x11(xfc); + } + + 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); + + 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); + 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); + xf_draw_screen(xfc, x, y, w, h); + xf_unlock_x11(xfc); + } + 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); + + 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); + } + } + 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); + xf_rail_paint(xfc, x, y, x + w, y + h); + xf_unlock_x11(xfc); + } + + 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); + + if (!gdi_resize(gdi, settings->DesktopWidth, settings->DesktopHeight)) + goto out; + + ret = xf_desktop_resize(context); +out: + xf_unlock_x11(xfc); + 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); + pending_status = XPending(xfc->display); + + if (pending_status) + { + ZeroMemory(&xevent, sizeof(xevent)); + XNextEvent(xfc->display, &xevent); + status = xf_event_process(instance, &xevent); + } + xf_unlock_x11(xfc); + if (!status) + break; + } + + return status; +} + +static char* xf_window_get_title(rdpSettings* settings) +{ + BOOL port; + char* windowTitle; + size_t size; + char* name; + const char* prefix = "FreeRDP:"; + + if (!settings) + return NULL; + + name = settings->ServerHostname; + + if (settings->WindowTitle) + return _strdup(settings->WindowTitle); + + port = (settings->ServerPort != 3389); + /* Just assume a window title is never longer than a filename... */ + size = strnlen(name, MAX_PATH) + 16; + windowTitle = calloc(size, sizeof(char)); + + if (!windowTitle) + return NULL; + + if (!port) + sprintf_s(windowTitle, size, "%s %s", prefix, name); + else + sprintf_s(windowTitle, size, "%s %s:%i", prefix, name, settings->ServerPort); + + return windowTitle; +} + +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 + windowTitle = xf_window_get_title(settings); + + if (!windowTitle) + return FALSE; + +#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 defined(CHANNEL_TSMF_CLIENT) + if (xfc->xv_context) + { + xf_tsmf_uninit(xfc, NULL); + xfc->xv_context = NULL; + } +#endif + + if (xfc->image) + { + xfc->image->data = NULL; + XDestroyImage(xfc->image); + xfc->image = NULL; + } + + if (xfc->bitmap_mono) + { + XFreePixmap(xfc->display, xfc->bitmap_mono); + xfc->bitmap_mono = 0; + } + + if (xfc->gc_mono) + { + XFreeGC(xfc->display, xfc->gc_mono); + xfc->gc_mono = 0; + } + + if (xfc->primary) + { + XFreePixmap(xfc->display, xfc->primary); + xfc->primary = 0; + } + + if (xfc->gc) + { + XFreeGC(xfc->display, xfc->gc); + xfc->gc = 0; + } + + 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); +} + +BOOL xf_toggle_control(xfContext* xfc) +{ + EncomspClientContext* encomsp; + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu; + encomsp = xfc->encomsp; + + if (!encomsp) + return FALSE; + + pdu.ParticipantId = 0; + pdu.Flags = ENCOMSP_REQUEST_VIEW; + + if (!xfc->controlToggle) + pdu.Flags |= ENCOMSP_REQUEST_INTERACT; + + encomsp->ChangeParticipantControlLevel(encomsp, &pdu); + xfc->controlToggle = !xfc->controlToggle; + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_encomsp_participant_created(EncomspClientContext* context, + const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + xfContext* xfc; + rdpSettings* settings; + BOOL request; + + if (!context || !context->custom || !participantCreated) + return ERROR_INVALID_PARAMETER; + + xfc = context->custom; + settings = xfc->context.settings; + + if (!settings) + return ERROR_INVALID_PARAMETER; + + request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl); + if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) && + !(participantCreated->Flags & ENCOMSP_MAY_INTERACT)) + xf_toggle_control(xfc); + + 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) +{ + WINPR_UNUSED(encomsp); + xfc->encomsp = NULL; +} + +void xf_lock_x11_(xfContext* xfc, const char* fkt) +{ + + if (!xfc->UseXThreads) + WaitForSingleObject(xfc->mutex, INFINITE); + else + XLockDisplay(xfc->display); + + xfc->locked++; + WLog_VRB(TAG, "%s:\t[%" PRIu32 "] from %s", __FUNCTION__, xfc->locked, fkt); +} + +void xf_unlock_x11_(xfContext* xfc, const char* fkt) +{ + if (xfc->locked == 0) + WLog_WARN(TAG, "X11: trying to unlock although not locked!"); + + WLog_VRB(TAG, "%s:\t[%" PRIu32 "] from %s", __FUNCTION__, xfc->locked - 1, fkt); + if (!xfc->UseXThreads) + ReleaseMutex(xfc->mutex); + else + XUnlockDisplay(xfc->display); + xfc->locked--; +} + +static BOOL xf_get_pixmap_info(xfContext* xfc) +{ + int 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; + WINPR_UNUSED(play_sound); + 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); +#endif /* WITH_XI */ + +static void xf_get_x11_button_map(xfContext* xfc, unsigned char* x11_map) +{ +#ifdef WITH_XI + int opcode, event, error; + 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"); + ptr_dev = NULL; + /* loop through every device, looking for a pointer */ + version = XGetExtensionVersion(xfc->display, INAME); + + if (version->major_version >= 2) + { + /* XID of pointer device using XInput version 2 */ + devices2 = XIQueryDevice(xfc->display, XIAllDevices, &num_devices); + + if (devices2) + { + for (i = 0; i < num_devices; ++i) + { + if ((devices2[i].use == XISlavePointer) && + (strncmp(devices2[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + ptr_dev = XOpenDevice(xfc->display, devices2[i].deviceid); + if (ptr_dev) + break; + } + } + + XIFreeDeviceInfo(devices2); + } + } + else + { + /* XID of pointer device using XInput version 1 */ + devices1 = XListInputDevices(xfc->display, &num_devices); + + if (devices1) + { + for (i = 0; i < num_devices; ++i) + { + if ((devices1[i].use == IsXExtensionPointer) && + (strncmp(devices1[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + ptr_dev = XOpenDevice(xfc->display, devices1[i].id); + if (ptr_dev) + break; + } + } + + XFreeDeviceList(devices1); + } + } + + XFree(version); + + /* get button mapping from input extension if there is a pointer device; */ + /* otherwise leave unchanged. */ + if (ptr_dev) + { + WLog_DBG(TAG, "Pointer device: %d", ptr_dev->device_id); + XGetDeviceButtonMapping(xfc->display, ptr_dev, x11_map, NUM_BUTTONS_MAPPED); + XCloseDevice(xfc->display, ptr_dev); + } + else + { + WLog_DBG(TAG, "No pointer device found!"); + } + } + else +#endif /* WITH_XI */ + { + WLog_DBG(TAG, "Get global pointer mapping (no XInput)"); + XGetPointerMapping(xfc->display, x11_map, NUM_BUTTONS_MAPPED); + } +} + +/* Assignment of physical (not logical) mouse buttons to wire flags. */ +/* Notice that the middle button is 2 in X11, but 3 in RDP. */ +static const button_map xf_button_flags[NUM_BUTTONS_MAPPED] = { + { Button1, PTR_FLAGS_BUTTON1 }, + { Button2, PTR_FLAGS_BUTTON3 }, + { Button3, PTR_FLAGS_BUTTON2 }, + { Button4, PTR_FLAGS_WHEEL | 0x78 }, + /* Negative value is 9bit twos complement */ + { Button5, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) }, + { 6, PTR_FLAGS_HWHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) }, + { 7, PTR_FLAGS_HWHEEL | 0x78 }, + { 8, PTR_XFLAGS_BUTTON1 }, + { 9, PTR_XFLAGS_BUTTON2 }, + { 97, PTR_XFLAGS_BUTTON1 }, + { 112, PTR_XFLAGS_BUTTON2 } +}; + +static UINT16 get_flags_for_button(int button) +{ + size_t x; + + for (x = 0; x < ARRAYSIZE(xf_button_flags); x++) + { + const button_map* map = &xf_button_flags[x]; + + if (map->button == button) + return map->flags; + } + + return 0; +} + +static void xf_button_map_init(xfContext* xfc) +{ + size_t pos = 0; + /* loop counter for array initialization */ + size_t physical; + /* logical mouse button which is used for each physical mouse */ + /* button (indexed from zero). This is the default map. */ + unsigned char x11_map[112] = { 0 }; + x11_map[0] = Button1; + x11_map[1] = Button2; + x11_map[2] = Button3; + x11_map[3] = Button4; + x11_map[4] = Button5; + x11_map[5] = 6; + x11_map[6] = 7; + x11_map[7] = 8; + x11_map[8] = 9; + x11_map[96] = 97; + x11_map[111] = 112; + + /* query system for actual remapping */ + if (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 < ARRAYSIZE(x11_map); ++physical) + { + const unsigned char logical = x11_map[physical]; + const UINT16 flags = get_flags_for_button(logical); + + if ((logical != 0) && (flags != 0)) + { + if (pos >= NUM_BUTTONS_MAPPED) + { + WLog_ERR(TAG, "Failed to map mouse button to RDP button, no space"); + } + else + { + button_map* map = &xfc->button_map[pos++]; + map->button = logical; + map->flags = get_flags_for_button(physical + Button1); + } + } + } +} + +/** + * Callback given to freerdp_connect() to process the pre-connect operations. + * It will fill the rdp_freerdp structure (instance) with the appropriate options to use for the + * connection. + * + * @param instance - pointer to the rdp_freerdp structure that contains the connection's parameters, + * and will be filled with the appropriate informations. + * + * @return TRUE if successful. FALSE otherwise. + * Can exit with error code XF_EXIT_PARSE_ARGUMENTS if there is an error in the parameters. + */ +static BOOL xf_pre_connect(freerdp* instance) +{ + rdpChannels* channels; + 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; + 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[MAX_PATH] = { 0 }; + ULONG size = sizeof(login_name) - 1; + + if (GetUserNameExA(NameSamCompatible, login_name, &size)) + { + if (!freerdp_settings_set_string(settings, FreeRDP_Username, login_name)) + return FALSE; + + WLog_INFO(TAG, "No user name set. - Using login name: %s", 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; + 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; + BOOL serverIsWindowsPlatform; + + 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; + + serverIsWindowsPlatform = (settings->OsMajorType == OSMAJORTYPE_WINDOWS); + if (!(xfc->clipboard = xf_clipboard_new(xfc, !serverIsWindowsPlatform))) + 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); + if(type != LOGON_MSG_SESSION_CONTINUE) + { + 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]; + wMessage msg; + wMessageQueue* queue; + 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) + { + if (!xf_process_x_events(xfc->context.instance)) + { + running = FALSE; + break; + } + } + + if (WaitForSingleObject(events[2], 0) == WAIT_OBJECT_0) + running = FALSE; + + break; + + default: + running = FALSE; + break; + } + } + + MessageQueue_PostQuit(queue, 0); + freerdp_abort_connect(xfc->context.instance); + 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_DBG(TAG, "Closed from X11"); + return FALSE; + } + } + + return TRUE; +} + +/** Main loop for the rdp connection. + * It will be run from the thread's entry point (thread_func()). + * It initiates the connection, and will continue to run until the session ends, + * processing events as they are received. + * @param 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 if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_LOGON_FAILURE) + exit_code = XF_EXIT_LOGON_FAILURE; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT) + exit_code = XF_EXIT_ACCOUNT_LOCKED_OUT; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_PRE_CONNECT_FAILED) + exit_code = XF_EXIT_PRE_CONNECT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_UNDEFINED) + exit_code = XF_EXIT_CONNECT_UNDEFINED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_POST_CONNECT_FAILED) + exit_code = XF_EXIT_POST_CONNECT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_DNS_ERROR) + exit_code = XF_EXIT_DNS_ERROR; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_DNS_NAME_NOT_FOUND) + exit_code = XF_EXIT_DNS_NAME_NOT_FOUND; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_FAILED) + exit_code = XF_EXIT_CONNECT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR) + exit_code = XF_EXIT_MCS_CONNECT_INITIAL_ERROR; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_TLS_CONNECT_FAILED) + exit_code = XF_EXIT_TLS_CONNECT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_INSUFFICIENT_PRIVILEGES) + exit_code = XF_EXIT_INSUFFICIENT_PRIVILEGES; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_CANCELLED) + exit_code = XF_EXIT_CONNECT_CANCELLED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED) + exit_code = XF_EXIT_SECURITY_NEGO_CONNECT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_TRANSPORT_FAILED) + exit_code = XF_EXIT_CONNECT_TRANSPORT_FAILED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED) + exit_code = XF_EXIT_CONNECT_PASSWORD_EXPIRED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE) + exit_code = XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_KDC_UNREACHABLE) + exit_code = XF_EXIT_CONNECT_KDC_UNREACHABLE; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED) + exit_code = XF_EXIT_CONNECT_ACCOUNT_DISABLED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED) + exit_code = XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_CLIENT_REVOKED) + exit_code = XF_EXIT_CONNECT_CLIENT_REVOKED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_WRONG_PASSWORD) + exit_code = XF_EXIT_CONNECT_WRONG_PASSWORD; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_ACCESS_DENIED) + exit_code = XF_EXIT_CONNECT_ACCESS_DENIED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION) + exit_code = XF_EXIT_CONNECT_ACCOUNT_RESTRICTION; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED) + exit_code = XF_EXIT_CONNECT_ACCOUNT_EXPIRED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED) + exit_code = XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED; + else if (freerdp_get_last_error(instance->context) == + FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS) + exit_code = XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS; + 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->window) + xf_floatbar_hide_and_show(xfc->window->floatbar); + + waitStatus = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if (waitStatus == WAIT_FAILED) + break; + + { + if (!freerdp_check_event_handles(context)) + { + if (client_auto_reconnect_ex(instance, handle_window_events)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + 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_CONNECT_NO_OR_MISSING_CREDENTIALS)) + return reason; + /* License error set */ + else if (reason >= 0x100 && reason <= 0x10A) + reason -= 0x100 + XF_EXIT_LICENSE_INTERNAL; + /* RDP protocol error set */ + else if (reason >= 0x10c9 && reason <= 0x1193) + reason = XF_EXIT_RDP; + /* There's no need to test protocol-independent codes: they match */ + else if (!(reason <= 0xC)) + reason = XF_EXIT_UNKNOWN; + + return reason; +} + +static void xf_TerminateEventHandler(void* context, TerminateEventArgs* e) +{ + rdpContext* ctx = (rdpContext*)context; + WINPR_UNUSED(e); + 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->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->PresentGatewayMessage = client_cli_present_gateway_message; + 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 = 0; + int actual_format = 0; + unsigned long nitems = 0, after = 0; + 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->_XWAYLAND_MAY_GRAB_KEYBOARD = + XInternAtom(xfc->display, "_XWAYLAND_MAY_GRAB_KEYBOARD", False); + xfc->_NET_WM_ICON = XInternAtom(xfc->display, "_NET_WM_ICON", False); + xfc->_MOTIF_WM_HINTS = XInternAtom(xfc->display, "_MOTIF_WM_HINTS", False); + xfc->_NET_CURRENT_DESKTOP = XInternAtom(xfc->display, "_NET_CURRENT_DESKTOP", False); + xfc->_NET_WORKAREA = XInternAtom(xfc->display, "_NET_WORKAREA", False); + xfc->_NET_WM_STATE = get_supported_atom(xfc, "_NET_WM_STATE"); + xfc->_NET_WM_STATE_FULLSCREEN = get_supported_atom(xfc, "_NET_WM_STATE_FULLSCREEN"); + xfc->_NET_WM_STATE_MAXIMIZED_HORZ = + XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xfc->_NET_WM_STATE_MAXIMIZED_VERT = + XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + xfc->_NET_WM_FULLSCREEN_MONITORS = get_supported_atom(xfc, "_NET_WM_FULLSCREEN_MONITORS"); + xfc->_NET_WM_NAME = XInternAtom(xfc->display, "_NET_WM_NAME", False); + xfc->_NET_WM_PID = XInternAtom(xfc->display, "_NET_WM_PID", False); + xfc->_NET_WM_WINDOW_TYPE = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE", False); + xfc->_NET_WM_WINDOW_TYPE_NORMAL = + XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + xfc->_NET_WM_WINDOW_TYPE_DIALOG = + XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + xfc->_NET_WM_WINDOW_TYPE_POPUP = XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP", False); + xfc->_NET_WM_WINDOW_TYPE_POPUP_MENU = + XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False); + xfc->_NET_WM_WINDOW_TYPE_UTILITY = + XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU = + XInternAtom(xfc->display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False); + xfc->_NET_WM_STATE_SKIP_TASKBAR = + XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_TASKBAR", False); + xfc->_NET_WM_STATE_SKIP_PAGER = XInternAtom(xfc->display, "_NET_WM_STATE_SKIP_PAGER", False); + xfc->_NET_WM_MOVERESIZE = XInternAtom(xfc->display, "_NET_WM_MOVERESIZE", False); + xfc->_NET_MOVERESIZE_WINDOW = XInternAtom(xfc->display, "_NET_MOVERESIZE_WINDOW", False); + xfc->UTF8_STRING = XInternAtom(xfc->display, "UTF8_STRING", FALSE); + xfc->WM_PROTOCOLS = XInternAtom(xfc->display, "WM_PROTOCOLS", False); + xfc->WM_DELETE_WINDOW = XInternAtom(xfc->display, "WM_DELETE_WINDOW", False); + xfc->WM_STATE = XInternAtom(xfc->display, "WM_STATE", False); + xfc->x11event = CreateFileDescriptorEvent(NULL, FALSE, FALSE, xfc->xfds, WINPR_FD_READ); + + if (!xfc->x11event) + { + WLog_ERR(TAG, "Could not create xfds event"); + goto fail_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..e3b00bf --- /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..37ed538 --- /dev/null +++ b/client/X11/xf_cliprdr.c @@ -0,0 +1,1978 @@ +/** + * 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 timestamp_property_atom; + Time selection_ownership_timestamp; + + 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; + XSelectionEvent* respond; + + Window owner; + BOOL sync; + + /* INCR mechanism */ + Atom incr_atom; + BOOL incr_starts; + BYTE* incr_data; + int incr_data_length; + + /* XFixes extension */ + int xfixes_event_base; + int xfixes_error_base; + BOOL xfixes_supported; + + /* File clipping */ + BOOL streams_supported; + BOOL file_formats_registered; + UINT32 file_capability_flags; + /* last sent data */ + CLIPRDR_FORMAT* lastSentFormats; + UINT32 lastSentNumFormats; +}; + +static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard); +static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp); + +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 = { 0 }; + 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, const BYTE* data, size_t size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 }; + + /* No request currently pending, do not send a response. */ + if (clipboard->requestedFormatId < 0) + return CHANNEL_RC_OK; + + /* Request handled, reset to invalid */ + clipboard->requestedFormatId = -1; + + 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) +{ + unsigned long 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 BOOL xf_clipboard_format_equal(const CLIPRDR_FORMAT* a, const CLIPRDR_FORMAT* b) +{ + if (a->formatId != b->formatId) + return FALSE; + if (!a->formatName && !b->formatName) + return TRUE; + + return strcmp(a->formatName, b->formatName) == 0; +} +static BOOL xf_clipboard_changed(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats) +{ + UINT32 x, y; + if (clipboard->lastSentNumFormats != numFormats) + return TRUE; + + for (x = 0; x < numFormats; x++) + { + const CLIPRDR_FORMAT* cur = &clipboard->lastSentFormats[x]; + BOOL contained = FALSE; + for (y = 0; y < numFormats; y++) + { + if (xf_clipboard_format_equal(cur, &formats[y])) + { + contained = TRUE; + break; + } + } + if (!contained) + return TRUE; + } + + return FALSE; +} + +static void xf_clipboard_formats_free(xfClipboard* clipboard) +{ + xf_cliprdr_free_formats(clipboard->lastSentFormats, clipboard->lastSentNumFormats); + clipboard->lastSentFormats = NULL; + clipboard->lastSentNumFormats = 0; +} +static BOOL xf_clipboard_copy_formats(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats) +{ + UINT32 x; + + xf_clipboard_formats_free(clipboard); + clipboard->lastSentFormats = calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + if (!clipboard->lastSentFormats) + return FALSE; + clipboard->lastSentNumFormats = numFormats; + for (x = 0; x < numFormats; x++) + { + CLIPRDR_FORMAT* lcur = &clipboard->lastSentFormats[x]; + const CLIPRDR_FORMAT* cur = &formats[x]; + *lcur = *cur; + if (cur->formatName) + lcur->formatName = _strdup(cur->formatName); + } + return FALSE; +} + +static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats) +{ + CLIPRDR_FORMAT_LIST formatList = { 0 }; + formatList.msgFlags = CB_RESPONSE_OK; + formatList.numFormats = numFormats; + formatList.formats = (CLIPRDR_FORMAT*)formats; + formatList.msgType = CB_FORMAT_LIST; + + if (!xf_clipboard_changed(clipboard, formats, numFormats)) + return CHANNEL_RC_OK; + + xf_clipboard_copy_formats(clipboard, formats, numFormats); + /* Ensure all pending requests are answered. */ + xf_cliprdr_send_data_response(clipboard, NULL, 0); + return clipboard->context->ClientFormatList(clipboard->context, &formatList); +} + +static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard) +{ + UINT32 numFormats = 0; + CLIPRDR_FORMAT* formats = NULL; + formats = xf_cliprdr_get_client_formats(clipboard, &numFormats); + xf_cliprdr_send_format_list(clipboard, formats, numFormats); + 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; + FILEDESCRIPTORW* file_array = (FILEDESCRIPTORW*)pDstData; + UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW); + pDstData = NULL; + DstSize = 0; + error = cliprdr_serialize_file_list_ex(clipboard->file_capability_flags, 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, 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) + { + + 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, bytes_left); + + if (data) + XFree(data); + + return TRUE; +} + +static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target) +{ + int i; + + if (clipboard->numTargets < 0) + return; + + if ((size_t)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, const XSelectionEvent* respond) +{ + xfContext* xfc = clipboard->xfc; + + if (respond->property != None) + { + XChangeProperty(xfc->display, respond->requestor, respond->property, XA_ATOM, 32, + PropModeReplace, (BYTE*)clipboard->targets, clipboard->numTargets); + } +} + +static void xf_cliprdr_provide_timestamp(xfClipboard* clipboard, const XSelectionEvent* respond) +{ + xfContext* xfc = clipboard->xfc; + + if (respond->property != None) + { + XChangeProperty(xfc->display, respond->requestor, respond->property, XA_INTEGER, 32, + PropModeReplace, (BYTE*)&clipboard->selection_ownership_timestamp, 1); + } +} + +static void xf_cliprdr_provide_data(xfClipboard* clipboard, const XSelectionEvent* respond, + const BYTE* data, UINT32 size) +{ + xfContext* xfc = clipboard->xfc; + + if (respond->property != None) + { + XChangeProperty(xfc->display, respond->requestor, respond->property, respond->target, 8, + PropModeReplace, data, size); + } +} + +static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard, + const XSelectionEvent* xevent) +{ + if (xevent->target == clipboard->targets[1]) + { + if (xevent->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->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, + const XSelectionRequestEvent* xevent) +{ + int fmt; + Atom type; + UINT32 formatId; + const char* formatName; + XSelectionEvent* 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->owner != xfc->drawable) + return FALSE; + + delayRespond = FALSE; + + if (!(respond = (XSelectionEvent*)calloc(1, sizeof(XSelectionEvent)))) + { + WLog_ERR(TAG, "failed to allocate XEvent data"); + return FALSE; + } + + respond->property = None; + respond->type = SelectionNotify; + respond->display = xevent->display; + respond->requestor = xevent->requestor; + respond->selection = xevent->selection; + respond->target = xevent->target; + respond->time = xevent->time; + + if (xevent->target == clipboard->targets[0]) /* TIMESTAMP */ + { + /* Someone else requests the selection's timestamp */ + respond->property = xevent->property; + xf_cliprdr_provide_timestamp(clipboard, respond); + } + else if (xevent->target == clipboard->targets[1]) /* TARGETS */ + { + /* Someone else requests our available formats */ + respond->property = xevent->property; + xf_cliprdr_provide_targets(clipboard, respond); + } + else + { + format = xf_cliprdr_get_server_format_by_atom(clipboard, xevent->target); + + if (format && (xevent->requestor != xfc->drawable)) + { + formatId = format->formatId; + formatName = format->formatName; + rawTransfer = FALSE; + + if (formatId == CF_RAW) + { + if (XGetWindowProperty(xfc->display, xevent->requestor, clipboard->property_atom, 0, + 4, 0, XA_INTEGER, &type, &fmt, &length, &bytes_left, + &data) != Success) + { + } + + if (data) + { + rawTransfer = TRUE; + CopyMemory(&formatId, data, 4); + XFree(data); + } + } + + /* 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->property = xevent->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->property = xevent->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->property = xevent->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) + { + union + { + XEvent* ev; + XSelectionEvent* sev; + } conv; + + conv.sev = respond; + XSendEvent(xfc->display, xevent->requestor, 0, 0, conv.ev); + XFlush(xfc->display); + free(respond); + } + + return TRUE; +} + +static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard, + const XSelectionClearEvent* xevent) +{ + xfContext* xfc = clipboard->xfc; + + WINPR_UNUSED(xevent); + + 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, const XPropertyEvent* xevent) +{ + xfCliprdrFormat* format; + xfContext* xfc = NULL; + + if (!clipboard) + return TRUE; + + xfc = clipboard->xfc; + + if (xevent->atom == clipboard->timestamp_property_atom) + { + /* This is the response to the property change we did + * in xf_cliprdr_prepare_to_set_selection_owner. Now + * we can set ourselves as the selection owner. (See + * comments in those functions below.) */ + xf_cliprdr_set_selection_owner(xfc, clipboard, xevent->time); + return TRUE; + } + + if (xevent->atom != clipboard->property_atom) + return FALSE; /* Not cliprdr-related */ + + if (xevent->window == clipboard->root_window) + { + xf_cliprdr_send_client_format_list(clipboard); + } + else if ((xevent->window == xfc->drawable) && (xevent->state == PropertyNewValue) && + clipboard->incr_starts) + { + format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId); + + if (format) + xf_cliprdr_get_requested_data(clipboard, format->atom); + } + + return TRUE; +} + +void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event) +{ + xfClipboard* clipboard; + + 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->xselection); + break; + + case SelectionRequest: + xf_cliprdr_process_selection_request(clipboard, &event->xselectionrequest); + break; + + case SelectionClear: + xf_cliprdr_process_selection_clear(clipboard, &event->xselectionclear); + break; + + case PropertyNotify: + xf_cliprdr_process_property_notify(clipboard, &event->xproperty); + 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 | CB_HUGE_FILE_SUPPORT_ENABLED; + + clipboard->file_capability_flags = generalCapabilitySet.generalFlags; + 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; + xfContext* xfc = clipboard->xfc; + UINT ret; + 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; + } + + ret = xf_cliprdr_send_format_list(clipboard, formats, numFormats); + 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, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + xfClipboard* clipboard = (xfClipboard*)context->custom; + UINT ret; + + WINPR_UNUSED(monitorReady); + + 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, + const 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; +} + +static void xf_cliprdr_prepare_to_set_selection_owner(xfContext* xfc, xfClipboard* clipboard) +{ + /* + * When you're writing to the selection in response to a + * normal X event like a mouse click or keyboard action, you + * get the selection timestamp by copying the time field out + * of that X event. Here, we're doing it on our own + * initiative, so we have to _request_ the X server time. + * + * There isn't a GetServerTime request in the X protocol, so I + * work around it by setting a property on our own window, and + * waiting for a PropertyNotify event to come back telling me + * it's been done - which will have a timestamp we can use. + */ + + /* We have to set the property to some value, but it doesn't + * matter what. Set it to its own name, which we have here + * anyway! */ + Atom value = clipboard->timestamp_property_atom; + + XChangeProperty(xfc->display, xfc->drawable, clipboard->timestamp_property_atom, XA_ATOM, 32, + PropModeReplace, (BYTE*)&value, 1); + XFlush(xfc->display); +} + +static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp) +{ + /* + * Actually set ourselves up as the selection owner, now that + * we have a timestamp to use. + */ + + clipboard->selection_ownership_timestamp = timestamp; + XSetSelectionOwner(xfc->display, clipboard->clipboard_atom, xfc->drawable, timestamp); + XFlush(xfc->display); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + UINT32 i; + int j; + xfClipboard* clipboard = (xfClipboard*)context->custom; + xfContext* xfc = clipboard->xfc; + UINT ret; + xf_clipboard_formats_free(clipboard); + 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); + if (xfc->remote_app) + xf_cliprdr_set_selection_owner(xfc, clipboard, CurrentTime); + else + xf_cliprdr_prepare_to_set_selection_owner(xfc, clipboard); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_format_list_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + // xfClipboard* clipboard = (xfClipboard*) context->custom; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + BOOL rawTransfer; + 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); + + clipboard->requestedFormatId = rawTransfer ? CF_RAW : formatId; + if (!format) + return xf_cliprdr_send_data_response(clipboard, NULL, 0); + + XConvertSelection(xfc->display, clipboard->clipboard_atom, format->atom, + clipboard->property_atom, xfc->drawable, CurrentTime); + XFlush(xfc->display); + /* After this point, we expect a SelectionNotify event from the clipboard owner. */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + BOOL bSuccess; + 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; + } + + if (strcmp(clipboard->data_format_name, "FileGroupDescriptorW") == 0) + { + srcFormatId = ClipboardGetFormatId(clipboard->system, "FileGroupDescriptorW"); + dstFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list"); + nullTerminated = FALSE; + } + } + 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_DBG(TAG, "skipping, empty data detected!"); + free(clipboard->respond); + clipboard->respond = NULL; + return CHANNEL_RC_OK; + } + + pDstData = (BYTE*)ClipboardGetData(clipboard->system, dstFormatId, &DstSize); + + if (!pDstData) + { + WLog_WARN(TAG, "failed to get clipboard data in format %s [source format %s]", + ClipboardGetFormatName(clipboard->system, dstFormatId), + ClipboardGetFormatName(clipboard->system, srcFormatId)); + } + + if (nullTerminated && pDstData) + { + BYTE* nullTerminator = memchr(pDstData, '\0', DstSize); + if (nullTerminator) + DstSize = nullTerminator - pDstData; + } + } + + /* 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); + { + union + { + XEvent* ev; + XSelectionEvent* sev; + } conv; + + conv.sev = clipboard->respond; + + XSendEvent(xfc->display, clipboard->respond->requestor, 0, 0, conv.ev); + XFlush(xfc->display); + } + free(clipboard->respond); + clipboard->respond = NULL; + return CHANNEL_RC_OK; +} + +static UINT +xf_cliprdr_server_file_size_request(xfClipboard* clipboard, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileSizeRequest request = { 0 }; + 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 = { 0 }; + 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 = { 0 }; + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + return context->ClientFileContentsResponse(context, &response); +} + +static UINT +xf_cliprdr_server_file_contents_request(CliprdrClientContext* context, + const 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 = { 0 }; + xfClipboard* clipboard = delegate->custom; + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + 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 = { 0 }; + xfClipboard* clipboard = delegate->custom; + WINPR_UNUSED(errorCode); + + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + 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 = { 0 }; + xfClipboard* clipboard = delegate->custom; + response.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + 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 = { 0 }; + xfClipboard* clipboard = delegate->custom; + WINPR_UNUSED(errorCode); + + response.msgFlags = CB_RESPONSE_FAIL; + response.streamId = request->streamId; + return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); +} + +static BOOL xf_cliprdr_clipboard_is_valid_unix_filename(LPCWSTR filename) +{ + LPCWSTR c; + + if (!filename) + return FALSE; + + if (filename[0] == L'\0') + return FALSE; + + /* Reserved characters */ + for (c = filename; *c; ++c) + { + if (*c == L'/') + return FALSE; + } + + return TRUE; +} + +xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) +{ + int i, n = 0; + rdpChannels* channels; + xfClipboard* clipboard; + const char* selectionAtom; + + 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); + selectionAtom = "CLIPBOARD"; + if (xfc->context.settings->XSelectionAtom) + selectionAtom = xfc->context.settings->XSelectionAtom; + clipboard->clipboard_atom = XInternAtom(xfc->display, selectionAtom, FALSE); + + if (clipboard->clipboard_atom == None) + { + WLog_ERR(TAG, "unable to get %s atom", selectionAtom); + goto error; + } + + clipboard->timestamp_property_atom = + XInternAtom(xfc->display, "_FREERDP_TIMESTAMP_PROPERTY", FALSE); + clipboard->property_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR", FALSE); + clipboard->raw_transfer_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE); + clipboard->raw_format_list_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE); + xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE); + XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask); +#ifdef WITH_XFIXES + + if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base, + &clipboard->xfixes_error_base)) + { + int xfmajor, 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; + /* TODO: set up a filesystem base path for local URI */ + /* clipboard->delegate->basePath = "file:///tmp/foo/bar/gaga"; */ + 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; + + if (relieveFilenameRestriction) + { + WLog_DBG(TAG, "Relieving CLIPRDR filename restriction"); + clipboard->delegate->IsFileNameComponentValid = xf_cliprdr_clipboard_is_valid_unix_filename; + } + + 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); + xf_clipboard_formats_free(clipboard); + 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..7f571c4 --- /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, BOOL relieveFilenameRestriction); +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, const XEvent* event); + +#endif /* FREERDP_CLIENT_X11_CLIPRDR_H */ diff --git a/client/X11/xf_disp.c b/client/X11/xf_disp.c new file mode 100644 index 0000000..32ddb62 --- /dev/null +++ b/client/X11/xf_disp.c @@ -0,0 +1,480 @@ +/** + * 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 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 = { 0 }; + 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; + + if (!xf_disp_settings_changed(xfDisp)) + return TRUE; + + xfDisp->lastSentDate = GetTickCount64(); + if (xfc->fullscreen && (settings->MonitorCount > 0)) + { + if (xf_disp_sendLayout(xfDisp->disp, settings->MonitorDefArray, settings->MonitorCount) != + CHANNEL_RC_OK) + return FALSE; + } + else + { + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = xfDisp->targetWidth; + layout.Height = xfDisp->targetHeight; + layout.Orientation = settings->DesktopOrientation; + layout.DesktopScaleFactor = settings->DesktopScaleFactor; + layout.DeviceScaleFactor = settings->DeviceScaleFactor; + layout.PhysicalWidth = xfDisp->targetWidth / 75 * 25.4f; + layout.PhysicalHeight = xfDisp->targetHeight / 75 * 25.4f; + + if (IFCALLRESULT(CHANNEL_RC_OK, xfDisp->disp->SendMonitorLayout, xfDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + + return xf_update_last_sent(xfDisp); +} + +static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 height) +{ + if ((xfDisp->targetWidth == width) && (xfDisp->targetHeight == height)) + return TRUE; + xfDisp->targetWidth = width; + xfDisp->targetHeight = height; + xfDisp->lastSentDate = GetTickCount64(); + return xf_disp_sendResize(xfDisp); +} + +static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp) +{ + XSizeHints* size_hints; + + 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; + + if (xfDisp->activated && !xfc->fullscreen) + { + xf_disp_set_window_resizable(xfDisp); + + if (e->firstActivation) + return; + + xf_disp_sendResize(xfDisp); + } +} + +static void xf_disp_OnGraphicsReset(void* context, GraphicsResetEventArgs* e) +{ + xfContext* xfc; + xfDispContext* xfDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + 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; + + WINPR_UNUSED(e); + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (!xfDisp->activated || xfc->fullscreen) + return; + + xf_disp_sendResize(xfDisp); +} + +static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEventArgs* e) +{ + xfContext* xfc; + xfDispContext* xfDisp; + rdpSettings* settings; + + WINPR_UNUSED(e); + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (!xfDisp->activated || !xfc->fullscreen) + return; + + xf_disp_sendResize(xfDisp); +} + +xfDispContext* xf_disp_new(xfContext* xfc) +{ + xfDispContext* ret; + + 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); + PubSub_SubscribeWindowStateChange(xfc->context.pubSub, xf_disp_OnWindowStateChange); + 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); + PubSub_UnsubscribeWindowStateChange(disp->xfc->context.pubSub, xf_disp_OnWindowStateChange); + } + + 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, const 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; + + return xf_disp_queueResize(xfDisp, width, height); +} + +static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + xfDispContext* xfDisp = (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 resizable"); + return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp) +{ + rdpSettings* settings; + + 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..9062501 --- /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, const XEvent* event); +BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height); +void xf_disp_resized(xfDispContext* disp); + +#endif /* FREERDP_CLIENT_X11_DISP_H */ diff --git a/client/X11/xf_event.c b/client/X11/xf_event.c new file mode 100644 index 0000000..bdcdcb5 --- /dev/null +++ b/client/X11/xf_event.c @@ -0,0 +1,1152 @@ +/** + * 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 + +#include "xf_rail.h" +#include "xf_window.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_input.h" +#include "xf_gfx.h" +#include "xf_graphics.h" + +#include "xf_event.h" +#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)) + { + char* context = NULL; + strtok_s(buffer, "\n", &context); + 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, const 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 || !xfc->window) + 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)) + { + char* context = NULL; + strtok_s(buffer, "\n", &context); + } + + pclose(actionScript); + return TRUE; +} + +void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y) +{ + rdpSettings* settings; + INT64 tx, ty; + + if (!xfc || !xfc->context.settings || !y || !x) + return; + + settings = xfc->context.settings; + tx = *x; + ty = *y; + if (!xfc->remote_app) + { +#ifdef WITH_XRENDER + + if (xf_picture_transform_required(xfc)) + { + double xScalingFactor = xfc->scaledWidth / (double)settings->DesktopWidth; + double yScalingFactor = xfc->scaledHeight / (double)settings->DesktopHeight; + tx = ((tx + xfc->offset_x) * xScalingFactor); + ty = ((ty + xfc->offset_y) * yScalingFactor); + } + +#endif + } + + CLAMP_COORDINATES(tx, ty); + *x = tx; + *y = ty; +} + +void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y) +{ + 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, const XExposeEvent* 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->x; + y = event->y; + w = event->width; + h = event->height; + } + + if (!app) + { + if (xfc->context.gdi->gfx) + { + xf_OutputExpose(xfc, x, y, w, h); + return TRUE; + } + xf_draw_screen(xfc, x, y, w, h); + } + else + { + xfAppWindow* appWindow; + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + xf_UpdateWindowArea(xfc, appWindow, x, y, w, h); + } + } + + return TRUE; +} + +static BOOL xf_event_VisibilityNotify(xfContext* xfc, const XVisibilityEvent* event, BOOL app) +{ + WINPR_UNUSED(app); + xfc->unobscured = event->state == VisibilityUnobscured; + return TRUE; +} + +BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app) +{ + 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, const XMotionEvent* event, BOOL app) +{ + if (xfc->window) + xf_floatbar_set_root_y(xfc->window->floatbar, event->y); + + if (xfc->use_xinput) + return TRUE; + + return xf_generic_MotionNotify(xfc, event->x, event->y, event->state, event->window, app); +} + +BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app, + BOOL down) +{ + UINT16 flags = 0; + rdpInput* input; + Window childWindow; + size_t i; + + for (i = 0; i < ARRAYSIZE(xfc->button_map); i++) + { + const button_map* cur = &xfc->button_map[i]; + + if (cur->button == button) + { + flags = cur->flags; + break; + } + } + + input = xfc->context.input; + + if (flags != 0) + { + if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) + { + if (down) + freerdp_input_send_mouse_event(input, flags, 0, 0); + } + else + { + BOOL extended = FALSE; + + if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2)) + { + extended = TRUE; + + if (down) + flags |= PTR_XFLAGS_DOWN; + } + else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3)) + { + if (down) + flags |= PTR_FLAGS_DOWN; + } + + if (app) + { + /* make sure window exists */ + if (!xf_AppWindowFromX11Window(xfc, window)) + return TRUE; + + /* Translate to desktop coordinates */ + XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y, + &x, &y, &childWindow); + } + + xf_event_adjust_coordinates(xfc, &x, &y); + + if (extended) + freerdp_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, const XButtonEvent* event, BOOL app) +{ + if (xfc->use_xinput) + return TRUE; + + return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app, TRUE); +} + +static BOOL xf_event_ButtonRelease(xfContext* xfc, const XButtonEvent* event, BOOL app) +{ + if (xfc->use_xinput) + return TRUE; + + return xf_generic_ButtonEvent(xfc, event->x, event->y, event->button, event->window, app, + FALSE); +} + +static BOOL xf_event_KeyPress(xfContext* xfc, const XKeyEvent* event, BOOL app) +{ + KeySym keysym; + char str[256]; + WINPR_UNUSED(app); + XLookupString((XKeyEvent*)event, str, sizeof(str), &keysym, NULL); + xf_keyboard_key_press(xfc, event->keycode, keysym); + return TRUE; +} + +static BOOL xf_event_KeyRelease(xfContext* xfc, const XKeyEvent* event, BOOL app) +{ + KeySym keysym; + char str[256]; + WINPR_UNUSED(app); + XLookupString((XKeyEvent*)event, str, sizeof(str), &keysym, NULL); + xf_keyboard_key_release(xfc, event->keycode, keysym); + return TRUE; +} + +static BOOL xf_event_FocusIn(xfContext* xfc, const XFocusInEvent* event, BOOL app) +{ + if (event->mode == NotifyGrab) + return TRUE; + + xfc->focused = TRUE; + + if (xfc->mouse_active && !app) + { + if (!xfc->window) + return FALSE; + + XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, GrabModeAsync, + CurrentTime); + } + + /* Release all keys, should already be done at FocusOut but might be missed + * if the WM decided to use an alternate event order */ + xf_keyboard_release_all_keypress(xfc); + xf_pointer_update_scale(xfc); + + if (app) + { + xfAppWindow* appWindow; + xf_rail_send_activate(xfc, event->window, TRUE); + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* Update the server with any window changes that occurred while the window was not focused. + */ + if (appWindow) + { + xf_rail_adjust_position(xfc, appWindow); + } + } + + xf_keyboard_focus_in(xfc); + return TRUE; +} + +static BOOL xf_event_FocusOut(xfContext* xfc, const XFocusOutEvent* event, BOOL app) +{ + if (event->mode == NotifyUngrab) + return TRUE; + + xfc->focused = FALSE; + + if (event->mode == NotifyWhileGrabbed) + XUngrabKeyboard(xfc->display, CurrentTime); + + xf_keyboard_release_all_keypress(xfc); + + if (app) + xf_rail_send_activate(xfc, event->window, FALSE); + + return TRUE; +} + +static BOOL xf_event_MappingNotify(xfContext* xfc, const XMappingEvent* event, BOOL app) +{ + WINPR_UNUSED(app); + + if (event->request == MappingModifier) + { + if (xfc->modifierMap) + XFreeModifiermap(xfc->modifierMap); + + xfc->modifierMap = XGetModifierMapping(xfc->display); + } + + return TRUE; +} + +static BOOL xf_event_ClientMessage(xfContext* xfc, const XClientMessageEvent* event, BOOL app) +{ + if ((event->message_type == xfc->WM_PROTOCOLS) && + ((Atom)event->data.l[0] == xfc->WM_DELETE_WINDOW)) + { + if (app) + { + xfAppWindow* appWindow; + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_CLOSE); + } + + return TRUE; + } + else + { + DEBUG_X11("Main window closed"); + return FALSE; + } + } + + return TRUE; +} + +static BOOL xf_event_EnterNotify(xfContext* xfc, const XEnterWindowEvent* event, BOOL app) +{ + if (!app) + { + if (!xfc->window) + return FALSE; + + xfc->mouse_active = TRUE; + + if (xfc->fullscreen) + XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime); + + if (xfc->focused) + XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, GrabModeAsync, + CurrentTime); + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* keep track of which window has focus so that we can apply pointer updates */ + xfc->appWindow = appWindow; + } + + return TRUE; +} + +static BOOL xf_event_LeaveNotify(xfContext* xfc, const XLeaveWindowEvent* event, BOOL app) +{ + if (!app) + { + xfc->mouse_active = FALSE; + XUngrabKeyboard(xfc->display, CurrentTime); + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* keep track of which window has focus so that we can apply pointer updates */ + if (xfc->appWindow == appWindow) + xfc->appWindow = NULL; + } + return TRUE; +} + +static BOOL xf_event_ConfigureNotify(xfContext* xfc, const XConfigureEvent* event, BOOL app) +{ + Window childWindow; + xfAppWindow* appWindow; + rdpSettings* settings; + settings = xfc->context.settings; + + WLog_DBG(TAG, "%s: x=%" PRId32 ", y=%" PRId32 ", w=%" PRId32 ", h=%" PRId32, __func__, event->x, + event->y, event->width, event->height); + + if (!app) + { + if (!xfc->window) + return FALSE; + + if (xfc->window->left != event->x) + xfc->window->left = event->x; + + if (xfc->window->top != event->y) + xfc->window->top = event->y; + + if (xfc->window->width != event->width || xfc->window->height != event->height) + { + xfc->window->width = event->width; + xfc->window->height = event->height; +#ifdef WITH_XRENDER + xfc->offset_x = 0; + xfc->offset_y = 0; + + if (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); + } + } + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + /* + * ConfigureNotify coordinates are expressed relative to the window parent. + * Translate these to root window coordinates. + */ + XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen), + 0, 0, &appWindow->x, &appWindow->y, &childWindow); + appWindow->width = event->width; + appWindow->height = event->height; + + /* + * Additional checks for not in a local move and not ignoring configure to send + * position update to server, also should the window not be focused then do not + * send to server yet (i.e. resizing using window decoration). + * The server will be updated when the window gets refocused. + */ + if (appWindow->decorations) + { + /* moving resizing using window decoration */ + xf_rail_adjust_position(xfc, appWindow); + } + else + { + if ((!event->send_event || appWindow->local_move.state == LMS_NOT_ACTIVE) && + !appWindow->rail_ignore_configure && xfc->focused) + xf_rail_adjust_position(xfc, appWindow); + } + } + } + return xf_pointer_update_scale(xfc); +} + +static BOOL xf_event_MapNotify(xfContext* xfc, const XMapEvent* event, BOOL app) +{ + xfAppWindow* appWindow; + + if (!app) + gdi_send_suppress_output(xfc->context.gdi, FALSE); + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow && (appWindow->rail_state == WINDOW_SHOW)) + { + /* local restore event */ + /* This is now handled as part of the PropertyNotify + * Doing this here would inhibit the ability to restore a maximized window + * that is minimized back to the maximized state + */ + // xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE); + appWindow->is_mapped = TRUE; + } + } + + return TRUE; +} + +static BOOL xf_event_UnmapNotify(xfContext* xfc, const XUnmapEvent* event, BOOL app) +{ + xfAppWindow* appWindow; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + xf_keyboard_release_all_keypress(xfc); + + if (!app) + gdi_send_suppress_output(xfc->context.gdi, TRUE); + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + appWindow->is_mapped = FALSE; + } + } + + return TRUE; +} + +static BOOL xf_event_PropertyNotify(xfContext* xfc, const XPropertyEvent* event, BOOL app) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + /* + * This section handles sending the appropriate commands to the rail server + * when the window has been minimized, maximized, restored locally + * ie. not using the buttons on the rail window itself + */ + if ((((Atom)event->atom == xfc->_NET_WM_STATE) && (event->state != PropertyDelete)) || + (((Atom)event->atom == xfc->WM_STATE) && (event->state != PropertyDelete))) + { + unsigned long 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->window); + + if (!appWindow) + return TRUE; + } + + if ((Atom)event->atom == xfc->_NET_WM_STATE) + { + status = xf_GetWindowProperty(xfc, event->window, xfc->_NET_WM_STATE, 12, &nitems, + &bytes, &prop); + + if (status) + { + if (appWindow) + { + appWindow->maxVert = FALSE; + appWindow->maxHorz = FALSE; + } + for (i = 0; i < nitems; i++) + { + if ((Atom)((UINT16**)prop)[i] == + XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False)) + { + maxVert = TRUE; + if (appWindow) + appWindow->maxVert = TRUE; + } + + if ((Atom)((UINT16**)prop)[i] == + XInternAtom(xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False)) + { + maxHorz = TRUE; + if (appWindow) + appWindow->maxHorz = TRUE; + } + } + + XFree(prop); + } + } + + if ((Atom)event->atom == xfc->WM_STATE) + { + status = + xf_GetWindowProperty(xfc, event->window, xfc->WM_STATE, 1, &nitems, &bytes, &prop); + + if (status) + { + /* If the window is in the iconic state */ + if (((UINT32)*prop == 3)) + { + minimized = TRUE; + if (appWindow) + appWindow->minimized = TRUE; + } + else + { + minimized = FALSE; + if (appWindow) + appWindow->minimized = FALSE; + } + + minimizedChanged = TRUE; + XFree(prop); + } + } + + if (app) + { + WINPR_ASSERT(appWindow); + if (appWindow->maxVert && appWindow->maxHorz && !appWindow->minimized) + { + if (appWindow->rail_state != WINDOW_SHOW_MAXIMIZED) + { + appWindow->rail_state = WINDOW_SHOW_MAXIMIZED; + xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MAXIMIZE); + } + } + else if (appWindow->minimized) + { + if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED) + { + appWindow->rail_state = WINDOW_SHOW_MINIMIZED; + xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MINIMIZE); + } + } + else + { + if (appWindow->rail_state != WINDOW_SHOW && appWindow->rail_state != WINDOW_HIDE) + { + appWindow->rail_state = WINDOW_SHOW; + xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE); + } + } + } + else if (minimizedChanged) + gdi_send_suppress_output(xfc->context.gdi, minimized); + } + + return TRUE; +} + +static BOOL xf_event_suppress_events(xfContext* xfc, xfAppWindow* appWindow, const XEvent* event) +{ + if (!xfc->remote_app) + return FALSE; + + switch (appWindow->local_move.state) + { + case LMS_NOT_ACTIVE: + + /* No local move in progress, nothing to do */ + + /* Prevent Configure from happening during indeterminant state of Horz or Vert Max only + */ + if ((event->type == ConfigureNotify) && appWindow->rail_ignore_configure) + { + appWindow->rail_ignore_configure = FALSE; + return TRUE; + } + + break; + + case LMS_STARTING: + + /* Local move initiated by RDP server, but we have not yet seen any updates from the X + * server */ + switch (event->type) + { + case ConfigureNotify: + /* Starting to see move events from the X server. Local move is now in progress. + */ + appWindow->local_move.state = LMS_ACTIVE; + /* Allow these events to be processed during move to keep our state up to date. + */ + break; + + case ButtonPress: + case ButtonRelease: + case KeyPress: + case KeyRelease: + case UnmapNotify: + /* + * A button release event means the X window server did not grab the + * mouse before the user released it. In this case we must cancel the + * local move. The event will be processed below as normal, below. + */ + break; + + case VisibilityNotify: + case PropertyNotify: + case Expose: + /* Allow these events to pass */ + break; + + default: + /* Eat any other events */ + return TRUE; + } + + break; + + case LMS_ACTIVE: + + /* Local move is in progress */ + switch (event->type) + { + case ConfigureNotify: + case VisibilityNotify: + case PropertyNotify: + case Expose: + case GravityNotify: + /* Keep us up to date on position */ + break; + + default: + /* Any other event terminates move */ + xf_rail_end_local_move(xfc, appWindow); + break; + } + + break; + + case LMS_TERMINATING: + /* Already sent RDP end move to server. Allow events to pass. */ + break; + } + + return FALSE; +} + +BOOL xf_event_process(freerdp* instance, const XEvent* event) +{ + BOOL status = TRUE; + 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->window) + { + if (xf_floatbar_check_event(xfc->window->floatbar, event)) + { + xf_floatbar_event_process(xfc->window->floatbar, 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->xexpose, xfc->remote_app); + break; + + case VisibilityNotify: + status = xf_event_VisibilityNotify(xfc, &event->xvisibility, xfc->remote_app); + break; + + case MotionNotify: + status = xf_event_MotionNotify(xfc, &event->xmotion, xfc->remote_app); + break; + + case ButtonPress: + status = xf_event_ButtonPress(xfc, &event->xbutton, xfc->remote_app); + break; + + case ButtonRelease: + status = xf_event_ButtonRelease(xfc, &event->xbutton, xfc->remote_app); + break; + + case KeyPress: + status = xf_event_KeyPress(xfc, &event->xkey, xfc->remote_app); + break; + + case KeyRelease: + status = xf_event_KeyRelease(xfc, &event->xkey, xfc->remote_app); + break; + + case FocusIn: + status = xf_event_FocusIn(xfc, &event->xfocus, xfc->remote_app); + break; + + case FocusOut: + status = xf_event_FocusOut(xfc, &event->xfocus, xfc->remote_app); + break; + + case EnterNotify: + status = xf_event_EnterNotify(xfc, &event->xcrossing, xfc->remote_app); + break; + + case LeaveNotify: + status = xf_event_LeaveNotify(xfc, &event->xcrossing, xfc->remote_app); + break; + + case NoExpose: + break; + + case GraphicsExpose: + break; + + case ConfigureNotify: + status = xf_event_ConfigureNotify(xfc, &event->xconfigure, xfc->remote_app); + break; + + case MapNotify: + status = xf_event_MapNotify(xfc, &event->xmap, xfc->remote_app); + break; + + case UnmapNotify: + status = xf_event_UnmapNotify(xfc, &event->xunmap, xfc->remote_app); + break; + + case ReparentNotify: + break; + + case MappingNotify: + status = xf_event_MappingNotify(xfc, &event->xmapping, xfc->remote_app); + break; + + case ClientMessage: + status = xf_event_ClientMessage(xfc, &event->xclient, xfc->remote_app); + break; + + case PropertyNotify: + status = xf_event_PropertyNotify(xfc, &event->xproperty, xfc->remote_app); + break; + + default: + if (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..2269d3e --- /dev/null +++ b/client/X11/xf_event.h @@ -0,0 +1,43 @@ +/** + * 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, const XEvent* event); +void xf_event_SendClientEvent(xfContext* xfc, xfWindow* window, Atom atom, unsigned int numArgs, + ...); + +void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y); +void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y); + +BOOL xf_generic_MotionNotify(xfContext* xfc, int x, int y, int state, Window window, BOOL app); +BOOL xf_generic_ButtonPress(xfContext* xfc, int x, int y, int button, Window window, BOOL app); +BOOL xf_generic_ButtonEvent(xfContext* xfc, int x, int y, int button, Window window, BOOL app, + BOOL down); + +#endif /* FREERDP_CLIENT_X11_EVENT_H */ diff --git a/client/X11/xf_floatbar.c b/client/X11/xf_floatbar.c new file mode 100644 index 0000000..0966ff5 --- /dev/null +++ b/client/X11/xf_floatbar.c @@ -0,0 +1,813 @@ +/** + * 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 "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 BOOL (*OnClick)(xfFloatbar*); + +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; + BOOL hasCursor; + xfContext* xfc; + DWORD flags; + BOOL created; + Window root_window; + char* title; +}; + +struct xf_floatbar_button +{ + int x; + int y; + int type; + bool focus; + bool clicked; + OnClick onclick; + Window handle; +}; + +static xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type); + +static BOOL xf_floatbar_button_onclick_close(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + return freerdp_abort_connect(floatbar->xfc->context.instance); +} + +static BOOL xf_floatbar_button_onclick_minimize(xfFloatbar* floatbar) +{ + xfContext* xfc; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + xfc = floatbar->xfc; + xf_SetWindowMinimized(xfc, xfc->window); + return TRUE; +} + +static BOOL xf_floatbar_button_onclick_restore(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + xf_toggle_fullscreen(floatbar->xfc); + return TRUE; +} + +static BOOL xf_floatbar_button_onclick_locked(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + floatbar->locked = (floatbar->locked) ? FALSE : TRUE; + return xf_floatbar_hide_and_show(floatbar); +} + +BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y) +{ + if (!floatbar) + return FALSE; + + floatbar->last_motion_y_root = y; + return TRUE; +} + +BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar) +{ + xfContext* xfc; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + if (!floatbar->created) + return TRUE; + + xfc = floatbar->xfc; + + if (!floatbar->locked) + { + if ((floatbar->mode == XF_FLOATBAR_MODE_NONE) && (floatbar->last_motion_y_root > 10) && + (floatbar->y > (FLOATBAR_HEIGHT * -1))) + { + floatbar->y = floatbar->y - 1; + XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y); + } + else if (floatbar->y < 0 && (floatbar->last_motion_y_root < 10)) + { + floatbar->y = floatbar->y + 1; + XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y); + } + } + + return TRUE; +} + +static BOOL create_floatbar(xfFloatbar* floatbar) +{ + xfContext* xfc; + Status status; + XWindowAttributes attr; + + if (floatbar->created) + return TRUE; + + xfc = floatbar->xfc; + status = XGetWindowAttributes(xfc->display, floatbar->root_window, &attr); + floatbar->x = attr.x + attr.width / 2 - FLOATBAR_DEFAULT_WIDTH / 2; + floatbar->y = 0; + + if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) + floatbar->y = -FLOATBAR_HEIGHT + 1; + + floatbar->handle = + XCreateWindow(xfc->display, floatbar->root_window, floatbar->x, 0, FLOATBAR_DEFAULT_WIDTH, + FLOATBAR_HEIGHT, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + floatbar->width = FLOATBAR_DEFAULT_WIDTH; + floatbar->height = FLOATBAR_HEIGHT; + floatbar->mode = XF_FLOATBAR_MODE_NONE; + floatbar->buttons[0] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_CLOSE); + floatbar->buttons[1] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_RESTORE); + floatbar->buttons[2] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_MINIMIZE); + floatbar->buttons[3] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_LOCKED); + XSelectInput(xfc->display, floatbar->handle, + ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask | + PropertyChangeMask); + floatbar->created = TRUE; + return TRUE; +} + +BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen) +{ + int i, size; + bool visible = False; + xfContext* xfc; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + xfc = floatbar->xfc; + + /* Only visible if enabled */ + if (floatbar->flags & 0x0001) + { + /* Visible if fullscreen and flag visible in fullscreen mode */ + visible |= ((floatbar->flags & 0x0010) != 0) && fullscreen; + /* Visible if window and flag visible in window mode */ + visible |= ((floatbar->flags & 0x0020) != 0) && !fullscreen; + } + + if (visible) + { + if (!create_floatbar(floatbar)) + return FALSE; + + XMapWindow(xfc->display, floatbar->handle); + size = ARRAYSIZE(floatbar->buttons); + + for (i = 0; i < size; i++) + { + XMapWindow(xfc->display, floatbar->buttons[i]->handle); + } + + /* If default is hidden (and not sticky) don't show on fullscreen state changes */ + if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) + floatbar->y = -FLOATBAR_HEIGHT + 1; + + xf_floatbar_hide_and_show(floatbar); + } + else if (floatbar->created) + { + XUnmapSubwindows(xfc->display, floatbar->handle); + XUnmapWindow(xfc->display, floatbar->handle); + } + + return TRUE; +} + +xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type) +{ + xfFloatbarButton* button; + button = (xfFloatbarButton*)calloc(1, sizeof(xfFloatbarButton)); + button->type = type; + + switch (type) + { + case XF_FLOATBAR_BUTTON_CLOSE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_close; + break; + + case XF_FLOATBAR_BUTTON_RESTORE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_restore; + break; + + case XF_FLOATBAR_BUTTON_MINIMIZE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_minimize; + break; + + case XF_FLOATBAR_BUTTON_LOCKED: + button->x = FLOATBAR_BORDER; + button->onclick = xf_floatbar_button_onclick_locked; + break; + + default: + break; + } + + button->y = 0; + button->focus = FALSE; + button->handle = XCreateWindow(floatbar->xfc->display, floatbar->handle, button->x, 0, + FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, CopyFromParent, + InputOutput, CopyFromParent, 0, NULL); + XSelectInput(floatbar->xfc->display, button->handle, + ExposureMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask | + LeaveWindowMask | EnterWindowMask | StructureNotifyMask); + return button; +} + +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* name, DWORD flags) +{ + xfFloatbar* floatbar; + + /* Floatbar not enabled */ + if ((flags & 0x0001) == 0) + return NULL; + + if (!xfc) + return NULL; + + /* Force disable with remote app */ + if (xfc->remote_app) + return NULL; + + floatbar = (xfFloatbar*)calloc(1, sizeof(xfFloatbar)); + + if (!floatbar) + return NULL; + + floatbar->title = _strdup(name); + + if (!floatbar->title) + goto fail; + + floatbar->root_window = window; + floatbar->flags = flags; + floatbar->xfc = xfc; + floatbar->locked = flags & 0x0002; + xf_floatbar_toggle_fullscreen(floatbar, FALSE); + return floatbar; +fail: + xf_floatbar_free(floatbar); + return NULL; +} + +static unsigned long xf_floatbar_get_color(xfFloatbar* floatbar, char* rgb_value) +{ + Colormap cmap; + XColor color; + Display* display = floatbar->xfc->display; + cmap = DefaultColormap(display, XDefaultScreen(display)); + XParseColor(display, cmap, rgb_value, &color); + XAllocColor(display, cmap, &color); + return color.pixel; +} + +static void xf_floatbar_event_expose(xfFloatbar* floatbar) +{ + GC gc, shape_gc; + Pixmap pmap; + XPoint shape[5], border[5]; + int len; + Display* display = floatbar->xfc->display; + + /* create the pixmap that we'll use for shaping the window */ + pmap = XCreatePixmap(display, floatbar->handle, floatbar->width, floatbar->height, 1); + gc = XCreateGC(display, floatbar->handle, 0, 0); + shape_gc = XCreateGC(display, pmap, 0, 0); + /* points for drawing the floatbar */ + shape[0].x = 0; + shape[0].y = 0; + shape[1].x = floatbar->width; + shape[1].y = 0; + shape[2].x = shape[1].x - FLOATBAR_BORDER; + shape[2].y = FLOATBAR_HEIGHT; + shape[3].x = shape[0].x + FLOATBAR_BORDER; + shape[3].y = FLOATBAR_HEIGHT; + shape[4].x = shape[0].x; + shape[4].y = shape[0].y; + /* points for drawing the border of the floatbar */ + border[0].x = shape[0].x; + border[0].y = shape[0].y - 1; + border[1].x = shape[1].x - 1; + border[1].y = shape[1].y - 1; + border[2].x = shape[2].x; + border[2].y = shape[2].y - 1; + border[3].x = shape[3].x - 1; + border[3].y = shape[3].y - 1; + border[4].x = border[0].x; + border[4].y = border[0].y; + /* Fill all pixels with 0 */ + XSetForeground(display, shape_gc, 0); + XFillRectangle(display, pmap, shape_gc, 0, 0, floatbar->width, floatbar->height); + /* Fill all pixels which should be shown with 1 */ + XSetForeground(display, shape_gc, 1); + XFillPolygon(display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin); + XShapeCombineMask(display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet); + /* draw the float bar */ + XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND)); + XFillPolygon(display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin); + /* draw an border for the floatbar */ + XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); + XDrawLines(display, floatbar->handle, gc, border, 5, CoordModeOrigin); + /* draw the host name connected to (limit to maximum file name) */ + len = strnlen(floatbar->title, MAX_PATH); + XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); + XDrawString(display, floatbar->handle, gc, floatbar->width / 2 - len * 2, 15, floatbar->title, + len); + XFreeGC(display, gc); + XFreeGC(display, shape_gc); +} + +static xfFloatbarButton* xf_floatbar_get_button(xfFloatbar* floatbar, Window window) +{ + int i, size; + size = ARRAYSIZE(floatbar->buttons); + + for (i = 0; i < size; i++) + { + if (floatbar->buttons[i]->handle == window) + { + return floatbar->buttons[i]; + } + } + + return NULL; +} + +static void xf_floatbar_button_update_positon(xfFloatbar* floatbar) +{ + xfFloatbarButton* button; + int i, size; + xfContext* xfc = floatbar->xfc; + 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(floatbar); + } +} + +static void xf_floatbar_button_event_expose(xfFloatbar* floatbar, Window window) +{ + xfFloatbarButton* button = xf_floatbar_get_button(floatbar, window); + static unsigned char* bits; + GC gc; + Pixmap pattern; + xfContext* xfc = floatbar->xfc; + + 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(floatbar, FLOATBAR_COLOR_BACKGROUND)); + else + XSetForeground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); + + XSetBackground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); + XCopyPlane(xfc->display, pattern, button->handle, gc, 0, 0, FLOATBAR_BUTTON_WIDTH, + FLOATBAR_BUTTON_WIDTH, 0, 0, 1); + XFreePixmap(xfc->display, pattern); + XFreeGC(xfc->display, gc); +} + +static void xf_floatbar_button_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event) +{ + xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + button->clicked = TRUE; +} + +static void xf_floatbar_button_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + if (button->clicked) + button->onclick(floatbar); + button->clicked = FALSE; + } +} + +static void xf_floatbar_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event) +{ + switch (event->button) + { + case Button1: + if (event->x <= FLOATBAR_BORDER) + floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT; + else if (event->x >= (floatbar->width - FLOATBAR_BORDER)) + floatbar->mode = XF_FLOATBAR_MODE_RESIZE_RIGHT; + else + floatbar->mode = XF_FLOATBAR_MODE_DRAGGING; + + break; + + default: + break; + } +} + +static void xf_floatbar_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event) +{ + switch (event->button) + { + case Button1: + floatbar->mode = XF_FLOATBAR_MODE_NONE; + break; + + default: + break; + } +} + +static void xf_floatbar_resize(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int x, width, movement; + xfContext* xfc = floatbar->xfc; + /* calculate movement which happened on the root window */ + movement = event->x_root - floatbar->last_motion_x_root; + + /* set x and width depending if movement happens on the left or right */ + if (floatbar->mode == XF_FLOATBAR_MODE_RESIZE_LEFT) + { + x = floatbar->x + movement; + width = floatbar->width + movement * -1; + } + else + { + x = floatbar->x; + width = floatbar->width + movement; + } + + /* only resize and move window if still above minimum width */ + if (FLOATBAR_MIN_WIDTH < width) + { + XMoveResizeWindow(xfc->display, floatbar->handle, x, 0, width, floatbar->height); + floatbar->x = x; + floatbar->width = width; + } +} + +static void xf_floatbar_dragging(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int x, movement; + xfContext* xfc = floatbar->xfc; + /* calculate movement and new x position */ + movement = event->x_root - floatbar->last_motion_x_root; + x = floatbar->x + movement; + + /* do nothing if floatbar would be moved out of the window */ + if (x < 0 || (x + floatbar->width) > xfc->window->width) + return; + + /* move window to new x position */ + XMoveWindow(xfc->display, floatbar->handle, x, 0); + /* update struct values for the next event */ + floatbar->last_motion_x_root = floatbar->last_motion_x_root + movement; + floatbar->x = x; +} + +static void xf_floatbar_event_motionnotify(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int mode; + Cursor cursor; + xfContext* xfc = floatbar->xfc; + mode = floatbar->mode; + cursor = XCreateFontCursor(xfc->display, XC_arrow); + + if ((event->state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_resize(floatbar, event); + } + else if ((event->state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_dragging(floatbar, event); + } + else + { + if (event->x <= FLOATBAR_BORDER || event->x >= floatbar->width - FLOATBAR_BORDER) + cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow); + } + + XDefineCursor(xfc->display, xfc->window->handle, cursor); + XFreeCursor(xfc->display, cursor); + floatbar->last_motion_x_root = event->x_root; +} + +static void xf_floatbar_button_event_focusin(xfFloatbar* floatbar, const XAnyEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + button->focus = TRUE; + xf_floatbar_button_event_expose(floatbar, event->window); + } +} + +static void xf_floatbar_button_event_focusout(xfFloatbar* floatbar, const XAnyEvent* event) +{ + xfFloatbarButton* button; + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + button->focus = FALSE; + xf_floatbar_button_event_expose(floatbar, event->window); + } +} + +static void xf_floatbar_event_focusout(xfFloatbar* floatbar) +{ + xfContext* xfc = floatbar->xfc; + + if (xfc->pointer) + { + XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); + } +} + +BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event) +{ + xfFloatbarButton* button; + size_t i, size; + + if (!floatbar || !floatbar->xfc || !event) + return FALSE; + + if (!floatbar->created) + return FALSE; + + 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(xfFloatbar* floatbar, const XEvent* event) +{ + if (!floatbar || !floatbar->xfc || !event) + return FALSE; + + if (!floatbar->created) + return FALSE; + + switch (event->type) + { + case Expose: + if (event->xexpose.window == floatbar->handle) + xf_floatbar_event_expose(floatbar); + else + xf_floatbar_button_event_expose(floatbar, event->xexpose.window); + + break; + + case MotionNotify: + xf_floatbar_event_motionnotify(floatbar, &event->xmotion); + break; + + case ButtonPress: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonpress(floatbar, &event->xbutton); + else + xf_floatbar_button_event_buttonpress(floatbar, &event->xbutton); + + break; + + case ButtonRelease: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonrelease(floatbar, &event->xbutton); + else + xf_floatbar_button_event_buttonrelease(floatbar, &event->xbutton); + + break; + + case EnterNotify: + case FocusIn: + if (event->xany.window != floatbar->handle) + xf_floatbar_button_event_focusin(floatbar, &event->xany); + + break; + + case LeaveNotify: + case FocusOut: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_focusout(floatbar); + else + xf_floatbar_button_event_focusout(floatbar, &event->xany); + + break; + + case ConfigureNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(floatbar); + + break; + + case PropertyNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(floatbar); + + break; + + default: + break; + } + + return floatbar->handle == event->xany.window; +} + +static void xf_floatbar_button_free(xfContext* xfc, xfFloatbarButton* button) +{ + if (!button) + return; + + if (button->handle) + { + XUnmapWindow(xfc->display, button->handle); + XDestroyWindow(xfc->display, button->handle); + } + + free(button); +} + +void xf_floatbar_free(xfFloatbar* floatbar) +{ + size_t i, size; + xfContext* xfc; + + if (!floatbar) + return; + + free(floatbar->title); + xfc = floatbar->xfc; + size = ARRAYSIZE(floatbar->buttons); + + 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..145514b --- /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" + +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* title, DWORD flags); +void xf_floatbar_free(xfFloatbar* floatbar); + +BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event); +BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event); +BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool visible); +BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar); +BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y); + +#endif /* FREERDP_CLIENT_X11_FLOATBAR_H */ diff --git a/client/X11/xf_gdi.c b/client/X11/xf_gdi.c new file mode 100644 index 0000000..4f52853 --- /dev/null +++ b/client/X11/xf_gdi.c @@ -0,0 +1,1114 @@ +/** + * 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 * 4ULL, 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); + + 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); + return TRUE; +} + +static BOOL xf_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt) +{ + xfContext* xfc = (xfContext*)context; + BOOL ret = FALSE; + xf_lock_x11(xfc); + + 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); + 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); + 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); + 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); + + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + + 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); + 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); + 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); + return ret; +} + +static BOOL xf_gdi_polygon_sc(rdpContext* context, const POLYGON_SC_ORDER* polygon_sc) +{ + UINT32 i; + int 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); + xf_set_rop2(xfc, polygon_sc->bRop2); + npoints = polygon_sc->numPoints + 1; + points = calloc(npoints, sizeof(XPoint)); + + if (!points) + { + xf_unlock_x11(xfc); + 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); + return ret; +} + +static BOOL xf_gdi_polygon_cb(rdpContext* context, POLYGON_CB_ORDER* polygon_cb) +{ + UINT32 i; + int 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); + 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); + 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); + 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); + + 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); + 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; + size_t size; + 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); + + 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); + size = cmd->bmp.width * cmd->bmp.height * GetBytesPerPixel(format) * 1ULL; + if (size > cmd->bmp.bitmapDataLength) + { + WLog_ERR(TAG, "Short nocodec message: got %" PRIu32 " bytes, require %" PRIuz, + cmd->bmp.bitmapDataLength, size); + goto fail; + } + + 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); + goto fail; + } + + ret = xf_gdi_update_screen(xfc, gdi->primary_buffer, gdi->stride, ®ion); +fail: + region16_uninit(®ion); + xf_unlock_x11(xfc); + 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..97d3ad3 --- /dev/null +++ b/client/X11/xf_gfx.c @@ -0,0 +1,418 @@ +/** + * 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; + double sx, sy; + 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.mappedWidth; + surfaceRect.bottom = surface->gdi.mappedHeight; + XSetClipMask(xfc->display, xfc->gc, None); + XSetFunction(xfc->display, xfc->gc, GXcopy); + XSetFillStyle(xfc->display, xfc->gc, FillSolid); + region16_intersect_rect(&(surface->gdi.invalidRegion), &(surface->gdi.invalidRegion), + &surfaceRect); + sx = surface->gdi.outputTargetWidth / (double)surface->gdi.mappedWidth; + sy = surface->gdi.outputTargetHeight / (double)surface->gdi.mappedHeight; + + if (!(rects = region16_rects(&surface->gdi.invalidRegion, &nbRects))) + return CHANNEL_RC_OK; + + for (x = 0; x < nbRects; x++) + { + const UINT32 nXSrc = rects[x].left; + const UINT32 nYSrc = rects[x].top; + const UINT32 swidth = rects[x].right - nXSrc; + const UINT32 sheight = rects[x].bottom - nYSrc; + const UINT32 nXDst = surfaceX + nXSrc * sx; + const UINT32 nYDst = surfaceY + nYSrc * sy; + const UINT32 dwidth = swidth * sx; + const UINT32 dheight = sheight * sy; + + if (surface->stage) + { + if (!freerdp_image_scale(surface->stage, gdi->dstFormat, surface->stageScanline, nXSrc, + nYSrc, dwidth, dheight, surface->gdi.data, surface->gdi.format, + surface->gdi.scanline, nXSrc, nYSrc, swidth, sheight)) + goto fail; + } + + if (xfc->remote_app) + { + XPutImage(xfc->display, xfc->primary, xfc->gc, surface->image, nXSrc, nYSrc, nXDst, + nYDst, dwidth, dheight); + xf_lock_x11(xfc); + xf_rail_paint(xfc, nXDst, nYDst, nXDst + dwidth, nYDst + dheight); + xf_unlock_x11(xfc); + } + 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, dwidth, dheight); + xf_draw_screen(xfc, nXDst, nYDst, dwidth, dheight); + } + else +#endif + { + XPutImage(xfc->display, xfc->drawable, xfc->gc, surface->image, nXSrc, nYSrc, nXDst, + nYDst, dwidth, dheight); + } + } + + rc = CHANNEL_RC_OK; +fail: + region16_clear(&surface->gdi.invalidRegion); + XSetClipMask(xfc->display, xfc->gc, None); + XSync(xfc->display, False); + return rc; +} + +static UINT xf_UpdateSurfaces(RdpgfxClientContext* context) +{ + UINT16 count; + UINT32 index; + UINT status = CHANNEL_RC_OK; + UINT16* pSurfaceIds = NULL; + rdpGdi* gdi = (rdpGdi*)context->custom; + xfContext* xfc; + + if (!gdi) + return status; + + if (gdi->suppressOutput) + return CHANNEL_RC_OK; + + xfc = (xfContext*)gdi->context; + EnterCriticalSection(&context->mux); + context->GetSurfaceIds(context, &pSurfaceIds, &count); + + for (index = 0; index < count; index++) + { + xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]); + + if (!surface) + continue; + + /* If UpdateSurfaceArea callback is available, the output has already been updated. */ + if (context->UpdateSurfaceArea) + { + if (surface->gdi.windowId != 0) + continue; + } + + status = ERROR_INTERNAL_ERROR; + + if (surface->gdi.outputMapped) + status = xf_OutputUpdate(xfc, surface); + + if (status != 0) + break; + } + + free(pSurfaceIds); + LeaveCriticalSection(&context->mux); + return status; +} + +UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height) +{ + UINT16 count; + UINT32 index; + UINT status = ERROR_INTERNAL_ERROR; + 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; + status = context->GetSurfaceIds(context, &pSurfaceIds, &count); + + if (status != CHANNEL_RC_OK) + goto fail; + + if (!TryEnterCriticalSection(&context->mux)) + { + free(pSurfaceIds); + return CHANNEL_RC_OK; + } + 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.outputTargetWidth; + surfaceRect.bottom = surface->gdi.outputOriginY + surface->gdi.outputTargetHeight; + + if (rectangles_intersection(&invalidRect, &surfaceRect, &intersection)) + { + /* Invalid rects are specified relative to surface origin */ + intersection.left -= surfaceRect.left; + intersection.top -= surfaceRect.top; + intersection.right -= surfaceRect.left; + intersection.bottom -= surfaceRect.top; + region16_union_rect(&surface->gdi.invalidRegion, &surface->gdi.invalidRegion, + &intersection); + } + } + + free(pSurfaceIds); + LeaveCriticalSection(&context->mux); + IFCALLRET(context->UpdateSurfaces, status, context); + + if (status != CHANNEL_RC_OK) + goto fail; + +fail: + return status; +} + +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 = x11_pad_scanline(createSurface->width, 0); + surface->gdi.height = x11_pad_scanline(createSurface->height, 0); + surface->gdi.mappedWidth = createSurface->width; + surface->gdi.mappedHeight = createSurface->height; + surface->gdi.outputTargetWidth = createSurface->width; + surface->gdi.outputTargetHeight = createSurface->height; + + switch (createSurface->pixelFormat) + { + case GFX_PIXEL_FORMAT_ARGB_8888: + surface->gdi.format = PIXEL_FORMAT_BGRA32; + break; + + case GFX_PIXEL_FORMAT_XRGB_8888: + surface->gdi.format = PIXEL_FORMAT_BGRX32; + break; + + default: + WLog_ERR(TAG, "%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 * 1ULL; + 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.mappedWidth, + surface->gdi.mappedHeight, 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 * 1ULL; + 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.mappedWidth, surface->gdi.mappedHeight, 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; + UINT status; + EnterCriticalSection(&context->mux); + surface = (xfGfxSurface*)context->GetSurfaceData(context, deleteSurface->surfaceId); + + if (surface) + { + if (surface->gdi.windowId > 0) + IFCALL(context->UnmapWindowForSurface, context, surface->gdi.windowId); + +#ifdef WITH_GFX_H264 + h264_context_free(surface->gdi.h264); +#endif + surface->image->data = NULL; + XDestroyImage(surface->image); + _aligned_free(surface->gdi.data); + _aligned_free(surface->stage); + region16_uninit(&surface->gdi.invalidRegion); + codecs = surface->gdi.codecs; + free(surface); + } + + status = context->SetSurfaceData(context, deleteSurface->surfaceId, NULL); + + if (codecs && codecs->progressive) + progressive_delete_surface_context(codecs->progressive, deleteSurface->surfaceId); + + LeaveCriticalSection(&context->mux); + return status; +} + +void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx) +{ + rdpGdi* gdi = xfc->context.gdi; + gdi_graphics_pipeline_init(gdi, gfx); + + if (!xfc->context.settings->SoftwareGdi) + { + 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..934e85a --- /dev/null +++ b/client/X11/xf_gfx.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphics Pipeline + * + * Copyright 2014 Marc-Andre Moreau + * 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; + +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..7050569 --- /dev/null +++ b/client/X11/xf_graphics.c @@ -0,0 +1,790 @@ +/** + * 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 +#include + +#include "xf_graphics.h" +#include "xf_gdi.h" +#include "xf_event.h" + +#include +#define TAG CLIENT_TAG("x11") + +static BOOL xf_Pointer_Set(rdpContext* context, const rdpPointer* pointer); + +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); + 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 ((INT64)depth != xfc->depth) + { + if (!(data = _aligned_malloc(bitmap->width * bitmap->height * 4ULL, 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); + 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); + + 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); + _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); + 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); + 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); + + if (primary) + xfc->drawing = xfc->primary; + else + xfc->drawing = ((xfBitmap*)bitmap)->pixmap; + + xf_unlock_x11(xfc); + return TRUE; +} + +static BOOL xf_Pointer_GetCursorForCurrentScale(rdpContext* context, const rdpPointer* pointer, + Cursor* cursor) +{ +#ifdef WITH_XCURSOR + UINT32 CursorFormat; + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + XcursorImage ci = { 0 }; + rdpSettings* settings; + UINT32 xTargetSize; + UINT32 yTargetSize; + double xscale; + double yscale; + size_t size; + int cursorIndex = -1; + + if (!context || !pointer || !context->gdi) + return FALSE; + + settings = xfc->context.settings; + + if (!settings) + return FALSE; + + xscale = (settings->SmartSizing ? xfc->scaledWidth / (double)settings->DesktopWidth : 1); + yscale = (settings->SmartSizing ? xfc->scaledHeight / (double)settings->DesktopHeight : 1); + xTargetSize = pointer->width * xscale; + yTargetSize = pointer->height * yscale; + + WLog_DBG(TAG, "%s: scaled: %" PRIu32 "x%" PRIu32 ", desktop: %" PRIu32 "x%" PRIu32, __func__, + xfc->scaledWidth, xfc->savedHeight, settings->DesktopWidth, settings->DesktopHeight); + + for (int i = 0; i < xpointer->nCursors; i++) + { + if (xpointer->cursorWidths[i] == xTargetSize && xpointer->cursorHeights[i] == yTargetSize) + { + cursorIndex = i; + } + } + + if (cursorIndex == -1) + { + xf_lock_x11(xfc); + + if (!xfc->invert) + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32; + else + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32; + + if (xpointer->nCursors == xpointer->mCursors) + { + xpointer->mCursors = (xpointer->mCursors == 0 ? 1 : xpointer->mCursors * 2); + + if (!(xpointer->cursorWidths = (UINT32*)realloc(xpointer->cursorWidths, + sizeof(UINT32) * xpointer->mCursors))) + { + xf_unlock_x11(xfc); + return FALSE; + } + if (!(xpointer->cursorHeights = (UINT32*)realloc(xpointer->cursorHeights, + sizeof(UINT32) * xpointer->mCursors))) + { + xf_unlock_x11(xfc); + return FALSE; + } + if (!(xpointer->cursors = + (Cursor*)realloc(xpointer->cursors, sizeof(Cursor) * xpointer->mCursors))) + { + xf_unlock_x11(xfc); + return FALSE; + } + } + + ci.version = XCURSOR_IMAGE_VERSION; + ci.size = sizeof(ci); + ci.width = xTargetSize; + ci.height = yTargetSize; + ci.xhot = pointer->xPos * xscale; + ci.yhot = pointer->yPos * yscale; + size = ci.height * ci.width * GetBytesPerPixel(CursorFormat) * 1ULL; + + if (!(ci.pixels = (XcursorPixel*)_aligned_malloc(size, 16))) + { + xf_unlock_x11(xfc); + return FALSE; + } + + const double xs = fabs(fabs(xscale) - 1.0); + const double ys = fabs(fabs(yscale) - 1.0); + WLog_DBG(TAG, + "%s: cursorIndex %" PRId32 " scaling pointer %" PRIu32 "x%" PRIu32 " --> %" PRIu32 + "x%" PRIu32 " [%lfx%lf]", + __func__, cursorIndex, pointer->width, pointer->height, ci.width, ci.height, + xscale, yscale); + if ((xs > DBL_EPSILON) || (ys > DBL_EPSILON)) + { + if (!freerdp_image_scale((BYTE*)ci.pixels, CursorFormat, 0, 0, 0, ci.width, ci.height, + (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, + pointer->width, pointer->height)) + { + _aligned_free(ci.pixels); + xf_unlock_x11(xfc); + return FALSE; + } + } + else + { + memcpy(ci.pixels, xpointer->cursorPixels, size); + } + + cursorIndex = xpointer->nCursors; + xpointer->cursorWidths[cursorIndex] = ci.width; + xpointer->cursorHeights[cursorIndex] = ci.height; + xpointer->cursors[cursorIndex] = XcursorImageLoadCursor(xfc->display, &ci); + xpointer->nCursors += 1; + _aligned_free(ci.pixels); + + xf_unlock_x11(xfc); + } + else + { + WLog_DBG(TAG, "%s: using cached cursor %" PRId32, __func__, cursorIndex); + } + + cursor[0] = xpointer->cursors[cursorIndex]; +#endif + return TRUE; +} + +/* Pointer Class */ +static Window xf_Pointer_get_window(xfContext* xfc) +{ + if (!xfc) + { + WLog_WARN(TAG, "xf_Pointer: Invalid context"); + return 0; + } + if (xfc->remote_app) + { + if (!xfc->appWindow) + { + WLog_WARN(TAG, "xf_Pointer: Invalid appWindow"); + return 0; + } + return xfc->appWindow->handle; + } + else + { + if (!xfc->window) + { + WLog_WARN(TAG, "xf_Pointer: Invalid window"); + return 0; + } + return xfc->window->handle; + } +} + +BOOL xf_pointer_update_scale(xfContext* xfc) +{ + xfPointer* pointer; + WINPR_ASSERT(xfc); + + pointer = xfc->pointer; + if (!pointer) + return TRUE; + + return xf_Pointer_Set(&xfc->context, &xfc->pointer->pointer); +} + +static BOOL xf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + BOOL rc = FALSE; +#ifdef WITH_XCURSOR + UINT32 CursorFormat; + size_t size; + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + + if (!context || !pointer || !context->gdi) + goto fail; + + if (!xfc->invert) + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32; + else + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32; + + xpointer->nCursors = 0; + xpointer->mCursors = 0; + + size = pointer->height * pointer->width * GetBytesPerPixel(CursorFormat) * 1ULL; + + if (!(xpointer->cursorPixels = (XcursorPixel*)_aligned_malloc(size, 16))) + goto fail; + + if (!freerdp_image_copy_from_pointer_data( + (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + { + _aligned_free(xpointer->cursorPixels); + return FALSE; + } + rc = TRUE; + +#endif +fail: + WLog_DBG(TAG, "%s: %ld", __func__, rc ? pointer : -1); + return rc; +} + +static void xf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + WLog_DBG(TAG, "%s: %p", __func__, pointer); + +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + + xf_lock_x11(xfc); + + _aligned_free(xpointer->cursorPixels); + free(xpointer->cursorWidths); + free(xpointer->cursorHeights); + + for (int i = 0; i < xpointer->nCursors; i++) + { + XFreeCursor(xfc->display, xpointer->cursors[i]); + } + + free(xpointer->cursors); + xpointer->nCursors = 0; + xpointer->mCursors = 0; + + xf_unlock_x11(xfc); +#endif +} + +static BOOL xf_Pointer_Set(rdpContext* context, const rdpPointer* pointer) +{ + WLog_DBG(TAG, "%s: %p", __func__, pointer); + +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + Window handle = xf_Pointer_get_window(xfc); + xfc->pointer = (xfPointer*)pointer; + + /* in RemoteApp mode, window can be null if none has had focus */ + + if (handle) + { + if (!xf_Pointer_GetCursorForCurrentScale(context, pointer, &(xfc->pointer->cursor))) + return FALSE; + xf_lock_x11(xfc); + XDefineCursor(xfc->display, handle, xfc->pointer->cursor); + xf_unlock_x11(xfc); + } + else + { + WLog_WARN(TAG, "%s: handle=%ld", __func__, handle); + } +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetNull(rdpContext* context) +{ + WLog_DBG(TAG, "%s", __func__); +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + static Cursor nullcursor = None; + Window handle = xf_Pointer_get_window(xfc); + xf_lock_x11(xfc); + + if (nullcursor == None) + { + XcursorImage ci; + 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 ((handle) && (nullcursor != None)) + XDefineCursor(xfc->display, handle, nullcursor); + + xf_unlock_x11(xfc); +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetDefault(rdpContext* context) +{ +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + Window handle = xf_Pointer_get_window(xfc); + xf_lock_x11(xfc); + xfc->pointer = NULL; + + if (handle) + XUndefineCursor(xfc->display, handle); + + xf_unlock_x11(xfc); +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + xfContext* xfc = (xfContext*)context; + XWindowAttributes current; + XSetWindowAttributes tmp; + BOOL ret = FALSE; + Status rc; + Window handle = xf_Pointer_get_window(xfc); + + if (!handle) + { + WLog_WARN(TAG, "%s: focus %d, handle%lu", __func__, xfc->focused, handle); + return TRUE; + } + + WLog_DBG(TAG, "%s: %" PRIu32 "x%" PRIu32, __func__, x, y); + if (xfc->remote_app && !xfc->focused) + return TRUE; + + xf_adjust_coordinates_to_screen(xfc, &x, &y); + + xf_lock_x11(xfc); + + rc = XGetWindowAttributes(xfc->display, handle, ¤t); + if (rc == 0) + { + WLog_WARN(TAG, "%s: XGetWindowAttributes==%d", __func__, rc); + goto out; + } + + tmp.event_mask = (current.your_event_mask & ~(PointerMotionMask)); + + rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp); + if (rc == 0) + { + WLog_WARN(TAG, "%s: XChangeWindowAttributes==%d", __func__, rc); + goto out; + } + + rc = XWarpPointer(xfc->display, None, handle, 0, 0, 0, 0, x, y); + if (rc == 0) + WLog_WARN(TAG, "%s: XWarpPointer==%d", __func__, rc); + tmp.event_mask = current.your_event_mask; + rc = XChangeWindowAttributes(xfc->display, handle, CWEventMask, &tmp); + if (rc == 0) + WLog_WARN(TAG, "%s: 2.try XChangeWindowAttributes==%d", __func__, rc); + ret = TRUE; +out: + xf_unlock_x11(xfc); + 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); + 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); + return TRUE; +} + +static void xf_Glyph_Free(rdpContext* context, rdpGlyph* glyph) +{ + xfContext* xfc = (xfContext*)context; + xf_lock_x11(xfc); + + if (((xfGlyph*)glyph)->pixmap != 0) + XFreePixmap(xfc->display, ((xfGlyph*)glyph)->pixmap); + + xf_unlock_x11(xfc); + 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); + + 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); + 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); + + 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); + 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 == 30) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32_DEPTH30 : PIXEL_FORMAT_BGRX32_DEPTH30; + else if (xfc->depth == 24) + { + if (aligned) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24; + } + else if (xfc->depth == 16) + DstFormat = PIXEL_FORMAT_RGB16; + else if (xfc->depth == 15) + DstFormat = PIXEL_FORMAT_RGB15; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + + return DstFormat; +} diff --git a/client/X11/xf_graphics.h b/client/X11/xf_graphics.h new file mode 100644 index 0000000..96e1d98 --- /dev/null +++ b/client/X11/xf_graphics.h @@ -0,0 +1,34 @@ +/** + * 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); + +BOOL xf_pointer_update_scale(xfContext* xfc); + +#endif /* FREERDP_CLIENT_X11_GRAPHICS_H */ diff --git a/client/X11/xf_input.c b/client/X11/xf_input.c new file mode 100644 index 0000000..50a52e0 --- /dev/null +++ b/client/X11/xf_input.c @@ -0,0 +1,669 @@ +/** + * 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_DBG(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_DBG(TAG, "%s %s touch device (id: %d, mode: %d), supporting %d touches.", + dev->name, (t->mode == XIDirectTouch) ? "direct" : "dependent", + dev->deviceid, t->mode, t->num_touches); + } + + XISetMask(masks[nmasks], XI_TouchBegin); + XISetMask(masks[nmasks], XI_TouchUpdate); + XISetMask(masks[nmasks], XI_TouchEnd); + nmasks++; + } + + if (xfc->use_xinput) + { + if (!touch && (class->type == XIButtonClass) && + strcmp(dev->name, "Virtual core pointer")) + { + WLog_DBG(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(const XGenericEventCookie* cookie) +{ + const 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(const XGenericEventCookie* cookie) +{ + const 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; + + WINPR_UNUSED(xfc); + 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; + + WINPR_UNUSED(xfc); + 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, const XEvent* event) +{ + union { + const XGenericEventCookie* cc; + XGenericEventCookie* vc; + } cookie; + cookie.cc = &event->xcookie; + XGetEventData(xfc->display, cookie.vc); + + if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode)) + { + switch (cookie.cc->evtype) + { + case XI_TouchBegin: + if (xf_input_is_duplicate(cookie.cc) == FALSE) + xf_input_touch_begin(xfc, cookie.cc->data); + + xf_input_save_last_event(cookie.cc); + break; + + case XI_TouchUpdate: + if (xf_input_is_duplicate(cookie.cc) == FALSE) + xf_input_touch_update(xfc, cookie.cc->data); + + xf_input_save_last_event(cookie.cc); + break; + + case XI_TouchEnd: + if (xf_input_is_duplicate(cookie.cc) == FALSE) + xf_input_touch_end(xfc, cookie.cc->data); + + xf_input_save_last_event(cookie.cc); + break; + + default: + WLog_ERR(TAG, "unhandled xi type= %d", cookie.cc->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie.vc); + return 0; +} + +#ifdef WITH_DEBUG_X11 +static char* xf_input_touch_state_string(DWORD flags) +{ + if (flags & CONTACT_FLAG_DOWN) + return "RDPINPUT::CONTACT_FLAG_DOWN"; + else if (flags & CONTACT_FLAG_UPDATE) + return "RDPINPUT::CONTACT_FLAG_UPDATE"; + else if (flags & CONTACT_FLAG_UP) + return "RDPINPUT::CONTACT_FLAG_UP"; + else if (flags & CONTACT_FLAG_INRANGE) + return "RDPINPUT::CONTACT_FLAG_INRANGE"; + else if (flags & CONTACT_FLAG_INCONTACT) + return "RDPINPUT::CONTACT_FLAG_INCONTACT"; + else if (flags & CONTACT_FLAG_CANCELED) + return "RDPINPUT::CONTACT_FLAG_CANCELED"; + else + return "RDPINPUT::CONTACT_FLAG_UNKNOWN"; +} +#endif + +static void xf_input_hide_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + + if (!xfc->cursorHidden) + { + XcursorImage ci; + XcursorPixel xp = 0; + static Cursor nullcursor = None; + xf_lock_x11(xfc); + 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); + } + +#endif +} + +static void xf_input_show_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + xf_lock_x11(xfc); + + if (xfc->cursorHidden) + { + if (xfc->window) + { + if (!xfc->pointer) + XUndefineCursor(xfc->display, xfc->window->handle); + else + XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); + } + + xfc->cursorHidden = FALSE; + } + + xf_unlock_x11(xfc); +#endif +} + +static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype) +{ + int x, 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_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail, + event->event, xfc->remote_app, TRUE); + break; + + case XI_ButtonRelease: + xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail, + event->event, xfc->remote_app, FALSE); + 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, const XEvent* event) +{ + union { + const XGenericEventCookie* cc; + XGenericEventCookie* vc; + } cookie; + cookie.cc = &event->xcookie; + XGetEventData(xfc->display, cookie.vc); + + if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode)) + { + switch (cookie.cc->evtype) + { + case XI_TouchBegin: + xf_input_touch_remote(xfc, cookie.cc->data, XI_TouchBegin); + break; + + case XI_TouchUpdate: + xf_input_touch_remote(xfc, cookie.cc->data, XI_TouchUpdate); + break; + + case XI_TouchEnd: + xf_input_touch_remote(xfc, cookie.cc->data, XI_TouchEnd); + break; + + default: + xf_input_event(xfc, cookie.cc->data, cookie.cc->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie.vc); + return 0; +} + +#else + +int xf_input_init(xfContext* xfc, Window window) +{ + return 0; +} + +#endif + +int xf_input_handle_event(xfContext* xfc, const XEvent* event) +{ +#ifdef WITH_XI + + 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..a961512 --- /dev/null +++ b/client/X11/xf_input.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Input + * + * Copyright 2013 Corey Clayton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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, const XEvent* event); + +#endif /* FREERDP_CLIENT_X11_INPUT_H */ diff --git a/client/X11/xf_keyboard.c b/client/X11/xf_keyboard.c new file mode 100644 index 0000000..377e9bd --- /dev/null +++ b/client/X11/xf_keyboard.c @@ -0,0 +1,655 @@ +/** + * 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 xf_sync_kbd_state(xfContext* xfc) +{ + const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc); + return freerdp_input_send_synchronize_event(xfc->context.input, syncFlags); +} + +static void xf_keyboard_clear(xfContext* xfc) +{ + ZeroMemory(xfc->KeyboardState, 256 * sizeof(BOOL)); +} + +static BOOL xf_keyboard_action_script_init(xfContext* xfc) +{ + FILE* keyScript; + char* keyCombination; + char buffer[1024] = { 0 }; + char command[1024] = { 0 }; + xfc->actionScriptExists = winpr_PathFileExists(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) + { + char* context = NULL; + strtok_s(buffer, "\n", &context); + 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_ex(xfc->KeyboardLayout, xfc->context.settings->KeyboardRemappingList); + 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_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; + } + } + xf_sync_kbd_state(xfc); +} + +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)) + { + xf_sync_kbd_state(xfc); + } + } +} + +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 }; + + xf_keyboard_clear(xfc); + + 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); + freerdp_input_send_focus_in_event(input, syncFlags); + xk_keyboard_update_modifier_keys(xfc); + + /* finish with a mouse pointer position like mstsc.exe if required */ + + if (xfc->remote_app) + return; + + if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state)) + { + if (x >= 0 && x < xfc->window->width && y >= 0 && y < xfc->window->height) + { + xf_event_adjust_coordinates(xfc, &x, &y); + freerdp_input_send_mouse_event(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) + winpr_str_append("Shift", combination, sizeof(combination), "+"); + + if (mod->Ctrl) + winpr_str_append("Ctrl", combination, sizeof(combination), "+"); + + if (mod->Alt) + winpr_str_append("Alt", combination, sizeof(combination), "+"); + + if (mod->Super) + winpr_str_append("Super", combination, sizeof(combination), "+"); + + winpr_str_append(keyStr, combination, sizeof(combination), NULL); + + 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) + { + char* context = NULL; + strtok_s(buffer, "\n", &context); + + if (strcmp(buffer, "key-local") == 0) + status = 0; + } + + if (pclose(keyScript) == -1) + status = -1; + + return status; +} + +static int xk_keyboard_get_modifier_keys(xfContext* xfc, XF_MODIFIER_KEYS* mod) +{ + mod->LeftShift = xf_keyboard_key_pressed(xfc, XK_Shift_L); + mod->RightShift = xf_keyboard_key_pressed(xfc, XK_Shift_R); + mod->Shift = mod->LeftShift || mod->RightShift; + mod->LeftAlt = xf_keyboard_key_pressed(xfc, XK_Alt_L); + mod->RightAlt = xf_keyboard_key_pressed(xfc, XK_Alt_R); + mod->Alt = mod->LeftAlt || mod->RightAlt; + mod->LeftCtrl = xf_keyboard_key_pressed(xfc, XK_Control_L); + mod->RightCtrl = xf_keyboard_key_pressed(xfc, XK_Control_R); + mod->Ctrl = mod->LeftCtrl || mod->RightCtrl; + mod->LeftSuper = xf_keyboard_key_pressed(xfc, XK_Super_L); + mod->RightSuper = xf_keyboard_key_pressed(xfc, XK_Super_R); + mod->Super = mod->LeftSuper || mod->RightSuper; + return 0; +} + +BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym) +{ + XF_MODIFIER_KEYS mod = { 0 }; + xk_keyboard_get_modifier_keys(xfc, &mod); + + // remember state of RightCtrl to ungrab keyboard if next action is release of RightCtrl + // do not return anything such that the key could be used by client if ungrab is not the goal + if (keysym == XK_Control_R) + { + if (mod.RightCtrl && xfc->firstPressRightCtrl) + { + // Right Ctrl is pressed, getting ready to ungrab + xfc->ungrabKeyboardWithRightCtrl = TRUE; + xfc->firstPressRightCtrl = FALSE; + } + } + else + { + // some other key has been pressed, abort ungrabbing + if (xfc->ungrabKeyboardWithRightCtrl) + xfc->ungrabKeyboardWithRightCtrl = FALSE; + } + + if (!xf_keyboard_execute_action_script(xfc, &mod, keysym)) + { + return TRUE; + } + + if (!xfc->remote_app && xfc->fullscreen_toggle) + { + if (keysym == XK_Return) + { + if (mod.Ctrl && mod.Alt) + { + /* Ctrl-Alt-Enter: toggle full screen */ + xf_toggle_fullscreen(xfc); + return TRUE; + } + } + } + + if ((keysym == XK_c) || (keysym == XK_C)) + { + if (mod.Ctrl && mod.Alt) + { + /* Ctrl-Alt-C: toggle control */ + if (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; + + xfc->firstPressRightCtrl = TRUE; + + if (!xfc->ungrabKeyboardWithRightCtrl) + return; + + // all requirements for ungrab are fulfilled, ungrabbing now + XF_MODIFIER_KEYS mod = { 0 }; + xk_keyboard_get_modifier_keys(xfc, &mod); + + if (!mod.RightCtrl) + { + if (!xfc->fullscreen) + { + xf_toggle_control(xfc); + } + + xfc->mouse_active = FALSE; + XUngrabKeyboard(xfc->display, CurrentTime); + } + + // ungrabbed + xfc->ungrabKeyboardWithRightCtrl = FALSE; +} + +BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + xfContext* xfc = (xfContext*)context; + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_SCROLL_LOCK, XK_Scroll_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_NUM_LOCK, XK_Num_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_CAPS_LOCK, XK_Caps_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_KANA_LOCK, XK_Kana_Lock); + return TRUE; +} + +BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} diff --git a/client/X11/xf_keyboard.h b/client/X11/xf_keyboard.h new file mode 100644 index 0000000..7492fa8 --- /dev/null +++ b/client/X11/xf_keyboard.h @@ -0,0 +1,63 @@ +/** + * 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_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..72a3dbe --- /dev/null +++ b/client/X11/xf_monitor.c @@ -0,0 +1,573 @@ +/** + * 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) +{ + UINT32 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) +{ + int nmonitors = 0; + int monitor_index = 0; + BOOL primaryMonitorFound = FALSE; + VIRTUAL_SCREEN* vscreen; + rdpSettings* 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 + + if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->context.settings) + return FALSE; + + settings = xfc->context.settings; + 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) + { + int i; + + 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) + { + int i; + + 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) + { + int i; + + 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) && (vscreen->nmonitors > current_monitor)) + { + monitor = vscreen->monitors + current_monitor; + + if (!monitor) + return FALSE; + + 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) + { + if (!vscreen->monitors) + return FALSE; + + *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 */ + { + int i; + + for (i = 0; i < vscreen->nmonitors; i++) + { + MONITOR_ATTRIBUTES* attrs; + + if (!xf_is_monitor_id_active(xfc, (UINT32)i)) + continue; + + if (!vscreen->monitors) + return FALSE; + + settings->MonitorDefArray[nmonitors].x = + (vscreen->monitors[i].area.left * + (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / + 100; + settings->MonitorDefArray[nmonitors].y = + (vscreen->monitors[i].area.top * + (settings->PercentScreenUseHeight ? settings->PercentScreen : 100)) / + 100; + settings->MonitorDefArray[nmonitors].width = + ((vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1) * + (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / + 100; + settings->MonitorDefArray[nmonitors].height = + ((vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1) * + (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) / + 100; + 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 ((UINT32)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) + { + INT32 width, height; + if (!vscreen->monitors) + return FALSE; + + width = vscreen->monitors[current_monitor].area.right - + vscreen->monitors[current_monitor].area.left + 1L; + height = vscreen->monitors[current_monitor].area.bottom - + vscreen->monitors[current_monitor].area.top + 1L; + + 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(width, (INT64)(*pMaxWidth)); + settings->MonitorDefArray[0].height = MIN(height, (INT64)(*pMaxHeight)); + settings->MonitorDefArray[0].orig_screen = current_monitor; + nmonitors = 1; + } + + settings->MonitorCount = nmonitors; + + /* If we have specific monitor information */ + if (settings->MonitorCount) + { + UINT32 i; + /* 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 / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.); + vY = + destY / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.); + vR = destR / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.); + vB = + destB / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.); + } + + 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 = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1); + *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1); + } + + /* some 2008 server freeze at logon if we announce support for monitor layout PDU with + * #monitors < 2. So let's announce it only if we have more than 1 monitor. + */ + if (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..090f599 --- /dev/null +++ b/client/X11/xf_rail.c @@ -0,0 +1,1200 @@ +/** + * 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; +}; + +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 != (INT64)appWindow->windowWidth || + appWindow->height != (INT64)appWindow->windowHeight) + { + windowMove.windowId = appWindow->windowId; + /* + * Calculate new size/position for the rail window(new values for + * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server + */ + windowMove.left = appWindow->x - appWindow->resizeMarginLeft; + windowMove.top = appWindow->y - appWindow->resizeMarginTop; + windowMove.right = appWindow->x + appWindow->width + appWindow->resizeMarginRight; + windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom; + xfc->rail->ClientWindowMove(xfc->rail, &windowMove); + } +} + +void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow) +{ + int x, 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 - appWindow->resizeMarginLeft; + windowMove.top = appWindow->y - appWindow->resizeMarginTop; + windowMove.right = + appWindow->x + + appWindow->width + appWindow->resizeMarginRight; /* In the update to RDP the position is one past the window */ + windowMove.bottom = appWindow->y + appWindow->height + appWindow->resizeMarginBottom; + xfc->rail->ClientWindowMove(xfc->rail, &windowMove); + /* + * Simulate button up at new position to end the local move (per RDP spec) + */ + XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x, + &child_y, &mask); + + /* only send the mouse coordinates if not a keyboard move or size */ + if ((appWindow->local_move.direction != _NET_WM_MOVERESIZE_MOVE_KEYBOARD) && + (appWindow->local_move.direction != _NET_WM_MOVERESIZE_SIZE_KEYBOARD)) + { + freerdp_input_send_mouse_event(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 = 0; + RECTANGLE_16 updateRect; + RECTANGLE_16 windowRect; + ULONG_PTR* pKeys = NULL; + xfAppWindow* appWindow; + const RECTANGLE_16* extents; + REGION16 windowInvalidRegion; + region16_init(&windowInvalidRegion); + if (xfc->railWindows) + count = HashTable_GetKeys(xfc->railWindows, &pKeys); + + for (index = 0; index < count; index++) + { + appWindow = xf_rail_get_window(xfc, *(UINT64*)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, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + xfAppWindow* appWindow = NULL; + xfContext* xfc = (xfContext*)context; + UINT32 fieldFlags = orderInfo->fieldFlags; + BOOL position_or_size_updated = FALSE; + appWindow = xf_rail_get_window(xfc, orderInfo->windowId); + + if (fieldFlags & WINDOW_ORDER_STATE_NEW) + { + if (!appWindow) + appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX, + windowState->windowOffsetY, windowState->windowWidth, + windowState->windowHeight, 0xFFFFFFFF); + + if (!appWindow) + return FALSE; + + appWindow->dwStyle = windowState->style; + appWindow->dwExStyle = windowState->extendedStyle; + + /* Ensure window always gets a window title */ + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + 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; + } + + xf_AppWindowInit(xfc, appWindow); + } + + if (!appWindow) + return FALSE; + + /* Keep track of any position/size update so that we can force a refresh of the window */ + if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) || + (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) || + (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) || + (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)) + { + position_or_size_updated = TRUE; + } + + /* Update Parameters */ + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + appWindow->windowOffsetX = windowState->windowOffsetX; + appWindow->windowOffsetY = windowState->windowOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + appWindow->windowWidth = windowState->windowWidth; + appWindow->windowHeight = windowState->windowHeight; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X) + { + appWindow->resizeMarginLeft = windowState->resizeMarginLeft; + appWindow->resizeMarginRight = windowState->resizeMarginRight; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y) + { + appWindow->resizeMarginTop = windowState->resizeMarginTop; + appWindow->resizeMarginBottom = windowState->resizeMarginBottom; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + appWindow->ownerWindowId = windowState->ownerWindowId; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + appWindow->dwStyle = windowState->style; + appWindow->dwExStyle = windowState->extendedStyle; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + appWindow->showState = windowState->showState; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = NULL; + + 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 == (INT64)appWindow->windowOffsetX && + appWindow->y == (INT64)appWindow->windowOffsetY && + appWindow->width == (INT64)appWindow->windowWidth && + appWindow->height == (INT64)appWindow->windowHeight) + { + xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth, + appWindow->windowHeight); + } + else + { + xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX, appWindow->windowOffsetY, + appWindow->windowWidth, appWindow->windowHeight); + } + + xf_SetWindowVisibilityRects(xfc, appWindow, visibilityRectsOffsetX, + visibilityRectsOffsetY, appWindow->visibilityRects, + appWindow->numVisibilityRects); + } + } + + /* We should only be using the visibility rects for shaping the window */ + /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects); + }*/ + return TRUE; +} + +static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + xfContext* xfc = (xfContext*)context; + return xf_rail_del_window(xfc, orderInfo->windowId); +} + +static xfRailIconCache* RailIconCache_New(rdpSettings* settings) +{ + xfRailIconCache* cache; + cache = calloc(1, sizeof(xfRailIconCache)); + + if (!cache) + return NULL; + + cache->numCaches = settings->RemoteAppNumIconCaches; + cache->numCacheEntries = settings->RemoteAppNumIconCacheEntries; + cache->entries = calloc(cache->numCaches * cache->numCacheEntries * 1ULL, 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]; +} + +/* + * _NET_WM_ICON format is defined as "array of CARDINAL" values which for + * Xlib must be represented with an array of C's "long" values. Note that + * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast + * the bitmap data as (unsigned char*), we have to copy all the pixels. + * + * The first two values are width and height followed by actual color data + * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal, + * left-to-right top-down order. + */ +static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon) +{ + BYTE* argbPixels = NULL; + BYTE* nextPixel; + long* pixels; + int i; + int nelements; + argbPixels = calloc(iconInfo->width * iconInfo->height * 1ULL, 4); + + if (!argbPixels) + goto error; + + if (!freerdp_image_copy_from_icon_data( + argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0, iconInfo->width, iconInfo->height, + iconInfo->bitsColor, iconInfo->cbBitsColor, iconInfo->bitsMask, iconInfo->cbBitsMask, + iconInfo->colorTable, iconInfo->cbColorTable, iconInfo->bpp)) + goto error; + + nelements = 2 + iconInfo->width * iconInfo->height; + pixels = realloc(railIcon->data, nelements * sizeof(long)); + + if (!pixels) + goto error; + + railIcon->data = pixels; + railIcon->length = nelements; + pixels[0] = iconInfo->width; + pixels[1] = iconInfo->height; + nextPixel = argbPixels; + + for (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 BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_ICON_ORDER* windowIcon) +{ + xfContext* xfc = (xfContext*)context; + xfAppWindow* railWindow; + xfRailIcon* icon; + BOOL replaceIcon; + railWindow = xf_rail_get_window(xfc, orderInfo->windowId); + + if (!railWindow) + return TRUE; + + icon = RailIconCache_Lookup(xfc->railIconCache, windowIcon->iconInfo->cacheId, + windowIcon->iconInfo->cacheEntry); + + if (!icon) + { + WLog_WARN(TAG, "failed to get icon from cache %02X:%04X", windowIcon->iconInfo->cacheId, + windowIcon->iconInfo->cacheEntry); + return FALSE; + } + + if (!convert_rail_icon(windowIcon->iconInfo, icon)) + { + WLog_WARN(TAG, "failed to convert icon for window %08X", orderInfo->windowId); + return FALSE; + } + + replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW); + xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon); + return TRUE; +} + +static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + xfContext* xfc = (xfContext*)context; + xfAppWindow* railWindow; + xfRailIcon* icon; + BOOL replaceIcon; + railWindow = xf_rail_get_window(xfc, orderInfo->windowId); + + if (!railWindow) + return TRUE; + + icon = RailIconCache_Lookup(xfc->railIconCache, windowCachedIcon->cachedIcon.cacheId, + windowCachedIcon->cachedIcon.cacheEntry); + + if (!icon) + { + WLog_WARN(TAG, "failed to get icon from cache %02X:%04X", + windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry); + return FALSE; + } + + replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW); + xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon); + return TRUE; +} + +static BOOL xf_rail_notify_icon_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_ICON) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) + { + } + + return TRUE; +} + +static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + return TRUE; +} + +static BOOL xf_rail_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + return TRUE; +} + +static BOOL xf_rail_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + xfContext* xfc = (xfContext*)context; + xf_rail_disable_remoteapp_mode(xfc); + return TRUE; +} + +static void xf_rail_register_update_callbacks(rdpUpdate* update) +{ + rdpWindowUpdate* window = update->window; + window->WindowCreate = xf_rail_window_common; + window->WindowUpdate = xf_rail_window_common; + window->WindowDelete = xf_rail_window_delete; + window->WindowIcon = xf_rail_window_icon; + window->WindowCachedIcon = xf_rail_window_cached_icon; + window->NotifyIconCreate = xf_rail_notify_icon_create; + window->NotifyIconUpdate = xf_rail_notify_icon_update; + window->NotifyIconDelete = xf_rail_notify_icon_delete; + window->MonitoredDesktop = xf_rail_monitored_desktop; + window->NonMonitoredDesktop = xf_rail_non_monitored_desktop; +} + +/* RemoteApp Virtual Channel Extension */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_execute_result(RailClientContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + xfContext* xfc = (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) +{ + // TODO: Actually apply param + return CHANNEL_RC_OK; +} + +static UINT xf_rail_server_start_cmd(RailClientContext* context) +{ + UINT status; + RAIL_EXEC_ORDER exec = { 0 }; + RAIL_SYSPARAM_ORDER sysparam = { 0 }; + RAIL_CLIENT_STATUS_ORDER clientStatus = { 0 }; + xfContext* xfc = (xfContext*)context->custom; + rdpSettings* settings = xfc->context.settings; + clientStatus.flags = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; + + if (settings->AutoReconnectionEnabled) + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT; + + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED; + status = context->ClientInformation(context, &clientStatus); + + if (status != CHANNEL_RC_OK) + return status; + + if (settings->RemoteAppLanguageBarSupported) + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ + status = context->ClientLanguageBarInfo(context, &langBarInfo); + + /* We want the language bar, but the server might not support it. */ + switch (status) + { + case CHANNEL_RC_OK: + case ERROR_BAD_CONFIGURATION: + break; + default: + return status; + } + } + + 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; + status = context->ClientSystemParam(context, &sysparam); + + if (status != CHANNEL_RC_OK) + return status; + + exec.RemoteApplicationProgram = settings->RemoteApplicationProgram; + exec.RemoteApplicationWorkingDir = settings->ShellWorkingDirectory; + exec.RemoteApplicationArguments = settings->RemoteApplicationCmdLine; + return context->ClientExecute(context, &exec); +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_handshake(RailClientContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + return xf_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_handshake_ex(RailClientContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + return xf_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_local_move_size(RailClientContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + int x = 0, y = 0; + int direction = 0; + Window child_window; + xfContext* xfc = (xfContext*)context->custom; + xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId); + + if (!appWindow) + return ERROR_INTERNAL_ERROR; + + switch (localMoveSize->moveSizeType) + { + case RAIL_WMSZ_LEFT: + direction = _NET_WM_MOVERESIZE_SIZE_LEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_RIGHT: + direction = _NET_WM_MOVERESIZE_SIZE_RIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOP: + direction = _NET_WM_MOVERESIZE_SIZE_TOP; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOPLEFT: + direction = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOPRIGHT: + direction = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOM: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOM; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOMLEFT: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOMRIGHT: + direction = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_MOVE: + direction = _NET_WM_MOVERESIZE_MOVE; + XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen), + localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window); + break; + + case RAIL_WMSZ_KEYMOVE: + direction = _NET_WM_MOVERESIZE_MOVE_KEYBOARD; + x = localMoveSize->posX; + y = localMoveSize->posY; + /* FIXME: local keyboard moves not working */ + return CHANNEL_RC_OK; + + case RAIL_WMSZ_KEYSIZE: + direction = _NET_WM_MOVERESIZE_SIZE_KEYBOARD; + x = localMoveSize->posX; + y = localMoveSize->posY; + /* FIXME: local keyboard moves not working */ + return CHANNEL_RC_OK; + } + + if (localMoveSize->isMoveSizeStart) + xf_StartLocalMoveSize(xfc, appWindow, direction, x, y); + else + xf_EndLocalMoveSize(xfc, appWindow); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_min_max_info(RailClientContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + xfContext* xfc = (xfContext*)context->custom; + xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId); + + if (appWindow) + { + xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight, + minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth, + minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth, + minMaxInfo->maxTrackHeight); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_language_bar_info(RailClientContext* context, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_get_appid_response(RailClientContext* context, + const RAIL_GET_APPID_RESP_ORDER* getAppIdResp) +{ + return CHANNEL_RC_OK; +} + +static BOOL rail_window_key_equals(void* key1, void* key2) +{ + const UINT64* k1 = (const UINT64*)key1; + const UINT64* k2 = (const UINT64*)key2; + + if (!k1 || !k2) + return FALSE; + + return *k1 == *k2; +} + +static UINT32 rail_window_key_hash(void* key) +{ + const UINT64* k1 = (const UINT64*)key; + return (UINT32)*k1; +} + +static void rail_window_free(void* value) +{ + xfAppWindow* appWindow = (xfAppWindow*)value; + + if (!appWindow) + return; + + xf_DestroyWindow(appWindow->xfc, appWindow); +} + +int xf_rail_init(xfContext* xfc, RailClientContext* rail) +{ + rdpContext* context = (rdpContext*)xfc; + + if (!xfc || !rail) + return 0; + + xfc->rail = rail; + xf_rail_register_update_callbacks(context->update); + rail->custom = (void*)xfc; + rail->ServerExecuteResult = xf_rail_server_execute_result; + rail->ServerSystemParam = xf_rail_server_system_param; + rail->ServerHandshake = xf_rail_server_handshake; + rail->ServerHandshakeEx = xf_rail_server_handshake_ex; + rail->ServerLocalMoveSize = xf_rail_server_local_move_size; + rail->ServerMinMaxInfo = xf_rail_server_min_max_info; + rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info; + rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response; + xfc->railWindows = HashTable_New(TRUE); + + if (!xfc->railWindows) + return 0; + + xfc->railWindows->keyCompare = rail_window_key_equals; + xfc->railWindows->hash = rail_window_key_hash; + 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) +{ + WINPR_UNUSED(rail); + + if (xfc->rail) + { + xfc->rail->custom = NULL; + xfc->rail = NULL; + } + + if (xfc->railWindows) + { + HashTable_Free(xfc->railWindows); + xfc->railWindows = NULL; + } + + if (xfc->railIconCache) + { + RailIconCache_Free(xfc->railIconCache); + xfc->railIconCache = NULL; + } + + return 1; +} + +xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width, + UINT32 height, UINT32 surfaceId) +{ + xfAppWindow* appWindow; + + if (!xfc) + return NULL; + + appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow)); + + if (!appWindow) + return NULL; + + appWindow->xfc = xfc; + appWindow->windowId = id; + appWindow->surfaceId = surfaceId; + appWindow->x = x; + appWindow->y = y; + appWindow->width = width; + appWindow->height = height; + xf_AppWindowCreate(xfc, appWindow); + HashTable_Add(xfc->railWindows, &appWindow->windowId, (void*)appWindow); + return appWindow; +} + +BOOL xf_rail_del_window(xfContext* xfc, UINT64 id) +{ + if (!xfc) + return FALSE; + + if (!xfc->railWindows) + return FALSE; + + return HashTable_Remove(xfc->railWindows, &id); +} + +xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id) +{ + if (!xfc) + return NULL; + + if (!xfc->railWindows) + return FALSE; + + return HashTable_GetItemValue(xfc->railWindows, &id); +} diff --git a/client/X11/xf_rail.h b/client/X11/xf_rail.h new file mode 100644 index 0000000..c99ed70 --- /dev/null +++ b/client/X11/xf_rail.h @@ -0,0 +1,49 @@ +/** + * 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); + +xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, UINT32 x, UINT32 y, UINT32 width, + UINT32 height, UINT32 surfaceId); +xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id); + +BOOL xf_rail_del_window(xfContext* xfc, UINT64 id); + +BOOL xf_rail_draw_window(xfContext* xfc, xfAppWindow* window, const char* data, UINT32 scanline, + UINT32 width, UINT32 height, const RECTANGLE_16* src, + const RECTANGLE_16* dst); + +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..87f1047 --- /dev/null +++ b/client/X11/xf_tsmf.c @@ -0,0 +1,475 @@ +/** + * 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; +} + +static 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 = NULL; + XShmSegmentInfo shminfo; + BOOL converti420yv12 = FALSE; + + if (!tsmf) + return -1; + + xfc = (xfContext*)tsmf->custom; + + if (!xfc) + return -1; + + xv = (xfXvContext*)xfc->xv_context; + + if (!xv) + return -1; + + if (xv->xv_port == 0) + return -1001; + + /* In case the player is minimized */ + if (event->x < -2048 || event->y < -2048 || event->numVisibleRects == 0) + { + return -1002; + } + + xrects = NULL; + numRects = event->numVisibleRects; + + if (numRects > 0) + { + xrects = (XRectangle*)calloc(numRects, sizeof(XRectangle)); + + if (!xrects) + return -1; + + for (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: + if (image->data_size < 0) + { + free(xrects); + return -2000; + } + else + { + const size_t size = ((UINT32)image->data_size <= event->frameSize) + ? (UINT32)image->data_size + : event->frameSize; + CopyMemory(image->data, event->frameData, size); + } + break; + } + + XvShmPutImage(xfc->display, xv->xv_port, xfc->window->handle, xfc->gc, image, 0, 0, + image->width, image->height, event->x, event->y, event->width, event->height, + FALSE); + + if (xv->xv_colorkey_atom == None) + XSetClipMask(xfc->display, xfc->gc, None); + + XSync(xfc->display, FALSE); + + XShmDetach(xfc->display, &shminfo); + XFree(image); + + free(xrects); + + return 1; +} + +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 < (unsigned int)ret; i++) + { + xv->xv_pixfmts[i] = fo[i].id; + WLog_DBG(TAG, "%c%c%c%c ", ((char*)(xv->xv_pixfmts + i))[0], + ((char*)(xv->xv_pixfmts + i))[1], ((char*)(xv->xv_pixfmts + i))[2], + ((char*)(xv->xv_pixfmts + i))[3]); + } + xv->xv_pixfmts[i] = 0; + } + XFree(fo); + + if (tsmf) + { + xfc->tsmf = tsmf; + tsmf->custom = (void*)xfc; + + tsmf->FrameEvent = xf_tsmf_xv_video_frame_event; + } + + return 1; +} + +int xf_tsmf_xv_uninit(xfContext* xfc, TsmfClientContext* tsmf) +{ + xfXvContext* xv = (xfXvContext*)xfc->xv_context; + + WINPR_UNUSED(tsmf); + if (xv) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, NULL); + } + if (xv->xv_pixfmts) + { + free(xv->xv_pixfmts); + xv->xv_pixfmts = NULL; + } + free(xv); + xfc->xv_context = NULL; + } + + if (xfc->tsmf) + { + xfc->tsmf->custom = NULL; + xfc->tsmf = NULL; + } + + return 1; +} + +#endif + +int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf) +{ +#ifdef WITH_XV + return xf_tsmf_xv_init(xfc, tsmf); +#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..9520454 --- /dev/null +++ b/client/X11/xf_video.c @@ -0,0 +1,107 @@ +/** + * 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; + + WINPR_UNUSED(video); + + 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..83708f0 --- /dev/null +++ b/client/X11/xf_video.h @@ -0,0 +1,33 @@ +/** + * 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..9b5b1c4 --- /dev/null +++ b/client/X11/xf_window.c @@ -0,0 +1,1143 @@ +/** + * 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 +#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 = strnlen(name, MAX_PATH); + 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 = { 0 }; + unsigned int i; + va_list argp; + va_start(argp, numArgs); + + xevent.xclient.type = ClientMessage; + xevent.xclient.serial = 0; + xevent.xclient.send_event = False; + xevent.xclient.display = xfc->display; + xevent.xclient.window = window; + xevent.xclient.message_type = atom; + xevent.xclient.format = 32; + + for (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); + xf_floatbar_toggle_fullscreen(xfc->window->floatbar, fullscreen); + + if (fullscreen) + { + xfc->savedWidth = xfc->window->width; + xfc->savedHeight = xfc->window->height; + xfc->savedPosX = xfc->window->left; + xfc->savedPosY = xfc->window->top; + startX = (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_DBG(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) >= (INT64)nitems) + { + free(prop); + return FALSE; + } + + plong = (long*)prop; + xfc->workArea.x = plong[xfc->current_desktop * 4 + 0]; + xfc->workArea.y = plong[xfc->current_desktop * 4 + 1]; + xfc->workArea.width = plong[xfc->current_desktop * 4 + 2]; + xfc->workArea.height = plong[xfc->current_desktop * 4 + 3]; + free(prop); + return TRUE; +} + +void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show) +{ + PropMotifWmHints hints; + 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, name, settings->Floatbar); + + if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD) + xf_SendClientEvent(xfc, window->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1); + + return window; +} + +void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height) +{ + XSizeHints* size_hints; + 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; + + xf_floatbar_free(window->floatbar); + + if (window->gc) + XFreeGC(xfc->display, window->gc); + + if (window->handle) + { + XUnmapWindow(xfc->display, window->handle); + XDestroyWindow(xfc->display, window->handle); + } + + if (window->xfwin) + munmap(0, sizeof(*window->xfwin)); + + if (window->shmid >= 0) + close(window->shmid); + + shm_unlink(get_shm_id()); + window->xfwin = (Window*)-1; + window->shmid = -1; + free(window); +} + +void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style) +{ + Atom window_type; + 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) +{ + if (!xfc || !appWindow) + return -1; + + xf_SetWindowDecorations(xfc, appWindow->handle, appWindow->decorations); + xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle); + xf_SetWindowPID(xfc, appWindow->handle, 0); + xf_ShowWindow(xfc, appWindow, WINDOW_SHOW); + XClearWindow(xfc->display, appWindow->handle); + XMapWindow(xfc->display, appWindow->handle); + /* Move doesn't seem to work until window is mapped. */ + xf_MoveWindow(xfc, appWindow, appWindow->x, appWindow->y, appWindow->width, appWindow->height); + xf_SetWindowText(xfc, appWindow, appWindow->title); + return 1; +} + +int xf_AppWindowCreate(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->maxVert = FALSE; + appWindow->maxHorz = FALSE; + appWindow->minimized = FALSE; + 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" PRIX64 "", 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); + + if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD) + xf_SendClientEvent(xfc, appWindow->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1); + + 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) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + + switch (state) + { + case WINDOW_HIDE: + XWithdrawWindow(xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MINIMIZED: + appWindow->minimized = TRUE; + XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MAXIMIZED: + /* Set the window as maximized */ + appWindow->maxHorz = TRUE; + appWindow->maxVert = TRUE; + xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD, + xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ, + 0); + + /* + * This is a workaround for the case where the window is maximized locally before the + * rail server is told to maximize the window, this appears to be a race condition where + * the local window with incomplete data and once the window is actually maximized on + * the server + * - an update of the new areas may not happen. So, we simply to do a full update of the + * entire window once the rail server notifies us that the window is now maximized. + */ + if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED) + { + xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth, + appWindow->windowHeight); + } + + break; + + case WINDOW_SHOW: + /* Ensure the window is not maximized */ + xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE, + xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ, + 0); + + /* + * Ignore configure requests until both the Maximized properties have been processed + * to prevent condition where WM overrides size of request due to one or both of these + * properties still being set - which causes a position adjustment to be sent back to + * the server thus causing the window to not return to its original size + */ + if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED) + appWindow->rail_ignore_configure = TRUE; + + if (appWindow->is_transient) + xf_SetWindowUnlisted(xfc, appWindow->handle); + + XMapWindow(xfc->display, appWindow->handle); + break; + } + + /* Save the current rail state of this window */ + appWindow->rail_state = state; + XFlush(xfc->display); +} + +void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects) +{ + 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; + + if (appWindow->surfaceId < UINT16_MAX) + return; + + ax = x + appWindow->windowOffsetX; + ay = y + appWindow->windowOffsetY; + + if (ax + width > appWindow->windowOffsetX + appWindow->width) + width = (appWindow->windowOffsetX + appWindow->width - 1) - ax; + + if (ay + height > appWindow->windowOffsetY + appWindow->height) + height = (appWindow->windowOffsetY + appWindow->height - 1) - ay; + + xf_lock_x11(xfc); + + if (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); +} + +void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow) +{ + if (!appWindow) + return; + + if (xfc->appWindow == appWindow) + xfc->appWindow = NULL; + + if (appWindow->gc) + XFreeGC(xfc->display, appWindow->gc); + + if (appWindow->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 = xf_rail_get_window(xfc, *(UINT64*)pKeys[index]); + + if (!appWindow) + return NULL; + + 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..0f85af1 --- /dev/null +++ b/client/X11/xf_window.h @@ -0,0 +1,192 @@ +/** + * 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 surfaceId; + UINT64 windowId; + UINT32 ownerWindowId; + + UINT32 dwStyle; + UINT32 dwExStyle; + UINT32 showState; + + INT32 clientOffsetX; + INT32 clientOffsetY; + UINT32 clientAreaWidth; + UINT32 clientAreaHeight; + + INT32 windowOffsetX; + INT32 windowOffsetY; + INT32 windowClientDeltaX; + INT32 windowClientDeltaY; + UINT32 windowWidth; + UINT32 windowHeight; + UINT32 numWindowRects; + RECTANGLE_16* windowRects; + + INT32 visibleOffsetX; + INT32 visibleOffsetY; + UINT32 numVisibilityRects; + RECTANGLE_16* visibilityRects; + + UINT32 localWindowOffsetCorrX; + UINT32 localWindowOffsetCorrY; + + UINT32 resizeMarginLeft; + UINT32 resizeMarginTop; + UINT32 resizeMarginRight; + UINT32 resizeMarginBottom; + + GC gc; + int shmid; + Window handle; + Window* xfwin; + BOOL fullscreen; + BOOL decorations; + BOOL is_mapped; + BOOL is_transient; + xfLocalMove local_move; + BYTE rail_state; + BOOL maxVert; + BOOL maxHorz; + BOOL minimized; + BOOL rail_ignore_configure; +}; + +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_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow); +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 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..636e60a --- /dev/null +++ b/client/X11/xfreerdp.h @@ -0,0 +1,365 @@ +/** + * 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 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef struct xf_context xfContext; + +#ifdef WITH_XCURSOR +#include +#endif + +#include + +#include "xf_window.h" +#include "xf_monitor.h" +#include "xf_channels.h" + +#if defined(CHANNEL_TSMF_CLIENT) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(XcursorUInt) +typedef unsigned int XcursorUInt; +#endif + +#if !defined(XcursorPixel) +typedef XcursorUInt XcursorPixel; +#endif + +struct xf_FullscreenMonitors +{ + UINT32 top; + UINT32 bottom; + UINT32 left; + UINT32 right; +}; +typedef struct xf_FullscreenMonitors xfFullscreenMonitors; + +struct xf_WorkArea +{ + UINT32 x; + UINT32 y; + UINT32 width; + UINT32 height; +}; +typedef struct xf_WorkArea xfWorkArea; + +struct xf_pointer +{ + rdpPointer pointer; + XcursorPixel* cursorPixels; + UINT32 nCursors; + UINT32 mCursors; + UINT32* cursorWidths; + UINT32* cursorHeights; + Cursor* cursors; + Cursor cursor; +}; +typedef struct xf_pointer xfPointer; + +struct xf_bitmap +{ + rdpBitmap bitmap; + Pixmap pixmap; + XImage* image; +}; +typedef struct xf_bitmap xfBitmap; + +struct xf_glyph +{ + rdpGlyph glyph; + Pixmap pixmap; +}; +typedef struct xf_glyph xfGlyph; + +typedef struct xf_clipboard xfClipboard; +typedef struct _xfDispContext xfDispContext; +typedef struct _xfVideoContext xfVideoContext; +typedef struct xf_rail_icon_cache xfRailIconCache; + +/* Number of buttons that are mapped from X11 to RDP button events. */ +#define NUM_BUTTONS_MAPPED 11 + +typedef struct +{ + int button; + UINT16 flags; +} button_map; + +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 controlToggle; + UINT32 KeyboardLayout; + BOOL KeyboardState[256]; + XModifierKeymap* modifierMap; + wArrayList* keyCombinations; + wArrayList* xevents; + BOOL actionScriptExists; + + XSetWindowAttributes attribs; + BOOL complex_regions; + VIRTUAL_SCREEN vscreen; +#if defined(CHANNEL_TSMF_CLIENT) + void* xv_context; +#endif + + Atom* supportedAtoms; + unsigned long supportedAtomCount; + + Atom UTF8_STRING; + + Atom _XWAYLAND_MAY_GRAB_KEYBOARD; + + Atom _NET_WM_ICON; + Atom _MOTIF_WM_HINTS; + Atom _NET_CURRENT_DESKTOP; + Atom _NET_WORKAREA; + + Atom _NET_SUPPORTED; + ATOM _NET_SUPPORTING_WM_CHECK; + + Atom _NET_WM_STATE; + Atom _NET_WM_STATE_FULLSCREEN; + Atom _NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_SKIP_TASKBAR; + Atom _NET_WM_STATE_SKIP_PAGER; + + Atom _NET_WM_FULLSCREEN_MONITORS; + + Atom _NET_WM_NAME; + Atom _NET_WM_PID; + + Atom _NET_WM_WINDOW_TYPE; + Atom _NET_WM_WINDOW_TYPE_NORMAL; + Atom _NET_WM_WINDOW_TYPE_DIALOG; + Atom _NET_WM_WINDOW_TYPE_UTILITY; + Atom _NET_WM_WINDOW_TYPE_POPUP; + Atom _NET_WM_WINDOW_TYPE_POPUP_MENU; + Atom _NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + + Atom _NET_WM_MOVERESIZE; + Atom _NET_MOVERESIZE_WINDOW; + + Atom WM_STATE; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + + /* Channels */ +#if defined(CHANNEL_TSMF_CLIENT) + TsmfClientContext* tsmf; +#endif + + xfClipboard* clipboard; + CliprdrClientContext* cliprdr; + xfVideoContext* xfVideo; + 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 */ + button_map button_map[NUM_BUTTONS_MAPPED]; + BYTE savedMaximizedState; + UINT32 locked; + BOOL firstPressRightCtrl; + BOOL ungrabKeyboardWithRightCtrl; +}; + +BOOL xf_create_window(xfContext* xfc); +void xf_toggle_fullscreen(xfContext* xfc); +BOOL 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_LOGON_FAILURE = 134, + XF_EXIT_ACCOUNT_LOCKED_OUT = 135, + XF_EXIT_PRE_CONNECT_FAILED = 136, + XF_EXIT_CONNECT_UNDEFINED = 137, + XF_EXIT_POST_CONNECT_FAILED = 138, + XF_EXIT_DNS_ERROR = 139, + XF_EXIT_DNS_NAME_NOT_FOUND = 140, + XF_EXIT_CONNECT_FAILED = 141, + XF_EXIT_MCS_CONNECT_INITIAL_ERROR = 142, + XF_EXIT_TLS_CONNECT_FAILED = 143, + XF_EXIT_INSUFFICIENT_PRIVILEGES = 144, + XF_EXIT_CONNECT_CANCELLED = 145, + XF_EXIT_SECURITY_NEGO_CONNECT_FAILED = 146, + XF_EXIT_CONNECT_TRANSPORT_FAILED = 147, + XF_EXIT_CONNECT_PASSWORD_EXPIRED = 148, + XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149, + XF_EXIT_CONNECT_KDC_UNREACHABLE = 150, + XF_EXIT_CONNECT_ACCOUNT_DISABLED = 151, + XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152, + XF_EXIT_CONNECT_CLIENT_REVOKED = 153, + XF_EXIT_CONNECT_WRONG_PASSWORD = 154, + XF_EXIT_CONNECT_ACCESS_DENIED = 155, + XF_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156, + XF_EXIT_CONNECT_ACCOUNT_EXPIRED = 157, + XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158, + XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159, + XF_EXIT_UNKNOWN = 255, +}; + +#define xf_lock_x11(xfc) xf_lock_x11_(xfc, __FUNCTION__); +#define xf_unlock_x11(xfc) xf_unlock_x11_(xfc, __FUNCTION__); + +void xf_lock_x11_(xfContext* xfc, const char* fkt); +void xf_unlock_x11_(xfContext* xfc, const char* fkt); + +BOOL xf_picture_transform_required(xfContext* xfc); + +#define xf_draw_screen(_xfc, _x, _y, _w, _h) \ + xf_draw_screen_((_xfc), (_x), (_y), (_w), (_h), __FUNCTION__, __FILE__, __LINE__) +void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file, + int line); + +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..b465a63 --- /dev/null +++ b/client/common/CMakeLists.txt @@ -0,0 +1,94 @@ +# 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 + file.c + geometry.c) + +if(NOT DEFINE_NO_DEPRECATED) + list(APPEND ${MODULE_PREFIX}_SRCS + compatibility.c + compatibility.h) +endif() + +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..6862deb --- /dev/null +++ b/client/common/client.c @@ -0,0 +1,805 @@ +/** + * 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; + + WLog_DBG(TAG, "This is %s", freerdp_get_build_config()); + 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, int argc, char* argv[]) +{ + int status, x; + int ret = -1; + char* filename; + char* password = NULL; + rdpAssistanceFile* file; + + if (!settings || !argv || (argc < 2)) + return -1; + + filename = argv[1]; + + for (x = 2; x < argc; x++) + { + const char* key = strstr(argv[x], "assistance:"); + + if (key) + password = strchr(key, ':') + 1; + } + + file = freerdp_assistance_file_new(); + + if (!file) + return -1; + + status = freerdp_assistance_parse_file(file, filename, password); + + if (status < 0) + goto out; + + if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings)) + goto out; + + ret = 0; +out: + freerdp_assistance_file_free(file); + return ret; +} + +/** Callback set in the rdp_freerdp structure, and used to get the user's password, + * if required to establish the connection. + * This function is actually called in credssp_ntlmssp_client_init() + * @see rdp_server_accept_nego() and rdp_check_fds() + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param username - unused + * @param password - on return: pointer to a character string that will be filled by the password + * entered by the user. Note that this character string will be allocated inside the function, and + * needs to be deallocated by the caller using free(), even in case this function fails. + * @param domain - unused + * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more + * details. + */ +static BOOL client_cli_authenticate_raw(freerdp* instance, 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': + fgetc(stdin); + return 1; + + case 't': + case 'T': + fgetc(stdin); + return 2; + + case 'n': + case 'N': + fgetc(stdin); + 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() + * @deprecated Use client_cli_verify_certificate_ex + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param host_mismatch Indicates the certificate host does not match. + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, BOOL host_mismatch) +{ + WINPR_UNUSED(common_name); + WINPR_UNUSED(host_mismatch); + + printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n"); + printf("Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("The above X.509 certificate could not be verified, possibly because you do not have\n" + "the CA certificate in your certificate store, or the certificate has expired.\n" + "Please look at the OpenSSL documentation on how to add a private CA to the store.\n"); + return client_cli_accept_certificate(instance->settings); +} + +/** 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 host The host currently connecting to + * @param port The port currently connecting to + * @param common_name The common name of the certificate, should match host or an alias of it + * @param subject The subject of the certificate + * @param issuer The certificate issuer name + * @param fingerprint The fingerprint of the certificate + * @param flags See VERIFY_CERT_FLAG_* for possible values. + * + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, DWORD flags) +{ + const char* type = "RDP-Server"; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type); + printf("\tCommon Name: %s\n", common_name); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + 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() + * @deprecated Use client_cli_verify_changed_certificate_ex + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param old_subject + * @param old_issuer + * @param old_fingerprint + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name, + const char* subject, const char* issuer, + const char* fingerprint, const char* old_subject, + const char* old_issuer, const char* old_fingerprint) +{ + WINPR_UNUSED(common_name); + + printf("WARNING: This callback is deprecated, migrate to " + "client_cli_verify_changed_certificate_ex\n"); + printf("!!! Certificate has changed !!!\n"); + printf("\n"); + printf("New Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("\n"); + printf("Old Certificate details:\n"); + printf("\tSubject: %s\n", old_subject); + printf("\tIssuer: %s\n", old_issuer); + printf("\tThumbprint: %s\n", old_fingerprint); + printf("\n"); + printf("The above X.509 certificate does not match the certificate used for previous " + "connections.\n" + "This may indicate that the certificate has been tampered with.\n" + "Please contact the administrator of the RDP server and clarify.\n"); + return client_cli_accept_certificate(instance->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 host The host currently connecting to + * @param port The port currently connecting to + * @param common_name The common name of the certificate, should match host or an alias of it + * @param subject The subject of the certificate + * @param issuer The certificate issuer name + * @param fingerprint The fingerprint of the certificate + * @param old_subject The subject of the previous certificate + * @param old_issuer The previous certificate issuer name + * @param old_fingerprint The fingerprint of the previous certificate + * @param flags See VERIFY_CERT_FLAG_* for possible values. + * + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint, DWORD flags) +{ + const char* type = "RDP-Server"; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type); + printf("\n"); + printf("New Certificate details:\n"); + printf("\tCommon Name: %s\n", common_name); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + 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"); + if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1) + { + printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n"); + printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n"); + printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n"); + printf("\tAll manually accepted certificates must be reconfirmed!\n"); + printf("\n"); + } + printf("The above X.509 certificate does not match the certificate used for previous " + "connections.\n" + "This may indicate that the certificate has been tampered with.\n" + "Please contact the administrator of the RDP server and clarify.\n"); + return client_cli_accept_certificate(instance->settings); +} + +BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, + const WCHAR* message) +{ + char answer; + const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message"; + + if (!isDisplayMandatory && !isConsentMandatory) + return TRUE; + + printf("%s:\n", msgType); +#if defined(WIN32) + printf("%.*S\n", (int)length, message); +#else + { + LPSTR msg; + if (ConvertFromUnicode(CP_UTF8, 0, message, (int)(length / 2), &msg, 0, NULL, NULL) < 1) + { + printf("Failed to convert message!\n"); + return FALSE; + } + printf("%s\n", msg); + free(msg); + } +#endif + + while (isConsentMandatory) + { + printf("I understand and agree to the terms of this policy (Y/N) \n"); + fflush(stdout); + answer = fgetc(stdin); + + if (feof(stdin)) + { + printf("\nError: Could not read answer from stdin.\n"); + return FALSE; + } + + switch (answer) + { + case 'y': + case 'Y': + fgetc(stdin); + return TRUE; + + case 'n': + case 'N': + fgetc(stdin); + return FALSE; + + default: + break; + } + + printf("\n"); + } + + return TRUE; +} + +BOOL client_auto_reconnect(freerdp* instance) +{ + return client_auto_reconnect_ex(instance, NULL); +} + +BOOL client_auto_reconnect_ex(freerdp* instance, BOOL (*window_events)(freerdp* instance)) +{ + BOOL retry = TRUE; + UINT32 error; + 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. */ + error = freerdp_error_info(instance); + switch (error) + { + case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED: + /* A network disconnect was detected */ + WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]", + freerdp_get_error_info_string(error)); + break; + case ERRINFO_SUCCESS: + /* A network disconnect was detected */ + WLog_INFO(TAG, "Network disconnect!"); + break; + default: + return FALSE; + } + + if (!settings->AutoReconnectionEnabled) + { + /* No auto-reconnect - just quit */ + return FALSE; + } + + /* Perform an auto-reconnect. */ + while (retry) + { + 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; + + switch (freerdp_get_last_error(instance->context)) + { + case FREERDP_ERROR_CONNECT_CANCELLED: + WLog_WARN(TAG, "Autoreconnect aborted by user"); + retry = FALSE; + break; + default: + break; + } + for (x = 0; x < 50; x++) + { + if (!IFCALLRESULT(TRUE, window_events, instance)) + return FALSE; + + Sleep(10); + } + } + + 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..d2d949b --- /dev/null +++ b/client/common/cmdline.c @@ -0,0 +1,3907 @@ +/** + * 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 + +#include "compatibility.h" +#include "cmdline.h" + +#include + +#define TAG CLIENT_TAG("common.cmdline") + +static BOOL freerdp_client_print_codepages(const char* arg) +{ + size_t count = 0, x; + DWORD column = 2; + const char* filter = NULL; + char buffer[80]; + RDP_CODEPAGE* pages; + + if (arg) + filter = strchr(arg, ',') + 1; + pages = freerdp_keyboard_get_matching_codepages(column, filter, &count); + if (!pages) + return TRUE; + + printf("%-10s %-8s %-60s %-36s %-48s\n", "", "", "", "", + ""); + for (x = 0; x < count; x++) + { + const RDP_CODEPAGE* page = &pages[x]; + if (strnlen(page->subLanguageSymbol, ARRAYSIZE(page->subLanguageSymbol)) > 0) + _snprintf(buffer, sizeof(buffer), "[%s|%s]", page->primaryLanguageSymbol, + page->subLanguageSymbol); + else + _snprintf(buffer, sizeof(buffer), "[%s]", page->primaryLanguageSymbol); + printf("id=0x%04" PRIx16 ": [%-6s] %-60s %-36s %-48s\n", page->id, page->locale, buffer, + page->primaryLanguage, page->subLanguage); + } + freerdp_codepages_free(pages); + return TRUE; +} + +static BOOL freerdp_path_valid(const char* path, BOOL* special) +{ + const char DynamicDrives[] = "DynamicDrives"; + BOOL isPath = FALSE; + BOOL isSpecial; + if (!path) + return FALSE; + + isSpecial = (strncmp(path, "*", 2) == 0) || + (strncmp(path, DynamicDrives, sizeof(DynamicDrives)) == 0) || + (strncmp(path, "%", 2) == 0) + ? TRUE + : FALSE; + if (!isSpecial) + isPath = winpr_PathFileExists(path); + + if (special) + *special = isSpecial; + + return isSpecial || isPath; +} + +static BOOL freerdp_sanitize_drive_name(char* name, const char* invalid, const char* replacement) +{ + if (!name || !invalid || !replacement) + return FALSE; + if (strlen(invalid) != strlen(replacement)) + return FALSE; + + while (*invalid != '\0') + { + const char what = *invalid++; + const char with = *replacement++; + + char* cur = name; + while ((cur = strchr(cur, what)) != NULL) + *cur = with; + } + return TRUE; +} + +static char* name_from_path(const char* path) +{ + const char* name = "NULL"; + if (path) + { + if (_strnicmp(path, "%", 2) == 0) + name = "home"; + else if (_strnicmp(path, "*", 2) == 0) + name = "hotplug-all"; + else if (_strnicmp(path, "DynamicDrives", 2) == 0) + name = "hotplug"; + else + name = path; + } + return _strdup(name); +} + +static BOOL freerdp_client_add_drive(rdpSettings* settings, const char* path, const char* name) +{ + RDPDR_DRIVE* drive; + + drive = (RDPDR_DRIVE*)calloc(1, sizeof(RDPDR_DRIVE)); + + if (!drive) + return FALSE; + + drive->Type = RDPDR_DTYP_FILESYSTEM; + + if (name) + { + /* Path was entered as secondary argument, swap */ + if (winpr_PathFileExists(name)) + { + if (!winpr_PathFileExists(path) || (!PathIsRelativeA(name) && PathIsRelativeA(path))) + { + const char* tmp = path; + path = name; + name = tmp; + } + } + } + + if (name) + { + if (!(drive->Name = _strdup(name))) + goto fail; + } + else /* We need a name to send to the server. */ + { + if (!(drive->Name = name_from_path(path))) + goto fail; + } + + if (!path || !freerdp_sanitize_drive_name(drive->Name, "\\/", "__")) + goto fail; + else + { + BOOL isSpecial = FALSE; + BOOL isPath = freerdp_path_valid(path, &isSpecial); + + if ((!isPath && !isSpecial) || !(drive->Path = _strdup(path))) + goto fail; + } + + if (!freerdp_device_collection_add(settings, (RDPDR_DEVICE*)drive)) + goto fail; + + return TRUE; + +fail: + free(drive->Path); + free(drive->Name); + free(drive); + return FALSE; +} + +static BOOL copy_value(const char* value, char** dst) +{ + if (!dst || !value) + return FALSE; + + free(*dst); + (*dst) = _strdup(value); + return (*dst) != NULL; +} + +static BOOL append_value(const char* value, char** dst) +{ + size_t x = 0, y; + size_t size; + char* tmp; + if (!dst || !value) + return FALSE; + + if (*dst) + x = strlen(*dst); + y = strlen(value); + + size = x + y + 2; + tmp = realloc(*dst, size); + if (!tmp) + return FALSE; + if (x == 0) + tmp[0] = '\0'; + else + winpr_str_append(",", tmp, size, NULL); + winpr_str_append(value, tmp, size, NULL); + *dst = tmp; + return TRUE; +} + +static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max) +{ + long long rc; + + if (!value || !result) + return FALSE; + + errno = 0; + rc = _strtoi64(value, NULL, 0); + + if (errno != 0) + return FALSE; + + if ((rc < min) || (rc > max)) + return FALSE; + + *result = rc; + return TRUE; +} + +static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max) +{ + unsigned long long rc; + + if (!value || !result) + return FALSE; + + errno = 0; + rc = _strtoui64(value, NULL, 0); + + if (errno != 0) + return FALSE; + + if ((rc < min) || (rc > max)) + return FALSE; + + *result = rc; + return TRUE; +} + +BOOL freerdp_client_print_version(void) +{ + printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, GIT_REVISION); + return TRUE; +} + +BOOL freerdp_client_print_buildconfig(void) +{ + printf("%s", freerdp_get_build_config()); + return TRUE; +} + +static char* print_token(char* text, size_t start_offset, size_t* current, size_t limit, + const char delimiter) +{ + int rc; + size_t len = strlen(text); + + if (*current < start_offset) + { + rc = printf("%*c", (int)(start_offset - *current), ' '); + if (rc < 0) + return NULL; + *current += (size_t)rc; + } + + if (*current + len > limit) + { + size_t x; + + for (x = MIN(len, limit - start_offset); x > 1; x--) + { + if (text[x] == delimiter) + { + printf("%.*s\n", (int)x, text); + *current = 0; + return &text[x]; + } + } + + return NULL; + } + + rc = printf("%s", text); + if (rc < 0) + return NULL; + *current += (size_t)rc; + return NULL; +} + +static size_t print_optionals(const char* text, size_t start_offset, size_t current) +{ + const size_t limit = 80; + char* str = _strdup(text); + char* cur = print_token(str, start_offset, ¤t, limit, '['); + + while (cur) + cur = print_token(cur, start_offset, ¤t, limit, '['); + + free(str); + return current; +} + +static size_t print_description(const char* text, size_t start_offset, size_t current) +{ + const size_t limit = 80; + char* str = _strdup(text); + char* cur = print_token(str, start_offset, ¤t, limit, ' '); + + while (cur) + { + cur++; + cur = print_token(cur, start_offset, ¤t, limit, ' '); + } + + free(str); + current += (size_t)printf("\n"); + return current; +} + +static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* arg) +{ + if (!arg) + return; + + do + { + int rc; + size_t pos = 0; + const size_t description_offset = 30 + 8; + + if (arg->Flags & COMMAND_LINE_VALUE_BOOL) + rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name); + else + rc = printf(" /%s", arg->Name); + + if (rc < 0) + return; + pos += (size_t)rc; + + if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) || + (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)) + { + if (arg->Format) + { + if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) + { + rc = printf("[:"); + if (rc < 0) + return; + pos += (size_t)rc; + pos = print_optionals(arg->Format, pos, pos); + rc = printf("]"); + if (rc < 0) + return; + pos += (size_t)rc; + } + else + { + rc = printf(":"); + if (rc < 0) + return; + pos += (size_t)rc; + pos = print_optionals(arg->Format, pos, pos); + } + + if (pos > description_offset) + { + printf("\n"); + pos = 0; + } + } + } + + rc = printf("%*c", (int)(description_offset - pos), ' '); + if (rc < 0) + return; + pos += (size_t)rc; + + if (arg->Flags & COMMAND_LINE_VALUE_BOOL) + { + rc = printf("%s ", arg->Default ? "Disable" : "Enable"); + if (rc < 0) + return; + pos += (size_t)rc; + } + + print_description(arg->Text, description_offset, pos); + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); +} + +BOOL freerdp_client_print_command_line_help(int argc, char** argv) +{ + return freerdp_client_print_command_line_help_ex(argc, argv, NULL); +} + +BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv, + COMMAND_LINE_ARGUMENT_A* custom) +{ + const char* name = "FreeRDP"; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(args)); + + if (argc > 0) + name = argv[0]; + + printf("\n"); + printf("FreeRDP - A Free Remote Desktop Protocol Implementation\n"); + printf("See www.freerdp.com for more information\n"); + printf("\n"); + printf("Usage: %s [file] [options] [/v:[: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(largs); + printf("\n"); + printf("Examples:\n"); + printf(" %s connection.rdp /p:Pwd123! /f\n", name); + printf(" %s /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n", name); + printf(" %s /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n", name); + printf(" %s /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 " + "/v:192.168.1.100\n", + name); + printf("\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("TCP redirection: /rdp2tcp:/usr/bin/rdp2tcp\n"); + printf("\n"); + printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n"); + printf("Audio Output Redirection: /sound:sys:alsa\n"); + printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n"); + printf("Audio Input Redirection: /microphone:sys:alsa\n"); + printf("\n"); + printf("Multimedia Redirection: /video\n"); +#ifdef CHANNEL_URBDRC_CLIENT + printf("USB Device Redirection: /usb:id:054c:0268#4669:6e6b,addr:04:0c\n"); +#endif + printf("\n"); + printf("For Gateways, the https_proxy environment variable is respected:\n"); +#ifdef _WIN32 + printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n"); +#else + printf(" export https_proxy=http://proxy.contoso.com:3128/\n"); +#endif + printf(" %s /g:rdp.contoso.com ...\n", name); + printf("\n"); + printf("More documentation is coming, in the meantime consult source files\n"); + printf("\n"); + return TRUE; +} + +static int freerdp_client_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv) +{ + if (index == 1) + { + size_t length; + rdpSettings* settings; + + if (argc <= index) + return -1; + + 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, size_t count, char** params) +{ + if (strcmp(params[0], "drive") == 0) + { + BOOL rc; + if (count < 2) + return FALSE; + + settings->DeviceRedirection = TRUE; + if (count < 3) + rc = freerdp_client_add_drive(settings, params[1], NULL); + else + rc = freerdp_client_add_drive(settings, params[2], params[1]); + + return rc; + } + else if (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 (!(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, size_t count, char** params) +{ + int index; + ADDIN_ARGV* args; + + if (!settings || !params || !params[0] || (count > INT_MAX)) + 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 = (int)count; + args->argv = (char**)calloc((size_t)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, size_t count, char** params) +{ + int index; + ADDIN_ARGV* args; + + if (!settings || !params || !params[0] || (count > INT_MAX)) + 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 = (int)count; + args->argv = (char**)calloc((size_t)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 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 = CommandLineParseCommaSeparatedValues(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 = CommandLineParseCommaSeparatedValues(arg->Value, &count); + status = freerdp_client_add_static_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "dvc") + { + char** p; + size_t count; + p = CommandLineParseCommaSeparatedValues(arg->Value, &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } + CommandLineSwitchCase(arg, "drive") + { + char** p; + size_t count; + p = CommandLineParseCommaSeparatedValuesEx(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 = CommandLineParseCommaSeparatedValuesEx(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 = CommandLineParseCommaSeparatedValuesEx(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 = CommandLineParseCommaSeparatedValuesEx(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 = CommandLineParseCommaSeparatedValuesEx(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 = CommandLineParseCommaSeparatedValuesEx(URBDRC_CHANNEL_NAME, 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 = CommandLineParseCommaSeparatedValuesEx("rdpsnd", arg->Value, &count); + status = freerdp_client_add_static_channel(settings, count, p); + if (status) + { + status = freerdp_client_add_dynamic_channel(settings, count, p); + } + free(p); + } + CommandLineSwitchCase(arg, "microphone") + { + char** p; + size_t count; + p = CommandLineParseCommaSeparatedValuesEx("audin", arg->Value, &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } +#if defined(CHANNEL_TSMF_CLIENT) + CommandLineSwitchCase(arg, "multimedia") + { + char** p; + size_t count; + p = CommandLineParseCommaSeparatedValuesEx("tsmf", arg->Value, &count); + status = freerdp_client_add_dynamic_channel(settings, count, p); + free(p); + } +#endif + 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; + size_t length = 0; + p = strchr(username, '\\'); + *user = NULL; + *domain = NULL; + + if (p) + { + length = (size_t)(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) + { + size_t length = (size_t)(p - hostname); + LONGLONG val; + + if (!value_to_int(p + 1, &val, 1, UINT16_MAX)) + return FALSE; + + *host = (char*)calloc(length + 1UL, sizeof(char)); + + if (!(*host)) + return FALSE; + + CopyMemory(*host, hostname, length); + (*host)[length] = '\0'; + *port = (UINT16)val; + } + else + { + *host = _strdup(hostname); + + if (!(*host)) + return FALSE; + + *port = -1; + } + + return TRUE; +} + +BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 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; + + /* Automatically activate GFX and RFX codec support */ +#ifdef WITH_GFX_H264 + settings->GfxAVC444 = TRUE; + settings->GfxH264 = TRUE; +#endif + settings->RemoteFxCodec = TRUE; + settings->SupportGraphicsPipeline = TRUE; + } + else + { + return FALSE; + } + + return TRUE; +} + +static 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 = (int)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 = (int)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 = (int)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) +{ + size_t length; + WINPR_UNUSED(context); + + if (index == 1) + { + if (argc < index) + return -1; + + length = 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; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(args)); + + flags = COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SILENCE_PARSER; + flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(largs); + status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL, + freerdp_detect_command_line_pre_filter, NULL); + + if (status < 0) + return status; + + arg = largs; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST)) + detect_status = -1; + + return detect_status; +} + +static int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, size_t* count, + BOOL ignoreUnknown) +{ + int status; + DWORD flags; + int detect_status; + COMMAND_LINE_ARGUMENT_A* arg; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(args)); + + flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SILENCE_PARSER; + flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(largs); + status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL, + freerdp_detect_command_line_pre_filter, NULL); + + if (status < 0) + return status; + + arg = largs; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST)) + detect_status = -1; + + return detect_status; +} + +static BOOL freerdp_client_detect_command_line(int argc, char** argv, DWORD* flags) +{ +#if !defined(DEFINE_NO_DEPRECATED) + int old_cli_status; + size_t old_cli_count; +#endif + int posix_cli_status; + size_t posix_cli_count; + int windows_cli_status; + size_t windows_cli_count; + BOOL compatibility = FALSE; + const BOOL ignoreUnknown = TRUE; + windows_cli_status = freerdp_detect_windows_style_command_line_syntax( + argc, argv, &windows_cli_count, ignoreUnknown); + posix_cli_status = + freerdp_detect_posix_style_command_line_syntax(argc, argv, &posix_cli_count, ignoreUnknown); +#if !defined(DEFINE_NO_DEPRECATED) + old_cli_status = freerdp_detect_old_command_line_syntax(argc, argv, &old_cli_count); +#endif + + /* 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; + } +#if !defined(DEFINE_NO_DEPRECATED) + 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); +#else + WLog_DBG(TAG, "windows: %d/%d posix: %d/%d", windows_cli_status, windows_cli_count, + posix_cli_status, posix_cli_count); +#endif + + 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; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(args)); + + if (status == COMMAND_LINE_STATUS_PRINT_VERSION) + { + freerdp_client_print_version(); + goto out; + } + + if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG) + { + freerdp_client_print_version(); + freerdp_client_print_buildconfig(); + goto out; + } + else if (status == COMMAND_LINE_STATUS_PRINT) + { + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(largs)); + CommandLineParseArgumentsA(argc, argv, largs, 0x112, NULL, NULL, NULL); + + arg = CommandLineFindArgumentA(largs, "kbd-lang-list"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + freerdp_client_print_codepages(arg->Value); + } + + arg = CommandLineFindArgumentA(largs, "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(largs, "monitor-list"); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + settings->ListMonitors = TRUE; + } + + goto out; + } + else if (status < 0) + { + freerdp_client_print_command_line_help_ex(argc, argv, custom); + goto out; + } + +out: + if (status <= COMMAND_LINE_STATUS_PRINT && status >= COMMAND_LINE_STATUS_PRINT_LAST) + return 0; + return status; +} + +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 _strnicmp(&str[strLen - extLen], ext, extLen) == 0; +} + +static void activate_smartcard_logon_rdp(rdpSettings* settings) +{ + settings->SmartcardLogon = TRUE; + /* TODO: why not? settings->UseRdpSecurityLayer = TRUE; */ + freerdp_settings_set_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; +} + +static BOOL prepare_default_settings(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* args, + BOOL rdp_file) +{ + size_t x; + const char* arguments[] = { "network", "gfx", "rfx", "bpp" }; + WINPR_ASSERT(settings); + WINPR_ASSERT(args); + + if (rdp_file) + return FALSE; + + for (x = 0; x < ARRAYSIZE(arguments); x++) + { + const char* arg = arguments[x]; + COMMAND_LINE_ARGUMENT_A* p = CommandLineFindArgumentA(args, arg); + if (p && (p->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + return FALSE; + } + + return freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT); +} + +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_ARGUMENT_A largs[ARRAYSIZE(args)]; + memcpy(largs, args, sizeof(args)); + + /* 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); + else + compatibility = freerdp_client_detect_command_line(argc - 1, &argv[1], &flags); + + freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, NULL); + freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, NULL); + freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, NULL); + +#if !defined(DEFINE_NO_DEPRECATED) + if (compatibility) + { + WLog_WARN(TAG, "----------------------------------------"); + WLog_WARN(TAG, "Using deprecated command-line interface!"); + WLog_WARN(TAG, "This will be removed with FreeRDP 3!"); + WLog_WARN(TAG, "----------------------------------------"); + return freerdp_client_parse_old_command_line_arguments(argc, argv, settings); + } + else +#endif + { + if (allowUnknown) + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + + if (ext) + { + if (freerdp_client_settings_parse_connection_file(settings, argv[1])) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (assist) + { + if (freerdp_client_settings_parse_assistance_file(settings, argc, argv) < 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + CommandLineClearArgumentsA(largs); + status = CommandLineParseArgumentsA(argc, argv, largs, flags, settings, + freerdp_client_command_line_pre_filter, + freerdp_client_command_line_post_filter); + + if (status < 0) + return status; + + prepare_default_settings(settings, largs, ext); + } + + CommandLineFindArgumentA(largs, "v"); + arg = largs; + errno = 0; + + do + { + BOOL enable = arg->Value ? TRUE : FALSE; + + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "v") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + free(settings->ServerHostname); + settings->ServerHostname = NULL; + p = strchr(arg->Value, '['); + + /* ipv4 */ + if (!p) + { + p = strchr(arg->Value, ':'); + + if (p) + { + LONGLONG val; + + if (!value_to_int(&p[1], &val, 1, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (size_t)(p - arg->Value); + settings->ServerPort = (UINT16)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 = (size_t)(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) == ':') + { + LONGLONG val; + + if (!value_to_int(&p[2], &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ServerPort = (UINT16)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; + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, -1, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopWidth = (UINT32)val; + } + CommandLineSwitchCase(arg, "h") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, -1, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopHeight = (UINT32)val; + } + CommandLineSwitchCase(arg, "size") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + 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 = (UINT32)w; + settings->DesktopHeight = (UINT32)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'; + { + LONGLONG val; + + if (!value_to_int(str, &val, 0, 100)) + { + free(str); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + settings->PercentScreen = (UINT32)val; + } + } + + free(str); + } + } + CommandLineSwitchCase(arg, "f") + { + settings->Fullscreen = enable; + } + CommandLineSwitchCase(arg, "suppress-output") + { + settings->SuppressOutput = 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 = CommandLineParseCommaSeparatedValues(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++) + { + LONGLONG val; + + if (!value_to_int(p[i], &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->MonitorIds[i] = (UINT32)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 = (UINT32)w; + settings->SmartSizingHeight = (UINT32)h; + } + } + CommandLineSwitchCase(arg, "bpp") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (settings->ColorDepth) + { + case 32: + case 24: + case 16: + case 15: + case 8: + settings->ColorDepth = (UINT32)val; + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 1, UINT32_MAX)) + { + 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 */ + val = rc; + errno = 0; + } + + settings->KeyboardLayout = (UINT32)val; + } + CommandLineSwitchCase(arg, "kbd-remap") + { + if (!copy_value(arg->Value, &settings->KeyboardRemappingList)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "kbd-lang") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 1, UINT32_MAX)) + { + WLog_ERR(TAG, "Could not identify keyboard active language %s", arg->Value); + WLog_ERR(TAG, "Use /kbd-lang-list to list available layouts"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + settings->KeyboardCodePage = (UINT32)val; + } + CommandLineSwitchCase(arg, "kbd-type") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardType = (UINT32)val; + } + CommandLineSwitchCase(arg, "kbd-subtype") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardSubType = (UINT32)val; + } + CommandLineSwitchCase(arg, "kbd-fn-key") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->KeyboardFunctionKey = (UINT32)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) + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + p = strchr(arg->Value, ':'); + + if (p) + { + size_t s; + LONGLONG val; + + if (!value_to_int(&p[1], &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + s = (size_t)(p - arg->Value); + settings->GatewayPort = (UINT32)val; + + if (!(settings->GatewayHostname = (char*)calloc(s + 1UL, sizeof(char)))) + return COMMAND_LINE_ERROR_MEMORY; + + strncpy(settings->GatewayHostname, arg->Value, s); + settings->GatewayHostname[s] = '\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 */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP)) + return COMMAND_LINE_ERROR_MEMORY; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + char* atPtr; + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + /* value is [scheme://][user:password@]hostname:port */ + p = strstr(arg->Value, "://"); + + if (p) + { + *p = '\0'; + + if (_stricmp("no_proxy", arg->Value) == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, + PROXY_TYPE_IGNORE)) + return COMMAND_LINE_ERROR_MEMORY; + } + if (_stricmp("http", arg->Value) == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, + PROXY_TYPE_HTTP)) + return COMMAND_LINE_ERROR_MEMORY; + } + else if (_stricmp("socks5", arg->Value) == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, + PROXY_TYPE_SOCKS)) + return COMMAND_LINE_ERROR_MEMORY; + } + 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'; + if (!freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, arg->Value)) + { + WLog_ERR(TAG, "unable to allocate proxy username"); + return COMMAND_LINE_ERROR_MEMORY; + } + + *atPtr = '\0'; + + if (!freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, colonPtr + 1)) + { + WLog_ERR(TAG, "unable to allocate proxy password"); + return COMMAND_LINE_ERROR_MEMORY; + } + + arg->Value = atPtr + 1; + } + + p = strchr(arg->Value, ':'); + + if (p) + { + LONGLONG val; + + if (!value_to_int(&p[1], &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (size_t)(p - arg->Value); + if (!freerdp_settings_set_uint16(settings, FreeRDP_ProxyPort, val)) + return FALSE; + *p = '\0'; + } + + p = strchr(arg->Value, '/'); + if (p) + *p = '\0'; + if (!freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, arg->Value)) + return FALSE; + } + 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) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + { + char* c = strchr(arg->Value, ','); + if (c) + { + *c++ = '\0'; + if (_stricmp(c, "no-websockets") != 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, + FALSE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (_stricmp(arg->Value, "http") == 0) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else if (_stricmp(arg->Value, "auto") == 0) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + } + CommandLineSwitchCase(arg, "gat") + { + if (!copy_value(arg->Value, &settings->GatewayAccessToken)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "gateway-usage-method") + { + UINT32 type = 0; + + 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 + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, TSC_PROXY_MODE_NONE_DIRECT, + TSC_PROXY_MODE_NONE_DETECT)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + freerdp_set_gateway_usage_method(settings, 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, "app-workdir") + { + if (!copy_value(arg->Value, &settings->RemoteApplicationWorkingDir)) + return COMMAND_LINE_ERROR_MEMORY; + } + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->CompressionLevel = (UINT32)val; + } + CommandLineSwitchCase(arg, "drives") + { + settings->RedirectDrives = enable; + } + CommandLineSwitchCase(arg, "home-drive") + { + settings->RedirectHomeDrive = enable; + } + CommandLineSwitchCase(arg, "ipv6") + { + settings->PreferIPv6OverIPv4 = enable; + } + CommandLineSwitchCase(arg, "clipboard") + { + if (arg->Value == BoolValueTrue || arg->Value == BoolValueFalse) + { + settings->RedirectClipboard = (arg->Value == BoolValueTrue); + } + else + { + int rc = 0; + char** p; + size_t count, x; + p = CommandLineParseCommaSeparatedValues(arg->Value, &count); + for (x = 0; (x < count) && (rc == 0); x++) + { + const char usesel[14] = "use-selection:"; + + const char* cur = p[x]; + if (_strnicmp(usesel, cur, sizeof(usesel)) == 0) + { + const char* val = &cur[sizeof(usesel)]; + if (!copy_value(val, &settings->XSelectionAtom)) + rc = COMMAND_LINE_ERROR_MEMORY; + settings->RedirectClipboard = TRUE; + } + else + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + free(p); + + if (rc) + return rc; + } + } + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case AUDIO_MODE_REDIRECT: + settings->AudioPlayback = TRUE; + break; + + case AUDIO_MODE_PLAY_ON_SERVER: + settings->RemoteConsoleAudio = TRUE; + break; + + case AUDIO_MODE_NONE: + settings->AudioPlayback = FALSE; + settings->RemoteConsoleAudio = FALSE; + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineSwitchCase(arg, "network") + { + UINT32 type = 0; + + 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 + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 1, 7)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + type = (UINT32)val; + } + + if (!freerdp_set_connection_type(settings, type)) + return COMMAND_LINE_ERROR; + } + 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 = (UINT32)x; + settings->DesktopPosY = (UINT32)y; + } + CommandLineSwitchCase(arg, "menu-anims") + { + settings->DisableMenuAnims = !enable; + } + CommandLineSwitchCase(arg, "themes") + { + settings->DisableThemes = !enable; + } + CommandLineSwitchCase(arg, "timeout") + { + ULONGLONG val; + if (!value_to_uint(arg->Value, &val, 1, 600000)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + settings->TcpAckTimeout = (UINT32)val; + } + 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) + { + int rc = CHANNEL_RC_OK; + char** p; + size_t count, x; + + p = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!p || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + for (x = 0; x < count; x++) + { + const char* val = p[x]; +#ifdef WITH_GFX_H264 + if (_strnicmp("AVC444", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = TRUE; + } + else if (_strnicmp("AVC420", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = FALSE; + } + else +#endif + if (_strnicmp("RFX", val, 4) == 0) + { + settings->GfxAVC444 = FALSE; + settings->GfxH264 = FALSE; + settings->RemoteFxCodec = TRUE; + } + else if (_strnicmp("mask:", val, 5) == 0) + { + ULONGLONG v; + const char* uv = &val[5]; + if (!value_to_uint(uv, &v, 0, UINT32_MAX)) + rc = COMMAND_LINE_ERROR; + else + settings->GfxCapsFilter = (UINT32)v; + } + else + rc = COMMAND_LINE_ERROR; + } + } + free(p); + if (rc != CHANNEL_RC_OK) + return rc; + } + } + 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; + } +#if !defined(DEFINE_NO_DEPRECATED) +#ifdef WITH_GFX_H264 + CommandLineSwitchCase(arg, "gfx-h264") + { + settings->SupportGraphicsPipeline = TRUE; + settings->GfxH264 = TRUE; + + if (arg->Value) + { + int rc = CHANNEL_RC_OK; + char** p; + size_t count, x; + + p = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!p || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + for (x = 0; x < count; x++) + { + const char* val = p[x]; + + if (_strnicmp("AVC444", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = TRUE; + } + else if (_strnicmp("AVC420", val, 7) == 0) + { + settings->GfxH264 = TRUE; + settings->GfxAVC444 = FALSE; + } + else if (_strnicmp("mask:", val, 5) == 0) + { + ULONGLONG v; + const char* uv = &val[5]; + if (!value_to_uint(uv, &v, 0, UINT32_MAX)) + rc = COMMAND_LINE_ERROR; + else + settings->GfxCapsFilter = (UINT32)v; + } + else + rc = COMMAND_LINE_ERROR; + } + } + free(p); + if (rc != CHANNEL_RC_OK) + return rc; + } + } +#endif +#endif + CommandLineSwitchCase(arg, "rfx") + { + settings->RemoteFxCodec = enable; + } + CommandLineSwitchCase(arg, "rfx-mode") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (strcmp(arg->Value, "video") == 0) + settings->RemoteFxCodecMode = 0x00; + else if (strcmp(arg->Value, "image") == 0) + settings->RemoteFxCodecMode = 0x02; + } + CommandLineSwitchCase(arg, "frame-ack") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->FrameAcknowledge = (UINT32)val; + } + CommandLineSwitchCase(arg, "nsc") + { + settings->NSCodec = enable; + } +#if defined(WITH_JPEG) + CommandLineSwitchCase(arg, "jpeg") + { + settings->JpegCodec = enable; + settings->JpegQuality = 75; + } + CommandLineSwitchCase(arg, "jpeg-quality") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, 100)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->JpegQuality = (UINT32)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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->SendPreconnectionPdu = TRUE; + settings->PreconnectionId = (UINT32)val; + } + CommandLineSwitchCase(arg, "sec") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + 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 = CommandLineParseCommaSeparatedValues(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) + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + promptForPassword = (_strnicmp(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") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, 5)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->TlsSecLevel = (UINT32)val; + } + CommandLineSwitchCase(arg, "enforce-tlsv1_2") + { + if (!(freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, TLS1_2_VERSION) && + freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, TLS1_2_VERSION))) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + CommandLineSwitchCase(arg, "cert") + { + int rc = 0; + char** p; + size_t count, x; + p = CommandLineParseCommaSeparatedValues(arg->Value, &count); + for (x = 0; (x < count) && (rc == 0); x++) + { + const char deny[] = "deny"; + const char ignore[] = "ignore"; + const char tofu[] = "tofu"; + const char name[5] = "name:"; + const char fingerprints[12] = "fingerprint:"; + + const char* cur = p[x]; + if (_strnicmp(deny, cur, sizeof(deny)) == 0) + settings->AutoDenyCertificate = TRUE; + else if (_strnicmp(ignore, cur, sizeof(ignore)) == 0) + settings->IgnoreCertificate = TRUE; + else if (_strnicmp(tofu, cur, 4) == 0) + settings->AutoAcceptCertificate = TRUE; + else if (_strnicmp(name, cur, sizeof(name)) == 0) + { + const char* val = &cur[sizeof(name)]; + if (!copy_value(val, &settings->CertificateName)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + else if (_strnicmp(fingerprints, cur, sizeof(fingerprints)) == 0) + { + const char* val = &cur[sizeof(fingerprints)]; + if (!append_value(val, &settings->CertificateAcceptedFingerprints)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + else + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + free(p); + + if (rc) + return rc; + } +#if !defined(DEFINE_NO_DEPRECATED) + 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, "cert-deny") + { + settings->AutoDenyCertificate = enable; + } +#endif + 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") + { + /* Defaults are enabled, visible, sticky, fullscreen */ + settings->Floatbar = 0x0017; + + if (arg->Value) + { + char* start = arg->Value; + + do + { + char* cur = start; + start = strchr(start, ','); + + if (start) + { + *start = '\0'; + start = start + 1; + } + + /* sticky:[on|off] */ + if (_strnicmp(cur, "sticky:", 7) == 0) + { + const char* val = cur + 7; + settings->Floatbar &= ~0x02u; + + if (_strnicmp(val, "on", 3) == 0) + settings->Floatbar |= 0x02u; + else if (_strnicmp(val, "off", 4) == 0) + settings->Floatbar &= ~0x02u; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + /* default:[visible|hidden] */ + else if (_strnicmp(cur, "default:", 8) == 0) + { + const char* val = cur + 8; + settings->Floatbar &= ~0x04u; + + if (_strnicmp(val, "visible", 8) == 0) + settings->Floatbar |= 0x04u; + else if (_strnicmp(val, "hidden", 7) == 0) + settings->Floatbar &= ~0x04u; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + /* show:[always|fullscreen|window] */ + else if (_strnicmp(cur, "show:", 5) == 0) + { + const char* val = cur + 5; + settings->Floatbar &= ~0x30u; + + if (_strnicmp(val, "always", 7) == 0) + settings->Floatbar |= 0x30u; + else if (_strnicmp(val, "fullscreen", 11) == 0) + settings->Floatbar |= 0x10u; + else if (_strnicmp(val, "window", 7) == 0) + settings->Floatbar |= 0x20u; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } while (start); + } + } + CommandLineSwitchCase(arg, "mouse-motion") + { + settings->MouseMotion = enable; + } + CommandLineSwitchCase(arg, "parent-window") + { + ULONGLONG val; + + if (!value_to_uint(arg->Value, &val, 0, UINT64_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ParentWindowId = (UINT64)val; + } + CommandLineSwitchCase(arg, "client-build-number") + { + ULONGLONG val; + + if (!value_to_uint(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ClientBuild, (UINT32)val)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + CommandLineSwitchCase(arg, "bitmap-cache") + { + settings->BitmapCacheEnabled = enable; + } + CommandLineSwitchCase(arg, "offscreen-cache") + { + settings->OffscreenSupportLevel = (UINT32)enable; + } + CommandLineSwitchCase(arg, "glyph-cache") + { + settings->GlyphSupportLevel = arg->Value ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE; + } + CommandLineSwitchCase(arg, "codec-cache") + { + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->MultifragMaxRequestSize = (UINT32)val; + } + CommandLineSwitchCase(arg, "max-loop-time") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, -1, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (val < 0) + settings->MaxTimeInCheckLoop = + 10 * 60 * 60 * 1000; /* 10 hours can be considered as infinite */ + else + settings->MaxTimeInCheckLoop = (UINT32)val; + } + CommandLineSwitchCase(arg, "auto-request-control") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, + enable)) + return COMMAND_LINE_ERROR; + } + 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") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, 1000)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->AutoReconnectMaxRetries = (UINT32)val; + } + CommandLineSwitchCase(arg, "reconnect-cookie") + { + BYTE* base64 = NULL; + int length; + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + 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, (size_t)length); + } + else + { + WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value); + } + + free(base64); + } + CommandLineSwitchCase(arg, "print-reconnect-cookie") + { + settings->PrintReconnectCookie = enable; + } + CommandLineSwitchCase(arg, "pwidth") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopPhysicalWidth = (UINT32)val; + } + CommandLineSwitchCase(arg, "pheight") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopPhysicalHeight = (UINT32)val; + } + CommandLineSwitchCase(arg, "orientation") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->DesktopOrientation = (UINT16)val; + } + CommandLineSwitchCase(arg, "old-license") + { + settings->OldLicenseBehaviour = TRUE; + } + CommandLineSwitchCase(arg, "scale") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 100, 180)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case 100: + case 140: + case 180: + settings->DesktopScaleFactor = (UINT32)val; + settings->DeviceScaleFactor = (UINT32)val; + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineSwitchCase(arg, "scale-desktop") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 100, 500)) + return FALSE; + + settings->DesktopScaleFactor = (UINT32)val; + } + CommandLineSwitchCase(arg, "scale-device") + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 100, 180)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case 100: + case 140: + case 180: + settings->DeviceScaleFactor = (UINT32)val; + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineSwitchCase(arg, "action-script") + { + if (!copy_value(arg->Value, &settings->ActionScript)) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "rdp2tcp") + { + free(settings->RDP2TCPArgs); + + if (!(settings->RDP2TCPArgs = _strdup(arg->Value))) + return COMMAND_LINE_ERROR_MEMORY; + } + CommandLineSwitchCase(arg, "fipsmode") + { + settings->FIPSMode = enable; + } + CommandLineSwitchCase(arg, "smartcard-logon") + { + if (!settings->SmartcardLogon) + activate_smartcard_logon_rdp(settings); + } + + CommandLineSwitchCase(arg, "tune") + { + size_t x, count; + char** p = CommandLineParseCommaSeparatedValuesEx("tune", arg->Value, &count); + if (!p) + return COMMAND_LINE_ERROR; + for (x = 1; x < count; x++) + { + char* cur = p[x]; + char* sep = strchr(cur, ':'); + if (!sep) + { + free(p); + return COMMAND_LINE_ERROR; + } + *sep++ = '\0'; + if (!freerdp_settings_set_value_for_name(settings, cur, sep)) + { + free(p); + return COMMAND_LINE_ERROR; + } + } + + free(p); + } + CommandLineSwitchCase(arg, "tune-list") + { + size_t x; + SSIZE_T type = 0; + + printf("%s\t%50s\t%s\t%s", "", "", "", "\n"); + for (x = 0; x < FreeRDP_Settings_StableAPI_MAX; x++) + { + const char* name = freerdp_settings_get_name_for_key(x); + type = freerdp_settings_get_type_for_key(x); + + switch (type) + { + case RDP_SETTINGS_TYPE_BOOL: + printf("%" PRIuz "\t%50s\tBOOL\t%s\n", x, name, + freerdp_settings_get_bool(settings, x) ? "TRUE" : "FALSE"); + break; + case RDP_SETTINGS_TYPE_UINT16: + printf("%" PRIuz "\t%50s\tUINT16\t%" PRIu16 "\n", x, name, + freerdp_settings_get_uint16(settings, x)); + break; + case RDP_SETTINGS_TYPE_INT16: + printf("%" PRIuz "\t%50s\tINT16\t%" PRId16 "\n", x, name, + freerdp_settings_get_int16(settings, x)); + break; + case RDP_SETTINGS_TYPE_UINT32: + printf("%" PRIuz "\t%50s\tUINT32\t%" PRIu32 "\n", x, name, + freerdp_settings_get_uint32(settings, x)); + break; + case RDP_SETTINGS_TYPE_INT32: + printf("%" PRIuz "\t%50s\tINT32\t%" PRId32 "\n", x, name, + freerdp_settings_get_int32(settings, x)); + break; + case RDP_SETTINGS_TYPE_UINT64: + printf("%" PRIuz "\t%50s\tUINT64\t%" PRIu64 "\n", x, name, + freerdp_settings_get_uint64(settings, x)); + break; + case RDP_SETTINGS_TYPE_INT64: + printf("%" PRIuz "\t%50s\tINT64\t%" PRId64 "\n", x, name, + freerdp_settings_get_int64(settings, x)); + break; + case RDP_SETTINGS_TYPE_STRING: + printf("%" PRIuz "\t%50s\tSTRING\t%s" + "\n", + x, name, freerdp_settings_get_string(settings, x)); + break; + case RDP_SETTINGS_TYPE_POINTER: + printf("%" PRIuz "\t%50s\tPOINTER\t%p" + "\n", + x, name, freerdp_settings_get_pointer(settings, x)); + break; + default: + break; + } + } + return COMMAND_LINE_STATUS_PRINT; + } + 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->FrameMarkerCommandEnabled = TRUE; + settings->ColorDepth = 32; + } + + arg = CommandLineFindArgumentA(largs, "port"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + LONGLONG val; + + if (!value_to_int(arg->Value, &val, 1, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + settings->ServerPort = (UINT32)val; + } + + arg = CommandLineFindArgumentA(largs, "p"); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + FillMemory(arg->Value, strlen(arg->Value), '*'); + } + + arg = CommandLineFindArgumentA(largs, "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)(void*)freerdp_load_channel_addin_entry( + name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + + if (!entryEx) + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (entryEx) + { + if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) + { + WLog_DBG(TAG, "loading channelEx %s", name); + return TRUE; + } + } + else if (entry) + { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) + { + WLog_DBG(TAG, "loading channel %s", name); + return TRUE; + } + } + + return FALSE; +} + +BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) +{ + UINT32 index; + ADDIN_ARGV* args; + + if (settings->AudioPlayback) + { + char* p[] = { "rdpsnd" }; + + if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(p), p)) + return FALSE; + } + + /* for audio playback also load the dynamic sound channel */ + if (settings->AudioPlayback) + { + char* p[] = { "rdpsnd" }; + + if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p)) + return FALSE; + } + + if (settings->AudioCapture) + { + char* p[] = { "audin" }; + + if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p)) + return FALSE; + } + + if ((freerdp_static_channel_collection_find(settings, "rdpsnd")) || + (freerdp_dynamic_channel_collection_find(settings, "rdpsnd")) +#if defined(CHANNEL_TSMF_CLIENT) + || (freerdp_dynamic_channel_collection_find(settings, "tsmf")) +#endif + ) + { + 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->DrivesToRedirect && (strlen(settings->DrivesToRedirect) != 0)) + { + /* + * Drives to redirect: + * + * Very similar to DevicesToRedirect, but can contain a + * comma-separated list of drive letters to redirect. + */ + char* value; + char* tok; + char* context = NULL; + + value = _strdup(settings->DrivesToRedirect); + if (!value) + return FALSE; + + tok = strtok_s(value, ";", &context); + if (!tok) + { + free(value); + return FALSE; + } + + while (tok) + { + /* Syntax: Comma seperated list of the following entries: + * '*' ... Redirect all drives, including hotplug + * 'DynamicDrives' ... hotplug + * '%' ... user home directory + *