Compare commits
142 Commits
master
...
stable-1.1
Author | SHA1 | Date | |
---|---|---|---|
|
33d9497253 | ||
|
3850ba376c | ||
|
7ff58e992a | ||
|
6b66f199e6 | ||
|
590fa7dbf6 | ||
|
a99ec8cb65 | ||
|
6fa64006a1 | ||
|
af9a8fbf31 | ||
|
f777230e15 | ||
|
4f2a7bd7d4 | ||
|
9f96ce255e | ||
|
851f0979d5 | ||
|
1c1d825b2f | ||
|
03ab683189 | ||
|
4c69c3ea14 | ||
|
605d0a8518 | ||
|
770c67d340 | ||
|
85856224f6 | ||
|
a700ee75f9 | ||
|
daea54925b | ||
|
b21ff842ef | ||
|
f42b03f7bb | ||
|
3cd91cf599 | ||
|
ab59334c66 | ||
|
892431326f | ||
|
a5f6ba04cd | ||
|
eb9112c84b | ||
|
ae025ae332 | ||
|
2228cd6e73 | ||
|
1fdf05da3f | ||
|
41313e6d70 | ||
|
dab7113f0f | ||
|
cd21ac3498 | ||
|
7974c5072c | ||
|
0027350ff0 | ||
|
598d30f8b8 | ||
|
b065492890 | ||
|
6c6fb62029 | ||
|
440916eae2 | ||
|
6d2d377ed1 | ||
|
6fc97c7179 | ||
|
248c918508 | ||
|
b07a5c11e4 | ||
|
dc7f6abbed | ||
|
7b5cd86a3e | ||
|
75b6e12c11 | ||
|
3b9b030b0e | ||
|
28f65ff297 | ||
|
3122946757 | ||
|
5ad707ddf3 | ||
|
46a00b5c9e | ||
|
01865f0e28 | ||
|
fc6b72017f | ||
|
2604ff20bd | ||
|
865b816b80 | ||
|
997d40639c | ||
|
580e6b68c9 | ||
|
69af35a1b5 | ||
|
7c4ebe2bc4 | ||
|
37ac70aa1c | ||
|
3754d07163 | ||
|
2ef6c552c4 | ||
|
323b081192 | ||
|
413c4dea65 | ||
|
88963aae5c | ||
|
30de5b226d | ||
|
5360f59940 | ||
|
abf84cf6e0 | ||
|
613b20d482 | ||
|
37602e6000 | ||
|
0380e652d0 | ||
|
23512af600 | ||
|
d2a9df1ce0 | ||
|
afec6957c4 | ||
|
d8d7a9c1c6 | ||
|
fe40452d65 | ||
|
214fe2198a | ||
|
ec6b84fde4 | ||
|
a91592bd68 | ||
|
48ce36fd11 | ||
|
313250d99b | ||
|
ebff396e90 | ||
|
c3923afa1f | ||
|
d1e6221b25 | ||
|
8d92adca1d | ||
|
ceae1b87a5 | ||
|
84f1001573 | ||
|
bd74f5c8b5 | ||
|
cf6b9d44ac | ||
|
4a5b19e816 | ||
|
605f956486 | ||
|
86c0c02975 | ||
|
ac63b9ae56 | ||
|
0e758fef5b | ||
|
eab49cf89f | ||
|
6e5e62da60 | ||
|
8be65019e6 | ||
|
9281014e4d | ||
|
25eda0dce7 | ||
|
387a75d1ef | ||
|
a5d1b98e5b | ||
|
e92eef9579 | ||
|
75e2b2a5ef | ||
|
cb5ffc497a | ||
|
5166dfec6e | ||
|
28ee436374 | ||
|
c3dc19cc5f | ||
|
7015f83d97 | ||
|
4bec3a6547 | ||
|
20cd361fc5 | ||
|
93cb8fc11a | ||
|
387f780414 | ||
|
4fe23c0788 | ||
|
84d4ec009c | ||
|
83e9adf30d | ||
|
2863a55f5b | ||
|
df7311b4f9 | ||
|
d817ef3516 | ||
|
75f23925cd | ||
|
41ce9a0969 | ||
|
434ac77b83 | ||
|
47b17b5386 | ||
|
d67624684f | ||
|
deb9d2bc8e | ||
|
b6d06ec012 | ||
|
c22b539153 | ||
|
fe221cdc56 | ||
|
67808b8bc3 | ||
|
f25f2e6055 | ||
|
e5bdfc5eed | ||
|
0fc2d1d1d2 | ||
|
eb2f3a02b4 | ||
|
14d4adb901 | ||
|
d6d0d81d08 | ||
|
fd8fc31ce6 | ||
|
a4a5baf0da | ||
|
aca7d9366e | ||
|
f3019b2b40 | ||
|
be51676541 | ||
|
359c5d258e | ||
|
46a691db02 | ||
|
cd68f79519 |
124
.clang-format
124
.clang-format
@ -1,124 +0,0 @@
|
||||
---
|
||||
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
|
||||
SortIncludes: false
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
...
|
||||
Language: Java
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
...
|
||||
Language: JavaScript
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
...
|
||||
Language: Proto
|
||||
...
|
||||
Language: TableGen
|
||||
...
|
||||
Language: TextProto
|
||||
...
|
128
.clang-tidy
128
.clang-tidy
@ -1,128 +0,0 @@
|
||||
---
|
||||
Checks: >
|
||||
-*,
|
||||
abseil-*,
|
||||
altera-*,
|
||||
boost-*,
|
||||
bugprone-*,
|
||||
cert-*,
|
||||
clang-analyzer*,
|
||||
concurrency-*,
|
||||
cppcoreguidelines*,
|
||||
google-*,
|
||||
hicpp-*,
|
||||
llvm-*,
|
||||
modernize-*,
|
||||
objc-*,
|
||||
openmp-*,
|
||||
performance-*,
|
||||
portability-*,
|
||||
readability-*,
|
||||
-altera-id-dependent-backward-branch,
|
||||
-altera-struct-pack-align,
|
||||
-altera-unroll-loops,
|
||||
-cppcoreguidelines-interfaces-global-init,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-assignment-in-if-condition,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-macro-parentheses,
|
||||
-cert-dcl16-c,
|
||||
-cert-env33-c,
|
||||
-cert-dcl50-cpp,
|
||||
-clang-analyzer-optin.performance.Padding,
|
||||
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
|
||||
-clang-analyzer-valist.Uninitialized,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-do-while,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-macro-to-enum,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-no-malloc,
|
||||
-google-readability-braces-around-statements,
|
||||
-google-readability-todo,
|
||||
-hicpp-avoid-c-arrays,
|
||||
-hicpp-braces-around-statements,
|
||||
-hicpp-no-array-decay,
|
||||
-hicpp-no-assembler,
|
||||
-hicpp-multiway-paths-covered,
|
||||
-hicpp-signed-bitwise,
|
||||
-hicpp-uppercase-literal-suffix,
|
||||
-hicpp-vararg,
|
||||
-hicpp-no-malloc,
|
||||
-llvm-header-guard,
|
||||
-llvm-include-order,
|
||||
-llvm-qualified-auto,
|
||||
-llvm-else-after-return,
|
||||
-readability-else-after-return,
|
||||
-readability-avoid-nested-conditional-operator,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-macro-to-enum,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-avoid-c-arrays,
|
||||
-readability-braces-around-statements,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-identifier-length,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-magic-numbers,
|
||||
-readability-math-missing-parentheses,
|
||||
-readability-misleading-indentation,
|
||||
-readability-qualified-auto,
|
||||
-readability-suspicious-call-argument,
|
||||
-readability-string-compare,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-performance-no-int-to-ptr,
|
||||
-performance-avoid-endl
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
FormatStyle: file
|
||||
User: nin
|
||||
CheckOptions:
|
||||
- key: readability-implicit-bool-conversion.AllowIntegerConditions
|
||||
value: 'true'
|
||||
- key: llvm-else-after-return.WarnOnConditionVariables
|
||||
value: 'false'
|
||||
- key: modernize-loop-convert.MinConfidence
|
||||
value: reasonable
|
||||
- key: modernize-replace-auto-ptr.IncludeStyle
|
||||
value: llvm
|
||||
- key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons
|
||||
value: 'false'
|
||||
- key: google-readability-namespace-comments.ShortNamespaceLines
|
||||
value: '10'
|
||||
- key: cert-err33-c.CheckedFunctions
|
||||
value: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;'
|
||||
- key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
|
||||
value: 'false'
|
||||
- key: cert-dcl16-c.NewSuffixes
|
||||
value: 'L;LL;LU;LLU'
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '1'
|
||||
- key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
|
||||
value: 'true'
|
||||
- key: google-readability-namespace-comments.SpacesBeforeComments
|
||||
value: '2'
|
||||
- key: modernize-loop-convert.MaxCopySize
|
||||
value: '16'
|
||||
- key: modernize-pass-by-value.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
- key: llvm-qualified-auto.AddConstToQualified
|
||||
value: 'false'
|
||||
- key: modernize-loop-convert.NamingStyle
|
||||
value: CamelCase
|
||||
- key: llvm-else-after-return.WarnOnUnfixable
|
||||
value: 'false'
|
||||
- key: google-readability-function-size.StatementThreshold
|
||||
value: '800'
|
||||
...
|
||||
|
||||
|
28
.github/ISSUE_TEMPLATE.md
vendored
28
.github/ISSUE_TEMPLATE.md
vendored
@ -1,28 +0,0 @@
|
||||
## 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
|
||||
* Matrix room : #FreeRDP:matrix.org (main)
|
||||
* XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
|
||||
* IRC channel : #freerdp @ irc.oftc.net (bridged)
|
||||
* 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!_
|
7
.github/ISSUE_TEMPLATE/backport.md
vendored
7
.github/ISSUE_TEMPLATE/backport.md
vendored
@ -1,7 +0,0 @@
|
||||
---
|
||||
name: Backport
|
||||
about: Create a issue to request/track a backport
|
||||
|
||||
---
|
||||
|
||||
Releated pull request for master:
|
55
.github/ISSUE_TEMPLATE/bug_report.md
vendored
55
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,55 +0,0 @@
|
||||
---
|
||||
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:
|
||||
* Matrix room : #FreeRDP:matrix.org (main)
|
||||
* XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
|
||||
* IRC channel : #freerdp @ irc.oftc.net (bridged)
|
||||
* 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**
|
||||
* FreeRDP version (`xfreerdp /version`)
|
||||
* Command line used
|
||||
* Output of `xfreerdp /buildconfig`
|
||||
* OS version connecting to (server side)
|
||||
* If available the log output from a run with `/log-level:trace 2>&1 | tee log.txt`
|
||||
* If you built it yourself add some notes which tag/commit/branch you have used, also your cmake parameters and
|
||||
compiler can help
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. Linux/Windows/Android/..]
|
||||
- Version/Distribution: [e.g. Debian 10, Windows 2008, Android 10]
|
||||
- Architecture: [amd64, arm]:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
** Please remove this text before submitting your issue!
|
||||
|
||||
_Thank you for reporting a bug!_
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,17 +0,0 @@
|
||||
---
|
||||
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.
|
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,28 +0,0 @@
|
||||
## 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 our chat to talk to other developers or help them test your pull might accelerate acceptance
|
||||
* Matrix room : #FreeRDP:matrix.org (main)
|
||||
* XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
|
||||
* IRC channel : #freerdp @ irc.oftc.net (bridged)
|
||||
* Joining our mailing list <freerdp-devel@lists.sourceforge.net> may be helpful too.
|
||||
|
||||
## Please remove this text before submitting your pull!
|
102
.github/workflows/abi-checker.yml
vendored
102
.github/workflows/abi-checker.yml
vendored
@ -1,102 +0,0 @@
|
||||
name: abi-checker
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
inputs:
|
||||
API_BASE_REF:
|
||||
description: 'Base revision for ABI compatibility check'
|
||||
required: true
|
||||
default: '3.0.0'
|
||||
pull_request:
|
||||
branches: [ master, stable* ]
|
||||
schedule:
|
||||
- cron: '30 4 * * SUN'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Run ABI checker on ubuntu-latest"
|
||||
steps:
|
||||
- name: "Check out pull request"
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'pull_request' }}
|
||||
uses: suzuki-shunsuke/get-pr-action@v0.1.0
|
||||
id: pr
|
||||
|
||||
- name: "Check out source"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{steps.pr.outputs.merge_commit_sha}}
|
||||
|
||||
- name: "Prepare environment"
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
sudo apt-get install -q -y \
|
||||
libxrandr-dev \
|
||||
libxinerama-dev \
|
||||
libusb-1.0-0-dev \
|
||||
xserver-xorg-dev \
|
||||
libswscale-dev \
|
||||
libswresample-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libavcodec-dev \
|
||||
libcups2-dev \
|
||||
libpulse-dev \
|
||||
libasound2-dev \
|
||||
libpcsclite-dev \
|
||||
libxcb-cursor-dev \
|
||||
libxcursor-dev \
|
||||
libcairo2-dev \
|
||||
libfaad-dev \
|
||||
libjpeg-dev \
|
||||
libgsm1-dev \
|
||||
ninja-build \
|
||||
libxfixes-dev \
|
||||
libxkbcommon-dev \
|
||||
libpkcs11-helper1-dev \
|
||||
libwayland-dev \
|
||||
libpam0g-dev \
|
||||
libxdamage-dev \
|
||||
libxcb-damage0-dev \
|
||||
libxtst-dev \
|
||||
libfuse3-dev \
|
||||
libsystemd-dev \
|
||||
libcairo2-dev \
|
||||
libsoxr-dev \
|
||||
libsdl2-dev \
|
||||
libkrb5-dev \
|
||||
libcjson-dev \
|
||||
libsdl2-ttf-dev \
|
||||
libwebkit2gtk-4.0-dev \
|
||||
libopus-dev \
|
||||
libwebp-dev \
|
||||
libpng-dev \
|
||||
libv4l-dev \
|
||||
libjpeg-dev \
|
||||
liburiparser-dev \
|
||||
cmake \
|
||||
clang \
|
||||
abigail-tools \
|
||||
pylint \
|
||||
curl
|
||||
|
||||
- name: "Prepare configuration"
|
||||
run: |
|
||||
mkdir -p abi-checker
|
||||
cp ci/cmake-preloads/config-abi.txt abi-checker/
|
||||
cp scripts/abi-suppr.txt abi-checker/
|
||||
curl https://gist.githubusercontent.com/akallabeth/aa35caed0d39241fa17c3dc8a0539ea3/raw/ef12f8c720ac6be51aa1878710e2502b1b39cf4c/check-abi -o abi-checker/check-abi
|
||||
chmod +x abi-checker/check-abi
|
||||
echo "GITHUB_BASE_REF=$GITHUB_BASE_REF"
|
||||
echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF"
|
||||
echo "API_BASE_REF=${{ inputs.API_BASE_REF || '3.0.0' }}"
|
||||
echo "HEAD=$(git rev-parse HEAD)"
|
||||
echo "remotes=$(git remote -v)"
|
||||
|
||||
- name: "Run ABI check..."
|
||||
env:
|
||||
BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.API_BASE_REF || '3.0.0' }}
|
||||
run: |
|
||||
echo "BASE_REF=$BASE_REF"
|
||||
./abi-checker/check-abi -s abi-checker/abi-suppr.txt --parameters="-Cabi-checker/config-abi.txt" $BASE_REF $(git rev-parse HEAD)
|
101
.github/workflows/alt-architectures.yml
vendored
101
.github/workflows/alt-architectures.yml
vendored
@ -1,101 +0,0 @@
|
||||
name: '[arm,ppc,ricsv] architecture builds'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
schedule:
|
||||
- cron: '30 5 * * SUN'
|
||||
|
||||
jobs:
|
||||
build_job:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Test on ${{ matrix.distro }}/${{ matrix.arch }}"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: armv6
|
||||
distro: bullseye
|
||||
- arch: armv7
|
||||
distro: bullseye
|
||||
- arch: aarch64
|
||||
distro: bullseye
|
||||
- arch: s390x
|
||||
distro: bullseye
|
||||
- arch: ppc64le
|
||||
distro: bullseye
|
||||
- arch: riscv64
|
||||
distro: ubuntu22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: uraimo/run-on-arch-action@v2.8.1
|
||||
name: "Run tests"
|
||||
id: build
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
distro: ${{ matrix.distro }}
|
||||
githubToken: ${{ github.token }}
|
||||
env: |
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
WLOG_LEVEL: 'trace'
|
||||
install: |
|
||||
apt-get update -q -y
|
||||
apt-get install -q -y \
|
||||
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 \
|
||||
libxcb-cursor-dev \
|
||||
libxcursor-dev \
|
||||
libcairo2-dev \
|
||||
libfaad-dev \
|
||||
libgsm1-dev \
|
||||
ninja-build \
|
||||
libxfixes-dev \
|
||||
libxkbcommon-dev \
|
||||
libxkbfile-dev \
|
||||
libwayland-dev \
|
||||
libpam0g-dev \
|
||||
libxdamage-dev \
|
||||
libxcb-damage0-dev \
|
||||
libxtst-dev \
|
||||
libfuse3-dev \
|
||||
libsystemd-dev \
|
||||
libsoxr-dev \
|
||||
libsdl2-dev \
|
||||
libsdl2-ttf-dev \
|
||||
libsdl2-image-dev \
|
||||
libkrb5-dev \
|
||||
libcjson-dev \
|
||||
libpkcs11-helper1-dev \
|
||||
libwebkit2gtk-4.0-dev \
|
||||
libopus-dev \
|
||||
libwebp-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
liburiparser-dev \
|
||||
libssl-dev \
|
||||
opensc-pkcs11 \
|
||||
libv4l-dev \
|
||||
cmake \
|
||||
clang
|
||||
run: |
|
||||
cmake -GNinja \
|
||||
-C ci/cmake-preloads/config-linux-all.txt \
|
||||
-B ci-build \
|
||||
-S . \
|
||||
-DCMAKE_INSTALL_PREFIX=/tmp/ci-test \
|
||||
-DCMAKE_C_COMPILER=/usr/bin/clang \
|
||||
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
|
||||
-DUSE_UNWIND=OFF \
|
||||
-DUSE_EXECINFO=OFF \
|
||||
-DWITH_SANITIZE_ADDRESS=OFF
|
||||
cmake --build ci-build --parallel $(nproc) --target install
|
||||
cmake --build ci-build --parallel $(nproc) --target test
|
24
.github/workflows/clang-tidy-post.yml
vendored
24
.github/workflows/clang-tidy-post.yml
vendored
@ -1,24 +0,0 @@
|
||||
name: Post clang-tidy review comments
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["clang-tidy-review"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: ZedThree/clang-tidy-review/post@v0.19.0
|
||||
# lgtm_comment_body, max_comments, and annotations need to be set on the posting workflow in a split setup
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
annotations: false
|
||||
max_comments: 10
|
27
.github/workflows/clang-tidy.yml
vendored
27
.github/workflows/clang-tidy.yml
vendored
@ -1,27 +0,0 @@
|
||||
name: clang-tidy-review
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, stable* ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Run clang-tidy
|
||||
- uses: ZedThree/clang-tidy-review@v0.19.0
|
||||
id: review
|
||||
with:
|
||||
split_workflow: true
|
||||
clang_tidy_checks: ''
|
||||
# List of packages to install
|
||||
apt_packages: libkrb5-dev,libxkbcommon-dev,libxkbfile-dev,libx11-dev,libwayland-dev,libxrandr-dev,libxi-dev,libxrender-dev,libxext-dev,libxinerama-dev,libxfixes-dev,libxcursor-dev,libxv-dev,libxdamage-dev,libxtst-dev,libcups2-dev,libcairo2-dev,libpcsclite-dev,libasound2-dev,libswscale-dev,libpulse-dev,libavformat-dev,libavcodec-dev,libavutil-dev,libfuse3-dev,libswresample-dev,libusb-1.0-0-dev,libudev-dev,libdbus-glib-1-dev,libpam0g-dev,uuid-dev,libcjson-dev,libsdl2-2.0-0,libsdl2-dev,libsdl2-ttf-dev,libsdl2-image-dev,libsystemd-dev,liburiparser-dev,libopus-dev,libwebp-dev,libjpeg-dev,libpng-dev,libgsm1-dev,libfaac-dev,libfaad-dev,libsoxr-dev,opencl-c-headers,opencl-headers,ocl-icd-opencl-dev,libssl-dev,libv4l-dev
|
||||
|
||||
# CMake command to run in order to generate compile_commands.json
|
||||
build_dir: tidy
|
||||
cmake_command: cmake -Btidy -S. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DWITH_DEBUG_ALL=ON -DWITH_INTERNAL_MD4=ON -DWITH_INTERNAL_MD5=ON -DWITH_INTERNAL_RC4=ON -DBUILD_TESTING=ON -DWINPR_UTILS_IMAGE_JPEG=ON -DWINPR_UTILS_IMAGE_PNG=ON -DWINPR_UTILS_IMAGE_WEBP=ON -DWITH_BINARY_VERSIONING=ON -DWITH_CAIRO=ON -DWITH_DSP_EXPERIMENTAL=ON -DWITH_FAAC=ON -DWITH_FAAD2=ON -DWITH_FREERDP_DEPRECATED=ON -DWITH_FREERDP_DEPRECATED_COMMANDLINE=ON -DWITH_GSM=ON -DWITH_OPUS=ON -DWITH_PROXY_EMULATE_SMARTCARD=ON -DWITH_PULSE=ON -DWITH_SMARTCARD_INSPECT=ON -DWITH_SOXR=ON -DWITH_UNICODE_BUILTIN=ON -DWITH_VAAPI=ON -DWITH_WINPR_DEPRECATED=ON -DWITH_SDL_IMAGE_DIALOGS=ON -DWITH_PROFILER=ON -DWITH_OPENCL=ON -DCHANNEL_TSMF=ON -DWITH_WEBVIEW=OFF
|
||||
|
||||
# Uploads an artefact containing clang_fixes.json
|
||||
- uses: ZedThree/clang-tidy-review/upload@v0.19.0
|
||||
id: upload-review
|
141
.github/workflows/codeql-analysis.yml
vendored
141
.github/workflows/codeql-analysis.yml
vendored
@ -1,141 +0,0 @@
|
||||
# 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:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
push:
|
||||
branches: [ "master", "stable*" ]
|
||||
pull_request:
|
||||
branches: [ "master", "stable*" ]
|
||||
schedule:
|
||||
- cron: '41 2 * * 2'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: c-cpp
|
||||
build-mode: manual
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# 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.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
libxrandr-dev \
|
||||
libxinerama-dev \
|
||||
libusb-1.0-0-dev \
|
||||
xserver-xorg-dev \
|
||||
libswscale-dev \
|
||||
libswresample-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libavcodec-dev \
|
||||
libcups2-dev \
|
||||
libv4l-dev \
|
||||
libpulse-dev \
|
||||
libasound2-dev \
|
||||
libpcsclite-dev \
|
||||
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 \
|
||||
libfuse3-dev \
|
||||
libsystemd-dev \
|
||||
libcairo2-dev \
|
||||
libsoxr-dev \
|
||||
libsdl2-dev \
|
||||
libkrb5-dev \
|
||||
libcjson-dev \
|
||||
libsdl2-ttf-dev \
|
||||
libsdl2-image-dev \
|
||||
libwebkit2gtk-4.0-dev \
|
||||
clang \
|
||||
libopus-dev \
|
||||
libwebp-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
liburiparser-dev
|
||||
mkdir ci-build
|
||||
cd ci-build
|
||||
export CC=/usr/bin/clang
|
||||
export CXX=/usr/bin/clang++
|
||||
export CFLAGS="-Weverything"
|
||||
export CXXFLAGS="-Weverything"
|
||||
cmake -GNinja ../ci/cmake-preloads/config-linux-all.txt ..
|
||||
cmake --build .
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
59
.github/workflows/coverity.yml
vendored
59
.github/workflows/coverity.yml
vendored
@ -1,59 +0,0 @@
|
||||
|
||||
name: Coverity
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'FreeRDP' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
devscripts \
|
||||
ninja-build \
|
||||
equivs \
|
||||
ccache \
|
||||
clang
|
||||
sudo mk-build-deps --install packaging/deb/freerdp-nightly/control
|
||||
|
||||
- name: Download Coverity build tool
|
||||
run: |
|
||||
wget -c -N https://scan.coverity.com/download/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=FreeRDP" -O coverity_tool.tar.gz
|
||||
mkdir coverity_tool
|
||||
tar xzf coverity_tool.tar.gz --strip 1 -C coverity_tool
|
||||
|
||||
- name: Build with Coverity build tool
|
||||
run: |
|
||||
export PATH=`pwd`/coverity_tool/bin:$PATH
|
||||
export CC=/usr/bin/clang
|
||||
export CXX=/usr/bin/clang++
|
||||
cov-configure --template --compiler clang --comptype clangcc
|
||||
# in source build is used to help coverity to determine relative file path
|
||||
cmake \
|
||||
-GNinja \
|
||||
-C ci/cmake-preloads/config-coverity.txt \
|
||||
-DCOVERITY_BUILD=ON \
|
||||
-Bcov-build \
|
||||
-S.
|
||||
cov-build --dir cov-int cmake --build cov-build
|
||||
|
||||
- name: Submit build result to Coverity Scan
|
||||
run: |
|
||||
tar czvf cov.tar.gz cov-int
|
||||
curl --form token=${{ secrets.COVERITY_SCAN_TOKEN }} \
|
||||
--form email=team+coverity@freerdp.com \
|
||||
--form file=@cov.tar.gz \
|
||||
--form version="Commit $GITHUB_SHA" \
|
||||
--form description="Build submitted via CI" \
|
||||
https://scan.coverity.com/builds?project=FreeRDP
|
45
.github/workflows/fuzzing.yml
vendored
45
.github/workflows/fuzzing.yml
vendored
@ -1,45 +0,0 @@
|
||||
name: Fuzzing testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
pull_request:
|
||||
branches: [ master, stable* ]
|
||||
schedule:
|
||||
- cron: "0 3 21 * *"
|
||||
|
||||
jobs:
|
||||
fuzzing:
|
||||
if: github.repository == 'FreeRDP/FreeRDP'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sanitizer: [address, undefined]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build fuzzers (${{ matrix.sanitizer }})
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'freerdp'
|
||||
dry-run: false
|
||||
sanitizer: ${{ matrix.sanitizer }}
|
||||
- name: Run fuzzers (${{ matrix.sanitizer }})
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'freerdp'
|
||||
fuzz-seconds: 600
|
||||
dry-run: false
|
||||
sanitizer: ${{ matrix.sanitizer }}
|
||||
- name: Upload crash
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: ${{ matrix.sanitizer }}-artifacts
|
||||
retention-days: 21
|
||||
path: ./out/artifacts
|
29
.github/workflows/issue-autoclose.yml
vendored
29
.github/workflows/issue-autoclose.yml
vendored
@ -1,29 +0,0 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
schedule:
|
||||
- cron: "33 3 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
days-before-stale: 30
|
||||
days-before-close: 30
|
||||
operations-per-run: 90
|
||||
exempt-all-milestones: true
|
||||
exempt-assignees: true
|
||||
exempt-issue-labels: "wip,pinned,help-wanted,blocker,feature"
|
||||
exempt-pr-labels: "wip,pinned,help-wanted,blocker,feature"
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
31
.github/workflows/mingw.yml
vendored
31
.github/workflows/mingw.yml
vendored
@ -1,31 +0,0 @@
|
||||
name: mingw-builder
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
schedule:
|
||||
- cron: '30 5 * * SUN'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Run mingw build on ubuntu-latest"
|
||||
steps:
|
||||
- name: "Check out source"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Prepare environment"
|
||||
run: |
|
||||
sudo apt-get update -q -y
|
||||
sudo apt-get install -q -y \
|
||||
git \
|
||||
nasm \
|
||||
meson \
|
||||
cmake \
|
||||
ninja-build \
|
||||
mingw-w64 \
|
||||
mingw-w64-tools \
|
||||
binutils-mingw-w64
|
||||
|
||||
- name: "Run mingw build..."
|
||||
run: |
|
||||
./scripts/mingw.sh
|
61
.github/workflows/timezone-update.yml
vendored
61
.github/workflows/timezone-update.yml
vendored
@ -1,61 +0,0 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: timezone-update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ master, stable* ]
|
||||
schedule:
|
||||
- cron: "0 5 11 * *"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Configure CMake
|
||||
run: cmake -G"Visual Studio 17 2022" -Bbuild -Swinpr\libwinpr\timezone\utils
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore build\tzextract.sln
|
||||
- name: Build & Install CMake
|
||||
run: cmake --build build --config Release
|
||||
- name: Update timezones
|
||||
run: build\Release\tzextract.exe winpr\libwinpr\timezone
|
||||
- name: Format code
|
||||
run: |
|
||||
clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/WindowsZones.c
|
||||
clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap.c
|
||||
clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap_static.h
|
||||
clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap.json
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update timezone definitions
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: timezone-patches
|
||||
branch-suffix: timestamp
|
||||
delete-branch: true
|
||||
title: '[timezones] Update definitions'
|
||||
body: |
|
||||
Timezone update
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
labels: |
|
||||
automated pr
|
||||
assignees: akallabeth
|
||||
reviewers: akallabeth
|
||||
draft: false
|
114
.gitignore
vendored
Normal file → Executable file
114
.gitignore
vendored
Normal file → Executable file
@ -1,5 +1,109 @@
|
||||
**/CMakeCache.txt
|
||||
**/CMakeFiles
|
||||
build
|
||||
checker
|
||||
abi-checker
|
||||
# CMake
|
||||
CMakeFiles/
|
||||
CMakeScripts/
|
||||
CMakeCache.txt
|
||||
config.h
|
||||
install_manifest*.txt
|
||||
CTestTestfile.cmake
|
||||
freerdp.pc
|
||||
Makefile
|
||||
Testing
|
||||
cmake_install.cmake
|
||||
CPackConfig.cmake
|
||||
CPackSourceConfig.cmake
|
||||
DartConfiguration.tcl
|
||||
_CPack_Packages
|
||||
external/*
|
||||
!external/README
|
||||
include/freerdp/version.h
|
||||
|
||||
# Packages
|
||||
*.zip
|
||||
*.exe
|
||||
*.sh
|
||||
*.deb
|
||||
*.rpm
|
||||
*.tar.Z
|
||||
*.tar.gz
|
||||
|
||||
# Eclipse
|
||||
*.project
|
||||
*.cproject
|
||||
*.settings
|
||||
|
||||
# .rdp files
|
||||
*.rdp
|
||||
*.RDP
|
||||
|
||||
# Documentation
|
||||
docs/api
|
||||
client/X11/xfreerdp.1
|
||||
client/X11/xfreerdp.1.xml
|
||||
client/X11/xfreerdp-channels.1.xml
|
||||
client/X11/xfreerdp-examples.1.xml
|
||||
|
||||
# Mac OS X
|
||||
.DS_Store
|
||||
*.xcodeproj/
|
||||
DerivedData/
|
||||
|
||||
# iOS
|
||||
FreeRDP.build
|
||||
Debug-*
|
||||
Release-*
|
||||
|
||||
# Windows
|
||||
*.vcxproj
|
||||
*.vcxproj.*
|
||||
*.vcproj
|
||||
*.vcproj.*
|
||||
*.sdf
|
||||
*.sln
|
||||
*.suo
|
||||
*.ncb
|
||||
*.opensdf
|
||||
Thumbs.db
|
||||
ipch
|
||||
Debug
|
||||
RelWithDebInfo
|
||||
*.lib
|
||||
*.exp
|
||||
*.pdb
|
||||
*.dll
|
||||
*.ilk
|
||||
*.resource.txt
|
||||
*.embed.manifest*
|
||||
*.intermediate.manifest*
|
||||
|
||||
# Binaries
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
bin
|
||||
libs
|
||||
cunit/test_freerdp
|
||||
client/X11/xfreerdp
|
||||
client/Mac/xcode
|
||||
client/Sample/sfreerdp
|
||||
client/DirectFB/dfreerdp
|
||||
server/Sample/sfreerdp-server
|
||||
server/X11/xfreerdp-server
|
||||
xcode
|
||||
|
||||
# Other
|
||||
*~
|
||||
*.dir
|
||||
Release
|
||||
Win32
|
||||
build*/
|
||||
*.orig
|
||||
|
||||
default.log
|
||||
*Amplifier XE*
|
||||
*Inspector XE*
|
||||
|
||||
*.cbp
|
||||
*.txt.user
|
||||
|
||||
*.autosave
|
||||
|
102
CMakeCPack.cmake
102
CMakeCPack.cmake
@ -1,102 +0,0 @@
|
||||
|
||||
# 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)
|
||||
|
||||
# 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 "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Install.bmp")
|
||||
set(CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Icon_96px.ico")
|
||||
set(CPACK_NSIS_MUI_UNICON "${PROJECT_SOURCE_DIR}/resource\\\\FreeRDP_Icon_96px.ico")
|
||||
|
||||
set(CPACK_COMPONENTS_ALL client server libraries headers symbols tools)
|
||||
|
||||
if(MSVC)
|
||||
string(FIND ${CMAKE_MSVC_RUNTIME_LIBRARY} "DLL" IS_SHARED)
|
||||
|
||||
if(NOT IS_SHARED STREQUAL "-1")
|
||||
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("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake" @ONLY)
|
||||
set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake")
|
||||
|
||||
include(CPack)
|
@ -1,10 +0,0 @@
|
||||
# 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()
|
970
CMakeLists.txt
Normal file → Executable file
970
CMakeLists.txt
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
619
ChangeLog
619
ChangeLog
@ -1,503 +1,118 @@
|
||||
# 2024-10-21 Version 3.9.0
|
||||
|
||||
We're proud to present the newest release of FreeRDP.
|
||||
This one brings some major code cleanup (we've addressed lots of clang-tidy
|
||||
warnings) as well as some highly anticipated new features.
|
||||
We also did update the API documentation quite a bit (still incomplete though,
|
||||
help always welcome ;))
|
||||
|
||||
So, what is new:
|
||||
* Support for RDPEAR (remote credential guard) /remoteGuard option for non windows clients
|
||||
* Global configuration file support, allowing to configure certificate
|
||||
accept/ignore/... default settings for all users
|
||||
* Simplified manpage generation, eliminates docbook and xmlto dependencies
|
||||
speeding up builds
|
||||
* New API for client channels to run tasks on RDP thread
|
||||
* New extended transport layer API
|
||||
* RDPECAM MJPEG support
|
||||
* the first updates of timezone definitions from our automated ci
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix bugs in SSE4.1/AVX2 image copy (#10720)
|
||||
* Add warnings for invalid monitor settings during connect (#10672)
|
||||
* Fix ALSA microphone support (#10664)
|
||||
* Fix modal windows in RAILS mode (#10629)
|
||||
* Update experimental SDL3 client (SDL3 API should now have been stabilized,
|
||||
various pull requests)
|
||||
* Fix keyboard layouts, the external JSON did miss a few (#10639)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.9.0...3.8.0
|
||||
|
||||
# 2024-08-30 Version 3.8.0
|
||||
|
||||
This is a bugfix release. Due to additional exports required by a bugfix the minor version was incremented
|
||||
|
||||
Noteworthy changes:
|
||||
* Reduce number of warnings on CI build (make dependency includes SYSTEM) (#10509)
|
||||
* Fix possible crashes with P11 certificate parsing (#10462, #10463)
|
||||
* Various clipboard related fixes (#10472, #10476, #10477, #10480, #10484)
|
||||
* Fix a race condition on DesktopResize (xfreerdp) (#10488)
|
||||
* Improve certificate warnings (#10489)
|
||||
* Try all possible resolved IP addresses for a DNS name on connect (#10491)
|
||||
* Fix an issue with GFX SolidFill alpha data (#10498)
|
||||
* Various fixes for SDL clients (#10504, #10492, #10471)
|
||||
* Fix serial and parallel redirection crashes (#10510)
|
||||
* Fix android build issues with NDK 27 (#10529)
|
||||
* Improve performance of some WinPR primitives (#10528)
|
||||
* Fix an issue with autoreconnect (#10523)
|
||||
* Support ssh-askpass like password reading (#10516)
|
||||
* Lots of code cleanups to reduce clang-tidy warnings (#10531, #10525, #10521, #10520, #10519, #10518)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.8.0...3.7.0
|
||||
|
||||
# 2024-08-08 Version 3.7.0
|
||||
|
||||
This release has accumulated quite a number of changes. Along bugfixes for 3.6.3 it also
|
||||
contains a number of improvements for distributors:
|
||||
|
||||
* Support for FDK-AAC for sound and microphone redirection (activate with -DWITH_FDK_AAC=ON build option)
|
||||
This allows enabling the AAC compression that do not ship faad2 and/or faac
|
||||
* Support keyboard layouts as JSON resources (activate with -DWITH_KEYBOARD_LAYOUT_FROM_FILE=ON build option,
|
||||
also requires JSON support)
|
||||
This allows editing keyboard layouts for existing releases should the need arise
|
||||
* Support timezones as JSON resources (activate with -DWITH_TIMEZONE_FROM_FILE=ON -DWITH_TIMEZONE_COMPILED=OFF build options,
|
||||
also requires JSON support)
|
||||
Allows reading the mapping between IANA and windows timezones from a JSON file, allowing easier updates without recompile
|
||||
* Improve shadow server compatibility with windows 11 24H2 RDP client
|
||||
Windows 7 RFX and bitmap updates with multiple rectangles have been deactivated, so adjust shadow to not send such.
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix ActionScript paramter (#10423)
|
||||
* Support keyboard layouts as JSON resource (#10394)
|
||||
* Support timezones as JSON resource and command line argument (#10428 #10393 #10391)
|
||||
* Deactivate AsyncUpdate (#10402)
|
||||
* Compatibility fixes for shadow with windows 11 24H2 (#10455 #10422 #10420 #10416)
|
||||
* Fix SDL client autoreconnect (#10390)
|
||||
* Fix xfreerdp clipboard locking (#10385)
|
||||
* Improve logging (#10426 #10441)
|
||||
* Improve warnings and code checks (#10381 #10401 #10403 #10405 #10406 #10410 #10421 #10454)
|
||||
* Support FDK-AAC (#10372)
|
||||
* Fix drive redirection state transitions (#10367 #10374)
|
||||
* Support mth:// routing token (#10366)
|
||||
* Ignore unsupported SetThreadPriority (#10363)
|
||||
* Fix reported documentation and code typos (#10365 #10368 #10370 #10369 #10431 #10446)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.7.0...3.6.3
|
||||
|
||||
# 2024-07-08 Version 3.6.3
|
||||
|
||||
Bugfix release for 3.6.2 issues reported
|
||||
|
||||
Noteworthy changes:
|
||||
* fix a graphics regression (#10352)
|
||||
* workaround for a protocol bug of older FreeRDP based servers (#10358)
|
||||
* fix possible NULL dereference in command line parser (#10348)
|
||||
* fix intrinsics detection (#10346, #10350)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.6.3...3.6.2
|
||||
|
||||
|
||||
# 2024-07-04 Version 3.6.2
|
||||
|
||||
Bugfix release for 3.6.1 issues detected during release tests
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix xfreerdp and sdl-freerdp manpage names (accidentally changed name)
|
||||
* Fix crash of wfreerdp
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.6.2...3.6.1
|
||||
|
||||
|
||||
# 2024-07-04 Version 3.6.1
|
||||
|
||||
Bugfix release for 3.6.0
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix missing dependency for ci abi-checker
|
||||
* Fix build WITH_SSE2/WITH_NEON: only enable support if the compiler
|
||||
also defines symbols that suggest support.
|
||||
* Fix incomplete changelog for 3.6.0:
|
||||
* Improved image copy (#10208)
|
||||
* Experimental [MS-RDPECAM] support by @oleg0421 (#10258)
|
||||
* Improved primitives (#10304)
|
||||
* Connection timeout for HTTP gateway transport (#10288)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.6.1...3.6.0
|
||||
|
||||
|
||||
# 2024-07-03 Version 3.6.0
|
||||
|
||||
With this release we did improve decoder speed so you should notice a significant
|
||||
speed improvement with progressive and other gfx codecs.
|
||||
We've also eliminated a couple of issues along the way, so an update
|
||||
is highly recommended.
|
||||
|
||||
Noteworthy changes:
|
||||
* Improved command line failure logging (#10333)
|
||||
* p11-kit support (#10081)
|
||||
* json-c support (#10183)
|
||||
* (experimental) SDL3 port SDL client (#10195)
|
||||
* New option '/gfx:frame-ack:off' for connection delay testing (#10214)
|
||||
* improved decoder speed (#10222, #10235)
|
||||
* xfreerdp floatbar hide bug (#10237)
|
||||
* winpr-makecert month bug (#10236)
|
||||
* kerberos kdcUrl check fixes (#10238)
|
||||
* timezone updates (#10120, #10144, #10170)
|
||||
* fixed a capability protocol violation bug (#10132)
|
||||
* fix SDL client dialog bug terminating on credential dialog (#10134)
|
||||
* some more oss-fuzz issues (#10126, #10141, #10148, #10161, #10239)
|
||||
* rails popup window fixes (#10160)
|
||||
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.6.0...3.5.1
|
||||
|
||||
|
||||
# 2024-04-22 Version 3.5.1
|
||||
|
||||
This release eliminates a bunch of issues detected during oss-fuzz runs.
|
||||
The test coverage was increased and detected issues eliminates, so an update
|
||||
is highly recommended.
|
||||
|
||||
Noteworthy changes:
|
||||
* Lots of fixes for oss-fuzz reports
|
||||
* Timezone detection fixes (#10106)
|
||||
* SDL key remapping support (#10103)
|
||||
* Improved help (#10099)
|
||||
* FreeBSD epoll detection fix (#10097)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.5.1...3.5.0
|
||||
|
||||
# 2024-04-16 Version 3.5.0
|
||||
|
||||
This release focus is on squashing bugs.
|
||||
The improved test coverage and ci builds revealed a number of previously
|
||||
unnoticed issues we have addressed and we also got a report from
|
||||
Evgeny Legerov of Kaspersky Lab identifying a number of out of bound reads
|
||||
in decoder components and one very nasty out of bound write.
|
||||
|
||||
CVE:
|
||||
CVE-2024-32041 [Low[ OutOfBound Read in zgfx_decompress_segment
|
||||
CVE-2024-32039 [Moderate] Integer overflow & OutOfBound Write in clear_decompress_residual_data
|
||||
CVE-2024-32040 [Low] integer underflow in nsc_rle_decode
|
||||
CVE-2024-32458 [Low] OutOfBound Read in planar_skip_plane_rle
|
||||
CVE-2024-32459 [Low] OutOfBound Read in ncrush_decompress
|
||||
CVE-2024-32460 [Low] OutOfBound Read in interleaved_decompress
|
||||
|
||||
Noteworthy changes:
|
||||
* location channel support #9981, #9984, #10065
|
||||
* bugfixes for report from Evgeny Legerov of Kaspersky Lab #10077
|
||||
* fuzzer tests from Evgeny Legerov of Kaspersky Lab #10078
|
||||
* bugfixes for coverty scanner #10066, #10068, #10069, #10070, #10075
|
||||
* clipboard and generic locking fixes #10076
|
||||
* split autoreconnect support from enabling it #10063
|
||||
* various nightly and workflow fixes #10064, #10058, #10062
|
||||
* always set wm-class to app_id #10051
|
||||
* refactored and simplified CMake #10046, #10047
|
||||
* fix relative mouse event sending #10010
|
||||
* improve and unify check for APIs used (POSIX, win32, mac, ...) #9995
|
||||
* fix termination for gateway connections #9985
|
||||
* fix drivestoredirect RDP file setting, ignore invalid #9989
|
||||
* drop IPP support #10038
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.5.0...3.4.0
|
||||
|
||||
# 2024-03-14 Version 3.4.0
|
||||
|
||||
This release concentrates on improving test coverage and ci builds.
|
||||
Some usability issues and inconvenient API functions were fixed on the way.
|
||||
|
||||
New features have been introduced (stub for location channel)
|
||||
|
||||
Noteworthy changes:
|
||||
* fix a bug in RAIL mode not activating window focus (#9973)
|
||||
* improve logging (#9969, #9943)
|
||||
* OpenSSL <= 1.1.1 build fixes (#9897)
|
||||
* improved help (#9899, #9905)
|
||||
* improved MINGW support (#9914, #9915, #9919, #9964, #9965, #9920)
|
||||
* fix right control ungrab for xfreerdp (#9960)
|
||||
* fix RPATH option settings (#9963)
|
||||
* fix SDL client screen updates (#9962, #9954)
|
||||
* fix issues with childSession under windows (#9961, #9956, #9922)
|
||||
* fix xfreerdp crash with +auth-only (#9947)
|
||||
* fix windows printer channel (#9934)
|
||||
* add support to enforce gateway policy (#9942)
|
||||
* improve big endian support (#9927)
|
||||
* ignore empty proxy environment variables (#9929)
|
||||
* improve quoting support for command line (#9912)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.4.0...3.3.0
|
||||
|
||||
|
||||
# 2024-02-22 Version 3.3.0
|
||||
|
||||
This release concentrates on code cleanup and overall quality improvements.
|
||||
Some usability issues and inconvenient API functions were fixed on the way.
|
||||
|
||||
New features have been introduced (better image clipboard) but that stays
|
||||
deactivated by default as we´re in a stable series.
|
||||
|
||||
Check the new CMake options:
|
||||
* PLUGIN_ABS_PATHS_DEFAULT disables loading of external channels from all
|
||||
but a specified absolute plugin directory defined by FREERDP_PLUGIN_PATH
|
||||
* WINPR_UTILS_IMAGE_PNG enables PNG support with libpng in winpr image/clipboard
|
||||
* WITH_LODEPNG enables PNG support with lodepng library in winpr image/clipboard
|
||||
* WINPR_UTILS_IMAGE_WEBP enables WEBP support in winpr image/clipboard
|
||||
* WINPR_UTILS_IMAGE_JPEG enables JPEG support in winpr image/clipboard
|
||||
* USE_EXECINFO enables or disables backtrace support with execinfo
|
||||
* WITH_WEBVIEW now defaults to OFF on windows, apple and android (not implemented)
|
||||
|
||||
Noteworthy changes:
|
||||
* Improved image clipboard (xfreerdp, wlfreerdp) (#9873, #9826)
|
||||
* Improved SDL client (#9875, #9887, #9883, #9878, #9792)
|
||||
* Allow plugin loader to only use absolute paths (#9809)
|
||||
* Improved TLS channel binding (#9838)
|
||||
* Add GCC/clang attribute malloc wrapper WINPR_ATTR_MALLOC (#9863)
|
||||
* Major clang-tidy code cleanups and bugfixes (#9799, #9834)
|
||||
* Provide some defaults for wObject functions (#9799)
|
||||
* Fix a bug in shadow with GFX breaking mstsc (#9818)
|
||||
* Improved manpages and help (#9813, #9804)
|
||||
* Blocking mode via transport IO interface (#9793)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.3.0...3.2.0
|
||||
|
||||
# 2024-01-19 Version 3.2.0
|
||||
|
||||
This release mostly addresses issues reported since the last release.
|
||||
Fixing some usablity and build issues as well as adding API functions
|
||||
that are needed from external projects
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix proxy module load check (#9777)
|
||||
* Improve kerberos error logging (#9771)
|
||||
* Improve mac client keyboard handling (#9767)
|
||||
* Add option to run client dynamic channel synchronous (#9764)
|
||||
* Move huge struct to heap (#9763)
|
||||
* Improved failure logging of license module (#9759)
|
||||
* Improve server side gfx logging (#9757)
|
||||
* Print shadow server help with printf instead of WLog (#9756)
|
||||
* Fix SDL client timer initialization (#9754)
|
||||
* Fix server peer message parsing (#9751)
|
||||
* Enable NEON instructions if __ARM_NEON is defined (#9748)
|
||||
* Add new proxy config file option TlsSecLevel (#9741)
|
||||
* Improve android and mac os build scripts (#9735)
|
||||
* Do not disable wayland support on BSD (#9730)
|
||||
* Fix issues with assistance file parsing (#9727, #9728)
|
||||
* Keyboard handling fixes for wayland client (#9725)
|
||||
* Fix relative pkg-config file paths (#9720)
|
||||
* Add new transport IO callback GetPublicKey (#9719)
|
||||
* Fix wayland client scaling (#9715)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.2.0...3.1.0
|
||||
|
||||
# 2023-12-22 Version 3.1.0
|
||||
|
||||
A new 3.1.0 minor release for the new 3.0.0 series.
|
||||
This contains bugfixes, adds (better) support for libressl and mbedtls and
|
||||
brings a bunch of improvements for the SDL client.
|
||||
|
||||
This comes with a price though, we now (optionally) require SDL_image if you
|
||||
want to build the sdl-client
|
||||
|
||||
Since there are multiple new features, some new files (man pages) and new
|
||||
optional dependencies we´ve directly incremented the minor version.
|
||||
|
||||
New CMake options:
|
||||
* SDL_USE_COMPILED_RESOURCES (default ON) builds fonts and images into SDL
|
||||
client. Set to OFF to install these resources as files. (was already part of
|
||||
3.0.0, but worth mentioning here)
|
||||
* WITH_SDL_IMAGE_DIALOGS (default OFF) Show some nice icons for SDL client
|
||||
connection dialogs. Requires SDL_image for build.
|
||||
* WITH_BINARY_VERSIONING (default OFF) Similar as for libraries the binaries,
|
||||
manpages and resource locations created by FreeRDP project are postfixed
|
||||
with the API version. Recommended if packagers want to install the package
|
||||
alongside FreeRDP 2 without conflicts.
|
||||
* RDTK_FORCE_STATIC_BUILD (default OFF) Build and link RDTK statically into
|
||||
shadow server. Recommended for packagers as this library is not really used
|
||||
outside of FreeRDP-shadow.
|
||||
* UWAC_FORCE_STATIC_BUILD (default OFF) Build and link UWAC statically into
|
||||
wlfreerdp. Recommended for packagers as this library is not really used
|
||||
outside of wlfreerdp.
|
||||
|
||||
Noteworthy changes:
|
||||
* Fix a nasty bug with relative mouse movement (#9677)
|
||||
* LibreSSL support enhancements (#9691, #9670)
|
||||
* mbedTLS support enhancements (#9662)
|
||||
* Improve building on mac OS (#9641)
|
||||
* New and improved manpages (#9690, #9650)
|
||||
* Unify CMake common options, add (optional) binary versioning and allow
|
||||
building rdtk and uwac as static dependencies (#9695)
|
||||
* SDL client improvements (#9693, #9657, #9659, #9683, #9680, #9657, #9664,
|
||||
#9656)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.1.0...3.0.0
|
||||
|
||||
# 2023-12-12 Version 3.0.0
|
||||
|
||||
Final 3.0.0 release just a little over two weeks after the last 3.0.0-rc0.
|
||||
This contains bugfixes, drops some legacy code, implements a small feature
|
||||
request and adds some improvements to the build system.
|
||||
|
||||
Most notably is the new PreventInSourceBuilds.cmake which does exactly what
|
||||
the name implies, it aborts builds where source equals build directory.
|
||||
If you can not use out of source tree builds for some reason, you can
|
||||
circumvent this measure with the CMake setting -DALLOW_IN_SOURCE_BUILD=ON
|
||||
|
||||
Noteworthy changes:
|
||||
* add support for AF_VSOCK #9561
|
||||
* xfreerdp drop X11 GDI implementation #9492
|
||||
* fixed connection freeze with childSession #9594
|
||||
* fixed relative mouse input issues #9608
|
||||
* fixed issues with drive redirection #9610
|
||||
* simplified mac build #9601
|
||||
* fixed TSMF to build again #9603
|
||||
* fixed command line /gfx parsing bug #9598
|
||||
* prevent in source tree build #9550
|
||||
* fixed various issues with settings #9595, #9596
|
||||
* add E2K cpu support in WinPR #9599
|
||||
* fixed wfreerdp DPI settings when used as embedded window #9593
|
||||
* android add mouse hover support #9495
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0..3.0.0-rc0
|
||||
|
||||
# 2023-11-27 Version 3.0.0-rc0
|
||||
|
||||
Nearly 2 months of testing, bugfixing and API refinements later we´re
|
||||
happy to announce the first release candidate for FreeRDP 3.0
|
||||
The API should now be considered stable and only minor changes (if at all)
|
||||
will happen from this point on, so every project using FreeRDP can check
|
||||
compatibility with upcoming 3.0
|
||||
|
||||
Noteworthy changes:
|
||||
* Updated rdpSettings API #9465:
|
||||
* getter/setter now use enum types for keys (generates compiler warnings for mismatch)
|
||||
* Refined functions (added missing, dropped problematic ones)
|
||||
* prepared opaque settings (direct struct access now deprecated)
|
||||
* Server side [MS-RDPEL] channel #9471
|
||||
* Relative mouse movement support #9459
|
||||
* relocatable pkg-config files (enable with -DPKG_CONFIG_RELOCATABLE=ON, #9453)
|
||||
* cliprdr dropped support for fuse2 (#9453)
|
||||
* added support for uriparser for clipboard file:// parsing (#9455)
|
||||
* aFreeRDP translation for traditional chinese (zh-rTW) added (#9450)
|
||||
* fixed sdl-freerdp crash on credential dialog (#9455)
|
||||
* fixed sdl-freerdp alt+tab in fullscreen (#9442)
|
||||
* added /connect-child-session option (WIN32 only, #9427)
|
||||
* fix rfx-image codec setup (#9425)
|
||||
* added missing cmake configuration for winpr-tools (#9453)
|
||||
* cleaned up cmake configuration files, dropped no longer required ones (#9455)
|
||||
* fixed x11 keyboard layout detection (#9433)
|
||||
* add missing API calls for server implementation (tested against ogon, #9453)
|
||||
* keep dynamic channels in a hash table instead of a list (#9448)
|
||||
* keep TSCredentials in server peer instance (#9430)
|
||||
* fix FFMPEG/AAC encoding (#9576)
|
||||
* support remote credential guard (#9574)
|
||||
* fix printing on mac os 14 (#9569)
|
||||
* improve RPC gateway support (#9508)
|
||||
* add opus audio support for gnome-remote-desktop (#9575)
|
||||
* server side handling of mouse cursor channel [MS-RDPEMSC] (#9513)
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0-rc0..3.0.0-beta4
|
||||
|
||||
# 2023-09-31 Version 3.0.0-beta4
|
||||
|
||||
Noteworthy changes:
|
||||
* Improved and fixed AVD authentication, now allows retries for
|
||||
machines just starting up
|
||||
* Improve RDP file parser, prepare new fields used by AVD
|
||||
* Fixed and improved pen support in multitouch implementation (xfreerdp)
|
||||
* Lots of smaller code and leak cleanups
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0-beta4..3.0.0-beta3
|
||||
|
||||
# 2023-08-31 Version 3.0.0-beta3
|
||||
|
||||
Noteworthy changes:
|
||||
* fix xfreerdp keyboard on mac os
|
||||
* Various crashes and input check fixes
|
||||
* Improved logging of autodetect, redirection and fastpath failures
|
||||
* Smartcard emulation now selectable at runtime
|
||||
* Allow certificates without a subject to pass client checks
|
||||
* Fix FindFirstFile issues on android
|
||||
* Add FREERDP_ENTRY_POINT to silence -Wmissing-prototypes warnings for
|
||||
library entry points
|
||||
* Add WINPR_RESTRICT to enable restrict (C99) or __restring (MSVC)
|
||||
keywords for compiler
|
||||
* Fix support for older OpenSSL versions
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0-beta3..3.0.0-beta2
|
||||
|
||||
# 2023-08-03 Version 3.0.0-beta2
|
||||
|
||||
Noteworthy changes:
|
||||
* Update CMake defaults, now all features are enabled by default with a platform
|
||||
independent option if multiple are available.
|
||||
* SDL client: (basic) multimonitor support
|
||||
* SDL client: fix dialog cleanup order (crash fix)
|
||||
* clipboard: fix FUSE shutdown crash
|
||||
* fixed drive redirection: FindNextFile did miss some files/directories
|
||||
* improved AAD support: honor rdp file options
|
||||
* improved (gateway) http failure logging
|
||||
* improved shadow server error handling
|
||||
* improved CMake configuration (using find_dependency)
|
||||
* updated timezone definitions
|
||||
* mbedTLS build fixed
|
||||
* improved MINGW build support
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0-beta2..3.0.0-beta1
|
||||
|
||||
# 2023-07-21 Version 3.0.0-beta1
|
||||
|
||||
We are pleased to announce the first beta release for the next stable 3.0
|
||||
series of FreeRDP. It has been a huge endeavour to implement all the new
|
||||
shiny bells and whistles as well as clean up the code base and we´re still
|
||||
ironing out some smaller glitches.
|
||||
This is the first API breaking change since the 2.0 series and there are
|
||||
some adjustments to be made for existing applications.
|
||||
See https://github.com/FreeRDP/FreeRDP/wiki/FreeRDP3-migration-notes for
|
||||
help (still incomplete)
|
||||
|
||||
Noteworthy changes:
|
||||
* Support for AAD/AVD authentication
|
||||
* Support for websocket transport
|
||||
* Support smartcard authentication (TLS and NLA)
|
||||
* Full smartcard emulation support (login with certificate + key)
|
||||
* Rewritten proxy, new module API
|
||||
* New reference client based on SDL2 (work in progress)
|
||||
* Rewritten logging, now parsing issues are all writing to the log so
|
||||
that issues with protocol incompatibilities can be easier analyzed
|
||||
by just turning on logging
|
||||
* Full OpenSSL 3 support
|
||||
* Internal implementations for RC4, MD4 and MD5 (required for non critical
|
||||
parts in RDP but not part of more recend SSL libraries)
|
||||
* Updated RDP protocol support
|
||||
* Improved xfreerdp remote app support
|
||||
* Reworked internal state machine for both client and server implementations
|
||||
* Server implementations can now make use of connect-time network autodetection
|
||||
* Improved clipboard handling, now also support server-to-client file transfer
|
||||
(currently xfreerdp only)
|
||||
* EnhancedRemoteApp support: Utilizing the more modern standard allows remote
|
||||
apps with less glitches and window shadows
|
||||
* Added client- and server-side handling for RDSTLS
|
||||
* Support for the graphics redirection channel
|
||||
|
||||
For a complete and detailed change log since the last release run:
|
||||
git log 3.0.0-beta1..2.10.0
|
||||
|
||||
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
|
||||
|
||||
|
36
README
Normal file
36
README
Normal file
@ -0,0 +1,36 @@
|
||||
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: http://www.freerdp.com/
|
||||
Issue tracker: https://github.com/FreeRDP/FreeRDP/issues
|
||||
Sources: https://github.com/FreeRDP/FreeRDP/
|
||||
Wiki: https://github.com/FreeRDP/FreeRDP/wiki
|
||||
Downloads and other resources: http://pub.freerdp.com
|
||||
API doc: http://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
|
||||
|
||||
|
43
README.md
43
README.md
@ -1,43 +0,0 @@
|
||||
# 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.
|
||||
|
||||
## Code Quality Status
|
||||
|
||||
[![abi-checker](https://github.com/FreeRDP/FreeRDP/actions/workflows/abi-checker.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/abi-checker.yml)
|
||||
[![clang-tidy-review](https://github.com/FreeRDP/FreeRDP/actions/workflows/clang-tidy.yml/badge.svg?event=pull_request_target)](https://github.com/FreeRDP/FreeRDP/actions/workflows/clang-tidy.yml)
|
||||
[![CodeQL](https://github.com/FreeRDP/FreeRDP/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/codeql-analysis.yml)
|
||||
[![mingw-builder](https://github.com/FreeRDP/FreeRDP/actions/workflows/mingw.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/mingw.yml)
|
||||
[![[arm,ppc,ricsv] architecture builds](https://github.com/FreeRDP/FreeRDP/actions/workflows/alt-architectures.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/alt-architectures.yml)
|
||||
[![coverity](https://scan.coverity.com/projects/616/badge.svg)](https://scan.coverity.com/projects/freerdp)
|
||||
|
||||
## 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/
|
||||
|
||||
Security policy: https://github.com/FreeRDP/FreeRDP/security/policy
|
||||
|
||||
Matrix room : #FreeRDP:matrix.org (main)
|
||||
XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged)
|
||||
IRC channel : #freerdp @ irc.oftc.net (bridged)
|
||||
Mailing list: https://lists.sourceforge.net/lists/listinfo/freerdp-devel
|
||||
|
||||
## Microsoft Open Specifications
|
||||
|
||||
Information regarding the Microsoft Open Specifications can be found at:
|
||||
https://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
|
114
SECURITY.md
114
SECURITY.md
@ -1,114 +0,0 @@
|
||||
# FreeRDP Security Policies and Procedures
|
||||
|
||||
This document describes the security policy and procedures for the [FreeRDP Project](https://github.com/FreeRDP/FreeRDP).
|
||||
The following topics are covered:
|
||||
|
||||
* [Supported Versions](#supported-versions)
|
||||
* [Reporting a Vulnerability](#reporting-a-vulnerability)
|
||||
* [Disclosure Procedure](#disclosure-procedure)
|
||||
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security is very important for us therefore we try to provide security updates and support for
|
||||
the latest stable version as well as for the development branch.
|
||||
Since our development branch is, like the protocol itself, a moving target we won't request CVEs for issues that are *only* found on the development branch.
|
||||
|
||||
The following table shows the currently supported versions:
|
||||
|
||||
| Version | Branch | Supported |
|
||||
| ------- |--------------| ------------------ |
|
||||
| < 2.0.0 | stable-1.x | :x: |
|
||||
| 2.x.x | stable-2.0 | :heavy_check_mark: |
|
||||
| 3.x.x | stable-3.0 | :white_check_mark: |
|
||||
| - | master | :white_check_mark: |
|
||||
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
**IMPORTANT**: Please, do not file security vulnerabilities as public issues on GitHub
|
||||
|
||||
In advance: **Thank you** for reporting a security vulnerability and making FreeRDP more stable! We really appreciate your effort.
|
||||
Please let us know who we should give the credit or attributions to.
|
||||
|
||||
|
||||
If you have found a security vulnerability in FreeRDP you can either directly open an [Advisory on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories/new)[^1] or send us an email to mailto:security@freerdp.com
|
||||
|
||||
In case of an email you can use the [FreeRDP security team GPG key](#reporting-gpg-key) for encrypted communication.
|
||||
|
||||
Once we receive a report we will review it and respond as soon as possible.
|
||||
|
||||
###
|
||||
|
||||
|
||||
## Disclosure procedure
|
||||
|
||||
When the FreeRDP team receives a report one of the team members will be assigned as primary contact.
|
||||
The primary contact will do all further communications and coordinate the fix and release process.
|
||||
|
||||
How your report will be handled:
|
||||
|
||||
* When a report is received we will acknowledge the reception and review the reported issue(s) as soon as possible.
|
||||
* Once confirmed we will determine the affected versions. If not reported via GitHub a [security advisory draft on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories) will be created for any issue. If it applies we will request a CVE.
|
||||
* On a private branch we will fix the issue and check the code for any potential similar problem.
|
||||
* After the fix is validated we will create and publish a new release for all supported versions and publish the advisories.
|
||||
|
||||
## Reporting GPG key
|
||||
|
||||
FreeRDP's security reporting public gpg key https://pub.freerdp.com/FreeRDP-security-team.pub.asc
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGBz+jsBEADaIM94hYfn/xDzncQwXl7/q6+06+ssqO3iUGqFr+0EPS+HxRjD
|
||||
BeKjVRSkuo0+QLQoZgCwkoltEj1xRWNqCTDMA+oZkZH8L82eqCnUQqgCOyNWAVMH
|
||||
6u6ValiZH3ruYxergBBHhyR4Ot2ia0xWN8MKTp+emLpzQ7goimGMo0mxR5FiDAdb
|
||||
QKz1q5bgs3bb2pLpERNF+z13OS10Mzk1zdr++1pov5PWOTBRKmvBtPJKswmDpb0y
|
||||
jQGeeqBFZwKzx0n6BTzDZtkqzTwvGhbm9Sb+qO0IO66IV8zQhPG/JUfDkByd6mX9
|
||||
Ykke0gxoRx54XqoRwZGNydOxMN6g3Oj1+ioWisltYLs/SzW20f3AMCoTeYyfjKtf
|
||||
01refrA3aRfhDctvW5/s2LP0OEG2P/yQYXiGhK6uVxShz3Oa5dhFwiS8G63omZRH
|
||||
AEqSk46EhAbbT4xfZ/Np209rhis4KW40cMMpI0F+XpyfT05ZQD6ytHTPgWTxv/OF
|
||||
G9zy2ysT0kq+t+Hb+1RWQUq/2Dz9Lf6xLZPgqtyzg8xiFxZ4i1kf/VDWa3M76zn3
|
||||
qMcj3SPOxKY//wW70jCxf44yD38NvSa1M2Sz/K/RJKWkRWP/jhV1UHYusbzCmsvm
|
||||
M9JkknNMJvGIjBDjHEVy6dlTaHQoHDY+Me9gsrEX0ZS9xXgAiB2IupabEwARAQAB
|
||||
tEJGcmVlUkRQIFNlY3VyaXR5IFRlYW0gKGh0dHBzOi8vZnJlZXJkcC5jb20pIDxz
|
||||
ZWN1cml0eUBmcmVlcmRwLmNvbT6JAk4EEwEKADgWIQRvuAE0sDt7JnxXu0o3Ibww
|
||||
YbfjNAUCYHP6OwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRA3IbwwYbfj
|
||||
NPviD/43NLg7YfjAlvj5GipSmgelLwlIA+L/qbrf4NAB+NZ9oqp3bBdj4e5gZmiI
|
||||
zd6bkANCqk21YiOE31medUfy+nfBQFVvj0oUg1X16C6RaIX5qA3Dwt5qBwKmDkT5
|
||||
j7JlxUS6Eluiau67ePiDYu2Wbp0qYuAmNUNL+Y2NCO9UJiy0Oq6YVXS971D5lC+a
|
||||
SX0x9pizmFV3zro+l6/3kHTVbPednfX99yz9SZge64aWXo3MXVN8JD0lR3+92l99
|
||||
XsFDc+lGeR4azLFIqXC4Cr5Lbk34Hw/VwUC32xxFUaJ2ZmV3pA8bhCtBSxSmxnHS
|
||||
H3hoBaD1WpuApbW8Psx6qsgoaSUdWsjluA4eQ5afJBf9O2NlT1mim5MAINY4PbWP
|
||||
o4zq3p1ABVTzuB8tsGA9o6DeYVUUrj7lCv9STdGRhm0472BDkp/gvKMBoPgg3Qez
|
||||
kvGKK7iVy8R/BOPjh9wP1art5JLVsralXGHA/5Ceid4ojKFzGIC9g3lnAPh+T/eM
|
||||
duyY9XH4un1r73r6DRqUoczSfHYbxhKxWt0cRNdIadcXXusMPV/w4J4j55WcLrBE
|
||||
5nopp/prJ5bYegUvRRrwVSFwLDxkE2dh68Zvlh5VWXIPFge0RPEAijYWR5qR2z+/
|
||||
VHgPYmliOnWFJN1rzekmWjKFtg5A57FkZyk3cp5x0/2xAX+TIbkCDQRgc/o7ARAA
|
||||
vw53CoVkMzBlisSEETNdEKQMaiQ8BtbC438v/b1mOOeoE0YCfSW7RyflA/TXHOah
|
||||
db0s3v/Kk2xmbjeMS9IJXlWviKKnOVMrMZvtJdQ4EKfqc5EpxNx7OiEofA/7n7Xs
|
||||
1YEt6KjYaM/vgANl9HA2UXzqSFiRhkWjj1WA7vhqCWUArpAMGeCDYab2BBfp6Z4f
|
||||
W9178N2vHH+Hh/uBwGUDnShU38GH8Nstkdcyw5puiJqNQBfZ1Fz9luzutp6zAgHz
|
||||
WzobeRPZCCXs7CfxcvpkFS0ctOteQtIRIfP+jbDnldMmClQ87UVcKv0pCCJkMLNk
|
||||
YUCMAb2UC2boCIf0omeeque4+FOphcO4+R/8jc6cYlQpgwUg2/IwBEEnCqtvo3qu
|
||||
k6uzONhfWZPtUdJd158MGKGTogXVXGzoGzxIrKkZ4W1VuuMiEmhIQZO8e7/4Iz4a
|
||||
Zp4qQXI8rsmNJN3lB5a7MWgrZ8mjllYRdfiTEvfQ+PiQqnG6PEHZ82om9kp555gs
|
||||
15UqhjHAqRRtfXzQvZko0ngAxxZNVFPwK8LnxkyEPClRBC5eV3ljI8cvCfnWD01q
|
||||
rCzSlSafFHCEUEQOhOrf/bBbXPkYTJw2KlumH5w9R6xQWgqneiD/+Qmqdclzdn36
|
||||
Pgbhyu6uSNZehbx5ptt/EM66JSAW7Q7W6Qnz5PNnHgEAEQEAAYkCNgQYAQoAIBYh
|
||||
BG+4ATSwO3smfFe7SjchvDBht+M0BQJgc/o7AhsMAAoJEDchvDBht+M0JYUQALlV
|
||||
dwmk6ZFq5dq0utWgutysL47b30BhYwNMVe0/6UW4h4TYaW6B3f58X7ik7EdYciyR
|
||||
68eYfwKGhuv/y90QaGXJMU13XHpoInSaHQRhn5M/GkN16DBXdBok70Fh9Gx89Zhs
|
||||
VKF3qwIVx5AO5CwrVA6F/iOiUEW31xiT7VFkbW1Cfl5H+M6nVXSR1bOdmxTObTz7
|
||||
CEeJMOVrZs36hVLMWLqZF0igVebO2AsDOY63fy/9MLn8ynCHhnAMvsm9ULWuFzGj
|
||||
OsJezChduaHqPkopgwihe7jthUn4qWjABbbzKkS6HLBpGAfCzUun+lMpvIEUf+EJ
|
||||
bpk7gj9xDEP6y96tV/dCeWb4p8N8webR8nVgsRxoEnfIdCkoB80iZGOzKfYYnvdz
|
||||
ngs8MIL6dC4Nc1/t9ECV4O/w4uwIH65nC1ay0YOK/O/j2SEfnVHQmAuOsgTz+pBn
|
||||
u6DIA2HsBzFdOCljtf3m4AeAaTbL7MBSDceApqg0lcrhjclqHJo1aJh3M6aVm3gq
|
||||
yUt7y26Hkh/vYEJwW4gqRho4gb7BvjTZh5LUbrjmRtexFQ1eWM82u23yYS2L+y2Y
|
||||
ejSKIKmJhXHqsgCVGYw5woZEEMzgpkoIWYG/Eoy+oVuU02QITh/Uc5VRsA9DuwSV
|
||||
Vw2F8gu/fHiadawxWIhUH+plFVQZc1KwgPcIMW3S
|
||||
=O0kP
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
[^1]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
|
@ -7,7 +7,7 @@
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@ -15,282 +15,221 @@
|
||||
# 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")
|
||||
set(PREFIX "CHANNEL")
|
||||
|
||||
cmake_parse_arguments(${PREFIX}
|
||||
""
|
||||
"NAME;TYPE;DESCRIPTION;SPECIFICATIONS;DEFAULT"
|
||||
""
|
||||
${ARGN})
|
||||
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()
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION)
|
||||
|
||||
if(${${CHANNEL_CLIENT_OPTION}})
|
||||
set(OPTION_CLIENT_DEFAULT ${${CHANNEL_CLIENT_OPTION}})
|
||||
endif()
|
||||
|
||||
if(${${CHANNEL_SERVER_OPTION}})
|
||||
set(OPTION_SERVER_DEFAULT ${${CHANNEL_SERVER_OPTION}})
|
||||
endif()
|
||||
|
||||
if(${${CHANNEL_OPTION}})
|
||||
set(OPTION_DEFAULT ${${CHANNEL_OPTION}})
|
||||
endif()
|
||||
|
||||
if(${OPTION_CLIENT_DEFAULT} OR ${OPTION_SERVER_DEFAULT})
|
||||
set(OPTION_DEFAULT "ON")
|
||||
endif()
|
||||
|
||||
set(CHANNEL_DEFAULT ${OPTION_DEFAULT})
|
||||
|
||||
set(CHANNEL_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel")
|
||||
option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT})
|
||||
|
||||
endmacro(define_channel_options)
|
||||
|
||||
macro(define_channel_client_options _channel_client_default)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION)
|
||||
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)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION)
|
||||
set(CHANNEL_CLIENT_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel client")
|
||||
option(${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}" ${_channel_client_default})
|
||||
cmake_dependent_option(${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}"
|
||||
${_channel_client_default} "${CHANNEL_OPTION}" OFF)
|
||||
endmacro(define_channel_client_options)
|
||||
|
||||
macro(define_channel_server_options _channel_server_default)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION)
|
||||
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)
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION)
|
||||
set(CHANNEL_SERVER_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel server")
|
||||
option(${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}" ${_channel_server_default})
|
||||
cmake_dependent_option(${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}"
|
||||
${_channel_server_default} "${CHANNEL_OPTION}" OFF)
|
||||
endmacro(define_channel_server_options)
|
||||
|
||||
macro(define_channel _channel_name)
|
||||
set(CHANNEL_NAME ${_channel_name})
|
||||
set(MODULE_NAME ${CHANNEL_NAME})
|
||||
string(TOUPPER "CHANNEL_${CHANNEL_NAME}" MODULE_PREFIX)
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
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)
|
||||
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)
|
||||
if (${_channel_prefix}_CLIENT)
|
||||
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()
|
||||
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)
|
||||
if (${_channel_prefix}_SERVER)
|
||||
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()
|
||||
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()
|
||||
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)
|
||||
if (NOT BUILD_SHARED_LIBS)
|
||||
foreach(_target_name IN ITEMS ${_targets})
|
||||
target_include_directories(${_target_name} INTERFACE $<INSTALL_INTERFACE:include>)
|
||||
endforeach()
|
||||
install(TARGETS ${_targets} DESTINATION ${_destination} EXPORT ${_export_target})
|
||||
endif()
|
||||
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()
|
||||
|
||||
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} OBJECT ${${_module_prefix}_SRCS})
|
||||
set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client")
|
||||
|
||||
if (${_module_prefix}_LIBS)
|
||||
target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
|
||||
if(${_dynamic} AND MSVC AND (NOT STATIC_CHANNELS))
|
||||
set(${_module_prefix}_SRCS ${${_module_prefix}_SRCS} module.def)
|
||||
endif()
|
||||
if(${_dynamic} AND (NOT STATIC_CHANNELS))
|
||||
add_library(${_module_name} ${${_module_prefix}_SRCS})
|
||||
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})
|
||||
endif()
|
||||
client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
|
||||
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()
|
||||
|
||||
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} OBJECT ${${_module_prefix}_SRCS})
|
||||
set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${_channel_name}/Client/Subsystem/${CHANNEL_SUBSYSTEM}")
|
||||
|
||||
if (${_module_prefix}_LIBS)
|
||||
target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
|
||||
if(${_dynamic} AND MSVC AND (NOT STATIC_CHANNELS))
|
||||
set(${_module_prefix}_SRCS ${${_module_prefix}_SRCS} module.def)
|
||||
endif()
|
||||
if(${_dynamic} AND (NOT STATIC_CHANNELS))
|
||||
add_library(${_module_name} ${${_module_prefix}_SRCS})
|
||||
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})
|
||||
endif()
|
||||
client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
|
||||
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()
|
||||
|
||||
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} OBJECT ${${_module_prefix}_SRCS})
|
||||
set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server")
|
||||
|
||||
if (${_module_prefix}_LIBS)
|
||||
target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS})
|
||||
if(${_dynamic} AND MSVC AND (NOT STATIC_CHANNELS))
|
||||
set(${_module_prefix}_SRCS ${${_module_prefix}_SRCS} module.def)
|
||||
endif()
|
||||
if(${_dynamic} AND (NOT STATIC_CHANNELS))
|
||||
add_library(${_module_name} ${${_module_prefix}_SRCS})
|
||||
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})
|
||||
endif()
|
||||
server_channel_install(${_module_name} ${FREERDP_ADDIN_PATH})
|
||||
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()
|
||||
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_CHANNELS)
|
||||
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()
|
||||
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()
|
||||
|
@ -1,27 +0,0 @@
|
||||
# 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()
|
@ -1,13 +0,0 @@
|
||||
|
||||
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})
|
||||
|
@ -1,32 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
|
@ -1,183 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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/channels.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_PLUGIN_ AINPUT_PLUGIN;
|
||||
struct AINPUT_PLUGIN_
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN base;
|
||||
AInputClientContext* context;
|
||||
UINT32 MajorVersion;
|
||||
UINT32 MinorVersion;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
|
||||
{
|
||||
UINT16 type = 0;
|
||||
AINPUT_PLUGIN* ainput = NULL;
|
||||
GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
|
||||
WINPR_ASSERT(callback);
|
||||
WINPR_ASSERT(data);
|
||||
|
||||
ainput = (AINPUT_PLUGIN*)callback->plugin;
|
||||
WINPR_ASSERT(ainput);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, data, 2))
|
||||
return ERROR_NO_DATA;
|
||||
Stream_Read_UINT16(data, type);
|
||||
switch (type)
|
||||
{
|
||||
case MSG_AINPUT_VERSION:
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, 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 = NULL;
|
||||
GENERIC_CHANNEL_CALLBACK* callback = NULL;
|
||||
BYTE buffer[32] = { 0 };
|
||||
UINT64 time = 0;
|
||||
wStream sbuffer = { 0 };
|
||||
wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
|
||||
|
||||
WINPR_ASSERT(s);
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
time = GetTickCount64();
|
||||
ainput = (AINPUT_PLUGIN*)context->handle;
|
||||
WINPR_ASSERT(ainput);
|
||||
|
||||
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->base.listener_callback->channel_callback;
|
||||
WINPR_ASSERT(callback);
|
||||
|
||||
{
|
||||
char ebuffer[128] = { 0 };
|
||||
WLog_VRB(TAG, "sending timestamp=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time,
|
||||
ainput_flags_to_string(flags, ebuffer, sizeof(ebuffer)), 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)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
|
||||
|
||||
free(callback);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
|
||||
{
|
||||
AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
|
||||
AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext));
|
||||
if (!context)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
context->handle = (void*)base;
|
||||
context->AInputSendInputEvent = ainput_send_input_event;
|
||||
|
||||
ainput->context = context;
|
||||
ainput->base.iface.pInterface = context;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
|
||||
{
|
||||
AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
|
||||
free(ainput->context);
|
||||
}
|
||||
|
||||
static const IWTSVirtualChannelCallback ainput_functions = { ainput_on_data_received,
|
||||
NULL, /* Open */
|
||||
ainput_on_close, NULL };
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE ainput_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
|
||||
{
|
||||
return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, AINPUT_DVC_CHANNEL_NAME,
|
||||
sizeof(AINPUT_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
|
||||
&ainput_functions, init_plugin_cb, terminate_plugin_cb);
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#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 */
|
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* 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 */
|
@ -1,28 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp
|
||||
)
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
|
@ -1,597 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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 <freerdp/freerdp.h>
|
||||
#include <freerdp/channels/ainput.h>
|
||||
#include <freerdp/server/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 = 0;
|
||||
HANDLE hEvent = NULL;
|
||||
DWORD StartTick = 0;
|
||||
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 = 0;
|
||||
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 = 0;
|
||||
wStream* s = NULL;
|
||||
|
||||
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) <= UINT32_MAX);
|
||||
if (!WTSVirtualChannelWrite(ainput->ainput_channel, Stream_BufferAs(s, char),
|
||||
(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 = 0;
|
||||
UINT64 time = 0;
|
||||
INT32 x = 0;
|
||||
INT32 y = 0;
|
||||
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, "received: time=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, 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)
|
||||
{
|
||||
void* 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 = 0;
|
||||
HANDLE events[2] = { 0 };
|
||||
ainput_server* ainput = (ainput_server*)arg;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
DWORD status = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
(void)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!");
|
||||
(void)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)
|
||||
{
|
||||
(void)SetEvent(ainput->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(ainput->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
(void)CloseHandle(ainput->thread);
|
||||
(void)CloseHandle(ainput->stopEvent);
|
||||
ainput->thread = NULL;
|
||||
ainput->stopEvent = NULL;
|
||||
}
|
||||
if (ainput->externalThread)
|
||||
{
|
||||
if (ainput->state != AINPUT_INITIAL)
|
||||
{
|
||||
(void)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:
|
||||
WINPR_PRAGMA_DIAG_PUSH
|
||||
WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
|
||||
ainput_server_context_free(&ainput->context);
|
||||
WINPR_PRAGMA_DIAG_POP
|
||||
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 = 0;
|
||||
UINT error = ERROR_INTERNAL_ERROR;
|
||||
ULONG BytesReturned = 0;
|
||||
ULONG ActualBytesReturned = 0;
|
||||
UINT16 MessageId = 0;
|
||||
wStream* s = NULL;
|
||||
|
||||
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, Stream_BufferAs(s, char),
|
||||
(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:
|
||||
{
|
||||
union
|
||||
{
|
||||
BYTE* pb;
|
||||
void* pv;
|
||||
} buffer;
|
||||
DWORD BytesReturned = 0;
|
||||
|
||||
buffer.pv = NULL;
|
||||
|
||||
if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer.pv,
|
||||
&BytesReturned) != TRUE)
|
||||
{
|
||||
WLog_ERR(TAG, "WTSVirtualChannelReady failed,");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*buffer.pb != 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.pv);
|
||||
}
|
||||
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);
|
||||
}
|
@ -4,6 +4,7 @@ set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
if(ANDROID)
|
||||
set(OPTION_CLIENT_DEFAULT OFF)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
endif()
|
||||
|
||||
|
@ -19,21 +19,27 @@ define_channel_client("audin")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_main.c
|
||||
audin_main.h
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp winpr
|
||||
)
|
||||
audin_main.h)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
|
||||
|
||||
if(WITH_OSS)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "")
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-common freerdp-utils)
|
||||
|
||||
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_PLUGIN_PATH})
|
||||
endif()
|
||||
|
||||
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client")
|
||||
|
||||
if(WITH_ALSA)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "")
|
||||
endif()
|
||||
@ -41,23 +47,3 @@ 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()
|
||||
|
||||
if(WITH_SNDIO)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "")
|
||||
endif()
|
||||
|
||||
if(WITH_IOSAUDIO)
|
||||
add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "")
|
||||
endif()
|
||||
|
@ -17,19 +17,25 @@
|
||||
|
||||
define_channel_client_subsystem("audin" "alsa" "")
|
||||
|
||||
find_package(ALSA REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_alsa.c
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${ALSA_LIBRARIES}
|
||||
)
|
||||
audin_alsa.c)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${ALSA_INCLUDE_DIRS})
|
||||
include_directories(${ALSA_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-utils)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${ALSA_LIBRARIES})
|
||||
|
||||
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
|
||||
endif()
|
||||
|
@ -3,8 +3,6 @@
|
||||
* 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.
|
||||
@ -19,7 +17,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -29,347 +29,353 @@
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/cmdline.h>
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
typedef struct
|
||||
typedef struct _AudinALSADevice
|
||||
{
|
||||
IAudinDevice iface;
|
||||
|
||||
char* device_name;
|
||||
UINT32 frames_per_packet;
|
||||
AUDIO_FORMAT aformat;
|
||||
UINT32 target_rate;
|
||||
UINT32 actual_rate;
|
||||
snd_pcm_format_t format;
|
||||
UINT32 target_channels;
|
||||
UINT32 actual_channels;
|
||||
int bytes_per_channel;
|
||||
int wformat;
|
||||
int block_size;
|
||||
|
||||
FREERDP_DSP_CONTEXT* dsp_context;
|
||||
|
||||
HANDLE thread;
|
||||
HANDLE stopEvent;
|
||||
|
||||
BYTE* buffer;
|
||||
int buffer_frames;
|
||||
|
||||
AudinReceive receive;
|
||||
void* user_data;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
wLog* log;
|
||||
size_t 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;
|
||||
}
|
||||
|
||||
default:
|
||||
return SND_PCM_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
|
||||
{
|
||||
int error = 0;
|
||||
SSIZE_T s = 0;
|
||||
UINT32 channels = alsa->aformat.nChannels;
|
||||
snd_pcm_hw_params_t* hw_params = NULL;
|
||||
snd_pcm_format_t format =
|
||||
audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample);
|
||||
int error;
|
||||
snd_pcm_hw_params_t* hw_params;
|
||||
|
||||
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));
|
||||
DEBUG_WARN("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_set_format(capture_handle, hw_params, alsa->format);
|
||||
snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsa->actual_rate, NULL);
|
||||
snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, &alsa->actual_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;
|
||||
|
||||
if ((alsa->actual_rate != alsa->target_rate) ||
|
||||
(alsa->actual_channels != alsa->target_channels))
|
||||
{
|
||||
DEBUG_DVC("actual rate %d / channel %d is "
|
||||
"different from target rate %d / channel %d, resampling required.",
|
||||
alsa->actual_rate, alsa->actual_channels,
|
||||
alsa->target_rate, alsa->target_channels);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static DWORD WINAPI audin_alsa_thread_func(LPVOID arg)
|
||||
static BOOL audin_alsa_thread_receive(AudinALSADevice* alsa, BYTE* src, int size)
|
||||
{
|
||||
DWORD error = CHANNEL_RC_OK;
|
||||
BYTE* buffer = NULL;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*)arg;
|
||||
int frames;
|
||||
int cframes;
|
||||
int ret = 0;
|
||||
int encoded_size;
|
||||
BYTE* encoded_data;
|
||||
int rbytes_per_frame;
|
||||
int tbytes_per_frame;
|
||||
|
||||
WINPR_ASSERT(alsa);
|
||||
rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel;
|
||||
tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel;
|
||||
|
||||
WLog_Print(alsa->log, WLOG_DEBUG, "in");
|
||||
if ((alsa->target_rate == alsa->actual_rate) &&
|
||||
(alsa->target_channels == alsa->actual_channels))
|
||||
{
|
||||
frames = size / rbytes_per_frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
alsa->dsp_context->resample(alsa->dsp_context, src, alsa->bytes_per_channel,
|
||||
alsa->actual_channels, alsa->actual_rate, size / rbytes_per_frame,
|
||||
alsa->target_channels, alsa->target_rate);
|
||||
frames = alsa->dsp_context->resampled_frames;
|
||||
DEBUG_DVC("resampled %d frames at %d to %d frames at %d",
|
||||
size / rbytes_per_frame, alsa->actual_rate, frames, alsa->target_rate);
|
||||
size = frames * tbytes_per_frame;
|
||||
src = alsa->dsp_context->resampled_buffer;
|
||||
}
|
||||
|
||||
while (frames > 0)
|
||||
{
|
||||
if (WaitForSingleObject(alsa->stopEvent, 0) == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
cframes = alsa->frames_per_packet - alsa->buffer_frames;
|
||||
|
||||
if (cframes > frames)
|
||||
cframes = frames;
|
||||
|
||||
CopyMemory(alsa->buffer + alsa->buffer_frames * tbytes_per_frame, src, cframes * tbytes_per_frame);
|
||||
|
||||
alsa->buffer_frames += cframes;
|
||||
|
||||
if (alsa->buffer_frames >= alsa->frames_per_packet)
|
||||
{
|
||||
if (alsa->wformat == WAVE_FORMAT_DVI_ADPCM)
|
||||
{
|
||||
alsa->dsp_context->encode_ima_adpcm(alsa->dsp_context,
|
||||
alsa->buffer, alsa->buffer_frames * tbytes_per_frame,
|
||||
alsa->target_channels, alsa->block_size);
|
||||
|
||||
encoded_data = alsa->dsp_context->adpcm_buffer;
|
||||
encoded_size = alsa->dsp_context->adpcm_size;
|
||||
|
||||
DEBUG_DVC("encoded %d to %d",
|
||||
alsa->buffer_frames * tbytes_per_frame, encoded_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded_data = alsa->buffer;
|
||||
encoded_size = alsa->buffer_frames * tbytes_per_frame;
|
||||
}
|
||||
|
||||
if (WaitForSingleObject(alsa->stopEvent, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
ret = 0;
|
||||
frames = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = alsa->receive(encoded_data, encoded_size, alsa->user_data);
|
||||
}
|
||||
|
||||
alsa->buffer_frames = 0;
|
||||
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
|
||||
src += cframes * tbytes_per_frame;
|
||||
frames -= cframes;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void* audin_alsa_thread_func(void* arg)
|
||||
{
|
||||
int error;
|
||||
BYTE* buffer;
|
||||
int rbytes_per_frame;
|
||||
int tbytes_per_frame;
|
||||
snd_pcm_t* capture_handle = NULL;
|
||||
const int rc = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0);
|
||||
if (rc < 0)
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) arg;
|
||||
|
||||
DEBUG_DVC("in");
|
||||
|
||||
rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel;
|
||||
tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel;
|
||||
alsa->buffer = (BYTE*) malloc(tbytes_per_frame * alsa->frames_per_packet);
|
||||
ZeroMemory(alsa->buffer, tbytes_per_frame * alsa->frames_per_packet);
|
||||
alsa->buffer_frames = 0;
|
||||
buffer = (BYTE*) malloc(rbytes_per_frame * alsa->frames_per_packet);
|
||||
ZeroMemory(buffer, rbytes_per_frame * alsa->frames_per_packet);
|
||||
freerdp_dsp_context_reset_adpcm(alsa->dsp_context);
|
||||
|
||||
do
|
||||
{
|
||||
WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(rc));
|
||||
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;
|
||||
const DWORD status = WaitForSingleObject(alsa->stopEvent, 0);
|
||||
|
||||
if (status == WAIT_FAILED)
|
||||
if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!",
|
||||
error);
|
||||
DEBUG_WARN("snd_pcm_open (%s)", snd_strerror(error));
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == WAIT_OBJECT_0)
|
||||
if (!audin_alsa_set_params(alsa, capture_handle))
|
||||
{
|
||||
WLog_Print(alsa->log, WLOG_DEBUG, "alsa->stopEvent requests termination");
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t framesRead = snd_pcm_readi(capture_handle, buffer, frames);
|
||||
|
||||
if (framesRead == 0)
|
||||
continue;
|
||||
|
||||
if (framesRead == -EPIPE)
|
||||
while (!(WaitForSingleObject(alsa->stopEvent, 0) == WAIT_OBJECT_0))
|
||||
{
|
||||
const int res = snd_pcm_recover(capture_handle, (int)framesRead, 0);
|
||||
if (res < 0)
|
||||
WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_recover (%s)", snd_strerror(res));
|
||||
error = snd_pcm_readi(capture_handle, buffer, alsa->frames_per_packet);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (framesRead < 0)
|
||||
{
|
||||
WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror((int)framesRead));
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
if (error == -EPIPE)
|
||||
{
|
||||
snd_pcm_recover(capture_handle, error, 0);
|
||||
continue;
|
||||
}
|
||||
else if (error < 0)
|
||||
{
|
||||
DEBUG_WARN("snd_pcm_readi (%s)", snd_strerror(error));
|
||||
break;
|
||||
}
|
||||
|
||||
error = alsa->receive(&alsa->aformat, buffer, (long)framesRead * 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;
|
||||
if (!audin_alsa_thread_receive(alsa, buffer, error * rbytes_per_frame))
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (0);
|
||||
|
||||
free(buffer);
|
||||
|
||||
free(alsa->buffer);
|
||||
alsa->buffer = NULL;
|
||||
|
||||
if (capture_handle)
|
||||
{
|
||||
const int res = snd_pcm_close(capture_handle);
|
||||
if (res < 0)
|
||||
WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_close (%s)", snd_strerror(res));
|
||||
}
|
||||
snd_pcm_close(capture_handle);
|
||||
|
||||
out:
|
||||
WLog_Print(alsa->log, WLOG_DEBUG, "out");
|
||||
SetEvent(alsa->stopEvent);
|
||||
|
||||
if (error && alsa->rdpcontext)
|
||||
setChannelError(alsa->rdpcontext, error, "audin_alsa_thread_func reported an error");
|
||||
DEBUG_DVC("out");
|
||||
|
||||
ExitThread(error);
|
||||
return error;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_alsa_free(IAudinDevice* device)
|
||||
static void audin_alsa_free(IAudinDevice* device)
|
||||
{
|
||||
AudinALSADevice* alsa = (AudinALSADevice*)device;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) device;
|
||||
|
||||
if (alsa)
|
||||
free(alsa->device_name);
|
||||
SetEvent(alsa->stopEvent);
|
||||
|
||||
freerdp_dsp_context_free(alsa->dsp_context);
|
||||
|
||||
free(alsa->device_name);
|
||||
|
||||
free(alsa);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static BOOL audin_alsa_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
|
||||
static BOOL audin_alsa_format_supported(IAudinDevice* device, audinFormat* 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))
|
||||
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;
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
if ((format->nSamplesPerSec <= 48000) &&
|
||||
(format->wBitsPerSample == 4) &&
|
||||
(format->nChannels == 1 || format->nChannels == 2))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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)
|
||||
static void audin_alsa_set_format(IAudinDevice* device, audinFormat* format, UINT32 FramesPerPacket)
|
||||
{
|
||||
AudinALSADevice* alsa = (AudinALSADevice*)device;
|
||||
int bs;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) device;
|
||||
|
||||
if (!alsa || !format)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
alsa->target_rate = format->nSamplesPerSec;
|
||||
alsa->actual_rate = format->nSamplesPerSec;
|
||||
alsa->target_channels = format->nChannels;
|
||||
alsa->actual_channels = format->nChannels;
|
||||
|
||||
alsa->aformat = *format;
|
||||
alsa->frames_per_packet = FramesPerPacket;
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
switch (format->wBitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
alsa->format = SND_PCM_FORMAT_S8;
|
||||
alsa->bytes_per_channel = 1;
|
||||
break;
|
||||
case 16:
|
||||
alsa->format = SND_PCM_FORMAT_S16_LE;
|
||||
alsa->bytes_per_channel = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
case WAVE_FORMAT_DVI_ADPCM:
|
||||
alsa->format = SND_PCM_FORMAT_S16_LE;
|
||||
alsa->bytes_per_channel = 2;
|
||||
bs = (format->nBlockAlign - 4 * format->nChannels) * 4;
|
||||
alsa->frames_per_packet = (alsa->frames_per_packet * format->nChannels * 2 /
|
||||
bs + 1) * bs / (format->nChannels * 2);
|
||||
DEBUG_DVC("aligned FramesPerPacket=%d",
|
||||
alsa->frames_per_packet);
|
||||
break;
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
alsa->wformat = format->wFormatTag;
|
||||
alsa->block_size = format->nBlockAlign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
|
||||
static void audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
|
||||
{
|
||||
AudinALSADevice* alsa = (AudinALSADevice*)device;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) device;
|
||||
|
||||
if (!device || !receive || !user_data)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
DEBUG_DVC("");
|
||||
|
||||
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:
|
||||
(void)CloseHandle(alsa->stopEvent);
|
||||
alsa->stopEvent = NULL;
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
alsa->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
alsa->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) audin_alsa_thread_func, alsa, 0, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_alsa_close(IAudinDevice* device)
|
||||
static void audin_alsa_close(IAudinDevice* device)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*)device;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) device;
|
||||
|
||||
if (!alsa)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
DEBUG_DVC("");
|
||||
|
||||
if (alsa->stopEvent)
|
||||
{
|
||||
(void)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;
|
||||
}
|
||||
|
||||
(void)CloseHandle(alsa->stopEvent);
|
||||
alsa->stopEvent = NULL;
|
||||
(void)CloseHandle(alsa->thread);
|
||||
alsa->thread = NULL;
|
||||
}
|
||||
SetEvent(alsa->stopEvent);
|
||||
|
||||
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, const ADDIN_ARGV* args)
|
||||
COMMAND_LINE_ARGUMENT_A audin_alsa_args[] =
|
||||
{
|
||||
int status = 0;
|
||||
DWORD flags = 0;
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = NULL;
|
||||
AudinALSADevice* alsa = 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);
|
||||
{ "audio-dev", COMMAND_LINE_VALUE_REQUIRED, "<device>", NULL, NULL, -1, NULL, "audio device name" },
|
||||
{ NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
|
||||
};
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
static void audin_alsa_parse_addin_args(AudinALSADevice* device, ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
AudinALSADevice* alsa = (AudinALSADevice*) device;
|
||||
|
||||
flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
|
||||
|
||||
status = CommandLineParseArgumentsA(args->argc, (const char**) args->argv, audin_alsa_args, flags, alsa, NULL, NULL);
|
||||
|
||||
arg = audin_alsa_args;
|
||||
|
||||
@ -378,85 +384,54 @@ static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, const ADDIN_ARG
|
||||
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
|
||||
continue;
|
||||
|
||||
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
|
||||
CommandLineSwitchStart(arg)
|
||||
|
||||
CommandLineSwitchCase(arg, "audio-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;
|
||||
CommandLineSwitchEnd(arg)
|
||||
}
|
||||
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
#ifdef STATIC_CHANNELS
|
||||
#define freerdp_audin_client_subsystem_entry alsa_freerdp_audin_client_subsystem_entry
|
||||
#endif
|
||||
|
||||
int freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)
|
||||
{
|
||||
const ADDIN_ARGV* args = NULL;
|
||||
AudinALSADevice* alsa = NULL;
|
||||
UINT error = 0;
|
||||
alsa = (AudinALSADevice*)calloc(1, sizeof(AudinALSADevice));
|
||||
ADDIN_ARGV* args;
|
||||
AudinALSADevice* alsa;
|
||||
|
||||
if (!alsa)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
alsa = (AudinALSADevice*) malloc(sizeof(AudinALSADevice));
|
||||
ZeroMemory(alsa, sizeof(AudinALSADevice));
|
||||
|
||||
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;
|
||||
}
|
||||
audin_alsa_parse_addin_args(alsa, args);
|
||||
|
||||
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;
|
||||
alsa->target_rate = 22050;
|
||||
alsa->actual_rate = 22050;
|
||||
alsa->format = SND_PCM_FORMAT_S16_LE;
|
||||
alsa->target_channels = 2;
|
||||
alsa->actual_channels = 2;
|
||||
alsa->bytes_per_channel = 2;
|
||||
|
||||
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)alsa)))
|
||||
{
|
||||
WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
|
||||
error);
|
||||
goto error_out;
|
||||
}
|
||||
alsa->dsp_context = freerdp_dsp_context_new();
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
error_out:
|
||||
free(alsa->device_name);
|
||||
free(alsa);
|
||||
return error;
|
||||
pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) alsa);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,17 +17,62 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
|
||||
#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H
|
||||
#ifndef __AUDIN_MAIN_H
|
||||
#define __AUDIN_MAIN_H
|
||||
|
||||
#include <freerdp/config.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>
|
||||
#include <freerdp/utils/debug.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("audin.client")
|
||||
#ifdef WITH_DEBUG_DVC
|
||||
#define DEBUG_DVC(fmt, ...) DEBUG_CLASS(DVC, fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_DVC(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
typedef BOOL (*AudinReceive) (BYTE* data, int size, void* user_data);
|
||||
|
||||
typedef struct audin_format audinFormat;
|
||||
struct audin_format
|
||||
{
|
||||
UINT16 wFormatTag;
|
||||
UINT16 nChannels;
|
||||
UINT32 nSamplesPerSec;
|
||||
UINT16 nBlockAlign;
|
||||
UINT16 wBitsPerSample;
|
||||
UINT16 cbSize;
|
||||
BYTE* data;
|
||||
};
|
||||
|
||||
typedef struct _IAudinDevice IAudinDevice;
|
||||
struct _IAudinDevice
|
||||
{
|
||||
void (*Open) (IAudinDevice* devplugin, AudinReceive receive, void* user_data);
|
||||
BOOL (*FormatSupported) (IAudinDevice* devplugin, audinFormat* format);
|
||||
void (*SetFormat) (IAudinDevice* devplugin, audinFormat* format, UINT32 FramesPerPacket);
|
||||
void (*Close) (IAudinDevice* devplugin);
|
||||
void (*Free) (IAudinDevice* devplugin);
|
||||
};
|
||||
|
||||
#define AUDIN_DEVICE_EXPORT_FUNC_NAME "freerdp_audin_client_subsystem_entry"
|
||||
|
||||
typedef void (*PREGISTERAUDINDEVICE)(IWTSPlugin* plugin, IAudinDevice* device);
|
||||
|
||||
struct _FREERDP_AUDIN_DEVICE_ENTRY_POINTS
|
||||
{
|
||||
IWTSPlugin* plugin;
|
||||
PREGISTERAUDINDEVICE pRegisterAudinDevice;
|
||||
ADDIN_ARGV* args;
|
||||
};
|
||||
typedef struct _FREERDP_AUDIN_DEVICE_ENTRY_POINTS FREERDP_AUDIN_DEVICE_ENTRY_POINTS;
|
||||
typedef FREERDP_AUDIN_DEVICE_ENTRY_POINTS* PFREERDP_AUDIN_DEVICE_ENTRY_POINTS;
|
||||
|
||||
typedef int (*PFREERDP_AUDIN_DEVICE_ENTRY)(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints);
|
||||
|
||||
#endif /* __AUDIN_MAIN_H */
|
||||
|
||||
#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */
|
||||
|
@ -1,39 +0,0 @@
|
||||
# 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" "ios" "")
|
||||
FIND_LIBRARY(CORE_AUDIO CoreAudio)
|
||||
FIND_LIBRARY(AVFOUNDATION AVFoundation)
|
||||
FIND_LIBRARY(AUDIO_TOOL AudioToolbox)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_ios.m
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${AVFOUNDATION}
|
||||
${CORE_AUDIO}
|
||||
${AUDIO_TOOL}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${MAC_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
@ -1,335 +0,0 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Input Redirection Virtual Channel - iOS 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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 <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioToolbox/AudioQueue.h>
|
||||
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
#define IOS_AUDIO_QUEUE_NUM_BUFFERS 100
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IAudinDevice iface;
|
||||
|
||||
AUDIO_FORMAT format;
|
||||
UINT32 FramesPerPacket;
|
||||
int dev_unit;
|
||||
|
||||
AudinReceive receive;
|
||||
void *user_data;
|
||||
|
||||
rdpContext *rdpcontext;
|
||||
|
||||
bool isOpen;
|
||||
AudioQueueRef audioQueue;
|
||||
AudioStreamBasicDescription audioFormat;
|
||||
AudioQueueBufferRef audioBuffers[IOS_AUDIO_QUEUE_NUM_BUFFERS];
|
||||
} AudinIosDevice;
|
||||
|
||||
static AudioFormatID audin_ios_get_format(const AUDIO_FORMAT *format)
|
||||
{
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
return kAudioFormatLinearPCM;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static AudioFormatFlags audin_ios_get_flags_for_format(const AUDIO_FORMAT *format)
|
||||
{
|
||||
switch (format->wFormatTag)
|
||||
{
|
||||
case WAVE_FORMAT_PCM:
|
||||
return kAudioFormatFlagIsSignedInteger;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL audin_ios_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format)
|
||||
{
|
||||
AudinIosDevice *ios = (AudinIosDevice *)device;
|
||||
AudioFormatID req_fmt = 0;
|
||||
|
||||
if (device == NULL || format == NULL)
|
||||
return FALSE;
|
||||
|
||||
req_fmt = audin_ios_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_ios_set_format(IAudinDevice *device, const AUDIO_FORMAT *format,
|
||||
UINT32 FramesPerPacket)
|
||||
{
|
||||
AudinIosDevice *ios = (AudinIosDevice *)device;
|
||||
|
||||
if (device == NULL || format == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
ios->FramesPerPacket = FramesPerPacket;
|
||||
ios->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);
|
||||
ios->audioFormat.mBitsPerChannel = format->wBitsPerSample;
|
||||
|
||||
if (format->wBitsPerSample == 0)
|
||||
ios->audioFormat.mBitsPerChannel = 16;
|
||||
|
||||
ios->audioFormat.mChannelsPerFrame = ios->format.nChannels;
|
||||
ios->audioFormat.mFramesPerPacket = 1;
|
||||
|
||||
ios->audioFormat.mBytesPerFrame =
|
||||
ios->audioFormat.mChannelsPerFrame * (ios->audioFormat.mBitsPerChannel / 8);
|
||||
ios->audioFormat.mBytesPerPacket =
|
||||
ios->audioFormat.mBytesPerFrame * ios->audioFormat.mFramesPerPacket;
|
||||
|
||||
ios->audioFormat.mFormatFlags = audin_ios_get_flags_for_format(format);
|
||||
ios->audioFormat.mFormatID = audin_ios_get_format(format);
|
||||
ios->audioFormat.mReserved = 0;
|
||||
ios->audioFormat.mSampleRate = ios->format.nSamplesPerSec;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static void ios_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
|
||||
const AudioTimeStamp *inStartTime, UInt32 inNumPackets,
|
||||
const AudioStreamPacketDescription *inPacketDesc)
|
||||
{
|
||||
AudinIosDevice *ios = (AudinIosDevice *)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 = ios->receive(&ios->format, buffer, buffer_size, ios->user_data);
|
||||
|
||||
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
|
||||
|
||||
if (error)
|
||||
{
|
||||
WLog_ERR(TAG, "ios->receive failed with error %" PRIu32 "", error);
|
||||
SetLastError(ERROR_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
static UINT audin_ios_close(IAudinDevice *device)
|
||||
{
|
||||
UINT errCode = CHANNEL_RC_OK;
|
||||
char errString[1024];
|
||||
OSStatus devStat;
|
||||
AudinIosDevice *ios = (AudinIosDevice *)device;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (ios->isOpen)
|
||||
{
|
||||
devStat = AudioQueueStop(ios->audioQueue, true);
|
||||
|
||||
if (devStat != 0)
|
||||
{
|
||||
errCode = GetLastError();
|
||||
WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]",
|
||||
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
|
||||
}
|
||||
|
||||
ios->isOpen = false;
|
||||
}
|
||||
|
||||
if (ios->audioQueue)
|
||||
{
|
||||
devStat = AudioQueueDispose(ios->audioQueue, true);
|
||||
|
||||
if (devStat != 0)
|
||||
{
|
||||
errCode = GetLastError();
|
||||
WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]",
|
||||
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
|
||||
}
|
||||
|
||||
ios->audioQueue = NULL;
|
||||
}
|
||||
|
||||
ios->receive = NULL;
|
||||
ios->user_data = NULL;
|
||||
return errCode;
|
||||
}
|
||||
|
||||
static UINT audin_ios_open(IAudinDevice *device, AudinReceive receive, void *user_data)
|
||||
{
|
||||
AudinIosDevice *ios = (AudinIosDevice *)device;
|
||||
DWORD errCode;
|
||||
char errString[1024];
|
||||
OSStatus devStat;
|
||||
|
||||
ios->receive = receive;
|
||||
ios->user_data = user_data;
|
||||
devStat = AudioQueueNewInput(&(ios->audioFormat), ios_audio_queue_input_cb, ios, NULL,
|
||||
kCFRunLoopCommonModes, 0, &(ios->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 (size_t index = 0; index < IOS_AUDIO_QUEUE_NUM_BUFFERS; index++)
|
||||
{
|
||||
devStat = AudioQueueAllocateBuffer(ios->audioQueue,
|
||||
ios->FramesPerPacket * 2 * ios->format.nChannels,
|
||||
&ios->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(ios->audioQueue, ios->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(ios->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;
|
||||
}
|
||||
|
||||
ios->isOpen = true;
|
||||
return CHANNEL_RC_OK;
|
||||
err_out:
|
||||
audin_ios_close(device);
|
||||
return CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
}
|
||||
|
||||
static UINT audin_ios_free(IAudinDevice *device)
|
||||
{
|
||||
AudinIosDevice *ios = (AudinIosDevice *)device;
|
||||
int error;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if ((error = audin_ios_close(device)))
|
||||
{
|
||||
WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error);
|
||||
}
|
||||
|
||||
free(ios);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
DWORD errCode;
|
||||
char errString[1024];
|
||||
const ADDIN_ARGV *args;
|
||||
AudinIosDevice *ios;
|
||||
UINT error;
|
||||
ios = (AudinIosDevice *)calloc(1, sizeof(AudinIosDevice));
|
||||
|
||||
if (!ios)
|
||||
{
|
||||
errCode = GetLastError();
|
||||
WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]",
|
||||
winpr_strerror(errCode, errString, sizeof(errString)), errCode);
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
ios->iface.Open = audin_ios_open;
|
||||
ios->iface.FormatSupported = audin_ios_format_supported;
|
||||
ios->iface.SetFormat = audin_ios_set_format;
|
||||
ios->iface.Close = audin_ios_close;
|
||||
ios->iface.Free = audin_ios_free;
|
||||
ios->rdpcontext = pEntryPoints->rdpcontext;
|
||||
ios->dev_unit = -1;
|
||||
args = pEntryPoints->args;
|
||||
|
||||
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)ios)))
|
||||
{
|
||||
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error);
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
error_out:
|
||||
free(ios);
|
||||
return error;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${AVFOUNDATION}
|
||||
${CORE_AUDIO}
|
||||
${AUDIO_TOOL}
|
||||
${APP_SERVICES}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${MAC_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
@ -1,463 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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
|
||||
{
|
||||
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;
|
||||
|
||||
if (format->nChannels != 2)
|
||||
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;
|
||||
|
||||
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 (size_t 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, const ADDIN_ARGV *args)
|
||||
{
|
||||
DWORD errCode;
|
||||
char errString[1024];
|
||||
int status;
|
||||
char *str_num, *eptr;
|
||||
DWORD flags;
|
||||
const 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;
|
||||
}
|
||||
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
DWORD errCode;
|
||||
char errString[1024];
|
||||
const 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;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
# 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" "")
|
||||
|
||||
find_package(OpenSLES REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
opensl_io.c
|
||||
audin_opensl_es.c
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${OpenSLES_LIBRARIES}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${OpenSLES_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
@ -1,336 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include <winpr/assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <freerdp/freerdp.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
|
||||
{
|
||||
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, "Invalid arguments context=%p, data=%p", 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);
|
||||
WINPR_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%04" 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);
|
||||
WINPR_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, const 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const 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;
|
||||
}
|
@ -1,388 +0,0 @@
|
||||
/*
|
||||
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 <winpr/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:
|
||||
WINPR_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;
|
||||
WINPR_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
|
||||
WINPR_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);
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
goto end_recopen;
|
||||
|
||||
// realize the audio recorder
|
||||
result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE);
|
||||
WINPR_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));
|
||||
WINPR_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));
|
||||
WINPR_ASSERT(!result);
|
||||
|
||||
if (SL_RESULT_SUCCESS != result)
|
||||
goto end_recopen;
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*p->recorderBufferQueue)
|
||||
->RegisterCallback(p->recorderBufferQueue, bqRecorderCallback, p);
|
||||
WINPR_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);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
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 */
|
@ -1,42 +0,0 @@
|
||||
# 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" "")
|
||||
|
||||
find_package(OSS REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_oss.c
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${OSS_LIBRARIES}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
include_directories(SYSTEM ${OSS_INCLUDE_DIRS})
|
||||
cleaning_configure_file(
|
||||
${CMAKE_SOURCE_DIR}/cmake/oss-includes.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/oss-includes.h
|
||||
@ONLY
|
||||
)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
@ -1,486 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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>
|
||||
#include <oss-includes.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IAudinDevice iface;
|
||||
|
||||
HANDLE thread;
|
||||
HANDLE stopEvent;
|
||||
|
||||
AUDIO_FORMAT format;
|
||||
UINT32 FramesPerPacket;
|
||||
int dev_unit;
|
||||
|
||||
AudinReceive receive;
|
||||
void* user_data;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
} AudinOSSDevice;
|
||||
|
||||
static void OSS_LOG_ERR(const char* _text, int _error)
|
||||
{
|
||||
if ((_error) != 0)
|
||||
{
|
||||
char buffer[256] = { 0 };
|
||||
WLog_ERR(TAG, "%s: %i - %s\n", (_text), (_error),
|
||||
winpr_strerror((_error), buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
int mixer_handle = 0;
|
||||
BYTE* buffer = NULL;
|
||||
unsigned long tmp = 0;
|
||||
size_t buffer_size = 0;
|
||||
AudinOSSDevice* oss = (AudinOSSDevice*)arg;
|
||||
UINT error = 0;
|
||||
DWORD status = 0;
|
||||
|
||||
if (oss == NULL)
|
||||
{
|
||||
error = ERROR_INVALID_PARAMETER;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (oss->dev_unit != -1)
|
||||
{
|
||||
(void)sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit);
|
||||
(void)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 =
|
||||
(1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull));
|
||||
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 = -1;
|
||||
status = WaitForSingleObject(oss->stopEvent, 0);
|
||||
|
||||
if (status == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (status == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
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!");
|
||||
(void)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 = 0;
|
||||
AudinOSSDevice* oss = (AudinOSSDevice*)device;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (oss->stopEvent != NULL)
|
||||
{
|
||||
(void)SetEvent(oss->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
(void)CloseHandle(oss->stopEvent);
|
||||
oss->stopEvent = NULL;
|
||||
(void)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 = 0;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if ((error = audin_oss_close(device)))
|
||||
{
|
||||
WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", 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, const ADDIN_ARGV* args)
|
||||
{
|
||||
int status = 0;
|
||||
char* str_num = NULL;
|
||||
char* eptr = NULL;
|
||||
DWORD flags = 0;
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = NULL;
|
||||
AudinOSSDevice* oss = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const ADDIN_ARGV* args = NULL;
|
||||
AudinOSSDevice* oss = NULL;
|
||||
UINT error = 0;
|
||||
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;
|
||||
}
|
@ -17,19 +17,25 @@
|
||||
|
||||
define_channel_client_subsystem("audin" "pulse" "")
|
||||
|
||||
find_package(PulseAudio REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_pulse.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${PULSEAUDIO_LIBRARY}
|
||||
${PULSEAUDIO_MAINLOOP_LIBRARY}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR})
|
||||
include_directories(${PULSE_INCLUDE_DIR})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-utils)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${PULSE_LIBRARY})
|
||||
|
||||
target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH})
|
||||
endif()
|
||||
|
@ -3,8 +3,6 @@
|
||||
* 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.
|
||||
@ -19,7 +17,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -27,19 +27,16 @@
|
||||
|
||||
#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/freerdp.h>
|
||||
#include <freerdp/codec/audio.h>
|
||||
#include <freerdp/client/audin.h>
|
||||
#include <freerdp/codec/dsp.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
typedef struct
|
||||
typedef struct _AudinPulseDevice
|
||||
{
|
||||
IAudinDevice iface;
|
||||
|
||||
@ -49,414 +46,423 @@ typedef struct
|
||||
pa_context* context;
|
||||
pa_sample_spec sample_spec;
|
||||
pa_stream* stream;
|
||||
AUDIO_FORMAT format;
|
||||
int format;
|
||||
int block_size;
|
||||
|
||||
size_t bytes_per_frame;
|
||||
size_t buffer_frames;
|
||||
FREERDP_DSP_CONTEXT* dsp_context;
|
||||
|
||||
int bytes_per_frame;
|
||||
BYTE* buffer;
|
||||
int 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)
|
||||
{
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
|
||||
pa_context_state_t state = pa_context_get_state(context);
|
||||
pa_context_state_t state;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) userdata;
|
||||
|
||||
WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state));
|
||||
state = pa_context_get_state(context);
|
||||
switch (state)
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
DEBUG_DVC("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);
|
||||
DEBUG_DVC("state %d", (int)state);
|
||||
pa_threaded_mainloop_signal (pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_DVC("state %d", (int)state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_pulse_connect(IAudinDevice* device)
|
||||
static BOOL audin_pulse_connect(IAudinDevice* device)
|
||||
{
|
||||
pa_context_state_t state = PA_CONTEXT_FAILED;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
pa_context_state_t state;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
if (!pulse->context)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
return FALSE;
|
||||
|
||||
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;
|
||||
DEBUG_WARN("pa_context_connect failed (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(pulse->mainloop);
|
||||
|
||||
if (pa_threaded_mainloop_start(pulse->mainloop) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
DEBUG_WARN("pa_threaded_mainloop_start failed (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
state = pa_context_get_state(pulse->context);
|
||||
|
||||
if (state == PA_CONTEXT_READY)
|
||||
break;
|
||||
|
||||
if (!PA_CONTEXT_IS_GOOD(state))
|
||||
{
|
||||
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;
|
||||
DEBUG_WARN("bad context state (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
break;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(pulse->mainloop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pulse->mainloop);
|
||||
WLog_Print(pulse->log, WLOG_DEBUG, "connected");
|
||||
return CHANNEL_RC_OK;
|
||||
if (state == PA_CONTEXT_READY)
|
||||
{
|
||||
DEBUG_DVC("connected");
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pa_context_disconnect(pulse->context);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_pulse_free(IAudinDevice* device)
|
||||
static void audin_pulse_free(IAudinDevice* device)
|
||||
{
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
DEBUG_DVC("");
|
||||
|
||||
if (!pulse)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
freerdp_dsp_context_free(pulse->dsp_context);
|
||||
free(pulse);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
|
||||
static BOOL audin_pulse_format_supported(IAudinDevice* device, audinFormat* format)
|
||||
{
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
|
||||
if (!pulse || !format)
|
||||
return FALSE;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
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))
|
||||
case 1: /* 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;
|
||||
}
|
||||
case 6: /* A-LAW */
|
||||
case 7: /* U-LAW */
|
||||
if (format->cbSize == 0 &&
|
||||
(format->nSamplesPerSec <= PA_RATE_MAX) &&
|
||||
(format->wBitsPerSample == 8) &&
|
||||
(format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x11: /* IMA ADPCM */
|
||||
if ((format->nSamplesPerSec <= PA_RATE_MAX) &&
|
||||
(format->wBitsPerSample == 4) &&
|
||||
(format->nChannels == 1 || format->nChannels == 2))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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)
|
||||
static void audin_pulse_set_format(IAudinDevice* device, audinFormat* format, UINT32 FramesPerPacket)
|
||||
{
|
||||
int bs;
|
||||
pa_sample_spec sample_spec = { 0 };
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
|
||||
if (!pulse || !format)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
if (!pulse->context)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
return;
|
||||
|
||||
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 */
|
||||
case 1: /* 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;
|
||||
|
||||
default:
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
case 6: /* A-LAW */
|
||||
sample_spec.format = PA_SAMPLE_ALAW;
|
||||
break;
|
||||
|
||||
case 7: /* U-LAW */
|
||||
sample_spec.format = PA_SAMPLE_ULAW;
|
||||
break;
|
||||
|
||||
case 0x11: /* IMA ADPCM */
|
||||
sample_spec.format = PA_SAMPLE_S16LE;
|
||||
bs = (format->nBlockAlign - 4 * format->nChannels) * 4;
|
||||
pulse->frames_per_packet = (pulse->frames_per_packet * format->nChannels * 2 /
|
||||
bs + 1) * bs / (format->nChannels * 2);
|
||||
DEBUG_DVC("aligned FramesPerPacket=%d",
|
||||
pulse->frames_per_packet);
|
||||
break;
|
||||
}
|
||||
|
||||
pulse->sample_spec = sample_spec;
|
||||
pulse->format = *format;
|
||||
return CHANNEL_RC_OK;
|
||||
pulse->format = format->wFormatTag;
|
||||
pulse->block_size = format->nBlockAlign;
|
||||
}
|
||||
|
||||
static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata)
|
||||
{
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)userdata;
|
||||
WINPR_ASSERT(pulse);
|
||||
pa_stream_state_t state;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) userdata;
|
||||
|
||||
pa_stream_state_t state = pa_stream_get_state(stream);
|
||||
|
||||
WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state));
|
||||
state = pa_stream_get_state(stream);
|
||||
switch (state)
|
||||
{
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
DEBUG_DVC("PA_STREAM_READY");
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
DEBUG_DVC("state %d", (int)state);
|
||||
pa_threaded_mainloop_signal(pulse->mainloop, 0);
|
||||
break;
|
||||
|
||||
case PA_STREAM_UNCONNECTED:
|
||||
case PA_STREAM_CREATING:
|
||||
default:
|
||||
DEBUG_DVC("state %d", (int)state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata)
|
||||
{
|
||||
const void* data = NULL;
|
||||
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);
|
||||
int frames;
|
||||
int cframes;
|
||||
BOOL ret;
|
||||
const void* data;
|
||||
const BYTE* src;
|
||||
int encoded_size;
|
||||
BYTE* encoded_data;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) userdata;
|
||||
|
||||
if (error && pulse->rdpcontext)
|
||||
setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error");
|
||||
/* There is a race condition here where we may receive this callback
|
||||
* before the buffer has been set up in the main code. It's probably
|
||||
* possible to fix with additional locking, but it's easier just to
|
||||
* ignore input until the buffer is ready.
|
||||
*/
|
||||
if (pulse->buffer == NULL)
|
||||
{
|
||||
/* fprintf(stderr, "%s: ignoring input, pulse buffer not ready.\n", __func__); */
|
||||
return;
|
||||
}
|
||||
|
||||
pa_stream_peek(stream, &data, &length);
|
||||
frames = length / pulse->bytes_per_frame;
|
||||
|
||||
DEBUG_DVC("length %d frames %d", (int) length, frames);
|
||||
|
||||
src = (const BYTE*) data;
|
||||
while (frames > 0)
|
||||
{
|
||||
cframes = pulse->frames_per_packet - pulse->buffer_frames;
|
||||
if (cframes > frames)
|
||||
cframes = frames;
|
||||
memcpy(pulse->buffer + pulse->buffer_frames * pulse->bytes_per_frame,
|
||||
src, cframes * pulse->bytes_per_frame);
|
||||
pulse->buffer_frames += cframes;
|
||||
if (pulse->buffer_frames >= pulse->frames_per_packet)
|
||||
{
|
||||
if (pulse->format == 0x11)
|
||||
{
|
||||
pulse->dsp_context->encode_ima_adpcm(pulse->dsp_context,
|
||||
pulse->buffer, pulse->buffer_frames * pulse->bytes_per_frame,
|
||||
pulse->sample_spec.channels, pulse->block_size);
|
||||
encoded_data = pulse->dsp_context->adpcm_buffer;
|
||||
encoded_size = pulse->dsp_context->adpcm_size;
|
||||
DEBUG_DVC("encoded %d to %d",
|
||||
pulse->buffer_frames * pulse->bytes_per_frame, encoded_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded_data = pulse->buffer;
|
||||
encoded_size = pulse->buffer_frames * pulse->bytes_per_frame;
|
||||
}
|
||||
|
||||
ret = pulse->receive(encoded_data, encoded_size, pulse->user_data);
|
||||
pulse->buffer_frames = 0;
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
src += cframes * pulse->bytes_per_frame;
|
||||
frames -= cframes;
|
||||
}
|
||||
|
||||
pa_stream_drop(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_pulse_close(IAudinDevice* device)
|
||||
|
||||
static void audin_pulse_close(IAudinDevice* device)
|
||||
{
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
if (!pulse)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
if (!pulse->context || !pulse->stream)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
DEBUG_DVC("");
|
||||
|
||||
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;
|
||||
if (pulse->buffer)
|
||||
{
|
||||
free(pulse->buffer);
|
||||
pulse->buffer = NULL;
|
||||
pulse->buffer_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
|
||||
static void audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data)
|
||||
{
|
||||
pa_stream_state_t state = PA_STREAM_FAILED;
|
||||
pa_stream_state_t state;
|
||||
pa_buffer_attr buffer_attr = { 0 };
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*)device;
|
||||
|
||||
if (!pulse || !receive || !user_data)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
if (!pulse->context)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
return;
|
||||
if (!pulse->sample_spec.rate || pulse->stream)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
return;
|
||||
|
||||
DEBUG_DVC("");
|
||||
|
||||
pulse->buffer = NULL;
|
||||
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);
|
||||
|
||||
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);
|
||||
DEBUG_DVC("pa_stream_new failed (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
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 */
|
||||
const size_t frag = pulse->bytes_per_frame * pulse->frames_per_packet;
|
||||
WINPR_ASSERT(frag <= UINT32_MAX);
|
||||
buffer_attr.fragsize = (uint32_t)frag;
|
||||
|
||||
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)
|
||||
buffer_attr.fragsize = pa_usec_to_bytes(500000, &pulse->sample_spec);
|
||||
if (pa_stream_connect_record(pulse->stream,
|
||||
pulse->device_name[0] ? pulse->device_name : NULL,
|
||||
&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);
|
||||
DEBUG_WARN("pa_stream_connect_playback failed (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
return;
|
||||
}
|
||||
|
||||
while (pulse->stream)
|
||||
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);
|
||||
DEBUG_WARN("bad stream state (%d)",
|
||||
pa_context_errno(pulse->context));
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
if (state == PA_STREAM_READY)
|
||||
{
|
||||
freerdp_dsp_context_reset_adpcm(pulse->dsp_context);
|
||||
pulse->buffer = malloc(pulse->bytes_per_frame * pulse->frames_per_packet);
|
||||
ZeroMemory(pulse->buffer, pulse->bytes_per_frame * pulse->frames_per_packet);
|
||||
pulse->buffer_frames = 0;
|
||||
DEBUG_DVC("connected");
|
||||
}
|
||||
else
|
||||
{
|
||||
audin_pulse_close(device);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, const ADDIN_ARGV* args)
|
||||
COMMAND_LINE_ARGUMENT_A audin_pulse_args[] =
|
||||
{
|
||||
int status = 0;
|
||||
DWORD flags = 0;
|
||||
const COMMAND_LINE_ARGUMENT_A* arg = NULL;
|
||||
AudinPulseDevice* pulse = 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 } };
|
||||
{ "audio-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);
|
||||
static void audin_pulse_parse_addin_args(AudinPulseDevice* device, ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
AudinPulseDevice* pulse = (AudinPulseDevice*) device;
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON;
|
||||
|
||||
status = CommandLineParseArgumentsA(args->argc, (const char**) args->argv, audin_pulse_args, flags, pulse, NULL, NULL);
|
||||
|
||||
arg = audin_pulse_args;
|
||||
|
||||
@ -465,92 +471,72 @@ static UINT audin_pulse_parse_addin_args(AudinPulseDevice* device, const ADDIN_A
|
||||
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
|
||||
continue;
|
||||
|
||||
CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev")
|
||||
CommandLineSwitchStart(arg)
|
||||
|
||||
CommandLineSwitchCase(arg, "audio-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;
|
||||
CommandLineSwitchEnd(arg)
|
||||
}
|
||||
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
#ifdef STATIC_CHANNELS
|
||||
#define freerdp_audin_client_subsystem_entry pulse_freerdp_audin_client_subsystem_entry
|
||||
#endif
|
||||
|
||||
int freerdp_audin_client_subsystem_entry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)
|
||||
{
|
||||
const ADDIN_ARGV* args = NULL;
|
||||
AudinPulseDevice* pulse = NULL;
|
||||
UINT error = 0;
|
||||
pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice));
|
||||
ADDIN_ARGV* args;
|
||||
AudinPulseDevice* pulse;
|
||||
|
||||
if (!pulse)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
pulse = (AudinPulseDevice*) malloc(sizeof(AudinPulseDevice));
|
||||
ZeroMemory(pulse, sizeof(AudinPulseDevice));
|
||||
|
||||
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;
|
||||
}
|
||||
audin_pulse_parse_addin_args(pulse, args);
|
||||
|
||||
if (!pulse->device_name)
|
||||
pulse->device_name = _strdup("default");
|
||||
|
||||
pulse->dsp_context = freerdp_dsp_context_new();
|
||||
|
||||
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;
|
||||
DEBUG_WARN("pa_threaded_mainloop_new failed");
|
||||
audin_pulse_free((IAudinDevice*) pulse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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;
|
||||
DEBUG_WARN("pa_context_new failed");
|
||||
audin_pulse_free((IAudinDevice*) pulse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse);
|
||||
|
||||
if ((error = audin_pulse_connect(&pulse->iface)))
|
||||
if (!audin_pulse_connect((IAudinDevice*) pulse))
|
||||
{
|
||||
WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed");
|
||||
goto error_out;
|
||||
audin_pulse_free((IAudinDevice*) pulse);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface)))
|
||||
{
|
||||
WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!",
|
||||
error);
|
||||
goto error_out;
|
||||
}
|
||||
pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) pulse);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
error_out:
|
||||
audin_pulse_free(&pulse->iface);
|
||||
return error;
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
|
||||
# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT 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" "sndio" "")
|
||||
|
||||
find_package(SNDIO REQUIRED)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin_sndio.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
${SNDIO_LIBRARIES}
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
include_directories(SYSTEM ${SNDIO_INCLUDE_DIRS})
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
||||
|
@ -1,352 +0,0 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Audio Input Redirection Virtual Channel - sndio 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>
|
||||
* Copyright 2020 Ingo Feinerer <feinerer@logic.at>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include <sndio.h>
|
||||
|
||||
#include <winpr/cmdline.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/channels/rdpsnd.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IAudinDevice device;
|
||||
|
||||
HANDLE thread;
|
||||
HANDLE stopEvent;
|
||||
|
||||
AUDIO_FORMAT format;
|
||||
UINT32 FramesPerPacket;
|
||||
|
||||
AudinReceive receive;
|
||||
void* user_data;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
} AudinSndioDevice;
|
||||
|
||||
static BOOL audin_sndio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format)
|
||||
{
|
||||
if (device == NULL || format == NULL)
|
||||
return FALSE;
|
||||
|
||||
return (format->wFormatTag == WAVE_FORMAT_PCM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_sndio_set_format(IAudinDevice* device, AUDIO_FORMAT* format,
|
||||
UINT32 FramesPerPacket)
|
||||
{
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
|
||||
|
||||
if (device == NULL || format == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (format->wFormatTag != WAVE_FORMAT_PCM)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
sndio->format = *format;
|
||||
sndio->FramesPerPacket = FramesPerPacket;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static void* audin_sndio_thread_func(void* arg)
|
||||
{
|
||||
struct sio_hdl* hdl;
|
||||
struct sio_par par;
|
||||
BYTE* buffer = NULL;
|
||||
size_t n, nbytes;
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)arg;
|
||||
UINT error = 0;
|
||||
DWORD status;
|
||||
|
||||
if (arg == NULL)
|
||||
{
|
||||
error = ERROR_INVALID_PARAMETER;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
hdl = sio_open(SIO_DEVANY, SIO_REC, 0);
|
||||
if (hdl == NULL)
|
||||
{
|
||||
WLog_ERR(TAG, "could not open audio device");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
sio_initpar(&par);
|
||||
par.bits = sndio->format.wBitsPerSample;
|
||||
par.rchan = sndio->format.nChannels;
|
||||
par.rate = sndio->format.nSamplesPerSec;
|
||||
if (!sio_setpar(hdl, &par))
|
||||
{
|
||||
WLog_ERR(TAG, "could not set audio parameters");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
if (!sio_getpar(hdl, &par))
|
||||
{
|
||||
WLog_ERR(TAG, "could not get audio parameters");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (!sio_start(hdl))
|
||||
{
|
||||
WLog_ERR(TAG, "could not start audio device");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
nbytes =
|
||||
(sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8));
|
||||
buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE));
|
||||
|
||||
if (buffer == NULL)
|
||||
{
|
||||
error = ERROR_NOT_ENOUGH_MEMORY;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
status = WaitForSingleObject(sndio->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;
|
||||
|
||||
n = sio_read(hdl, buffer, nbytes);
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
WLog_ERR(TAG, "could not read");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n < nbytes)
|
||||
continue;
|
||||
|
||||
if ((error = sndio->receive(&sndio->format, buffer, nbytes, sndio->user_data)))
|
||||
{
|
||||
WLog_ERR(TAG, "sndio->receive failed with error %" PRIu32 "", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err_out:
|
||||
if (error && sndio->rdpcontext)
|
||||
setChannelError(sndio->rdpcontext, error, "audin_sndio_thread_func reported an error");
|
||||
|
||||
if (hdl != NULL)
|
||||
{
|
||||
WLog_INFO(TAG, "sio_close");
|
||||
sio_stop(hdl);
|
||||
sio_close(hdl);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
ExitThread(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_sndio_open(IAudinDevice* device, AudinReceive receive, void* user_data)
|
||||
{
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
|
||||
sndio->receive = receive;
|
||||
sndio->user_data = user_data;
|
||||
|
||||
if (!(sndio->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateEvent failed");
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (!(sndio->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)audin_sndio_thread_func,
|
||||
sndio, 0, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateThread failed");
|
||||
(void)CloseHandle(sndio->stopEvent);
|
||||
sndio->stopEvent = NULL;
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_sndio_close(IAudinDevice* device)
|
||||
{
|
||||
UINT error;
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (sndio->stopEvent != NULL)
|
||||
{
|
||||
(void)SetEvent(sndio->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(sndio->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
(void)CloseHandle(sndio->stopEvent);
|
||||
sndio->stopEvent = NULL;
|
||||
(void)CloseHandle(sndio->thread);
|
||||
sndio->thread = NULL;
|
||||
}
|
||||
|
||||
sndio->receive = NULL;
|
||||
sndio->user_data = NULL;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_sndio_free(IAudinDevice* device)
|
||||
{
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
|
||||
int error;
|
||||
|
||||
if (device == NULL)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if ((error = audin_sndio_close(device)))
|
||||
{
|
||||
WLog_ERR(TAG, "audin_sndio_close failed with error code %d", error);
|
||||
}
|
||||
|
||||
free(sndio);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT audin_sndio_parse_addin_args(AudinSndioDevice* device, ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
COMMAND_LINE_ARGUMENT_A* arg;
|
||||
AudinSndioDevice* sndio = (AudinSndioDevice*)device;
|
||||
COMMAND_LINE_ARGUMENT_A audin_sndio_args[] = { { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } };
|
||||
flags =
|
||||
COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
|
||||
status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, audin_sndio_args,
|
||||
flags, sndio, NULL, NULL);
|
||||
|
||||
if (status < 0)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
arg = audin_sndio_args;
|
||||
|
||||
do
|
||||
{
|
||||
if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT))
|
||||
continue;
|
||||
|
||||
CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg)
|
||||
} while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE sndio_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
ADDIN_ARGV* args;
|
||||
AudinSndioDevice* sndio;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
sndio = (AudinSndioDevice*)calloc(1, sizeof(AudinSndioDevice));
|
||||
|
||||
if (sndio == NULL)
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
|
||||
sndio->device.Open = audin_sndio_open;
|
||||
sndio->device.FormatSupported = audin_sndio_format_supported;
|
||||
sndio->device.SetFormat = audin_sndio_set_format;
|
||||
sndio->device.Close = audin_sndio_close;
|
||||
sndio->device.Free = audin_sndio_free;
|
||||
sndio->rdpcontext = pEntryPoints->rdpcontext;
|
||||
args = pEntryPoints->args;
|
||||
|
||||
if (args->argc > 1)
|
||||
{
|
||||
ret = audin_sndio_parse_addin_args(sndio, args);
|
||||
|
||||
if (ret != CHANNEL_RC_OK)
|
||||
{
|
||||
WLog_ERR(TAG, "error parsing arguments");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)sndio)))
|
||||
{
|
||||
WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return ret;
|
||||
error:
|
||||
audin_sndio_free(&sndio->device);
|
||||
return ret;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
# 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)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
freerdp
|
||||
winmm.lib
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "")
|
@ -1,566 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/wtsapi.h>
|
||||
#include <winpr/cmdline.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/client/audin.h>
|
||||
|
||||
#include "audin_main.h"
|
||||
|
||||
/* fix missing definitions in mingw */
|
||||
#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE
|
||||
#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
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 = NULL;
|
||||
int size = 0;
|
||||
WAVEHDR waveHdr[4] = { 0 };
|
||||
DWORD status = 0;
|
||||
MMRESULT rc = 0;
|
||||
|
||||
if (!winmm->hWaveIn)
|
||||
{
|
||||
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 (int 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 (int 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)
|
||||
{
|
||||
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
|
||||
|
||||
if (!winmm)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
for (UINT32 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;
|
||||
|
||||
(void)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;
|
||||
}
|
||||
|
||||
(void)CloseHandle(winmm->thread);
|
||||
(void)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)
|
||||
{
|
||||
AudinWinmmDevice* winmm = (AudinWinmmDevice*)device;
|
||||
|
||||
if (!winmm || !format)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
winmm->frames_per_packet = FramesPerPacket;
|
||||
|
||||
for (UINT32 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;
|
||||
|
||||
if (format->nChannels != 1)
|
||||
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!");
|
||||
(void)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, const ADDIN_ARGV* args)
|
||||
{
|
||||
int status;
|
||||
DWORD flags;
|
||||
const 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_audin_client_subsystem_entry(
|
||||
PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints))
|
||||
{
|
||||
const 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;
|
||||
}
|
@ -18,10 +18,15 @@
|
||||
define_channel_server("audin")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
audin.c
|
||||
)
|
||||
audin.c)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp
|
||||
)
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-codec freerdp-utils)
|
||||
|
||||
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server")
|
||||
|
File diff suppressed because it is too large
Load Diff
2
channels/client/.gitignore
vendored
Normal file
2
channels/client/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
tables.c
|
||||
|
@ -2,8 +2,6 @@
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
# Copyright 2024 Armin Novak <anovak@thincast.com>
|
||||
# Copyright 2024 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.
|
||||
@ -25,79 +23,50 @@ set(${MODULE_PREFIX}_SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tables.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/addin.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/addin.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/generic_dynvc.c)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/init.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/init.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/open.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/open.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/channels.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/channels.h)
|
||||
|
||||
if(CHANNEL_STATIC_CLIENT_ENTRIES)
|
||||
list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES)
|
||||
list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES)
|
||||
endif()
|
||||
|
||||
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})
|
||||
list(APPEND ${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 VCAPITYPE ${ENTRY_POINT_NAME}(IDRDYNVC_ENTRY_POINTS* pEntryPoints);")
|
||||
elseif(${ENTRY} MATCHES "DeviceServiceEntry$")
|
||||
set(ENTRY_POINT_IMPORT "extern UINT VCAPITYPE ${ENTRY_POINT_NAME}(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);")
|
||||
else()
|
||||
set(ENTRY_POINT_IMPORT "extern UINT VCAPITYPE ${ENTRY_POINT_NAME}(void);")
|
||||
endif()
|
||||
|
||||
string(APPEND ${STATIC_ENTRY}_IMPORTS "\n${ENTRY_POINT_IMPORT}")
|
||||
string(APPEND ${STATIC_ENTRY}_TABLE "\n\t{ \"${STATIC_MODULE_CHANNEL}\", ${ENTRY_POINT_NAME} },")
|
||||
endif()
|
||||
endforeach()
|
||||
if(${${STATIC_MODULE}_CLIENT_ENTRY} STREQUAL ${STATIC_ENTRY})
|
||||
set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME})
|
||||
set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL})
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME})
|
||||
|
||||
set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${${STATIC_MODULE}_CLIENT_ENTRY}")
|
||||
set(ENTRY_POINT_IMPORT "extern void ${ENTRY_POINT_NAME}();")
|
||||
set(${STATIC_ENTRY}_IMPORTS "${${STATIC_ENTRY}_IMPORTS}\n${ENTRY_POINT_IMPORT}")
|
||||
set(${STATIC_ENTRY}_TABLE "${${STATIC_ENTRY}_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", ${ENTRY_POINT_NAME} },")
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "\nextern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[];\n")
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[] =\n{")
|
||||
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}")
|
||||
if(${STATIC_ENTRY} STREQUAL "VirtualChannelEntry")
|
||||
set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_VC")
|
||||
set(CLIENT_STATIC_ENTRY_INITIALIZER ".csevc")
|
||||
elseif(${STATIC_ENTRY} STREQUAL "VirtualChannelEntryEx")
|
||||
set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_VCEX")
|
||||
set(CLIENT_STATIC_ENTRY_INITIALIZER ".csevcex")
|
||||
elseif(${STATIC_ENTRY} MATCHES "DVCPluginEntry$")
|
||||
set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_DVC")
|
||||
set(CLIENT_STATIC_ENTRY_INITIALIZER ".csedvc")
|
||||
elseif(${STATIC_ENTRY} MATCHES "DeviceServiceEntry$")
|
||||
set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_DSE")
|
||||
set(CLIENT_STATIC_ENTRY_INITIALIZER ".csedse")
|
||||
else()
|
||||
set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY")
|
||||
set(CLIENT_STATIC_ENTRY_INITIALIZER ".cse")
|
||||
endif()
|
||||
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES "\nextern const ${CLIENT_STATIC_ENTRY_TYPE} CLIENT_${STATIC_ENTRY}_TABLE[];\n")
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES "const ${CLIENT_STATIC_ENTRY_TYPE} CLIENT_${STATIC_ENTRY}_TABLE[] =\n{")
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES "\n${${STATIC_ENTRY}_TABLE}")
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES "\n\t{ NULL, NULL }\n};")
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "\n\t{ \"${STATIC_ENTRY}\", { ${CLIENT_STATIC_ENTRY_INITIALIZER} = CLIENT_${STATIC_ENTRY}_TABLE } },")
|
||||
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()
|
||||
|
||||
string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "\n\t{ NULL, { .cse = NULL } }\n};")
|
||||
|
||||
set(CLIENT_STATIC_ADDIN_TABLE "extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[];\n")
|
||||
string(APPEND CLIENT_STATIC_ADDIN_TABLE "const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[] =\n{")
|
||||
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 "extern const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[];\nconst STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{")
|
||||
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 "")
|
||||
@ -112,40 +81,34 @@ foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES})
|
||||
endif()
|
||||
string(LENGTH "${STATIC_SUBSYSTEM_TYPE}" _type_length)
|
||||
set(SUBSYSTEM_MODULE_NAME "${STATIC_MODULE_NAME}-${STATIC_SUBSYSTEM}")
|
||||
list(APPEND ${MODULE_PREFIX}_LIBS ${SUBSYSTEM_MODULE_NAME})
|
||||
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()
|
||||
string(APPEND SUBSYSTEM_TABLE "\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} },")
|
||||
set(SUBSYSTEM_IMPORT "extern UINT VCAPITYPE ${STATIC_SUBSYSTEM_ENTRY}(void*);")
|
||||
string(APPEND CLIENT_STATIC_SUBSYSTEM_IMPORTS "\n${SUBSYSTEM_IMPORT}")
|
||||
endforeach()
|
||||
string(APPEND SUBSYSTEM_TABLE "\n\t{ NULL, NULL, NULL }\n};")
|
||||
string(APPEND CLIENT_STATIC_SUBSYSTEM_TABLES "\n${SUBSYSTEM_TABLE}")
|
||||
|
||||
foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY})
|
||||
set (ENTRY_POINT_NAME ${STATIC_MODULE_CHANNEL}_${ENTRY})
|
||||
if(${ENTRY} STREQUAL "VirtualChannelEntry")
|
||||
set(ENTRY_INITIALIZER ".csevc")
|
||||
elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx")
|
||||
set(ENTRY_INITIALIZER ".csevcex")
|
||||
elseif(${ENTRY} MATCHES "DVCPluginEntry$")
|
||||
set(ENTRY_INITIALIZER ".csedvc")
|
||||
elseif(${ENTRY} MATCHES "DeviceServiceEntry$")
|
||||
set(ENTRY_INITIALIZER ".csedse")
|
||||
else()
|
||||
set(ENTRY_INITIALIZER ".cse")
|
||||
endif()
|
||||
string(APPEND CLIENT_STATIC_ADDIN_TABLE "\n\t{ \"${STATIC_MODULE_CHANNEL}\", \"${ENTRY}\", { ${ENTRY_INITIALIZER} = ${ENTRY_POINT_NAME} }, ${SUBSYSTEM_TABLE_NAME} },")
|
||||
set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} },")
|
||||
set(SUBSYSTEM_IMPORT "extern void ${STATIC_SUBSYSTEM_ENTRY}();")
|
||||
set(CLIENT_STATIC_SUBSYSTEM_IMPORTS "${CLIENT_STATIC_SUBSYSTEM_IMPORTS}\n${SUBSYSTEM_IMPORT}")
|
||||
endforeach()
|
||||
set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ NULL, NULL, NULL }\n};")
|
||||
set(CLIENT_STATIC_SUBSYSTEM_TABLES "${CLIENT_STATIC_SUBSYSTEM_TABLES}\n${SUBSYSTEM_TABLE}")
|
||||
set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${${STATIC_MODULE}_CLIENT_ENTRY}")
|
||||
set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", ${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },")
|
||||
endforeach()
|
||||
string(APPEND CLIENT_STATIC_ADDIN_TABLE "\n\t{ NULL, NULL, { .cse = NULL }, NULL }\n};")
|
||||
set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, NULL, NULL }\n};")
|
||||
|
||||
cleaning_configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tables.c.in ${CMAKE_CURRENT_BINARY_DIR}/tables.c)
|
||||
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_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-utils)
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE winpr
|
||||
MODULES winpr-crt winpr-path winpr-file winpr-synch winpr-library winpr-interlocked)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE)
|
||||
set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE)
|
||||
|
@ -3,8 +3,6 @@
|
||||
* 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.
|
||||
@ -19,44 +17,41 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/path.h>
|
||||
#include <winpr/string.h>
|
||||
#include <winpr/file.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/library.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <freerdp/freerdp.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)
|
||||
void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table, const char* identifier)
|
||||
{
|
||||
size_t index = 0;
|
||||
const STATIC_ENTRY* pEntry = &table->table.cse[index++];
|
||||
int index = 0;
|
||||
STATIC_ENTRY* pEntry;
|
||||
|
||||
pEntry = (STATIC_ENTRY*) &table->table[index++];
|
||||
|
||||
while (pEntry->entry != NULL)
|
||||
{
|
||||
static_entry_fn_t fkt = pEntry->entry;
|
||||
if (strcmp(pEntry->name, identifier) == 0)
|
||||
return WINPR_FUNC_PTR_CAST(fkt, void*);
|
||||
{
|
||||
return (void*) pEntry->entry;
|
||||
}
|
||||
|
||||
pEntry = &table->table.cse[index++];
|
||||
pEntry = (STATIC_ENTRY*) &table->table[index++];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -64,17 +59,19 @@ static void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABL
|
||||
|
||||
void* freerdp_channels_client_find_static_entry(const char* name, const char* identifier)
|
||||
{
|
||||
size_t index = 0;
|
||||
const STATIC_ENTRY_TABLE* pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++];
|
||||
int index = 0;
|
||||
STATIC_ENTRY_TABLE* pEntry;
|
||||
|
||||
while (pEntry->table.cse != NULL)
|
||||
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 = &CLIENT_STATIC_ENTRY_TABLES[index++];
|
||||
pEntry = (STATIC_ENTRY_TABLE*) &CLIENT_STATIC_ENTRY_TABLES[index++];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -82,337 +79,203 @@ void* freerdp_channels_client_find_static_entry(const char* name, const char* id
|
||||
|
||||
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)
|
||||
FREERDP_ADDIN** freerdp_channels_list_client_static_addins(LPSTR pszName, LPSTR pszSubsystem, LPSTR pszType, DWORD dwFlags)
|
||||
{
|
||||
DWORD nAddins = 0;
|
||||
int i, j;
|
||||
DWORD nAddins;
|
||||
FREERDP_ADDIN* pAddin;
|
||||
FREERDP_ADDIN** ppAddins = NULL;
|
||||
const STATIC_SUBSYSTEM_ENTRY* subsystems = 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 = (FREERDP_ADDIN**) malloc(sizeof(FREERDP_ADDIN*) * 128);
|
||||
ppAddins[nAddins] = NULL;
|
||||
|
||||
for (size_t i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++)
|
||||
for (i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++)
|
||||
{
|
||||
FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN));
|
||||
const STATIC_ADDIN_TABLE* table = &CLIENT_STATIC_ADDIN_TABLE[i];
|
||||
if (!pAddin)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
goto error_out;
|
||||
}
|
||||
pAddin = (FREERDP_ADDIN*) malloc(sizeof(FREERDP_ADDIN));
|
||||
ZeroMemory(pAddin, sizeof(FREERDP_ADDIN));
|
||||
|
||||
strcpy(pAddin->cName, CLIENT_STATIC_ADDIN_TABLE[i].name);
|
||||
|
||||
(void)sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", table->name);
|
||||
pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_STATIC;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_NAME;
|
||||
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
subsystems = table->table;
|
||||
|
||||
for (size_t j = 0; subsystems[j].name != NULL; j++)
|
||||
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));
|
||||
pAddin = (FREERDP_ADDIN*) malloc(sizeof(FREERDP_ADDIN));
|
||||
ZeroMemory(pAddin, sizeof(FREERDP_ADDIN));
|
||||
|
||||
if (!pAddin)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
goto error_out;
|
||||
}
|
||||
strcpy(pAddin->cName, CLIENT_STATIC_ADDIN_TABLE[i].name);
|
||||
strcpy(pAddin->cSubsystem, subsystems[j].name);
|
||||
|
||||
(void)sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", table->name);
|
||||
(void)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;
|
||||
}
|
||||
}
|
||||
|
||||
ppAddins[nAddins] = NULL;
|
||||
|
||||
return ppAddins;
|
||||
error_out:
|
||||
freerdp_channels_addin_list_free(ppAddins);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HANDLE FindFirstFileUTF8(LPCSTR pszSearchPath, WIN32_FIND_DATAW* FindData)
|
||||
FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPSTR pszName, LPSTR pszSubsystem, LPSTR pszType, DWORD dwFlags)
|
||||
{
|
||||
HANDLE hdl = INVALID_HANDLE_VALUE;
|
||||
if (!pszSearchPath)
|
||||
return hdl;
|
||||
WCHAR* wpath = ConvertUtf8ToWCharAlloc(pszSearchPath, NULL);
|
||||
if (!wpath)
|
||||
return hdl;
|
||||
|
||||
hdl = FindFirstFileW(wpath, FindData);
|
||||
free(wpath);
|
||||
|
||||
return hdl;
|
||||
}
|
||||
|
||||
static FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPCSTR pszName, LPCSTR pszSubsystem,
|
||||
LPCSTR pszType, DWORD dwFlags)
|
||||
{
|
||||
int nDashes = 0;
|
||||
HANDLE hFind = NULL;
|
||||
DWORD nAddins = 0;
|
||||
LPSTR pszPattern = NULL;
|
||||
size_t cchPattern = 0;
|
||||
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 = NULL;
|
||||
LPSTR pszSearchPath = NULL;
|
||||
size_t cchSearchPath = 0;
|
||||
size_t cchAddinPath = 0;
|
||||
size_t cchInstallPrefix = 0;
|
||||
FREERDP_ADDIN** ppAddins = NULL;
|
||||
WIN32_FIND_DATAW FindData = { 0 };
|
||||
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);
|
||||
LPCSTR pszExtension;
|
||||
LPSTR pszSearchPath;
|
||||
size_t cchSearchPath;
|
||||
size_t cchAddinPath;
|
||||
size_t cchInstallPrefix;
|
||||
FREERDP_ADDIN** ppAddins;
|
||||
WIN32_FIND_DATAA FindData;
|
||||
|
||||
if (!pszPattern)
|
||||
{
|
||||
WLog_ERR(TAG, "malloc failed!");
|
||||
return NULL;
|
||||
}
|
||||
cchAddinPath = strlen(pszAddinPath);
|
||||
cchInstallPrefix = strlen(pszInstallPrefix);
|
||||
|
||||
pszExtension = PathGetSharedLibraryExtensionA(0);
|
||||
|
||||
cchPattern = 128 + strlen(pszExtension) + 2;
|
||||
pszPattern = (LPSTR) malloc(cchPattern + 1);
|
||||
|
||||
if (pszName && pszSubsystem && pszType)
|
||||
{
|
||||
(void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-%s-%s.%s",
|
||||
pszName, pszSubsystem, pszType, pszExtension);
|
||||
sprintf_s(pszPattern, cchPattern, "%s-client-%s-%s.%s", pszName, pszSubsystem, pszType, pszExtension);
|
||||
}
|
||||
else if (pszName && pszType)
|
||||
{
|
||||
(void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-?-%s.%s",
|
||||
pszName, pszType, pszExtension);
|
||||
sprintf_s(pszPattern, cchPattern, "%s-client-?-%s.%s", pszName, pszType, pszExtension);
|
||||
}
|
||||
else if (pszName)
|
||||
{
|
||||
(void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client*.%s",
|
||||
pszName, pszExtension);
|
||||
sprintf_s(pszPattern, cchPattern, "%s-client*.%s", pszName, pszExtension);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "?-client*.%s",
|
||||
pszExtension);
|
||||
sprintf_s(pszPattern, cchPattern, "?-client*.%s", pszExtension);
|
||||
}
|
||||
|
||||
cchPattern = strnlen(pszPattern, cchPattern);
|
||||
cchPattern = strlen(pszPattern);
|
||||
|
||||
cchSearchPath = cchInstallPrefix + cchAddinPath + cchPattern + 3;
|
||||
pszSearchPath = (LPSTR)calloc(cchSearchPath + 1, sizeof(char));
|
||||
|
||||
if (!pszSearchPath)
|
||||
{
|
||||
WLog_ERR(TAG, "malloc failed!");
|
||||
free(pszPattern);
|
||||
return NULL;
|
||||
}
|
||||
pszSearchPath = (LPSTR) malloc(cchSearchPath + 1);
|
||||
|
||||
CopyMemory(pszSearchPath, pszInstallPrefix, cchInstallPrefix);
|
||||
pszSearchPath[cchInstallPrefix] = '\0';
|
||||
const HRESULT hr1 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath);
|
||||
const HRESULT hr2 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern);
|
||||
free(pszPattern);
|
||||
|
||||
if (FAILED(hr1) || FAILED(hr2))
|
||||
{
|
||||
free(pszSearchPath);
|
||||
return NULL;
|
||||
}
|
||||
NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath);
|
||||
NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern);
|
||||
|
||||
hFind = FindFirstFileUTF8(pszSearchPath, &FindData);
|
||||
cchSearchPath = strlen(pszSearchPath);
|
||||
|
||||
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;
|
||||
}
|
||||
ppAddins = (FREERDP_ADDIN**) malloc(sizeof(FREERDP_ADDIN*) * 128);
|
||||
ppAddins[nAddins] = NULL;
|
||||
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
return ppAddins;
|
||||
|
||||
do
|
||||
{
|
||||
char* cFileName = NULL;
|
||||
BOOL used = FALSE;
|
||||
FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN));
|
||||
|
||||
if (!pAddin)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
cFileName =
|
||||
ConvertWCharNToUtf8Alloc(FindData.cFileName, ARRAYSIZE(FindData.cFileName), NULL);
|
||||
if (!cFileName)
|
||||
goto skip;
|
||||
char* p[5];
|
||||
FREERDP_ADDIN* pAddin;
|
||||
|
||||
nDashes = 0;
|
||||
for (size_t index = 0; cFileName[index]; index++)
|
||||
nDashes += (cFileName[index] == '-') ? 1 : 0;
|
||||
pAddin = (FREERDP_ADDIN*) malloc(sizeof(FREERDP_ADDIN));
|
||||
ZeroMemory(pAddin, sizeof(FREERDP_ADDIN));
|
||||
|
||||
for (index = 0; FindData.cFileName[index]; index++)
|
||||
nDashes += (FindData.cFileName[index] == '-') ? 1 : 0;
|
||||
|
||||
if (nDashes == 1)
|
||||
{
|
||||
size_t len = 0;
|
||||
char* p[2] = { 0 };
|
||||
/* <name>-client.<extension> */
|
||||
p[0] = cFileName;
|
||||
p[1] = strchr(p[0], '-');
|
||||
if (!p[1])
|
||||
goto skip;
|
||||
p[1] += 1;
|
||||
|
||||
len = (size_t)(p[1] - p[0]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
|
||||
p[0] = FindData.cFileName;
|
||||
p[1] = strchr(p[0], '-') + 1;
|
||||
|
||||
strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1);
|
||||
|
||||
pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_NAME;
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
|
||||
used = TRUE;
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
}
|
||||
else if (nDashes == 2)
|
||||
{
|
||||
size_t len = 0;
|
||||
char* p[4] = { 0 };
|
||||
/* <name>-client-<subsystem>.<extension> */
|
||||
p[0] = cFileName;
|
||||
p[1] = strchr(p[0], '-');
|
||||
if (!p[1])
|
||||
goto skip;
|
||||
p[1] += 1;
|
||||
p[2] = strchr(p[1], '-');
|
||||
if (!p[2])
|
||||
goto skip;
|
||||
p[2] += 1;
|
||||
p[3] = strchr(p[2], '.');
|
||||
if (!p[3])
|
||||
goto skip;
|
||||
p[3] += 1;
|
||||
|
||||
len = (size_t)(p[1] - p[0]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
|
||||
p[0] = FindData.cFileName;
|
||||
p[1] = strchr(p[0], '-') + 1;
|
||||
p[2] = strchr(p[1], '-') + 1;
|
||||
p[3] = strchr(p[2], '.') + 1;
|
||||
|
||||
len = (size_t)(p[3] - p[2]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1));
|
||||
strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1);
|
||||
strncpy(pAddin->cSubsystem, p[2], (p[3] - p[2]) - 1);
|
||||
|
||||
pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_NAME;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM;
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
|
||||
used = TRUE;
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
}
|
||||
else if (nDashes == 3)
|
||||
{
|
||||
size_t len = 0;
|
||||
char* p[5] = { 0 };
|
||||
/* <name>-client-<subsystem>-<type>.<extension> */
|
||||
p[0] = cFileName;
|
||||
p[1] = strchr(p[0], '-');
|
||||
if (!p[1])
|
||||
goto skip;
|
||||
p[1] += 1;
|
||||
p[2] = strchr(p[1], '-');
|
||||
if (!p[2])
|
||||
goto skip;
|
||||
p[2] += 1;
|
||||
p[3] = strchr(p[2], '-');
|
||||
if (!p[3])
|
||||
goto skip;
|
||||
p[3] += 1;
|
||||
p[4] = strchr(p[3], '.');
|
||||
if (!p[4])
|
||||
goto skip;
|
||||
p[4] += 1;
|
||||
|
||||
len = (size_t)(p[1] - p[0]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1));
|
||||
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 = (size_t)(p[3] - p[2]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1));
|
||||
|
||||
len = (size_t)(p[4] - p[3]);
|
||||
if (len < 1)
|
||||
{
|
||||
WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName);
|
||||
goto skip;
|
||||
}
|
||||
strncpy(pAddin->cType, p[3], MIN(ARRAYSIZE(pAddin->cType), len - 1));
|
||||
strncpy(pAddin->cName, p[0], (p[1] - p[0]) - 1);
|
||||
strncpy(pAddin->cSubsystem, p[2], (p[3] - p[2]) - 1);
|
||||
strncpy(pAddin->cType, p[3], (p[4] - p[3]) - 1);
|
||||
|
||||
pAddin->dwFlags = FREERDP_ADDIN_CLIENT;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_NAME;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM;
|
||||
pAddin->dwFlags |= FREERDP_ADDIN_TYPE;
|
||||
|
||||
ppAddins[nAddins++] = pAddin;
|
||||
|
||||
used = TRUE;
|
||||
}
|
||||
|
||||
skip:
|
||||
free(cFileName);
|
||||
if (!used)
|
||||
else
|
||||
{
|
||||
free(pAddin);
|
||||
|
||||
} while (FindNextFileW(hFind, &FindData));
|
||||
}
|
||||
}
|
||||
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)
|
||||
FREERDP_ADDIN** freerdp_channels_list_addins(LPSTR pszName, LPSTR pszSubsystem, LPSTR pszType, DWORD dwFlags)
|
||||
{
|
||||
if (dwFlags & FREERDP_ADDIN_STATIC)
|
||||
return freerdp_channels_list_client_static_addins(pszName, pszSubsystem, pszType, dwFlags);
|
||||
@ -424,338 +287,49 @@ FREERDP_ADDIN** freerdp_channels_list_addins(LPCSTR pszName, LPCSTR pszSubsystem
|
||||
|
||||
void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins)
|
||||
{
|
||||
if (!ppAddins)
|
||||
return;
|
||||
int index;
|
||||
|
||||
for (size_t index = 0; ppAddins[index] != NULL; index++)
|
||||
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)
|
||||
void* freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPSTR pszSubsystem, LPSTR pszType, DWORD dwFlags)
|
||||
{
|
||||
for (size_t i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != NULL; i++)
|
||||
int i, j;
|
||||
STATIC_SUBSYSTEM_ENTRY* subsystems;
|
||||
|
||||
for (i = 0; CLIENT_STATIC_ADDIN_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 (strcmp(CLIENT_STATIC_ADDIN_TABLE[i].name, pszName) == 0)
|
||||
{
|
||||
if (type && (strncmp(table->type, type, MAX_PATH) != 0))
|
||||
continue;
|
||||
|
||||
if (pszSubsystem != NULL)
|
||||
{
|
||||
const STATIC_SUBSYSTEM_ENTRY* subsystems = table->table;
|
||||
subsystems = (STATIC_SUBSYSTEM_ENTRY*) CLIENT_STATIC_ADDIN_TABLE[i].table;
|
||||
|
||||
for (; subsystems->name != NULL; subsystems++)
|
||||
for (j = 0; subsystems[j].name != NULL; j++)
|
||||
{
|
||||
/* 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 (strcmp(subsystems[j].name, pszSubsystem) == 0)
|
||||
{
|
||||
static_subsystem_entry_fn_t fkt = subsystems->entry;
|
||||
|
||||
if (pszType)
|
||||
{
|
||||
if (strncmp(subsystems->type, pszType, MAX_PATH) == 0)
|
||||
return WINPR_FUNC_PTR_CAST(fkt, PVIRTUALCHANNELENTRY);
|
||||
if (strcmp(subsystems[j].type, pszType) == 0)
|
||||
return (void*) subsystems[j].entry;
|
||||
}
|
||||
else
|
||||
return WINPR_FUNC_PTR_CAST(fkt, PVIRTUALCHANNELENTRY);
|
||||
{
|
||||
return (void*) subsystems[j].entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX)
|
||||
{
|
||||
if (!freerdp_channels_is_virtual_channel_entry_ex(pszName))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return table->entry.csevc;
|
||||
return (void*) CLIENT_STATIC_ADDIN_TABLE[i].entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
wMessageQueue* queue;
|
||||
wStream* data_in;
|
||||
HANDLE thread;
|
||||
char* channel_name;
|
||||
rdpContext* ctx;
|
||||
LPVOID userdata;
|
||||
MsgHandler msg_handler;
|
||||
} msg_proc_internals;
|
||||
|
||||
static DWORD WINAPI channel_client_thread_proc(LPVOID userdata)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
wStream* data = NULL;
|
||||
wMessage message = { 0 };
|
||||
msg_proc_internals* internals = userdata;
|
||||
|
||||
WINPR_ASSERT(internals);
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (!MessageQueue_Wait(internals->queue))
|
||||
{
|
||||
WLog_ERR(TAG, "MessageQueue_Wait failed!");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
if (!MessageQueue_Peek(internals->queue, &message, TRUE))
|
||||
{
|
||||
WLog_ERR(TAG, "MessageQueue_Peek failed!");
|
||||
error = ERROR_INTERNAL_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.id == WMQ_QUIT)
|
||||
break;
|
||||
|
||||
if (message.id == 0)
|
||||
{
|
||||
data = (wStream*)message.wParam;
|
||||
|
||||
if ((error = internals->msg_handler(internals->userdata, data)))
|
||||
{
|
||||
WLog_ERR(TAG, "msg_handler failed with error %" PRIu32 "!", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error && internals->ctx)
|
||||
{
|
||||
char msg[128];
|
||||
(void)_snprintf(msg, 127,
|
||||
"%s_virtual_channel_client_thread reported an"
|
||||
" error",
|
||||
internals->channel_name);
|
||||
setChannelError(internals->ctx, error, msg);
|
||||
}
|
||||
ExitThread(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void free_msg(void* obj)
|
||||
{
|
||||
wMessage* msg = (wMessage*)obj;
|
||||
|
||||
if (msg && (msg->id == 0))
|
||||
{
|
||||
wStream* s = (wStream*)msg->wParam;
|
||||
Stream_Free(s, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void channel_client_handler_free(msg_proc_internals* internals)
|
||||
{
|
||||
if (!internals)
|
||||
return;
|
||||
|
||||
if (internals->thread)
|
||||
(void)CloseHandle(internals->thread);
|
||||
MessageQueue_Free(internals->queue);
|
||||
Stream_Free(internals->data_in, TRUE);
|
||||
free(internals->channel_name);
|
||||
free(internals);
|
||||
}
|
||||
|
||||
/* Create message queue and thread or not, depending on settings */
|
||||
void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, MsgHandler msg_handler,
|
||||
const char* channel_name)
|
||||
{
|
||||
msg_proc_internals* internals = calloc(1, sizeof(msg_proc_internals));
|
||||
if (!internals)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return NULL;
|
||||
}
|
||||
internals->msg_handler = msg_handler;
|
||||
internals->userdata = userdata;
|
||||
if (channel_name)
|
||||
{
|
||||
internals->channel_name = _strdup(channel_name);
|
||||
if (!internals->channel_name)
|
||||
goto fail;
|
||||
}
|
||||
WINPR_ASSERT(ctx);
|
||||
WINPR_ASSERT(ctx->settings);
|
||||
internals->ctx = ctx;
|
||||
if ((freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags) &
|
||||
THREADING_FLAGS_DISABLE_THREADS) == 0)
|
||||
{
|
||||
wObject obj = { 0 };
|
||||
obj.fnObjectFree = free_msg;
|
||||
internals->queue = MessageQueue_New(&obj);
|
||||
if (!internals->queue)
|
||||
{
|
||||
WLog_ERR(TAG, "MessageQueue_New failed!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(internals->thread =
|
||||
CreateThread(NULL, 0, channel_client_thread_proc, (void*)internals, 0, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateThread failed!");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return internals;
|
||||
|
||||
fail:
|
||||
channel_client_handler_free(internals);
|
||||
return NULL;
|
||||
}
|
||||
/* post a message in the queue or directly call the processing handler */
|
||||
UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength,
|
||||
UINT32 totalLength, UINT32 dataFlags)
|
||||
{
|
||||
msg_proc_internals* internals = MsgsHandle;
|
||||
wStream* data_in = NULL;
|
||||
|
||||
if (!internals)
|
||||
{
|
||||
/* TODO: return some error here */
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
|
||||
{
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
if (dataFlags & CHANNEL_FLAG_FIRST)
|
||||
{
|
||||
if (internals->data_in)
|
||||
{
|
||||
if (!Stream_EnsureCapacity(internals->data_in, totalLength))
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
else
|
||||
internals->data_in = Stream_New(NULL, totalLength);
|
||||
}
|
||||
|
||||
if (!(data_in = internals->data_in))
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
|
||||
{
|
||||
Stream_Free(internals->data_in, TRUE);
|
||||
internals->data_in = NULL;
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write(data_in, pData, dataLength);
|
||||
|
||||
if (dataFlags & CHANNEL_FLAG_LAST)
|
||||
{
|
||||
if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
|
||||
{
|
||||
char msg[128];
|
||||
(void)_snprintf(msg, 127, "%s_plugin_process_received: read error",
|
||||
internals->channel_name);
|
||||
WLog_ERR(TAG, msg);
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
internals->data_in = NULL;
|
||||
Stream_SealLength(data_in);
|
||||
Stream_SetPosition(data_in, 0);
|
||||
|
||||
if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) &
|
||||
THREADING_FLAGS_DISABLE_THREADS) != 0)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
if ((error = internals->msg_handler(internals->userdata, data_in)))
|
||||
{
|
||||
WLog_ERR(TAG,
|
||||
"msg_handler failed with error"
|
||||
" %" PRIu32 "!",
|
||||
error);
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
else if (!MessageQueue_Post(internals->queue, NULL, 0, (void*)data_in, NULL))
|
||||
{
|
||||
WLog_ERR(TAG, "MessageQueue_Post failed!");
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
/* Tear down queue and thread */
|
||||
UINT channel_client_quit_handler(void* MsgsHandle)
|
||||
{
|
||||
msg_proc_internals* internals = MsgsHandle;
|
||||
UINT rc = 0;
|
||||
if (!internals)
|
||||
{
|
||||
/* TODO: return some error here */
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
WINPR_ASSERT(internals->ctx);
|
||||
WINPR_ASSERT(internals->ctx->settings);
|
||||
|
||||
if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) &
|
||||
THREADING_FLAGS_DISABLE_THREADS) == 0)
|
||||
{
|
||||
if (internals->queue && internals->thread)
|
||||
{
|
||||
if (MessageQueue_PostQuit(internals->queue, 0) &&
|
||||
(WaitForSingleObject(internals->thread, INFINITE) == WAIT_FAILED))
|
||||
{
|
||||
rc = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channel_client_handler_free(internals);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Channel Addins
|
||||
*
|
||||
@ -17,12 +17,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
typedef UINT (*MsgHandler)(LPVOID userdata, wStream* data);
|
||||
|
||||
FREERDP_API void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata,
|
||||
MsgHandler handler, const char* channel_name);
|
||||
|
||||
UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength,
|
||||
UINT32 totalLength, UINT32 dataFlags);
|
||||
|
||||
UINT channel_client_quit_handler(void* MsgsHandle);
|
||||
|
785
channels/client/channels.c
Normal file
785
channels/client/channels.c
Normal file
@ -0,0 +1,785 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2009-2011 Jay Sorg
|
||||
* Copyright 2010-2011 Vic Lee
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/constants.h>
|
||||
#include <freerdp/client/channels.h>
|
||||
#include <freerdp/channels/channels.h>
|
||||
#include <freerdp/svc.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/utils/event.h>
|
||||
#include <freerdp/utils/debug.h>
|
||||
|
||||
#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 "addin.h"
|
||||
#include "init.h"
|
||||
#include "open.h"
|
||||
|
||||
#include "channels.h"
|
||||
|
||||
/**
|
||||
* MS compatible plugin interface
|
||||
* reference:
|
||||
* http://msdn.microsoft.com/en-us/library/aa383580.aspx
|
||||
*
|
||||
* Notes on threads:
|
||||
* Many virtual channel plugins are built using threads.
|
||||
* Non main threads may call MyVirtualChannelOpen,
|
||||
* MyVirtualChannelClose, or MyVirtualChannelWrite.
|
||||
* Since the plugin's VirtualChannelEntry function is called
|
||||
* from the main thread, MyVirtualChannelInit has to be called
|
||||
* from the main thread.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current channel manager reference passes from VirtualChannelEntry to
|
||||
* VirtualChannelInit for the pInitHandle.
|
||||
*/
|
||||
|
||||
void* g_pInterface;
|
||||
CHANNEL_INIT_DATA g_ChannelInitData;
|
||||
|
||||
static wArrayList* g_ChannelsList = NULL;
|
||||
|
||||
/* To generate unique sequence for all open handles */
|
||||
int g_open_handle_sequence;
|
||||
|
||||
/* For locking the global resources */
|
||||
static HANDLE g_mutex_init;
|
||||
|
||||
rdpChannels* freerdp_channels_find_by_open_handle(int open_handle, int* pindex)
|
||||
{
|
||||
int i, j;
|
||||
BOOL found = FALSE;
|
||||
rdpChannels* channels = NULL;
|
||||
|
||||
ArrayList_Lock(g_ChannelsList);
|
||||
|
||||
i = j = 0;
|
||||
channels = (rdpChannels*) ArrayList_GetItem(g_ChannelsList, i++);
|
||||
|
||||
while (channels)
|
||||
{
|
||||
for (j = 0; j < channels->openDataCount; j++)
|
||||
{
|
||||
if (channels->openDataList[j].OpenHandle == open_handle)
|
||||
{
|
||||
*pindex = j;
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
|
||||
channels = (rdpChannels*) ArrayList_GetItem(g_ChannelsList, i++);
|
||||
}
|
||||
|
||||
ArrayList_Unlock(g_ChannelsList);
|
||||
|
||||
return (found) ? channels : NULL;
|
||||
}
|
||||
|
||||
rdpChannels* freerdp_channels_find_by_instance(freerdp* instance)
|
||||
{
|
||||
int index;
|
||||
BOOL found = FALSE;
|
||||
rdpChannels* channels = NULL;
|
||||
|
||||
ArrayList_Lock(g_ChannelsList);
|
||||
|
||||
index = 0;
|
||||
channels = (rdpChannels*) ArrayList_GetItem(g_ChannelsList, index++);
|
||||
|
||||
while (channels)
|
||||
{
|
||||
if (channels->instance == instance)
|
||||
{
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
channels = (rdpChannels*) ArrayList_GetItem(g_ChannelsList, index++);
|
||||
}
|
||||
|
||||
ArrayList_Unlock(g_ChannelsList);
|
||||
|
||||
return (found) ? channels : NULL;
|
||||
}
|
||||
|
||||
CHANNEL_OPEN_DATA* freerdp_channels_find_channel_open_data_by_name(rdpChannels* channels, const char* channel_name)
|
||||
{
|
||||
int index;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
for (index = 0; index < channels->openDataCount; index++)
|
||||
{
|
||||
pChannelOpenData = &channels->openDataList[index];
|
||||
|
||||
if (strcmp(channel_name, pChannelOpenData->name) == 0)
|
||||
return pChannelOpenData;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* returns rdpChannel for the channel id passed in */
|
||||
rdpChannel* freerdp_channels_find_channel_by_id(rdpChannels* channels, rdpSettings* settings, int channel_id, int* pindex)
|
||||
{
|
||||
int index;
|
||||
int count;
|
||||
rdpChannel* channel;
|
||||
|
||||
count = settings->ChannelCount;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
channel = &settings->ChannelDefArray[index];
|
||||
|
||||
if (channel->ChannelId == channel_id)
|
||||
{
|
||||
if (pindex != 0)
|
||||
*pindex = index;
|
||||
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* returns rdpChannel for the channel name passed in */
|
||||
rdpChannel* freerdp_channels_find_channel_by_name(rdpChannels* channels,
|
||||
rdpSettings* settings, const char* channel_name, int* pindex)
|
||||
{
|
||||
int index;
|
||||
int count;
|
||||
rdpChannel* channel;
|
||||
|
||||
count = settings->ChannelCount;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
channel = &settings->ChannelDefArray[index];
|
||||
|
||||
if (strcmp(channel_name, channel->Name) == 0)
|
||||
{
|
||||
if (pindex != 0)
|
||||
*pindex = index;
|
||||
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelWrite(UINT32 openHandle, void* pData, UINT32 dataLength, void* pUserData)
|
||||
{
|
||||
int index;
|
||||
rdpChannels* channels;
|
||||
CHANNEL_OPEN_EVENT* item;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
channels = freerdp_channels_find_by_open_handle(openHandle, &index);
|
||||
|
||||
if ((!channels) || (index < 0) || (index >= CHANNEL_MAX_COUNT))
|
||||
{
|
||||
DEBUG_CHANNELS("error bad channel handle");
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
}
|
||||
|
||||
if (!channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error not connected");
|
||||
return CHANNEL_RC_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!pData)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad pData");
|
||||
return CHANNEL_RC_NULL_DATA;
|
||||
}
|
||||
|
||||
if (!dataLength)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad dataLength");
|
||||
return CHANNEL_RC_ZERO_LENGTH;
|
||||
}
|
||||
|
||||
pChannelOpenData = &channels->openDataList[index];
|
||||
|
||||
if (pChannelOpenData->flags != 2)
|
||||
{
|
||||
DEBUG_CHANNELS("error not open");
|
||||
return CHANNEL_RC_NOT_OPEN;
|
||||
}
|
||||
|
||||
if (!channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error not connected");
|
||||
return CHANNEL_RC_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
item = (CHANNEL_OPEN_EVENT*) malloc(sizeof(CHANNEL_OPEN_EVENT));
|
||||
item->Data = pData;
|
||||
item->DataLength = dataLength;
|
||||
item->UserData = pUserData;
|
||||
item->Index = index;
|
||||
|
||||
MessageQueue_Post(channels->MsgPipe->Out, (void*) channels, 0, (void*) item, NULL);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelEventPush(UINT32 openHandle, wMessage* event)
|
||||
{
|
||||
int index;
|
||||
rdpChannels* channels;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
channels = freerdp_channels_find_by_open_handle(openHandle, &index);
|
||||
|
||||
if ((!channels) || (index < 0) || (index >= CHANNEL_MAX_COUNT))
|
||||
{
|
||||
DEBUG_CHANNELS("error bad channels handle");
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
}
|
||||
|
||||
if (!channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error not connected");
|
||||
return CHANNEL_RC_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!event)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad event");
|
||||
return CHANNEL_RC_NULL_DATA;
|
||||
}
|
||||
|
||||
pChannelOpenData = &channels->openDataList[index];
|
||||
|
||||
if (pChannelOpenData->flags != 2)
|
||||
{
|
||||
DEBUG_CHANNELS("error not open");
|
||||
return CHANNEL_RC_NOT_OPEN;
|
||||
}
|
||||
|
||||
if (!channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error not connected");
|
||||
return CHANNEL_RC_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* We really intend to use the In queue for events, but we're pushing on both
|
||||
* to wake up threads waiting on the out queue. Doing this cleanly would require
|
||||
* breaking freerdp_pop_event() a bit too early in this refactoring.
|
||||
*/
|
||||
|
||||
MessageQueue_Post(channels->MsgPipe->In, (void*) channels, 1, (void*) event, NULL);
|
||||
MessageQueue_Post(channels->MsgPipe->Out, (void*) channels, 1, (void*) event, NULL);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* this is called shortly after the application starts and
|
||||
* before any other function in the file
|
||||
* called only from main thread
|
||||
*/
|
||||
int freerdp_channels_global_init(void)
|
||||
{
|
||||
g_open_handle_sequence = 1;
|
||||
g_mutex_init = CreateMutex(NULL, FALSE, NULL);
|
||||
|
||||
if (!g_ChannelsList)
|
||||
g_ChannelsList = ArrayList_New(TRUE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int freerdp_channels_global_uninit(void)
|
||||
{
|
||||
/* TODO: free channels list */
|
||||
|
||||
CloseHandle(g_mutex_init);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
rdpChannels* freerdp_channels_new(void)
|
||||
{
|
||||
rdpChannels* channels;
|
||||
|
||||
channels = (rdpChannels*) malloc(sizeof(rdpChannels));
|
||||
ZeroMemory(channels, sizeof(rdpChannels));
|
||||
|
||||
channels->MsgPipe = MessagePipe_New();
|
||||
|
||||
ArrayList_Add(g_ChannelsList, (void*) channels);
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
void freerdp_channels_free(rdpChannels* channels)
|
||||
{
|
||||
MessagePipe_Free(channels->MsgPipe);
|
||||
|
||||
/* TODO: remove from channels list */
|
||||
|
||||
free(channels);
|
||||
}
|
||||
|
||||
int freerdp_channels_client_load(rdpChannels* channels, rdpSettings* settings, void* entry, void* data)
|
||||
{
|
||||
int status;
|
||||
CHANNEL_ENTRY_POINTS_EX ep;
|
||||
CHANNEL_CLIENT_DATA* pChannelClientData;
|
||||
|
||||
if (channels->clientDataCount + 1 >= CHANNEL_MAX_COUNT)
|
||||
{
|
||||
fprintf(stderr, "error: too many channels\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
pChannelClientData = &channels->clientDataList[channels->clientDataCount];
|
||||
pChannelClientData->entry = (PVIRTUALCHANNELENTRY) entry;
|
||||
|
||||
ep.cbSize = sizeof(ep);
|
||||
ep.protocolVersion = VIRTUAL_CHANNEL_VERSION_WIN2000;
|
||||
ep.pVirtualChannelInit = FreeRDP_VirtualChannelInit;
|
||||
ep.pVirtualChannelOpen = FreeRDP_VirtualChannelOpen;
|
||||
ep.pVirtualChannelClose = FreeRDP_VirtualChannelClose;
|
||||
ep.pVirtualChannelWrite = FreeRDP_VirtualChannelWrite;
|
||||
|
||||
g_pInterface = NULL;
|
||||
ep.MagicNumber = FREERDP_CHANNEL_MAGIC_NUMBER;
|
||||
ep.ppInterface = &g_pInterface;
|
||||
ep.pExtendedData = data;
|
||||
ep.pVirtualChannelEventPush = FreeRDP_VirtualChannelEventPush;
|
||||
|
||||
/* enable VirtualChannelInit */
|
||||
channels->can_call_init = TRUE;
|
||||
channels->settings = settings;
|
||||
|
||||
WaitForSingleObject(g_mutex_init, INFINITE);
|
||||
|
||||
g_ChannelInitData.channels = channels;
|
||||
status = pChannelClientData->entry((PCHANNEL_ENTRY_POINTS) &ep);
|
||||
|
||||
ReleaseMutex(g_mutex_init);
|
||||
|
||||
/* disable MyVirtualChannelInit */
|
||||
channels->settings = NULL;
|
||||
channels->can_call_init = FALSE;
|
||||
|
||||
if (!status)
|
||||
{
|
||||
fprintf(stderr, "error: channel export function call failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* this is called when processing the command line parameters
|
||||
* called only from main thread
|
||||
*/
|
||||
int freerdp_channels_load_plugin(rdpChannels* channels, rdpSettings* settings, const char* name, void* data)
|
||||
{
|
||||
void* entry;
|
||||
|
||||
DEBUG_CHANNELS("%s", name);
|
||||
|
||||
entry = (PVIRTUALCHANNELENTRY) freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
DEBUG_CHANNELS("failed to find export function");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return freerdp_channels_client_load(channels, settings, entry, data);
|
||||
}
|
||||
|
||||
int freerdp_drdynvc_on_channel_connected(DrdynvcClientContext* context, const char* name, void* pInterface)
|
||||
{
|
||||
int status = 0;
|
||||
ChannelConnectedEventArgs e;
|
||||
rdpChannels* channels = (rdpChannels*) context->custom;
|
||||
freerdp* instance = channels->instance;
|
||||
|
||||
EventArgsInit(&e, "freerdp");
|
||||
e.name = name;
|
||||
e.pInterface = pInterface;
|
||||
PubSub_OnChannelConnected(instance->context->pubSub, instance->context, &e);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int freerdp_drdynvc_on_channel_disconnected(DrdynvcClientContext* context, const char* name, void* pInterface)
|
||||
{
|
||||
int status = 0;
|
||||
ChannelDisconnectedEventArgs e;
|
||||
rdpChannels* channels = (rdpChannels*) context->custom;
|
||||
freerdp* instance = channels->instance;
|
||||
|
||||
EventArgsInit(&e, "freerdp");
|
||||
e.name = name;
|
||||
e.pInterface = pInterface;
|
||||
PubSub_OnChannelDisconnected(instance->context->pubSub, instance->context, &e);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* go through and inform all the libraries that we are initialized
|
||||
* called only from main thread
|
||||
*/
|
||||
int freerdp_channels_pre_connect(rdpChannels* channels, freerdp* instance)
|
||||
{
|
||||
int index;
|
||||
CHANNEL_CLIENT_DATA* pChannelClientData;
|
||||
|
||||
DEBUG_CHANNELS("enter");
|
||||
channels->instance = instance;
|
||||
|
||||
for (index = 0; index < channels->clientDataCount; index++)
|
||||
{
|
||||
pChannelClientData = &channels->clientDataList[index];
|
||||
|
||||
if (pChannelClientData->pChannelInitEventProc)
|
||||
pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle, CHANNEL_EVENT_INITIALIZED, 0, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* go through and inform all the libraries that we are connected
|
||||
* this will tell the libraries that its ok to call MyVirtualChannelOpen
|
||||
* called only from main thread
|
||||
*/
|
||||
int freerdp_channels_post_connect(rdpChannels* channels, freerdp* instance)
|
||||
{
|
||||
int index;
|
||||
char* hostname;
|
||||
int hostnameLength;
|
||||
CHANNEL_CLIENT_DATA* pChannelClientData;
|
||||
|
||||
channels->is_connected = 1;
|
||||
hostname = instance->settings->ServerHostname;
|
||||
hostnameLength = strlen(hostname);
|
||||
|
||||
DEBUG_CHANNELS("hostname [%s] channels->num_libs [%d]", hostname, channels->clientDataCount);
|
||||
|
||||
for (index = 0; index < channels->clientDataCount; index++)
|
||||
{
|
||||
pChannelClientData = &channels->clientDataList[index];
|
||||
|
||||
if (pChannelClientData->pChannelInitEventProc)
|
||||
pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle, CHANNEL_EVENT_CONNECTED, hostname, hostnameLength);
|
||||
}
|
||||
|
||||
channels->drdynvc = (DrdynvcClientContext*) freerdp_channels_get_static_channel_interface(channels, "drdynvc");
|
||||
|
||||
if (channels->drdynvc)
|
||||
{
|
||||
channels->drdynvc->custom = (void*) channels;
|
||||
channels->drdynvc->OnChannelConnected = freerdp_drdynvc_on_channel_connected;
|
||||
channels->drdynvc->OnChannelDisconnected = freerdp_drdynvc_on_channel_disconnected;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* data coming from the server to the client
|
||||
* called only from main thread
|
||||
*/
|
||||
int freerdp_channels_data(freerdp* instance, int channel_id, void* data, int data_size, int flags, int total_size)
|
||||
{
|
||||
int index;
|
||||
rdpChannel* channel;
|
||||
rdpChannels* channels;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
channels = freerdp_channels_find_by_instance(instance);
|
||||
|
||||
if (!channels)
|
||||
{
|
||||
DEBUG_CHANNELS("could not find channel manager");
|
||||
return 1;
|
||||
}
|
||||
|
||||
channel = freerdp_channels_find_channel_by_id(channels, instance->settings, channel_id, &index);
|
||||
|
||||
if (!channel)
|
||||
{
|
||||
DEBUG_CHANNELS("could not find channel id");
|
||||
return 1;
|
||||
}
|
||||
|
||||
pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, channel->Name);
|
||||
|
||||
if (!pChannelOpenData)
|
||||
{
|
||||
DEBUG_CHANNELS("could not find channel name");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pChannelOpenData->pChannelOpenEventProc)
|
||||
{
|
||||
pChannelOpenData->pChannelOpenEventProc(pChannelOpenData->OpenHandle,
|
||||
CHANNEL_EVENT_DATA_RECEIVED, data, data_size, total_size, flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a plugin-defined event to the plugin.
|
||||
* called only from main thread
|
||||
* @param channels the channel manager instance
|
||||
* @param event an event object created by freerdp_event_new()
|
||||
*/
|
||||
FREERDP_API int freerdp_channels_send_event(rdpChannels* channels, wMessage* event)
|
||||
{
|
||||
const char* name = NULL;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
switch (GetMessageClass(event->id))
|
||||
{
|
||||
case DebugChannel_Class:
|
||||
name = "rdpdbg";
|
||||
break;
|
||||
|
||||
case CliprdrChannel_Class:
|
||||
name = "cliprdr";
|
||||
break;
|
||||
|
||||
case TsmfChannel_Class:
|
||||
name = "tsmf";
|
||||
break;
|
||||
|
||||
case RailChannel_Class:
|
||||
name = "rail";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!name)
|
||||
{
|
||||
DEBUG_CHANNELS("unknown event_class %d", GetMessageClass(event->id));
|
||||
freerdp_event_free(event);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, name);
|
||||
|
||||
if (!pChannelOpenData)
|
||||
{
|
||||
DEBUG_CHANNELS("could not find channel name %s", name);
|
||||
freerdp_event_free(event);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pChannelOpenData->pChannelOpenEventProc)
|
||||
{
|
||||
pChannelOpenData->pChannelOpenEventProc(pChannelOpenData->OpenHandle, CHANNEL_EVENT_USER,
|
||||
event, sizeof(wMessage), sizeof(wMessage), 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* called only from main thread
|
||||
*/
|
||||
static void freerdp_channels_process_sync(rdpChannels* channels, freerdp* instance)
|
||||
{
|
||||
wMessage message;
|
||||
wMessage* event;
|
||||
rdpChannel* channel;
|
||||
CHANNEL_OPEN_EVENT* item;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
while (MessageQueue_Peek(channels->MsgPipe->Out, &message, TRUE))
|
||||
{
|
||||
if (message.id == WMQ_QUIT)
|
||||
break;
|
||||
|
||||
if (message.id == 0)
|
||||
{
|
||||
item = (CHANNEL_OPEN_EVENT*) message.wParam;
|
||||
|
||||
if (!item)
|
||||
break;
|
||||
|
||||
pChannelOpenData = &channels->openDataList[item->Index];
|
||||
|
||||
channel = freerdp_channels_find_channel_by_name(channels, instance->settings,
|
||||
pChannelOpenData->name, &item->Index);
|
||||
|
||||
if (channel)
|
||||
instance->SendChannelData(instance, channel->ChannelId, item->Data, item->DataLength);
|
||||
|
||||
if (pChannelOpenData->pChannelOpenEventProc)
|
||||
{
|
||||
pChannelOpenData->pChannelOpenEventProc(pChannelOpenData->OpenHandle,
|
||||
CHANNEL_EVENT_WRITE_COMPLETE, item->UserData, item->DataLength, item->DataLength, 0);
|
||||
}
|
||||
|
||||
free(item);
|
||||
}
|
||||
else if (message.id == 1)
|
||||
{
|
||||
event = (wMessage*) message.wParam;
|
||||
|
||||
/**
|
||||
* Ignore for now, the same event is being pushed on the In queue,
|
||||
* and we're pushing it on the Out queue just to wake other threads
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called only from main thread
|
||||
*/
|
||||
BOOL freerdp_channels_get_fds(rdpChannels* channels, freerdp* instance, void** read_fds,
|
||||
int* read_count, void** write_fds, int* write_count)
|
||||
{
|
||||
void* pfd;
|
||||
|
||||
pfd = GetEventWaitObject(MessageQueue_Event(channels->MsgPipe->Out));
|
||||
|
||||
if (pfd)
|
||||
{
|
||||
read_fds[*read_count] = pfd;
|
||||
(*read_count)++;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void* freerdp_channels_get_static_channel_interface(rdpChannels* channels, const char* name)
|
||||
{
|
||||
void* pInterface = NULL;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, name);
|
||||
|
||||
if (pChannelOpenData)
|
||||
pInterface = pChannelOpenData->pInterface;
|
||||
|
||||
return pInterface;
|
||||
}
|
||||
|
||||
HANDLE freerdp_channels_get_event_handle(freerdp* instance)
|
||||
{
|
||||
HANDLE event = NULL;
|
||||
rdpChannels* channels;
|
||||
|
||||
channels = instance->context->channels;
|
||||
event = MessageQueue_Event(channels->MsgPipe->Out);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
int freerdp_channels_process_pending_messages(freerdp* instance)
|
||||
{
|
||||
rdpChannels* channels;
|
||||
|
||||
channels = instance->context->channels;
|
||||
|
||||
if (WaitForSingleObject(MessageQueue_Event(channels->MsgPipe->Out), 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
freerdp_channels_process_sync(channels, instance);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* called only from main thread
|
||||
*/
|
||||
BOOL freerdp_channels_check_fds(rdpChannels* channels, freerdp* instance)
|
||||
{
|
||||
if (WaitForSingleObject(MessageQueue_Event(channels->MsgPipe->Out), 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
freerdp_channels_process_sync(channels, instance);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wMessage* freerdp_channels_pop_event(rdpChannels* channels)
|
||||
{
|
||||
wMessage message;
|
||||
wMessage* event = NULL;
|
||||
|
||||
if (MessageQueue_Peek(channels->MsgPipe->In, &message, TRUE))
|
||||
{
|
||||
if (message.id == 1)
|
||||
{
|
||||
event = (wMessage*) message.wParam;
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void freerdp_channels_close(rdpChannels* channels, freerdp* instance)
|
||||
{
|
||||
int index;
|
||||
CHANNEL_CLIENT_DATA* pChannelClientData;
|
||||
|
||||
DEBUG_CHANNELS("closing");
|
||||
channels->is_connected = 0;
|
||||
freerdp_channels_check_fds(channels, instance);
|
||||
|
||||
/* tell all libraries we are shutting down */
|
||||
for (index = 0; index < channels->clientDataCount; index++)
|
||||
{
|
||||
pChannelClientData = &channels->clientDataList[index];
|
||||
|
||||
if (pChannelClientData->pChannelInitEventProc)
|
||||
pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle, CHANNEL_EVENT_TERMINATED, 0, 0);
|
||||
}
|
||||
}
|
122
channels/client/channels.h
Normal file
122
channels/client/channels.h
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_PRIVATE_CLIENT_CHANNELS
|
||||
#define FREERDP_PRIVATE_CLIENT_CHANNELS
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/constants.h>
|
||||
|
||||
#include <freerdp/svc.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/utils/event.h>
|
||||
#include <freerdp/utils/debug.h>
|
||||
#include <freerdp/client/channels.h>
|
||||
#include <freerdp/client/drdynvc.h>
|
||||
#include <freerdp/channels/channels.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define CHANNEL_MAX_COUNT 30
|
||||
|
||||
struct rdp_channel_client_data
|
||||
{
|
||||
PVIRTUALCHANNELENTRY entry;
|
||||
PCHANNEL_INIT_EVENT_FN pChannelInitEventProc;
|
||||
void* pInitHandle;
|
||||
};
|
||||
typedef struct rdp_channel_client_data CHANNEL_CLIENT_DATA;
|
||||
|
||||
struct rdp_channel_open_data
|
||||
{
|
||||
char name[8];
|
||||
int OpenHandle;
|
||||
int options;
|
||||
int flags;
|
||||
void* pInterface;
|
||||
PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc;
|
||||
};
|
||||
typedef struct rdp_channel_open_data CHANNEL_OPEN_DATA;
|
||||
|
||||
struct _CHANNEL_OPEN_EVENT
|
||||
{
|
||||
void* Data;
|
||||
UINT32 DataLength;
|
||||
void* UserData;
|
||||
int Index;
|
||||
};
|
||||
typedef struct _CHANNEL_OPEN_EVENT CHANNEL_OPEN_EVENT;
|
||||
|
||||
/**
|
||||
* pInitHandle: handle that identifies the client connection
|
||||
* Obtained by the client with VirtualChannelInit
|
||||
* Used by the client with VirtualChannelOpen
|
||||
*/
|
||||
|
||||
struct rdp_channel_init_data
|
||||
{
|
||||
rdpChannels* channels;
|
||||
void* pInterface;
|
||||
};
|
||||
typedef struct rdp_channel_init_data CHANNEL_INIT_DATA;
|
||||
|
||||
struct rdp_channels
|
||||
{
|
||||
/* internal */
|
||||
|
||||
int clientDataCount;
|
||||
CHANNEL_CLIENT_DATA clientDataList[CHANNEL_MAX_COUNT];
|
||||
|
||||
int openDataCount;
|
||||
CHANNEL_OPEN_DATA openDataList[CHANNEL_MAX_COUNT];
|
||||
|
||||
int initDataCount;
|
||||
CHANNEL_INIT_DATA initDataList[CHANNEL_MAX_COUNT];
|
||||
|
||||
/* control for entry into MyVirtualChannelInit */
|
||||
int can_call_init;
|
||||
rdpSettings* settings;
|
||||
|
||||
/* true once freerdp_channels_post_connect is called */
|
||||
int is_connected;
|
||||
|
||||
/* used for locating the channels for a given instance */
|
||||
freerdp* instance;
|
||||
|
||||
wMessagePipe* MsgPipe;
|
||||
|
||||
DrdynvcClientContext* drdynvc;
|
||||
};
|
||||
|
||||
#ifdef WITH_DEBUG_CHANNELS
|
||||
#define DEBUG_CHANNELS(fmt, ...) DEBUG_CLASS(CHANNELS, fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_CHANNELS(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
rdpChannels* freerdp_channels_find_by_open_handle(int open_handle, int* pindex);
|
||||
CHANNEL_OPEN_DATA* freerdp_channels_find_channel_open_data_by_name(rdpChannels* channels, const char* channel_name);
|
||||
|
||||
#endif /* FREERDP_PRIVATE_CLIENT_CHANNELS */
|
@ -1,214 +0,0 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Dynamic channel
|
||||
*
|
||||
* Copyright 2022 David Fort <contact@hardening-consulting.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#include <freerdp/log.h>
|
||||
#include <freerdp/client/channels.h>
|
||||
|
||||
#define TAG FREERDP_TAG("genericdynvc")
|
||||
|
||||
static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data,
|
||||
BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* callback = NULL;
|
||||
GENERIC_DYNVC_PLUGIN* plugin = NULL;
|
||||
GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
|
||||
|
||||
if (!listener_callback || !listener_callback->plugin)
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
|
||||
plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin;
|
||||
WLog_Print(plugin->log, WLOG_TRACE, "...");
|
||||
|
||||
callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize);
|
||||
if (!callback)
|
||||
{
|
||||
WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* implant configured channel callbacks */
|
||||
callback->iface = *plugin->channel_callbacks;
|
||||
|
||||
callback->plugin = listener_callback->plugin;
|
||||
callback->channel_mgr = listener_callback->channel_mgr;
|
||||
callback->channel = pChannel;
|
||||
|
||||
listener_callback->channel_callback = callback;
|
||||
listener_callback->channel = pChannel;
|
||||
|
||||
*ppCallback = (IWTSVirtualChannelCallback*)callback;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin,
|
||||
IWTSVirtualChannelManager* pChannelMgr)
|
||||
{
|
||||
UINT rc = 0;
|
||||
GENERIC_LISTENER_CALLBACK* listener_callback = NULL;
|
||||
GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
|
||||
|
||||
if (!plugin)
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
|
||||
if (!pChannelMgr)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
if (plugin->initialized)
|
||||
{
|
||||
WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
WLog_Print(plugin->log, WLOG_TRACE, "...");
|
||||
listener_callback = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
|
||||
if (!listener_callback)
|
||||
{
|
||||
WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
plugin->listener_callback = listener_callback;
|
||||
listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection;
|
||||
listener_callback->plugin = pPlugin;
|
||||
listener_callback->channel_mgr = pChannelMgr;
|
||||
rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface,
|
||||
&plugin->listener);
|
||||
|
||||
plugin->listener->pInterface = plugin->iface.pInterface;
|
||||
plugin->initialized = (rc == CHANNEL_RC_OK);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static UINT generic_plugin_terminated(IWTSPlugin* pPlugin)
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
if (!plugin)
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
|
||||
WLog_Print(plugin->log, WLOG_TRACE, "...");
|
||||
|
||||
/* some channels (namely rdpei), look at initialized to see if they should continue to run */
|
||||
plugin->initialized = FALSE;
|
||||
|
||||
if (plugin->terminatePluginFn)
|
||||
plugin->terminatePluginFn(plugin);
|
||||
|
||||
if (plugin->listener_callback)
|
||||
{
|
||||
IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr;
|
||||
if (mgr)
|
||||
IFCALL(mgr->DestroyListener, mgr, plugin->listener);
|
||||
}
|
||||
|
||||
free(plugin->listener_callback);
|
||||
free(plugin->dynvc_name);
|
||||
free(plugin);
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin)
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
if (!pluginn)
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
|
||||
pluginn->attached = TRUE;
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin)
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
if (!plugin)
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
|
||||
plugin->attached = FALSE;
|
||||
return error;
|
||||
}
|
||||
|
||||
UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag,
|
||||
const char* name, size_t pluginSize, size_t channelCallbackSize,
|
||||
const IWTSVirtualChannelCallback* channel_callbacks,
|
||||
DYNVC_PLUGIN_INIT_FN initPluginFn,
|
||||
DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn)
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN* plugin = NULL;
|
||||
UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
|
||||
WINPR_ASSERT(pEntryPoints);
|
||||
WINPR_ASSERT(pEntryPoints->GetPlugin);
|
||||
WINPR_ASSERT(logTag);
|
||||
WINPR_ASSERT(name);
|
||||
WINPR_ASSERT(pluginSize >= sizeof(*plugin));
|
||||
WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK));
|
||||
|
||||
plugin = (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name);
|
||||
if (plugin != NULL)
|
||||
return CHANNEL_RC_ALREADY_INITIALIZED;
|
||||
|
||||
plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, pluginSize);
|
||||
if (!plugin)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
plugin->log = WLog_Get(logTag);
|
||||
plugin->attached = TRUE;
|
||||
plugin->channel_callbacks = channel_callbacks;
|
||||
plugin->channelCallbackSize = channelCallbackSize;
|
||||
plugin->iface.Initialize = generic_dynvc_plugin_initialize;
|
||||
plugin->iface.Connected = NULL;
|
||||
plugin->iface.Disconnected = NULL;
|
||||
plugin->iface.Terminated = generic_plugin_terminated;
|
||||
plugin->iface.Attached = generic_dynvc_plugin_attached;
|
||||
plugin->iface.Detached = generic_dynvc_plugin_detached;
|
||||
plugin->terminatePluginFn = terminatePluginFn;
|
||||
|
||||
if (initPluginFn)
|
||||
{
|
||||
rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints);
|
||||
rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints);
|
||||
|
||||
error = initPluginFn(plugin, context, settings);
|
||||
if (error != CHANNEL_RC_OK)
|
||||
goto error;
|
||||
}
|
||||
|
||||
plugin->dynvc_name = _strdup(name);
|
||||
if (!plugin->dynvc_name)
|
||||
goto error;
|
||||
|
||||
error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface);
|
||||
if (error == CHANNEL_RC_OK)
|
||||
return error;
|
||||
|
||||
error:
|
||||
generic_plugin_terminated(&plugin->iface);
|
||||
return error;
|
||||
}
|
135
channels/client/init.c
Normal file
135
channels/client/init.c
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "channels.h"
|
||||
|
||||
#include "init.h"
|
||||
|
||||
extern int g_open_handle_sequence;
|
||||
|
||||
extern void* g_pInterface;
|
||||
extern CHANNEL_INIT_DATA g_ChannelInitData;
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelInit(void** ppInitHandle, PCHANNEL_DEF pChannel,
|
||||
int channelCount, UINT32 versionRequested, PCHANNEL_INIT_EVENT_FN pChannelInitEventProc)
|
||||
{
|
||||
int index;
|
||||
void* pInterface;
|
||||
rdpChannel* channel;
|
||||
rdpChannels* channels;
|
||||
PCHANNEL_DEF pChannelDef;
|
||||
CHANNEL_INIT_DATA* pChannelInitData;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
CHANNEL_CLIENT_DATA* pChannelClientData;
|
||||
|
||||
if (!ppInitHandle)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad init handle");
|
||||
return CHANNEL_RC_BAD_INIT_HANDLE;
|
||||
}
|
||||
|
||||
channels = g_ChannelInitData.channels;
|
||||
pInterface = g_pInterface;
|
||||
|
||||
pChannelInitData = &(channels->initDataList[channels->initDataCount]);
|
||||
*ppInitHandle = pChannelInitData;
|
||||
channels->initDataCount++;
|
||||
|
||||
pChannelInitData->channels = channels;
|
||||
pChannelInitData->pInterface = pInterface;
|
||||
|
||||
DEBUG_CHANNELS("enter");
|
||||
|
||||
if (!channels->can_call_init)
|
||||
{
|
||||
DEBUG_CHANNELS("error not in entry");
|
||||
return CHANNEL_RC_NOT_IN_VIRTUALCHANNELENTRY;
|
||||
}
|
||||
|
||||
if (channels->openDataCount + channelCount >= CHANNEL_MAX_COUNT)
|
||||
{
|
||||
DEBUG_CHANNELS("error too many channels");
|
||||
return CHANNEL_RC_TOO_MANY_CHANNELS;
|
||||
}
|
||||
|
||||
if (!pChannel)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad channel");
|
||||
return CHANNEL_RC_BAD_CHANNEL;
|
||||
}
|
||||
|
||||
if (channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error already connected");
|
||||
return CHANNEL_RC_ALREADY_CONNECTED;
|
||||
}
|
||||
|
||||
if (versionRequested != VIRTUAL_CHANNEL_VERSION_WIN2000)
|
||||
{
|
||||
DEBUG_CHANNELS("warning version");
|
||||
}
|
||||
|
||||
for (index = 0; index < channelCount; index++)
|
||||
{
|
||||
pChannelDef = &pChannel[index];
|
||||
|
||||
if (freerdp_channels_find_channel_open_data_by_name(channels, pChannelDef->name) != 0)
|
||||
{
|
||||
DEBUG_CHANNELS("error channel already used");
|
||||
return CHANNEL_RC_BAD_CHANNEL;
|
||||
}
|
||||
}
|
||||
|
||||
pChannelClientData = &channels->clientDataList[channels->clientDataCount];
|
||||
pChannelClientData->pChannelInitEventProc = pChannelInitEventProc;
|
||||
pChannelClientData->pInitHandle = *ppInitHandle;
|
||||
channels->clientDataCount++;
|
||||
|
||||
for (index = 0; index < channelCount; index++)
|
||||
{
|
||||
pChannelDef = &pChannel[index];
|
||||
pChannelOpenData = &channels->openDataList[channels->openDataCount];
|
||||
|
||||
pChannelOpenData->OpenHandle = g_open_handle_sequence++;
|
||||
|
||||
pChannelOpenData->flags = 1; /* init */
|
||||
strncpy(pChannelOpenData->name, pChannelDef->name, CHANNEL_NAME_LEN);
|
||||
pChannelOpenData->options = pChannelDef->options;
|
||||
|
||||
if (channels->settings->ChannelCount < CHANNEL_MAX_COUNT)
|
||||
{
|
||||
channel = channels->settings->ChannelDefArray + channels->settings->ChannelCount;
|
||||
strncpy(channel->Name, pChannelDef->name, 7);
|
||||
channel->options = pChannelDef->options;
|
||||
channels->settings->ChannelCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_CHANNELS("warning more than %d channels", CHANNEL_MAX_COUNT);
|
||||
}
|
||||
|
||||
channels->openDataCount++;
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
28
channels/client/init.h
Normal file
28
channels/client/init.h
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_PRIVATE_CLIENT_CHANNELS_INIT
|
||||
#define FREERDP_PRIVATE_CLIENT_CHANNELS_INIT
|
||||
|
||||
#include "channels.h"
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelInit(void** ppInitHandle, PCHANNEL_DEF pChannel,
|
||||
int channelCount, UINT32 versionRequested, PCHANNEL_INIT_EVENT_FN pChannelInitEventProc);
|
||||
|
||||
#endif /* FREERDP_PRIVATE_CLIENT_CHANNELS_INIT */
|
103
channels/client/open.c
Normal file
103
channels/client/open.c
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "open.h"
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelOpen(void* pInitHandle, UINT32* pOpenHandle,
|
||||
char* pChannelName, PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc)
|
||||
{
|
||||
void* pInterface;
|
||||
rdpChannels* channels;
|
||||
CHANNEL_INIT_DATA* pChannelInitData;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
DEBUG_CHANNELS("enter");
|
||||
|
||||
pChannelInitData = (CHANNEL_INIT_DATA*) pInitHandle;
|
||||
channels = pChannelInitData->channels;
|
||||
pInterface = pChannelInitData->pInterface;
|
||||
|
||||
if (!pOpenHandle)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad channel handle");
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
}
|
||||
|
||||
if (!pChannelOpenEventProc)
|
||||
{
|
||||
DEBUG_CHANNELS("error bad proc");
|
||||
return CHANNEL_RC_BAD_PROC;
|
||||
}
|
||||
|
||||
if (!channels->is_connected)
|
||||
{
|
||||
DEBUG_CHANNELS("error not connected");
|
||||
return CHANNEL_RC_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
pChannelOpenData = freerdp_channels_find_channel_open_data_by_name(channels, pChannelName);
|
||||
|
||||
if (!pChannelOpenData)
|
||||
{
|
||||
DEBUG_CHANNELS("error channel name");
|
||||
return CHANNEL_RC_UNKNOWN_CHANNEL_NAME;
|
||||
}
|
||||
|
||||
if (pChannelOpenData->flags == 2)
|
||||
{
|
||||
DEBUG_CHANNELS("error channel already open");
|
||||
return CHANNEL_RC_ALREADY_OPEN;
|
||||
}
|
||||
|
||||
pChannelOpenData->flags = 2; /* open */
|
||||
pChannelOpenData->pInterface = pInterface;
|
||||
pChannelOpenData->pChannelOpenEventProc = pChannelOpenEventProc;
|
||||
*pOpenHandle = pChannelOpenData->OpenHandle;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelClose(UINT32 openHandle)
|
||||
{
|
||||
int index;
|
||||
rdpChannels* channels;
|
||||
CHANNEL_OPEN_DATA* pChannelOpenData;
|
||||
|
||||
DEBUG_CHANNELS("enter");
|
||||
|
||||
channels = freerdp_channels_find_by_open_handle(openHandle, &index);
|
||||
|
||||
if ((channels == NULL) || (index < 0) || (index >= CHANNEL_MAX_COUNT))
|
||||
{
|
||||
DEBUG_CHANNELS("error bad channels");
|
||||
return CHANNEL_RC_BAD_CHANNEL_HANDLE;
|
||||
}
|
||||
|
||||
pChannelOpenData = &channels->openDataList[index];
|
||||
|
||||
if (pChannelOpenData->flags != 2)
|
||||
{
|
||||
DEBUG_CHANNELS("error not open");
|
||||
return CHANNEL_RC_NOT_OPEN;
|
||||
}
|
||||
|
||||
pChannelOpenData->flags = 0;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
30
channels/client/open.h
Normal file
30
channels/client/open.h
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Client Channels
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_PRIVATE_CLIENT_CHANNELS_OPEN
|
||||
#define FREERDP_PRIVATE_CLIENT_CHANNELS_OPEN
|
||||
|
||||
#include "channels.h"
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelOpen(void* pInitHandle, UINT32* pOpenHandle,
|
||||
char* pChannelName, PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc);
|
||||
|
||||
UINT32 FreeRDP_VirtualChannelClose(UINT32 openHandle);
|
||||
|
||||
#endif /* FREERDP_PRIVATE_CLIENT_CHANNELS_OPEN */
|
@ -3,8 +3,6 @@
|
||||
* 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.
|
||||
@ -19,15 +17,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/dvc.h>
|
||||
#include <freerdp/channels/rdpdr.h>
|
||||
#include "tables.h"
|
||||
|
||||
${CLIENT_STATIC_TYPEDEFS}
|
||||
${CLIENT_STATIC_ENTRY_IMPORTS}
|
||||
${CLIENT_STATIC_SUBSYSTEM_IMPORTS}
|
||||
|
||||
${CLIENT_STATIC_ENTRY_TABLES}
|
||||
|
||||
${CLIENT_STATIC_ENTRY_TABLES_LIST}
|
||||
|
||||
${CLIENT_STATIC_SUBSYSTEM_IMPORTS}
|
||||
|
||||
${CLIENT_STATIC_SUBSYSTEM_TABLES}
|
||||
|
||||
${CLIENT_STATIC_ADDIN_TABLE}
|
||||
|
||||
|
@ -17,89 +17,34 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <winpr/platform.h>
|
||||
#include <winpr/wtsapi.h>
|
||||
#include <freerdp/svc.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <freerdp/channels/rdpdr.h>
|
||||
|
||||
/* The 'entry' function pointers have variable arguments. */
|
||||
WINPR_PRAGMA_DIAG_PUSH
|
||||
WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES
|
||||
|
||||
typedef UINT(VCAPITYPE* static_entry_fn_t)();
|
||||
typedef struct
|
||||
struct _STATIC_ENTRY
|
||||
{
|
||||
const char* name;
|
||||
static_entry_fn_t entry;
|
||||
} STATIC_ENTRY;
|
||||
const void* entry;
|
||||
};
|
||||
typedef struct _STATIC_ENTRY STATIC_ENTRY;
|
||||
|
||||
typedef BOOL(VCAPITYPE* static_entry_vc_fn_t)(PCHANNEL_ENTRY_POINTS);
|
||||
typedef struct
|
||||
struct _STATIC_ENTRY_TABLE
|
||||
{
|
||||
const char* name;
|
||||
static_entry_vc_fn_t entry;
|
||||
} STATIC_ENTRY_VC;
|
||||
const STATIC_ENTRY* table;
|
||||
};
|
||||
typedef struct _STATIC_ENTRY_TABLE STATIC_ENTRY_TABLE;
|
||||
|
||||
typedef BOOL(VCAPITYPE* static_entry_vcex_fn_t)(PCHANNEL_ENTRY_POINTS, PVOID);
|
||||
typedef struct
|
||||
{
|
||||
const char* name;
|
||||
static_entry_vcex_fn_t entry;
|
||||
} STATIC_ENTRY_VCEX;
|
||||
|
||||
typedef UINT(VCAPITYPE* static_entry_dvc_fn_t)(IDRDYNVC_ENTRY_POINTS*);
|
||||
typedef struct
|
||||
{
|
||||
const char* name;
|
||||
static_entry_dvc_fn_t entry;
|
||||
} STATIC_ENTRY_DVC;
|
||||
|
||||
typedef UINT(VCAPITYPE* static_entry_dse_fn_t)(PDEVICE_SERVICE_ENTRY_POINTS);
|
||||
typedef struct
|
||||
{
|
||||
const char* name;
|
||||
static_entry_dse_fn_t entry;
|
||||
} STATIC_ENTRY_DSE;
|
||||
|
||||
typedef union
|
||||
{
|
||||
const STATIC_ENTRY* cse;
|
||||
const STATIC_ENTRY_VC* csevc;
|
||||
const STATIC_ENTRY_VCEX* csevcex;
|
||||
const STATIC_ENTRY_DVC* csedvc;
|
||||
const STATIC_ENTRY_DSE* csedse;
|
||||
} static_entry_u;
|
||||
|
||||
typedef union
|
||||
{
|
||||
static_entry_fn_t cse;
|
||||
static_entry_vc_fn_t csevc;
|
||||
static_entry_vcex_fn_t csevcex;
|
||||
static_entry_dvc_fn_t csedvc;
|
||||
static_entry_dse_fn_t csedse;
|
||||
} static_entry_fn_u;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char* name;
|
||||
static_entry_u table;
|
||||
} STATIC_ENTRY_TABLE;
|
||||
|
||||
typedef UINT(VCAPITYPE* static_subsystem_entry_fn_t)(void*);
|
||||
typedef struct
|
||||
struct _STATIC_SUBSYSTEM_ENTRY
|
||||
{
|
||||
const char* name;
|
||||
const char* type;
|
||||
static_subsystem_entry_fn_t entry;
|
||||
} STATIC_SUBSYSTEM_ENTRY;
|
||||
const void* entry;
|
||||
};
|
||||
typedef struct _STATIC_SUBSYSTEM_ENTRY STATIC_SUBSYSTEM_ENTRY;
|
||||
|
||||
typedef struct
|
||||
struct _STATIC_ADDIN_TABLE
|
||||
{
|
||||
const char* name;
|
||||
const char* type;
|
||||
static_entry_fn_u entry;
|
||||
const void* entry;
|
||||
const STATIC_SUBSYSTEM_ENTRY* table;
|
||||
} STATIC_ADDIN_TABLE;
|
||||
|
||||
WINPR_PRAGMA_DIAG_POP
|
||||
};
|
||||
typedef struct _STATIC_ADDIN_TABLE STATIC_ADDIN_TABLE;
|
||||
|
@ -21,6 +21,3 @@ if(WITH_CLIENT_CHANNELS)
|
||||
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
||||
if(WITH_SERVER_CHANNELS)
|
||||
add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
|
||||
define_channel_options(NAME "cliprdr" TYPE "static"
|
||||
DESCRIPTION "Clipboard Virtual Channel Extension"
|
||||
|
@ -18,15 +18,28 @@
|
||||
define_channel_client("cliprdr")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
cliprdr_constants.h
|
||||
cliprdr_format.c
|
||||
cliprdr_format.h
|
||||
cliprdr_main.c
|
||||
cliprdr_main.h
|
||||
../cliprdr_common.h
|
||||
../cliprdr_common.c
|
||||
)
|
||||
cliprdr_main.h)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr freerdp
|
||||
)
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-utils)
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE winpr
|
||||
MODULES winpr-crt)
|
||||
|
||||
if(NOT STATIC_CHANNELS)
|
||||
install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_PLUGIN_PATH})
|
||||
endif()
|
||||
|
||||
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client")
|
||||
|
58
channels/cliprdr/client/cliprdr_constants.h
Normal file
58
channels/cliprdr/client/cliprdr_constants.h
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Clipboard Virtual Channel
|
||||
*
|
||||
* Copyright 2009-2011 Jay Sorg
|
||||
* 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 __CLIPRDR_CONSTANTS
|
||||
#define __CLIPRDR_CONSTANTS
|
||||
|
||||
/* CLIPRDR_HEADER.msgType */
|
||||
#define CB_MONITOR_READY 0x0001
|
||||
#define CB_FORMAT_LIST 0x0002
|
||||
#define CB_FORMAT_LIST_RESPONSE 0x0003
|
||||
#define CB_FORMAT_DATA_REQUEST 0x0004
|
||||
#define CB_FORMAT_DATA_RESPONSE 0x0005
|
||||
#define CB_TEMP_DIRECTORY 0x0006
|
||||
#define CB_CLIP_CAPS 0x0007
|
||||
#define CB_FILECONTENTS_REQUEST 0x0008
|
||||
#define CB_FILECONTENTS_RESPONSE 0x0009
|
||||
#define CB_LOCK_CLIPDATA 0x000A
|
||||
#define CB_UNLOCK_CLIPDATA 0x000B
|
||||
|
||||
/* CLIPRDR_HEADER.msgFlags */
|
||||
#define CB_RESPONSE_OK 0x0001
|
||||
#define CB_RESPONSE_FAIL 0x0002
|
||||
#define CB_ASCII_NAMES 0x0004
|
||||
|
||||
/* CLIPRDR_CAPS_SET.capabilitySetType */
|
||||
#define CB_CAPSTYPE_GENERAL 0x0001
|
||||
|
||||
/* CLIPRDR_GENERAL_CAPABILITY.lengthCapability */
|
||||
#define CB_CAPSTYPE_GENERAL_LEN 12
|
||||
|
||||
/* CLIPRDR_GENERAL_CAPABILITY.version */
|
||||
#define CB_CAPS_VERSION_1 0x00000001
|
||||
#define CB_CAPS_VERSION_2 0x00000002
|
||||
|
||||
/* CLIPRDR_GENERAL_CAPABILITY.generalFlags */
|
||||
#define CB_USE_LONG_FORMAT_NAMES 0x00000002
|
||||
#define CB_STREAM_FILECLIP_ENABLED 0x00000004
|
||||
#define CB_FILECLIP_NO_FILE_PATHS 0x00000008
|
||||
#define CB_CAN_LOCK_CLIPDATA 0x00000010
|
||||
|
||||
#endif /* __CLIPRDR_CONSTANTS */
|
@ -4,8 +4,6 @@
|
||||
*
|
||||
* 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.
|
||||
@ -20,225 +18,345 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/print.h>
|
||||
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/settings.h>
|
||||
#include <freerdp/constants.h>
|
||||
#include <freerdp/utils/svc_plugin.h>
|
||||
#include <freerdp/client/cliprdr.h>
|
||||
|
||||
#include "cliprdr_constants.h"
|
||||
#include "cliprdr_main.h"
|
||||
#include "cliprdr_format.h"
|
||||
#include "../cliprdr_common.h"
|
||||
|
||||
CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
|
||||
const UINT32 checkMask)
|
||||
#define CFSTR_HTML "H\0T\0M\0L\0 \0F\0o\0r\0m\0a\0t\0\0"
|
||||
#define CFSTR_PNG "P\0N\0G\0\0"
|
||||
#define CFSTR_JPEG "J\0F\0I\0F\0\0"
|
||||
#define CFSTR_GIF "G\0I\0F\0\0"
|
||||
|
||||
void cliprdr_process_format_list_event(cliprdrPlugin* cliprdr, RDP_CB_FORMAT_LIST_EVENT* cb_event)
|
||||
{
|
||||
const UINT32 maskData =
|
||||
checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
|
||||
const UINT32 maskFiles =
|
||||
checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
|
||||
WINPR_ASSERT(list);
|
||||
int i;
|
||||
wStream* s;
|
||||
|
||||
CLIPRDR_FORMAT_LIST filtered = { 0 };
|
||||
filtered.common.msgType = CB_FORMAT_LIST;
|
||||
filtered.numFormats = list->numFormats;
|
||||
filtered.formats = calloc(filtered.numFormats, sizeof(CLIPRDR_FORMAT));
|
||||
DEBUG_CLIPRDR("Sending Clipboard Format List");
|
||||
|
||||
size_t wpos = 0;
|
||||
if ((mask & checkMask) == checkMask)
|
||||
if (cb_event->raw_format_data)
|
||||
{
|
||||
for (size_t x = 0; x < list->numFormats; x++)
|
||||
{
|
||||
const CLIPRDR_FORMAT* format = &list->formats[x];
|
||||
CLIPRDR_FORMAT* cur = &filtered.formats[x];
|
||||
cur->formatId = format->formatId;
|
||||
if (format->formatName)
|
||||
cur->formatName = _strdup(format->formatName);
|
||||
wpos++;
|
||||
}
|
||||
s = cliprdr_packet_new(CB_FORMAT_LIST, 0, cb_event->raw_format_data_size);
|
||||
Stream_Write(s, cb_event->raw_format_data, cb_event->raw_format_data_size);
|
||||
}
|
||||
else if ((mask & maskFiles) != 0)
|
||||
else
|
||||
{
|
||||
for (size_t x = 0; x < list->numFormats; x++)
|
||||
wStream* body = Stream_New(NULL, 64);
|
||||
|
||||
for (i = 0; i < cb_event->num_formats; i++)
|
||||
{
|
||||
const CLIPRDR_FORMAT* format = &list->formats[x];
|
||||
CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
|
||||
const char* name;
|
||||
int name_length;
|
||||
|
||||
if (!format->formatName)
|
||||
continue;
|
||||
if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0 ||
|
||||
strcmp(format->formatName, type_FileContents) == 0)
|
||||
switch (cb_event->formats[i])
|
||||
{
|
||||
cur->formatId = format->formatId;
|
||||
cur->formatName = _strdup(format->formatName);
|
||||
wpos++;
|
||||
case CB_FORMAT_HTML:
|
||||
name = CFSTR_HTML; name_length = sizeof(CFSTR_HTML);
|
||||
break;
|
||||
case CB_FORMAT_PNG:
|
||||
name = CFSTR_PNG; name_length = sizeof(CFSTR_PNG);
|
||||
break;
|
||||
case CB_FORMAT_JPEG:
|
||||
name = CFSTR_JPEG; name_length = sizeof(CFSTR_JPEG);
|
||||
break;
|
||||
case CB_FORMAT_GIF:
|
||||
name = CFSTR_GIF; name_length = sizeof(CFSTR_GIF);
|
||||
break;
|
||||
default:
|
||||
name = "\0\0";
|
||||
name_length = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cliprdr->use_long_format_names)
|
||||
name_length = 32;
|
||||
|
||||
Stream_EnsureRemainingCapacity(body, 4 + name_length);
|
||||
|
||||
Stream_Write_UINT32(body, cb_event->formats[i]);
|
||||
Stream_Write(body, name, name_length);
|
||||
}
|
||||
|
||||
Stream_SealLength(body);
|
||||
s = cliprdr_packet_new(CB_FORMAT_LIST, 0, Stream_Length(body));
|
||||
Stream_Write(s, Stream_Buffer(body), Stream_Length(body));
|
||||
Stream_Free(body, TRUE);
|
||||
}
|
||||
else if ((mask & maskData) != 0)
|
||||
|
||||
cliprdr_packet_send(cliprdr, s);
|
||||
}
|
||||
|
||||
static void cliprdr_send_format_list_response(cliprdrPlugin* cliprdr)
|
||||
{
|
||||
wStream* s;
|
||||
DEBUG_CLIPRDR("Sending Clipboard Format List Response");
|
||||
s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, CB_RESPONSE_OK, 0);
|
||||
cliprdr_packet_send(cliprdr, s);
|
||||
}
|
||||
|
||||
void cliprdr_process_short_format_names(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, UINT16 flags)
|
||||
{
|
||||
int i;
|
||||
BOOL ascii;
|
||||
int num_formats;
|
||||
CLIPRDR_FORMAT_NAME* format_name;
|
||||
|
||||
num_formats = length / 36;
|
||||
|
||||
if (num_formats <= 0)
|
||||
{
|
||||
for (size_t x = 0; x < list->numFormats; x++)
|
||||
cliprdr->format_names = NULL;
|
||||
cliprdr->num_format_names = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_formats * 36 != length)
|
||||
DEBUG_WARN("dataLen %d not divided by 36!", length);
|
||||
|
||||
ascii = (flags & CB_ASCII_NAMES) ? TRUE : FALSE;
|
||||
|
||||
cliprdr->format_names = (CLIPRDR_FORMAT_NAME*) malloc(sizeof(CLIPRDR_FORMAT_NAME) * num_formats);
|
||||
cliprdr->num_format_names = num_formats;
|
||||
|
||||
for (i = 0; i < num_formats; i++)
|
||||
{
|
||||
format_name = &cliprdr->format_names[i];
|
||||
|
||||
Stream_Read_UINT32(s, format_name->id);
|
||||
|
||||
if (ascii)
|
||||
{
|
||||
const CLIPRDR_FORMAT* format = &list->formats[x];
|
||||
CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
|
||||
|
||||
if (!format->formatName ||
|
||||
(strcmp(format->formatName, type_FileGroupDescriptorW) != 0 &&
|
||||
strcmp(format->formatName, type_FileContents) != 0))
|
||||
{
|
||||
cur->formatId = format->formatId;
|
||||
if (format->formatName)
|
||||
cur->formatName = _strdup(format->formatName);
|
||||
wpos++;
|
||||
}
|
||||
format_name->name = _strdup((char*) s->pointer);
|
||||
format_name->length = strlen(format_name->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
format_name->name = NULL;
|
||||
format_name->length = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) s->pointer, 32 / 2, &format_name->name, 0, NULL, NULL);
|
||||
}
|
||||
|
||||
Stream_Seek(s, 32);
|
||||
}
|
||||
WINPR_ASSERT(wpos <= UINT32_MAX);
|
||||
filtered.numFormats = (UINT32)wpos;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
|
||||
UINT16 msgFlags)
|
||||
void cliprdr_process_long_format_names(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, UINT16 flags)
|
||||
{
|
||||
CLIPRDR_FORMAT_LIST formatList = { 0 };
|
||||
CLIPRDR_FORMAT_LIST filteredFormatList = { 0 };
|
||||
CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
int allocated_formats = 8;
|
||||
BYTE* end_mark;
|
||||
CLIPRDR_FORMAT_NAME* format_name;
|
||||
|
||||
Stream_GetPointer(s, end_mark);
|
||||
end_mark += length;
|
||||
|
||||
cliprdr->format_names = (CLIPRDR_FORMAT_NAME*) malloc(sizeof(CLIPRDR_FORMAT_NAME) * allocated_formats);
|
||||
cliprdr->num_format_names = 0;
|
||||
|
||||
formatList.common.msgType = CB_FORMAT_LIST;
|
||||
formatList.common.msgFlags = msgFlags;
|
||||
formatList.common.dataLen = dataLen;
|
||||
|
||||
if ((error = cliprdr_read_format_list(s, &formatList, cliprdr->useLongFormatNames)))
|
||||
goto error_out;
|
||||
|
||||
const UINT32 mask =
|
||||
freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
|
||||
filteredFormatList = cliprdr_filter_format_list(
|
||||
&formatList, mask, CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
|
||||
if (filteredFormatList.numFormats == 0)
|
||||
goto error_out;
|
||||
|
||||
WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatList: numFormats: %" PRIu32 "",
|
||||
filteredFormatList.numFormats);
|
||||
|
||||
if (context->ServerFormatList)
|
||||
while (Stream_GetRemainingLength(s) >= 6)
|
||||
{
|
||||
if ((error = context->ServerFormatList(context, &filteredFormatList)))
|
||||
WLog_ERR(TAG, "ServerFormatList failed with error %" PRIu32 "", error);
|
||||
BYTE* p;
|
||||
int name_len;
|
||||
|
||||
if (cliprdr->num_format_names >= allocated_formats)
|
||||
{
|
||||
allocated_formats *= 2;
|
||||
cliprdr->format_names = (CLIPRDR_FORMAT_NAME*) realloc(cliprdr->format_names,
|
||||
sizeof(CLIPRDR_FORMAT_NAME) * allocated_formats);
|
||||
}
|
||||
|
||||
format_name = &cliprdr->format_names[cliprdr->num_format_names++];
|
||||
Stream_Read_UINT32(s, format_name->id);
|
||||
|
||||
format_name->name = NULL;
|
||||
format_name->length = 0;
|
||||
|
||||
for (p = Stream_Pointer(s), name_len = 0; p + 1 < end_mark; p += 2, name_len += 2)
|
||||
{
|
||||
if (*((unsigned short*) p) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
format_name->length = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(s), name_len / 2, &format_name->name, 0, NULL, NULL);
|
||||
|
||||
Stream_Seek(s, name_len + 2);
|
||||
}
|
||||
|
||||
error_out:
|
||||
cliprdr_free_format_list(&filteredFormatList);
|
||||
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)
|
||||
void cliprdr_process_format_list(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;
|
||||
int i;
|
||||
UINT32 format;
|
||||
BOOL supported;
|
||||
CLIPRDR_FORMAT_NAME* format_name;
|
||||
RDP_CB_FORMAT_LIST_EVENT* cb_event;
|
||||
|
||||
WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse");
|
||||
cb_event = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new(CliprdrChannel_Class,
|
||||
CliprdrChannel_FormatList, NULL, NULL);
|
||||
|
||||
formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
|
||||
formatListResponse.common.msgFlags = msgFlags;
|
||||
formatListResponse.common.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 = { 0 };
|
||||
CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest");
|
||||
|
||||
formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
|
||||
formatDataRequest.common.msgFlags = msgFlags;
|
||||
formatDataRequest.common.dataLen = dataLen;
|
||||
|
||||
if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
|
||||
return error;
|
||||
|
||||
const UINT32 mask =
|
||||
freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
|
||||
if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
|
||||
if (dataLen > 0)
|
||||
{
|
||||
return cliprdr_send_error_response(cliprdr, CB_FORMAT_DATA_RESPONSE);
|
||||
cb_event->raw_format_data = (BYTE*) malloc(dataLen);
|
||||
memcpy(cb_event->raw_format_data, Stream_Pointer(s), dataLen);
|
||||
cb_event->raw_format_data_size = dataLen;
|
||||
}
|
||||
|
||||
context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
|
||||
IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest);
|
||||
if (error)
|
||||
WLog_ERR(TAG, "ServerFormatDataRequest failed with error %" PRIu32 "!", error);
|
||||
if (cliprdr->use_long_format_names)
|
||||
cliprdr_process_long_format_names(cliprdr, s, dataLen, msgFlags);
|
||||
else
|
||||
cliprdr_process_short_format_names(cliprdr, s, dataLen, msgFlags);
|
||||
|
||||
return error;
|
||||
}
|
||||
if (cliprdr->num_format_names > 0)
|
||||
cb_event->formats = (UINT32*) malloc(sizeof(UINT32) * cliprdr->num_format_names);
|
||||
|
||||
/**
|
||||
* 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 = { 0 };
|
||||
CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
cb_event->num_formats = 0;
|
||||
|
||||
WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataResponse");
|
||||
|
||||
formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
|
||||
formatDataResponse.common.msgFlags = msgFlags;
|
||||
formatDataResponse.common.dataLen = dataLen;
|
||||
|
||||
if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
|
||||
return error;
|
||||
|
||||
const UINT32 mask =
|
||||
freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
|
||||
if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
|
||||
for (i = 0; i < cliprdr->num_format_names; i++)
|
||||
{
|
||||
WLog_WARN(TAG,
|
||||
"Received ServerFormatDataResponse but remote -> local clipboard is disabled");
|
||||
return CHANNEL_RC_OK;
|
||||
supported = TRUE;
|
||||
format_name = &cliprdr->format_names[i];
|
||||
format = format_name->id;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CB_FORMAT_TEXT:
|
||||
case CB_FORMAT_DIB:
|
||||
case CB_FORMAT_UNICODETEXT:
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
if (format_name->length > 0)
|
||||
{
|
||||
DEBUG_CLIPRDR("format: %s", format_name->name);
|
||||
|
||||
if (strcmp(format_name->name, "HTML Format") == 0)
|
||||
{
|
||||
format = CB_FORMAT_HTML;
|
||||
break;
|
||||
}
|
||||
if (strcmp(format_name->name, "PNG") == 0)
|
||||
{
|
||||
format = CB_FORMAT_PNG;
|
||||
break;
|
||||
}
|
||||
if (strcmp(format_name->name, "JFIF") == 0)
|
||||
{
|
||||
format = CB_FORMAT_JPEG;
|
||||
break;
|
||||
}
|
||||
if (strcmp(format_name->name, "GIF") == 0)
|
||||
{
|
||||
format = CB_FORMAT_GIF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
supported = FALSE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (supported)
|
||||
cb_event->formats[cb_event->num_formats++] = format;
|
||||
|
||||
if (format_name->length > 0)
|
||||
free(format_name->name);
|
||||
}
|
||||
|
||||
IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse);
|
||||
if (error)
|
||||
WLog_ERR(TAG, "ServerFormatDataResponse failed with error %" PRIu32 "!", error);
|
||||
free(cliprdr->format_names);
|
||||
cliprdr->format_names = NULL;
|
||||
|
||||
return error;
|
||||
cliprdr->num_format_names = 0;
|
||||
|
||||
svc_plugin_send_event((rdpSvcPlugin*) cliprdr, (wMessage*) cb_event);
|
||||
cliprdr_send_format_list_response(cliprdr);
|
||||
}
|
||||
|
||||
void cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags)
|
||||
{
|
||||
/* http://msdn.microsoft.com/en-us/library/hh872154.aspx */
|
||||
wMessage* event;
|
||||
|
||||
if ((msgFlags & CB_RESPONSE_FAIL) != 0)
|
||||
{
|
||||
/* In case of an error the clipboard will not be synchronized with the server.
|
||||
* Post this event to restart format negociation and data transfer. */
|
||||
event = freerdp_event_new(CliprdrChannel_Class, CliprdrChannel_MonitorReady, NULL, NULL);
|
||||
svc_plugin_send_event((rdpSvcPlugin*) cliprdr, event);
|
||||
}
|
||||
}
|
||||
|
||||
void cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags)
|
||||
{
|
||||
RDP_CB_DATA_REQUEST_EVENT* cb_event;
|
||||
|
||||
cb_event = (RDP_CB_DATA_REQUEST_EVENT*) freerdp_event_new(CliprdrChannel_Class,
|
||||
CliprdrChannel_DataRequest, NULL, NULL);
|
||||
|
||||
Stream_Read_UINT32(s, cb_event->format);
|
||||
svc_plugin_send_event((rdpSvcPlugin*) cliprdr, (wMessage*) cb_event);
|
||||
}
|
||||
|
||||
void cliprdr_process_format_data_response_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_RESPONSE_EVENT* cb_event)
|
||||
{
|
||||
wStream* s;
|
||||
|
||||
DEBUG_CLIPRDR("Sending Format Data Response");
|
||||
|
||||
if (cb_event->size > 0)
|
||||
{
|
||||
s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, CB_RESPONSE_OK, cb_event->size);
|
||||
Stream_Write(s, cb_event->data, cb_event->size);
|
||||
}
|
||||
else
|
||||
{
|
||||
s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, CB_RESPONSE_FAIL, 0);
|
||||
}
|
||||
|
||||
cliprdr_packet_send(cliprdr, s);
|
||||
}
|
||||
|
||||
void cliprdr_process_format_data_request_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_REQUEST_EVENT* cb_event)
|
||||
{
|
||||
wStream* s;
|
||||
|
||||
DEBUG_CLIPRDR("Sending Format Data Request");
|
||||
|
||||
s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4);
|
||||
Stream_Write_UINT32(s, cb_event->format);
|
||||
cliprdr_packet_send(cliprdr, s);
|
||||
}
|
||||
|
||||
void cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags)
|
||||
{
|
||||
RDP_CB_DATA_RESPONSE_EVENT* cb_event;
|
||||
|
||||
cb_event = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new(CliprdrChannel_Class,
|
||||
CliprdrChannel_DataResponse, NULL, NULL);
|
||||
|
||||
if (dataLen > 0)
|
||||
{
|
||||
cb_event->size = dataLen;
|
||||
cb_event->data = (BYTE*) malloc(dataLen);
|
||||
CopyMemory(cb_event->data, Stream_Pointer(s), dataLen);
|
||||
}
|
||||
|
||||
svc_plugin_send_event((rdpSvcPlugin*) cliprdr, (wMessage*) cb_event);
|
||||
}
|
||||
|
@ -4,8 +4,6 @@
|
||||
*
|
||||
* 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.
|
||||
@ -20,18 +18,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
|
||||
#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
|
||||
#ifndef __CLIPRDR_FORMAT_H
|
||||
#define __CLIPRDR_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);
|
||||
CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
|
||||
const UINT32 checkMask);
|
||||
void cliprdr_process_format_list_event(cliprdrPlugin* cliprdr, RDP_CB_FORMAT_LIST_EVENT* cb_event);
|
||||
void cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags);
|
||||
void cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags);
|
||||
|
||||
#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H */
|
||||
void cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags);
|
||||
void cliprdr_process_format_data_response_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_RESPONSE_EVENT* cb_event);
|
||||
|
||||
void cliprdr_process_format_data_request_event(cliprdrPlugin* cliprdr, RDP_CB_DATA_REQUEST_EVENT* cb_event);
|
||||
void cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, UINT16 msgFlags);
|
||||
|
||||
#endif /* __CLIPRDR_FORMAT_H */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,6 @@
|
||||
*
|
||||
* 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.
|
||||
@ -19,43 +18,41 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
|
||||
#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
|
||||
#ifndef __CLIPRDR_MAIN_H
|
||||
#define __CLIPRDR_MAIN_H
|
||||
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include <freerdp/svc.h>
|
||||
#include <freerdp/addin.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
#include <freerdp/client/cliprdr.h>
|
||||
#include <freerdp/utils/debug.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("cliprdr.client")
|
||||
|
||||
typedef struct
|
||||
struct _CLIPRDR_FORMAT_NAME
|
||||
{
|
||||
CHANNEL_DEF channelDef;
|
||||
CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
|
||||
UINT32 id;
|
||||
char* name;
|
||||
int length;
|
||||
};
|
||||
typedef struct _CLIPRDR_FORMAT_NAME CLIPRDR_FORMAT_NAME;
|
||||
|
||||
CliprdrClientContext* context;
|
||||
struct cliprdr_plugin
|
||||
{
|
||||
rdpSvcPlugin plugin;
|
||||
BOOL received_caps;
|
||||
BOOL use_long_format_names;
|
||||
BOOL stream_fileclip_enabled;
|
||||
BOOL fileclip_no_file_paths;
|
||||
BOOL can_lock_clipdata;
|
||||
CLIPRDR_FORMAT_NAME* format_names;
|
||||
int num_format_names;
|
||||
};
|
||||
typedef struct cliprdr_plugin cliprdrPlugin;
|
||||
|
||||
wLog* log;
|
||||
void* InitHandle;
|
||||
DWORD OpenHandle;
|
||||
void* MsgsHandle;
|
||||
wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen);
|
||||
void cliprdr_packet_send(cliprdrPlugin* cliprdr, wStream* data_out);
|
||||
|
||||
BOOL capabilitiesReceived;
|
||||
BOOL useLongFormatNames;
|
||||
BOOL streamFileClipEnabled;
|
||||
BOOL fileClipNoFilePaths;
|
||||
BOOL canLockClipData;
|
||||
BOOL hasHugeFileSupport;
|
||||
BOOL initialFormatListSent;
|
||||
} cliprdrPlugin;
|
||||
#ifdef WITH_DEBUG_CLIPRDR
|
||||
#define DEBUG_CLIPRDR(fmt, ...) DEBUG_CLASS(CLIPRDR, fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_CLIPRDR(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr);
|
||||
UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type);
|
||||
|
||||
extern const char type_FileGroupDescriptorW[];
|
||||
extern const char type_FileContents[];
|
||||
|
||||
#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H */
|
||||
#endif /* __CLIPRDR_MAIN_H */
|
||||
|
3
channels/cliprdr/client/module.def
Normal file
3
channels/cliprdr/client/module.def
Normal file
@ -0,0 +1,3 @@
|
||||
LIBRARY "cliprdr"
|
||||
EXPORTS
|
||||
VirtualChannelEntry @1
|
@ -1,558 +0,0 @@
|
||||
/**
|
||||
* 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 const char* CB_MSG_TYPE_STR(UINT32 type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CB_TYPE_NONE:
|
||||
return "CB_TYPE_NONE";
|
||||
case CB_MONITOR_READY:
|
||||
return "CB_MONITOR_READY";
|
||||
case CB_FORMAT_LIST:
|
||||
return "CB_FORMAT_LIST";
|
||||
case CB_FORMAT_LIST_RESPONSE:
|
||||
return "CB_FORMAT_LIST_RESPONSE";
|
||||
case CB_FORMAT_DATA_REQUEST:
|
||||
return "CB_FORMAT_DATA_REQUEST";
|
||||
case CB_FORMAT_DATA_RESPONSE:
|
||||
return "CB_FORMAT_DATA_RESPONSE";
|
||||
case CB_TEMP_DIRECTORY:
|
||||
return "CB_TEMP_DIRECTORY";
|
||||
case CB_CLIP_CAPS:
|
||||
return "CB_CLIP_CAPS";
|
||||
case CB_FILECONTENTS_REQUEST:
|
||||
return "CB_FILECONTENTS_REQUEST";
|
||||
case CB_FILECONTENTS_RESPONSE:
|
||||
return "CB_FILECONTENTS_RESPONSE";
|
||||
case CB_LOCK_CLIPDATA:
|
||||
return "CB_LOCK_CLIPDATA";
|
||||
case CB_UNLOCK_CLIPDATA:
|
||||
return "CB_UNLOCK_CLIPDATA";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* CB_MSG_TYPE_STRING(UINT16 type, char* buffer, size_t size)
|
||||
{
|
||||
(void)_snprintf(buffer, size, "%s [0x%04" PRIx16 "]", CB_MSG_TYPE_STR(type), type);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char* CB_MSG_FLAGS_STRING(UINT16 msgFlags, char* buffer, size_t size)
|
||||
{
|
||||
if ((msgFlags & CB_RESPONSE_OK) != 0)
|
||||
winpr_str_append("CB_RESPONSE_OK", buffer, size, "|");
|
||||
if ((msgFlags & CB_RESPONSE_FAIL) != 0)
|
||||
winpr_str_append("CB_RESPONSE_FAIL", buffer, size, "|");
|
||||
if ((msgFlags & CB_ASCII_NAMES) != 0)
|
||||
winpr_str_append("CB_ASCII_NAMES", buffer, size, "|");
|
||||
|
||||
const size_t len = strnlen(buffer, size);
|
||||
if (!len)
|
||||
winpr_str_append("NONE", buffer, size, "");
|
||||
|
||||
char val[32] = { 0 };
|
||||
(void)_snprintf(val, sizeof(val), "[0x%04" PRIx16 "]", msgFlags);
|
||||
winpr_str_append(val, buffer, size, "|");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
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, "cbRequested must be %" PRIu32 ", got %" PRIu32 "", sizeof(UINT64),
|
||||
request->cbRequested);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (request->nPositionHigh != 0 || request->nPositionLow != 0)
|
||||
{
|
||||
WLog_ERR(TAG, "nPositionHigh and nPositionLow must be set to 0");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen)
|
||||
{
|
||||
wStream* s = NULL;
|
||||
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_Write_UINT32(s, 0);
|
||||
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 = NULL;
|
||||
|
||||
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 = NULL;
|
||||
|
||||
if (!unlockClipboardData)
|
||||
return NULL;
|
||||
|
||||
s = cliprdr_packet_new(CB_UNLOCK_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 = NULL;
|
||||
|
||||
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 = NULL;
|
||||
|
||||
if (!response)
|
||||
return NULL;
|
||||
|
||||
s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, response->common.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, BOOL useAsciiNames)
|
||||
{
|
||||
WINPR_ASSERT(formatList);
|
||||
|
||||
if (formatList->common.msgType != CB_FORMAT_LIST)
|
||||
WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatList->common.msgType);
|
||||
|
||||
if (useLongFormatNames && useAsciiNames)
|
||||
WLog_WARN(TAG, "called with invalid arguments useLongFormatNames=true && "
|
||||
"useAsciiNames=true. useAsciiNames requires "
|
||||
"useLongFormatNames=false, ignoring argument.");
|
||||
|
||||
const UINT32 length = formatList->numFormats * 36;
|
||||
const size_t formatNameCharSize =
|
||||
(useLongFormatNames || !useAsciiNames) ? sizeof(WCHAR) : sizeof(CHAR);
|
||||
|
||||
wStream* s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length);
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "cliprdr_packet_new failed!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (UINT32 index = 0; index < formatList->numFormats; index++)
|
||||
{
|
||||
const CLIPRDR_FORMAT* format = &(formatList->formats[index]);
|
||||
|
||||
const char* szFormatName = format->formatName;
|
||||
size_t formatNameLength = 0;
|
||||
if (szFormatName)
|
||||
formatNameLength = strlen(szFormatName);
|
||||
|
||||
size_t formatNameMaxLength = formatNameLength + 1; /* Ensure '\0' termination in output */
|
||||
if (!Stream_EnsureRemainingCapacity(s,
|
||||
4 + MAX(32, formatNameMaxLength * formatNameCharSize)))
|
||||
goto fail;
|
||||
|
||||
Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */
|
||||
|
||||
if (!useLongFormatNames)
|
||||
{
|
||||
formatNameMaxLength = useAsciiNames ? 32 : 16;
|
||||
formatNameLength = MIN(formatNameMaxLength - 1, formatNameLength);
|
||||
}
|
||||
|
||||
if (szFormatName && (formatNameLength > 0))
|
||||
{
|
||||
if (useAsciiNames)
|
||||
{
|
||||
Stream_Write(s, szFormatName, formatNameLength);
|
||||
Stream_Zero(s, formatNameMaxLength - formatNameLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Stream_Write_UTF16_String_From_UTF8(s, formatNameMaxLength, szFormatName,
|
||||
formatNameLength, TRUE) < 0)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
Stream_Zero(s, formatNameMaxLength * formatNameCharSize);
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
fail:
|
||||
Stream_Free(s, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UINT cliprdr_read_unlock_clipdata(wStream* s, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
|
||||
{
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
|
||||
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_CheckAndLogRequiredLength(TAG, s, 4))
|
||||
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_CheckAndLogRequiredLength(TAG, s, response->common.dataLen))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
if (response->common.dataLen)
|
||||
response->requestedFormatData = Stream_ConstPointer(s);
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
UINT cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* request)
|
||||
{
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
|
||||
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_CheckAndLogRequiredLength(TAG, s, 4))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
Stream_Read_UINT32(s, response->streamId); /* streamId (4 bytes) */
|
||||
response->requestedData = Stream_ConstPointer(s); /* requestedFileContentsData */
|
||||
|
||||
WINPR_ASSERT(response->common.dataLen >= 4);
|
||||
response->cbRequested = response->common.dataLen - 4;
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList, BOOL useLongFormatNames)
|
||||
{
|
||||
UINT32 index = 0;
|
||||
size_t formatNameLength = 0;
|
||||
const char* szFormatName = NULL;
|
||||
const WCHAR* wszFormatName = NULL;
|
||||
wStream sub1buffer = { 0 };
|
||||
CLIPRDR_FORMAT* formats = NULL;
|
||||
UINT error = ERROR_INTERNAL_ERROR;
|
||||
|
||||
const BOOL asciiNames = (formatList->common.msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE;
|
||||
|
||||
index = 0;
|
||||
/* empty format list */
|
||||
formatList->formats = NULL;
|
||||
formatList->numFormats = 0;
|
||||
|
||||
wStream* sub1 =
|
||||
Stream_StaticConstInit(&sub1buffer, Stream_ConstPointer(s), formatList->common.dataLen);
|
||||
if (!Stream_SafeSeek(s, formatList->common.dataLen))
|
||||
return ERROR_INVALID_DATA;
|
||||
|
||||
if (!formatList->common.dataLen)
|
||||
{
|
||||
}
|
||||
else if (!useLongFormatNames)
|
||||
{
|
||||
const size_t cap = Stream_Capacity(sub1) / 36ULL;
|
||||
if (cap > UINT32_MAX)
|
||||
{
|
||||
WLog_ERR(TAG, "Invalid short format list length: %" PRIuz "", cap);
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
formatList->numFormats = (UINT32)cap;
|
||||
|
||||
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)
|
||||
{
|
||||
CLIPRDR_FORMAT* format = &formats[index];
|
||||
|
||||
Stream_Read_UINT32(sub1, format->formatId); /* formatId (4 bytes) */
|
||||
|
||||
/* 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 = Stream_ConstPointer(sub1);
|
||||
wszFormatName = Stream_ConstPointer(sub1);
|
||||
if (!Stream_SafeSeek(sub1, 32))
|
||||
goto error_out;
|
||||
|
||||
free(format->formatName);
|
||||
format->formatName = NULL;
|
||||
|
||||
if (asciiNames)
|
||||
{
|
||||
if (szFormatName[0])
|
||||
{
|
||||
/* ensure null termination */
|
||||
format->formatName = strndup(szFormatName, 31);
|
||||
if (!format->formatName)
|
||||
{
|
||||
WLog_ERR(TAG, "malloc failed!");
|
||||
error = CHANNEL_RC_NO_MEMORY;
|
||||
goto error_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wszFormatName[0])
|
||||
{
|
||||
format->formatName = ConvertWCharNToUtf8Alloc(wszFormatName, 16, NULL);
|
||||
if (!format->formatName)
|
||||
goto error_out;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wStream sub2buffer = sub1buffer;
|
||||
wStream* sub2 = &sub2buffer;
|
||||
|
||||
while (Stream_GetRemainingLength(sub1) > 0)
|
||||
{
|
||||
size_t rest = 0;
|
||||
if (!Stream_SafeSeek(sub1, 4)) /* formatId (4 bytes) */
|
||||
goto error_out;
|
||||
|
||||
wszFormatName = Stream_ConstPointer(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 = 0;
|
||||
CLIPRDR_FORMAT* format = &formats[index];
|
||||
|
||||
Stream_Read_UINT32(sub2, format->formatId); /* formatId (4 bytes) */
|
||||
|
||||
free(format->formatName);
|
||||
format->formatName = NULL;
|
||||
|
||||
wszFormatName = Stream_ConstPointer(sub2);
|
||||
rest = Stream_GetRemainingLength(sub2);
|
||||
formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR));
|
||||
if (!Stream_SafeSeek(sub2, (formatNameLength + 1) * sizeof(WCHAR)))
|
||||
goto error_out;
|
||||
|
||||
if (formatNameLength)
|
||||
{
|
||||
format->formatName =
|
||||
ConvertWCharNToUtf8Alloc(wszFormatName, formatNameLength, NULL);
|
||||
if (!format->formatName)
|
||||
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)
|
||||
{
|
||||
if (formatList == NULL)
|
||||
return;
|
||||
|
||||
if (formatList->formats)
|
||||
{
|
||||
for (UINT32 index = 0; index < formatList->numFormats; index++)
|
||||
{
|
||||
free(formatList->formats[index].formatName);
|
||||
}
|
||||
|
||||
free(formatList->formats);
|
||||
formatList->formats = NULL;
|
||||
formatList->numFormats = 0;
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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 const char* CB_MSG_TYPE_STRING(UINT16 type, char* buffer, size_t size);
|
||||
FREERDP_LOCAL const char* CB_MSG_FLAGS_STRING(UINT16 msgFlags, char* buffer, size_t size);
|
||||
|
||||
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, BOOL useAsciiNames);
|
||||
|
||||
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 */
|
@ -1,30 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp
|
||||
)
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
|
File diff suppressed because it is too large
Load Diff
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
typedef struct
|
||||
{
|
||||
HANDLE vcm;
|
||||
HANDLE Thread;
|
||||
HANDLE StopEvent;
|
||||
void* ChannelHandle;
|
||||
HANDLE ChannelEvent;
|
||||
|
||||
wStream* s;
|
||||
char temporaryDirectory[260];
|
||||
} CliprdrServerPrivate;
|
||||
|
||||
#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H */
|
@ -1,26 +0,0 @@
|
||||
# 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()
|
@ -1,12 +0,0 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
|
||||
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})
|
@ -1,32 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
winpr
|
||||
)
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
|
@ -1,323 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/assert.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 <freerdp/client/channels.h>
|
||||
|
||||
#include "disp_main.h"
|
||||
#include "../disp_common.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GENERIC_DYNVC_PLUGIN base;
|
||||
|
||||
DispClientContext* context;
|
||||
UINT32 MaxNumMonitors;
|
||||
UINT32 MaxMonitorAreaFactorA;
|
||||
UINT32 MaxMonitorAreaFactorB;
|
||||
} DISP_PLUGIN;
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT
|
||||
disp_send_display_control_monitor_layout_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32 NumMonitors,
|
||||
const DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
|
||||
{
|
||||
UINT status = 0;
|
||||
wStream* s = NULL;
|
||||
DISP_PLUGIN* disp = NULL;
|
||||
UINT32 MonitorLayoutSize = 0;
|
||||
DISPLAY_CONTROL_HEADER header = { 0 };
|
||||
|
||||
WINPR_ASSERT(callback);
|
||||
WINPR_ASSERT(Monitors || (NumMonitors == 0));
|
||||
|
||||
disp = (DISP_PLUGIN*)callback->plugin;
|
||||
WINPR_ASSERT(disp);
|
||||
|
||||
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, "NumMonitors=%" PRIu32 "", NumMonitors);
|
||||
|
||||
for (UINT32 index = 0; index < NumMonitors; index++)
|
||||
{
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT current = Monitors[index];
|
||||
current.Width -= (current.Width % 2);
|
||||
|
||||
if (current.Width < 200)
|
||||
current.Width = 200;
|
||||
|
||||
if (current.Width > 8192)
|
||||
current.Width = 8192;
|
||||
|
||||
if (current.Width % 2)
|
||||
current.Width++;
|
||||
|
||||
if (current.Height < 200)
|
||||
current.Height = 200;
|
||||
|
||||
if (current.Height > 8192)
|
||||
current.Height = 8192;
|
||||
|
||||
Stream_Write_UINT32(s, current.Flags); /* Flags (4 bytes) */
|
||||
Stream_Write_INT32(s, current.Left); /* Left (4 bytes) */
|
||||
Stream_Write_INT32(s, current.Top); /* Top (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.Width); /* Width (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.Height); /* Height (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.PhysicalWidth); /* PhysicalWidth (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.PhysicalHeight); /* PhysicalHeight (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.Orientation); /* Orientation (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */
|
||||
Stream_Write_UINT32(s, current.DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */
|
||||
WLog_DBG(TAG,
|
||||
"\t%" PRIu32 " : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32
|
||||
") W/H=%" PRIu32 "x%" PRIu32 ")",
|
||||
index, current.Flags, current.Left, current.Top, current.Width, current.Height);
|
||||
WLog_DBG(TAG,
|
||||
"\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 " Orientation: %" PRIu32
|
||||
"",
|
||||
current.PhysicalWidth, current.PhysicalHeight, current.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(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
{
|
||||
DISP_PLUGIN* disp = NULL;
|
||||
DispClientContext* context = NULL;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(callback);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
disp = (DISP_PLUGIN*)callback->plugin;
|
||||
WINPR_ASSERT(disp);
|
||||
|
||||
context = disp->context;
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
|
||||
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(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
{
|
||||
UINT32 error = 0;
|
||||
DISPLAY_CONTROL_HEADER header = { 0 };
|
||||
|
||||
WINPR_ASSERT(callback);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
|
||||
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)
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = NULL;
|
||||
GENERIC_CHANNEL_CALLBACK* callback = NULL;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
disp = (DISP_PLUGIN*)context->handle;
|
||||
WINPR_ASSERT(disp);
|
||||
|
||||
callback = disp->base.listener_callback->channel_callback;
|
||||
|
||||
return disp_send_display_control_monitor_layout_pdu(callback, NumMonitors, Monitors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_plugin_initialize(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext,
|
||||
rdpSettings* settings)
|
||||
{
|
||||
DispClientContext* context = NULL;
|
||||
DISP_PLUGIN* disp = (DISP_PLUGIN*)base;
|
||||
|
||||
WINPR_ASSERT(disp);
|
||||
disp->MaxNumMonitors = 16;
|
||||
disp->MaxMonitorAreaFactorA = 8192;
|
||||
disp->MaxMonitorAreaFactorB = 8192;
|
||||
|
||||
context = (DispClientContext*)calloc(1, sizeof(DispClientContext));
|
||||
if (!context)
|
||||
{
|
||||
WLog_Print(base->log, WLOG_ERROR, "unable to allocate DispClientContext");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
context->handle = (void*)disp;
|
||||
context->SendMonitorLayout = disp_send_monitor_layout;
|
||||
|
||||
disp->base.iface.pInterface = disp->context = context;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static void disp_plugin_terminated(GENERIC_DYNVC_PLUGIN* base)
|
||||
{
|
||||
DISP_PLUGIN* disp = (DISP_PLUGIN*)base;
|
||||
|
||||
WINPR_ASSERT(disp);
|
||||
|
||||
free(disp->context);
|
||||
}
|
||||
|
||||
static const IWTSVirtualChannelCallback disp_callbacks = { disp_on_data_received, NULL, /* Open */
|
||||
disp_on_close, NULL };
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE disp_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
|
||||
{
|
||||
return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, DISP_DVC_CHANNEL_NAME,
|
||||
sizeof(DISP_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
|
||||
&disp_callbacks, disp_plugin_initialize,
|
||||
disp_plugin_terminated);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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 */
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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_CheckAndLogRequiredLength(TAG, s, 8))
|
||||
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;
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* 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 */
|
@ -1,32 +0,0 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS
|
||||
freerdp
|
||||
)
|
||||
include_directories(..)
|
||||
|
||||
add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
|
@ -1,639 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include "disp_main.h"
|
||||
#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 <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 = 0;
|
||||
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(const DISPLAY_CONTROL_MONITOR_LAYOUT* monitor)
|
||||
{
|
||||
WINPR_ASSERT(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->Height);
|
||||
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;
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT_PDU pdu = { 0 };
|
||||
|
||||
WINPR_ASSERT(s);
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
|
||||
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_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.NumMonitors,
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE))
|
||||
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 (UINT32 index = 0; index < pdu.NumMonitors; index++)
|
||||
{
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT* 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%" PRIu32 " : 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 = 0;
|
||||
size_t end = 0;
|
||||
DISPLAY_CONTROL_HEADER header = { 0 };
|
||||
|
||||
WINPR_ASSERT(s);
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
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: %" PRIuz ", Expected: %" PRIuz "", end,
|
||||
(beg + header.length));
|
||||
Stream_SetPosition(s, (beg + header.length));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT disp_server_handle_messages(DispServerContext* context)
|
||||
{
|
||||
DWORD BytesReturned = 0;
|
||||
void* buffer = NULL;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
DispServerPrivate* priv = NULL;
|
||||
wStream* s = NULL;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
priv = context->priv;
|
||||
WINPR_ASSERT(priv);
|
||||
|
||||
s = priv->input_stream;
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
const size_t cap = Stream_Capacity(s);
|
||||
if (cap > UINT32_MAX)
|
||||
return CHANNEL_RC_NO_BUFFER;
|
||||
|
||||
if (WTSVirtualChannelRead(priv->disp_channel, 0, Stream_BufferAs(s, char), (ULONG)cap,
|
||||
&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 = NULL;
|
||||
DWORD status = 0;
|
||||
DWORD nCount = 0;
|
||||
HANDLE events[8] = { 0 };
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
priv = context->priv;
|
||||
WINPR_ASSERT(priv);
|
||||
|
||||
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 = NULL;
|
||||
DWORD BytesReturned = 0;
|
||||
PULONG pSessionId = NULL;
|
||||
void* buffer = NULL;
|
||||
UINT32 channelId = 0;
|
||||
BOOL status = TRUE;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
priv = context->priv;
|
||||
WINPR_ASSERT(priv);
|
||||
|
||||
priv->SessionId = WTS_CURRENT_SESSION;
|
||||
|
||||
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 =
|
||||
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;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
if (!(priv->thread =
|
||||
CreateThread(NULL, 0, disp_server_thread_func, (void*)context, 0, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateEvent failed!");
|
||||
(void)CloseHandle(priv->stopEvent);
|
||||
priv->stopEvent = NULL;
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
goto out_close;
|
||||
}
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
out_close:
|
||||
(void)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 = 0;
|
||||
ULONG written = 0;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
const size_t pos = Stream_GetPosition(s);
|
||||
|
||||
WINPR_ASSERT(pos <= UINT32_MAX);
|
||||
if (!WTSVirtualChannelWrite(context->priv->disp_channel, Stream_BufferAs(s, char), (UINT32)pos,
|
||||
&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 = NULL;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
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 = NULL;
|
||||
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
priv = context->priv;
|
||||
WINPR_ASSERT(priv);
|
||||
|
||||
if (priv->thread)
|
||||
{
|
||||
(void)SetEvent(priv->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
(void)CloseHandle(priv->thread);
|
||||
(void)CloseHandle(priv->stopEvent);
|
||||
priv->thread = NULL;
|
||||
priv->stopEvent = NULL;
|
||||
}
|
||||
|
||||
if (priv->disp_channel)
|
||||
{
|
||||
(void)WTSVirtualChannelClose(priv->disp_channel);
|
||||
priv->disp_channel = NULL;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
DispServerContext* disp_server_context_new(HANDLE vcm)
|
||||
{
|
||||
DispServerContext* context = NULL;
|
||||
DispServerPrivate* priv = NULL;
|
||||
context = (DispServerContext*)calloc(1, sizeof(DispServerContext));
|
||||
|
||||
if (!context)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerContext failed!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv = context->priv = (DispServerPrivate*)calloc(1, sizeof(DispServerPrivate));
|
||||
|
||||
if (!context->priv)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerPrivate failed!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->input_stream = Stream_New(NULL, 4);
|
||||
|
||||
if (!priv->input_stream)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
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;
|
||||
fail:
|
||||
WINPR_PRAGMA_DIAG_PUSH
|
||||
WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
|
||||
disp_server_context_free(context);
|
||||
WINPR_PRAGMA_DIAG_POP
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void disp_server_context_free(DispServerContext* context)
|
||||
{
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
if (context->priv)
|
||||
{
|
||||
disp_server_close(context);
|
||||
Stream_Free(context->priv->input_stream, TRUE);
|
||||
free(context->priv);
|
||||
}
|
||||
|
||||
free(context);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* 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 s_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 */
|
@ -21,6 +21,3 @@ if(WITH_CLIENT_CHANNELS)
|
||||
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
||||
if(WITH_SERVER_CHANNELS)
|
||||
add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
set(OPTION_DEFAULT OFF)
|
||||
set(OPTION_CLIENT_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT ON)
|
||||
set(OPTION_SERVER_DEFAULT OFF)
|
||||
|
||||
define_channel_options(NAME "drdynvc" TYPE "static"
|
||||
DESCRIPTION "Dynamic Virtual Channel Extension"
|
||||
|
@ -20,6 +20,23 @@ define_channel_client("drdynvc")
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
drdynvc_main.c
|
||||
drdynvc_main.h
|
||||
)
|
||||
drdynvc_types.h
|
||||
dvcman.c
|
||||
dvcman.h)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
|
||||
|
||||
set_target_properties(${MODULE_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE freerdp
|
||||
MODULES freerdp-utils)
|
||||
|
||||
set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS
|
||||
MONOLITHIC ${MONOLITHIC_BUILD}
|
||||
MODULE winpr
|
||||
MODULES winpr-synch)
|
||||
|
||||
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client")
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,6 @@
|
||||
* 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.
|
||||
@ -19,115 +17,40 @@
|
||||
* 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>
|
||||
#ifndef __DRDYNVC_MAIN_H
|
||||
#define __DRDYNVC_MAIN_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>
|
||||
#include <freerdp/utils/svc_plugin.h>
|
||||
|
||||
#define CREATE_REQUEST_PDU 0x01
|
||||
#define DATA_FIRST_PDU 0x02
|
||||
#define DATA_PDU 0x03
|
||||
#define CLOSE_REQUEST_PDU 0x04
|
||||
#define CAPABILITY_REQUEST_PDU 0x05
|
||||
|
||||
typedef struct drdynvc_plugin drdynvcPlugin;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSVirtualChannelManager iface;
|
||||
|
||||
drdynvcPlugin* drdynvc;
|
||||
|
||||
wArrayList* plugin_names;
|
||||
wArrayList* plugins;
|
||||
|
||||
wHashTable* listeners;
|
||||
wHashTable* channelsById;
|
||||
wStreamPool* pool;
|
||||
} DVCMAN;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSListener iface;
|
||||
|
||||
DVCMAN* dvcman;
|
||||
char* channel_name;
|
||||
UINT32 flags;
|
||||
IWTSListenerCallback* listener_callback;
|
||||
} DVCMAN_LISTENER;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IDRDYNVC_ENTRY_POINTS iface;
|
||||
|
||||
DVCMAN* dvcman;
|
||||
const ADDIN_ARGV* args;
|
||||
rdpContext* context;
|
||||
} DVCMAN_ENTRY_POINTS;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DVC_CHANNEL_INIT,
|
||||
DVC_CHANNEL_RUNNING,
|
||||
DVC_CHANNEL_CLOSED
|
||||
} DVC_CHANNEL_STATE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSVirtualChannel iface;
|
||||
|
||||
volatile LONG refCounter;
|
||||
DVC_CHANNEL_STATE state;
|
||||
DVCMAN* dvcman;
|
||||
void* pInterface;
|
||||
UINT32 channel_id;
|
||||
char* channel_name;
|
||||
IWTSVirtualChannelCallback* channel_callback;
|
||||
|
||||
wStream* dvc_data;
|
||||
UINT32 dvc_data_length;
|
||||
CRITICAL_SECTION lock;
|
||||
} DVCMAN_CHANNEL;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DRDYNVC_STATE_INITIAL,
|
||||
DRDYNVC_STATE_CAPABILITIES,
|
||||
DRDYNVC_STATE_READY,
|
||||
DRDYNVC_STATE_OPENING_CHANNEL,
|
||||
DRDYNVC_STATE_SEND_RECEIVE,
|
||||
DRDYNVC_STATE_FINAL
|
||||
} DRDYNVC_STATE;
|
||||
|
||||
struct drdynvc_plugin
|
||||
{
|
||||
CHANNEL_DEF channelDef;
|
||||
CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
|
||||
rdpSvcPlugin plugin;
|
||||
|
||||
wLog* log;
|
||||
HANDLE thread;
|
||||
BOOL async;
|
||||
wStream* data_in;
|
||||
void* InitHandle;
|
||||
DWORD OpenHandle;
|
||||
wMessageQueue* queue;
|
||||
|
||||
DRDYNVC_STATE state;
|
||||
DrdynvcClientContext* context;
|
||||
|
||||
UINT16 version;
|
||||
int version;
|
||||
int PriorityCharge0;
|
||||
int PriorityCharge1;
|
||||
int PriorityCharge2;
|
||||
int PriorityCharge3;
|
||||
rdpContext* rdpcontext;
|
||||
int channel_error;
|
||||
|
||||
IWTSVirtualChannelManager* channel_mgr;
|
||||
};
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H */
|
||||
int drdynvc_write_data(drdynvcPlugin* plugin, UINT32 ChannelId, BYTE* data, UINT32 data_size);
|
||||
int drdynvc_push_event(drdynvcPlugin* plugin, wMessage* event);
|
||||
|
||||
#endif
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user