Import Upstream version 2.8.1
This commit is contained in:
commit
de0fdeebc1
|
@ -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
|
||||
...
|
|
@ -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!_
|
|
@ -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!_
|
|
@ -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.
|
|
@ -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!
|
|
@ -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
|
|
@ -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/
|
|
@ -0,0 +1 @@
|
|||
2.8.1
|
|
@ -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
|
|
@ -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)
|
|
@ -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()
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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 */
|
|
@ -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()
|
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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()
|
|
@ -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})
|
|
@ -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
|
@ -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 */
|
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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})
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
tables.c
|
||||
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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.
|
||||
*/
|
|
@ -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}
|
||||
|
|
@ -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;
|
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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
|
@ -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 */
|
|
@ -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()
|
|
@ -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})
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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
|
@ -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 */
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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()
|
||||
|
|
@ -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})
|
||||
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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")
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
|
@ -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})
|
||||
|
|
@ -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
|
@ -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
Loading…
Reference in New Issue