Import Upstream version 2.8.1

This commit is contained in:
zhouganqing 2022-11-21 15:16:05 +08:00
commit de0fdeebc1
2412 changed files with 569827 additions and 0 deletions

125
.clang-format Normal file
View File

@ -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
...

25
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -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 <freerdp-devel@lists.sourceforge.net>
* 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!_

56
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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 <freerdp-devel@lists.sourceforge.net>
* 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!_

View File

@ -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.

25
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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 <freerdp-devel@lists.sourceforge.net> may be helpful too.
## Please remove this text before submitting your pull!

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -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

154
.gitignore vendored Normal file
View File

@ -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/

1
.source_version Normal file
View File

@ -0,0 +1 @@
2.8.1

56
.travis.yml Normal file
View File

@ -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

102
CMakeCPack.cmake Normal file
View File

@ -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)

View File

@ -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()

1117
CMakeLists.txt Normal file

File diff suppressed because it is too large Load Diff

638
ChangeLog Normal file
View File

@ -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=<value> 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

202
LICENSE Normal file
View File

@ -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.

30
README.md Normal file
View File

@ -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

11
buildflags.h.in Normal file
View File

@ -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 */

346
channels/CMakeLists.txt Normal file
View File

@ -0,0 +1,346 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set(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()

View File

@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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()

View File

@ -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})

View File

@ -0,0 +1,34 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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")

View File

@ -0,0 +1,315 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <stdio.h>
#include <stdlib.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include "ainput_main.h"
#include <freerdp/channels/log.h>
#include <freerdp/client/ainput.h>
#include <freerdp/channels/ainput.h>
#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;
}

View File

@ -0,0 +1,43 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#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 */

View File

@ -0,0 +1,59 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <winpr/string.h>
#include <freerdp/channels/ainput.h>
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 */

View File

@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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")

View File

@ -0,0 +1,587 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <winpr/stream.h>
#include <freerdp/server/ainput.h>
#include <freerdp/channels/ainput.h>
#include <freerdp/channels/log.h>
#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);
}

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,58 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -0,0 +1,32 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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})

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <alsa/asoundlib.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>",
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/audin.h>
#define TAG CHANNELS_TAG("audin.client")
#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */

View File

@ -0,0 +1,35 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
# 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})

View File

@ -0,0 +1,466 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - Mac OS X implementation
*
* Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/debug.h>
#include <winpr/cmdline.h>
#import <AVFoundation/AVFoundation.h>
#define __COREFOUNDATION_CFPLUGINCOM__ 1
#define IUNKNOWN_C_GUTS \
void *_reserved; \
void *QueryInterface; \
void *AddRef; \
void *Release
#include <CoreAudio/CoreAudioTypes.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioToolbox/AudioQueue.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>",
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;
}

View File

@ -0,0 +1,33 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2013 Armin Novak <armin.novak@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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})

View File

@ -0,0 +1,342 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OpenSL ES implementation
*
* Copyright 2013 Armin Novak <armin.novak@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include <SLES/OpenSLES.h>
#include <freerdp/client/audin.h>
#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, "<device>", 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;
}

View File

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 <assert.h>
#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);
}

View File

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <freerdp/api.h>
#include <stdlib.h>
#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 */

View File

@ -0,0 +1,33 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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})

View File

@ -0,0 +1,488 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OSS implementation
*
* Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <unistd.h>
#if defined(__OpenBSD__)
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
#include <sys/ioctl.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>",
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;
}

View File

@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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})

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <pulse/pulseaudio.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/codec/audio.h>
#include <freerdp/client/audin.h>
#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, "<device>",
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;
}

View File

@ -0,0 +1,38 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -0,0 +1,568 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - WinMM implementation
*
* Copyright 2013 Zhang Zhaolong <zhangzl2013@126.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <mmsystem.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <freerdp/addin.h>
#include <freerdp/client/audin.h>
#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, "<device>",
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;
}

View File

@ -0,0 +1,29 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <freerdp/codec/dsp.h>
#include <freerdp/codec/audio.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/audin.h>
#include <freerdp/server/audin.h>
#include <freerdp/channels/log.h>
#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);
}

2
channels/client/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
tables.c

View File

@ -0,0 +1,117 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set(MODULE_NAME "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)

470
channels/client/addin.c Normal file
View File

@ -0,0 +1,470 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Channel Addins
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/path.h>
#include <winpr/file.h>
#include <winpr/synch.h>
#include <winpr/library.h>
#include <winpr/collections.h>
#include <freerdp/addin.h>
#include <freerdp/build-config.h>
#include <freerdp/client/channels.h>
#include "tables.h"
#include "addin.h"
#include <freerdp/channels/log.h>
#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 };
/* <name>-client.<extension> */
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 };
/* <name>-client-<subsystem>.<extension> */
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 };
/* <name>-client-<subsystem>-<type>.<extension> */
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;
}

18
channels/client/addin.h Normal file
View File

@ -0,0 +1,18 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Channel Addins
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

View File

@ -0,0 +1,32 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Static Entry Point Tables
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/channels/rdpdr.h>
#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}

51
channels/client/tables.h Normal file
View File

@ -0,0 +1,51 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Static Entry Point Tables
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/svc.h>
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;

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,33 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/print.h>
#include <freerdp/types.h>
#include <freerdp/constants.h>
#include <freerdp/client/cliprdr.h>
#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;
}

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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 */

File diff suppressed because it is too large Load Diff

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
#include <winpr/stream.h>
#include <freerdp/svc.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#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 */

View File

@ -0,0 +1,588 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Cliprdr common
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/log.h>
#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;
}
}

View File

@ -0,0 +1,61 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Cliprdr common
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_RDPECLIP_COMMON_H
#define FREERDP_CHANNEL_RDPECLIP_COMMON_H
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/cliprdr.h>
#include <freerdp/api.h>
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 */

View File

@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Clipboard Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
#define FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/stream.h>
#include <winpr/thread.h>
#include <freerdp/server/cliprdr.h>
#include <freerdp/channels/log.h>
#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 */

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,41 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -0,0 +1,419 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Display Update Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/print.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <winpr/cmdline.h>
#include <winpr/collections.h>
#include <freerdp/addin.h>
#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;
}

View File

@ -0,0 +1,38 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Display Update Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DISP_CLIENT_MAIN_H
#define FREERDP_CHANNEL_DISP_CLIENT_MAIN_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/disp.h>
#define TAG CHANNELS_TAG("disp.client")
#endif /* FREERDP_CHANNEL_DISP_CLIENT_MAIN_H */

View File

@ -0,0 +1,60 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* RDPEDISP Virtual Channel Extension
*
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/log.h>
#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;
}

View File

@ -0,0 +1,32 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* RDPEDISP Virtual Channel Extension
*
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DISP_COMMON_H
#define FREERDP_CHANNEL_DISP_COMMON_H
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/disp.h>
#include <freerdp/api.h>
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 */

View File

@ -0,0 +1,32 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -0,0 +1,597 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* RDPEDISP Virtual Channel Extension
*
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "disp_main.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/log.h>
#include <freerdp/server/disp.h>
#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);
}

View File

@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* RDPEDISP Virtual Channel Extension
*
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DISP_SERVER_MAIN_H
#define FREERDP_CHANNEL_DISP_SERVER_MAIN_H
#include <freerdp/server/disp.h>
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 */

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

File diff suppressed because it is too large Load Diff

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H
#define FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H
#include <winpr/wlog.h>
#include <winpr/synch.h>
#include <freerdp/settings.h>
#include <winpr/collections.h>
#include <freerdp/api.h>
#include <freerdp/svc.h>
#include <freerdp/dvc.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/drdynvc.h>
#include <freerdp/freerdp.h>
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 */

View File

@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -0,0 +1,205 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Dynamic Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/print.h>
#include <winpr/stream.h>
#include <freerdp/channels/log.h>
#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);
}
}

View File

@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Dynamic Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H
#define FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <freerdp/settings.h>
#include <freerdp/server/drdynvc.h>
struct _drdynvc_server_private
{
HANDLE Thread;
HANDLE StopEvent;
void* ChannelHandle;
};
#endif /* FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H */

View File

@ -0,0 +1,23 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
define_channel("drive")
if(WITH_CLIENT_CHANNELS)
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
endif()

View File

@ -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})

View File

@ -0,0 +1,36 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -0,0 +1,934 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* File System Virtual Channel
*
* Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2010-2011 Vic Lee
* Copyright 2012 Gerald Richter
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2016 Inuvika Inc.
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
* Copyright 2017 Armin Novak <armin.novak@thincast.com>
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winpr/wtypes.h>
#include <winpr/crt.h>
#include <winpr/path.h>
#include <winpr/file.h>
#include <winpr/stream.h>
#include <freerdp/channels/rdpdr.h>
#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;
}

View File

@ -0,0 +1,69 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* File System Virtual Channel
*
* Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2010-2011 Vic Lee
* Copyright 2012 Gerald Richter
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2016 Inuvika Inc.
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H
#include <winpr/stream.h>
#include <freerdp/channels/log.h>
#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 */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,33 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <winpr/crt.h>
#include <winpr/stream.h>
#include "echo_main.h"
#include <freerdp/channels/log.h>
#include <freerdp/channels/echo.h>
#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;
}

View File

@ -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 <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#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 */

View File

@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <freerdp/server/echo.h>
#include <freerdp/channels/log.h>
#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);
}

View File

@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@ -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})

View File

@ -0,0 +1,29 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Multiparty Virtual Channel
*
* Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H
#define FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/collections.h>
#include <freerdp/api.h>
#include <freerdp/channels/log.h>
#include <freerdp/svc.h>
#include <freerdp/addin.h>
#include <freerdp/client/encomsp.h>
#define TAG CHANNELS_TAG("encomsp.client")
typedef struct encomsp_plugin encomspPlugin;
#endif /* FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H */

Some files were not shown because too many files have changed in this diff Show More