mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
52 Commits
5.0.0-beta
...
5.0.0
Author | SHA1 | Date | |
---|---|---|---|
a25427c7cc | |||
99f93c4bdd | |||
6af3af61d5 | |||
14a1547e11 | |||
60ade46481 | |||
7c21e5732e | |||
f5db53f217 | |||
3400f88665 | |||
ad3cb5dcfd | |||
5ecda806bd | |||
af3f29169c | |||
af97978841 | |||
4f89378c45 | |||
4fc8a3aecc | |||
1626ae5546 | |||
8b7fd3dd46 | |||
73848a7370 | |||
2e48dd24c4 | |||
ad1f28480c | |||
91cabe1202 | |||
f869f3df76 | |||
dd4971b1cc | |||
749ecc976b | |||
d85c86e3a0 | |||
3303acfcca | |||
1cd12c1023 | |||
1da0214201 | |||
226c81ce78 | |||
ca34981aae | |||
828dbde75c | |||
ac102de1e8 | |||
20e654186c | |||
f42cd2177a | |||
2479501879 | |||
371c414281 | |||
3a5f0d89b9 | |||
9f68e0166b | |||
8b85658c61 | |||
a9c9363d4a | |||
95df4782f3 | |||
9d899376a5 | |||
d8c042fe4a | |||
c355c72f4b | |||
e6c48990d6 | |||
620f11e8a3 | |||
df13ad30b7 | |||
2297432f90 | |||
be48d0bfe9 | |||
2027394d33 | |||
d7de347b37 | |||
9b879baf38 | |||
947f261d17 |
107
.clang-format
Normal file
107
.clang-format
Normal file
@ -0,0 +1,107 @@
|
||||
# please use clang-format version 8 or later
|
||||
|
||||
Standard: Cpp11
|
||||
AccessModifierOffset: -8
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
#AllowAllArgumentsOnNextLine: false # requires clang-format 9
|
||||
#AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
#AllowShortLambdasOnASingleLine: Inline # requires clang-format 9
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakStringLiterals: false # apparently unpredictable
|
||||
ColumnLimit: 132
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- 'json_object_foreach'
|
||||
- 'json_object_foreach_safe'
|
||||
- 'json_array_foreach'
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 8
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
#ObjCBinPackProtocolList: Auto # requires clang-format 7
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
#SpaceAfterLogicalNot: false # requires clang-format 9
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
#SpaceBeforeCtorInitializerColon: true # requires clang-format 7
|
||||
#SpaceBeforeInheritanceColon: true # requires clang-format 7
|
||||
SpaceBeforeParens: ControlStatements
|
||||
#SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
#StatementMacros: # requires clang-format 8
|
||||
# - 'Q_OBJECT'
|
||||
TabWidth: 8
|
||||
#TypenameMacros: # requires clang-format 9
|
||||
# - 'DARRAY'
|
||||
UseTab: ForContinuationAndIndentation
|
||||
---
|
||||
Language: ObjC
|
11
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
11
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -27,10 +27,11 @@ body:
|
||||
- macOS 10.15
|
||||
- macOS 10.14
|
||||
- macOS 10.13
|
||||
- Ubuntu 22.04 LTS
|
||||
- Ubuntu 21.04
|
||||
- Ubuntu 20.10
|
||||
- Ubuntu 20.04
|
||||
- Ubuntu 18.04
|
||||
- Ubuntu 20.04 LTS
|
||||
- Ubuntu 18.04 LTS
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
@ -48,6 +49,11 @@ body:
|
||||
label: OBS Studio Version
|
||||
description: What version of OBS Studio are you using?
|
||||
options:
|
||||
- 27.2.4
|
||||
- 27.2.3
|
||||
- 27.2.2
|
||||
- 27.2.1
|
||||
- 27.2.0
|
||||
- 27.1.3
|
||||
- 27.1.1
|
||||
- 27.1.0
|
||||
@ -69,6 +75,7 @@ body:
|
||||
label: obs-websocket Version
|
||||
description: What version of obs-websocket are you using?
|
||||
options:
|
||||
- 5.0.0
|
||||
- 5.0.0-beta1
|
||||
- 5.0.0-alpha3
|
||||
- 5.0.0-alpha2
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -29,6 +29,6 @@ Tested OS(s):
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] I have read the [Contributing Guidelines](https://github.com/obsproject/obs-websocket/wiki/Contributing-Guidelines).
|
||||
- [ ] All commit messages are properly formatted and commits squashed where appropriate.
|
||||
- [ ] My code is not on the `master` branch.
|
||||
- [ ] My code is not on `master` or a `release/*` branch.
|
||||
- [ ] The code has been tested.
|
||||
- [ ] I have included updates to all appropriate documentation.
|
||||
|
54
.github/workflows/generate_docs.yml
vendored
54
.github/workflows/generate_docs.yml
vendored
@ -1,27 +1,27 @@
|
||||
name: 'Generate docs'
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/generated/**'
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docs-build:
|
||||
name: 'Generate docs [Ubuntu]'
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
env:
|
||||
CHECKOUT_REF: ${{ github.ref }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
IS_CI: "true"
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
- name: 'Generate docs'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
./CI/generate-docs.sh
|
||||
name: 'Generate docs'
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/generated/**'
|
||||
branches:
|
||||
- release/5.0.0
|
||||
|
||||
jobs:
|
||||
docs-build:
|
||||
name: 'Generate docs [Ubuntu]'
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
env:
|
||||
CHECKOUT_REF: ${{ github.ref }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
IS_CI: "true"
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/obs-websocket
|
||||
- name: 'Generate docs'
|
||||
working-directory: ${{ github.workspace }}/obs-websocket
|
||||
run: |
|
||||
./CI/generate-docs.sh
|
||||
|
24
.github/workflows/lint.yml
vendored
Normal file
24
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Code Quality
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release/5.0.0
|
||||
pull_request:
|
||||
branches:
|
||||
- release/5.0.0
|
||||
|
||||
jobs:
|
||||
markdown:
|
||||
name: Lint Markdown
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') != true
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Generate docs
|
||||
run: cd docs && ./build_docs.sh
|
||||
- name: Run markdownlint-cli
|
||||
uses: nosborn/github-action-markdown-cli@v3.0.1
|
||||
with:
|
||||
files: .
|
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@ -5,7 +5,7 @@ on:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
branches:
|
||||
- master
|
||||
- release/5.0.0
|
||||
tags:
|
||||
- '[45].[0-9]+.[0-9]+*'
|
||||
pull_request:
|
||||
@ -13,7 +13,7 @@ on:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
branches:
|
||||
- master
|
||||
- release/5.0.0
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
@ -334,9 +334,9 @@ jobs:
|
||||
cd ./build
|
||||
sudo checkinstall -y --type=debian --fstrans=no -nodoc \
|
||||
--backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion="$CHECKINSTALL_VERSION" \
|
||||
--pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \
|
||||
--pkglicense="GPLv2.0" --maintainer="tt2468@irltoolkit.com" --pkggroup="video" \
|
||||
--pkgsource="${{ github.event.repository.html_url }}" \
|
||||
--requires="obs-studio,libqt5network5,libqt5concurrent5,qt5-image-formats-plugins" \
|
||||
--requires="obs-studio,libqt5network5,qt5-image-formats-plugins" \
|
||||
--pakdir="../package"
|
||||
sudo chmod ao+r ../package/*
|
||||
sudo mv ../package/* ../package/${{ env.LINUX_FILENAME }}
|
||||
|
2
.markdownlintignore
Normal file
2
.markdownlintignore
Normal file
@ -0,0 +1,2 @@
|
||||
/deps
|
||||
/docs/comments/node_modules
|
3
.markdownlintrc
Normal file
3
.markdownlintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"line-length": false
|
||||
}
|
57
CI/check-format.sh
Executable file
57
CI/check-format.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
if [ ${#} -eq 1 ]; then
|
||||
VERBOSITY="--verbose"
|
||||
else
|
||||
VERBOSITY=""
|
||||
fi
|
||||
|
||||
# Runs the Clang Formatter in parallel on the code base.
|
||||
# Return codes:
|
||||
# - 1 there are files to be formatted
|
||||
# - 0 everything looks fine
|
||||
|
||||
# Get CPU count
|
||||
OS=$(uname)
|
||||
NPROC=1
|
||||
if [[ ${OS} = "Linux" ]] ; then
|
||||
NPROC=$(nproc)
|
||||
elif [[ ${OS} = "Darwin" ]] ; then
|
||||
NPROC=$(sysctl -n hw.physicalcpu)
|
||||
fi
|
||||
|
||||
# Discover clang-format
|
||||
if type clang-format-12 2> /dev/null ; then
|
||||
CLANG_FORMAT=clang-format-12
|
||||
elif type clang-format 2> /dev/null ; then
|
||||
# Clang format found, but need to check version
|
||||
CLANG_FORMAT=clang-format
|
||||
V=$(clang-format --version)
|
||||
if [[ $V != *"version 12.0"* ]]; then
|
||||
echo "clang-format is not 12.0 (returned ${V})"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No appropriate clang-format found (expected clang-format-12.0.0, or clang-format)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
find . -type d \( \
|
||||
-path ./\*build\* -o \
|
||||
-path ./deps/websocketpp -o \
|
||||
-path ./deps/asio -o \
|
||||
-path ./deps/json -o \
|
||||
-path ./deps/qr \
|
||||
\) -prune -false -type f -o \
|
||||
-name '*.h' -or \
|
||||
-name '*.hpp' -or \
|
||||
-name '*.m' -or \
|
||||
-name '*.m,' -or \
|
||||
-name '*.c' -or \
|
||||
-name '*.cpp' \
|
||||
| xargs -L100 -P ${NPROC} ${CLANG_FORMAT} ${VERBOSITY} -i -style=file -fallback-style=none
|
@ -85,7 +85,7 @@ configure_file(
|
||||
)
|
||||
|
||||
|
||||
# Inlude sources
|
||||
# Include sources
|
||||
set(obs-websocket_SOURCES
|
||||
src/obs-websocket.cpp
|
||||
src/Config.cpp
|
||||
@ -129,7 +129,6 @@ set(obs-websocket_SOURCES
|
||||
src/utils/Json.cpp
|
||||
src/utils/Obs.cpp
|
||||
src/utils/Obs_StringHelper.cpp
|
||||
src/utils/Obs_EnumHelper.cpp
|
||||
src/utils/Obs_NumberHelper.cpp
|
||||
src/utils/Obs_ArrayHelper.cpp
|
||||
src/utils/Obs_ObjectHelper.cpp
|
||||
|
@ -1,3 +1,5 @@
|
||||
<!-- markdownlint-disable no-inline-html -->
|
||||
|
||||
# obs-websocket
|
||||
|
||||
<p align="center">
|
||||
@ -6,7 +8,6 @@
|
||||
|
||||
WebSocket API for OBS Studio.
|
||||
|
||||
[](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml)
|
||||
[](https://discord.gg/WBaSQ3A)
|
||||
[](https://opencollective.com/obs-websocket-dev)
|
||||
|
||||
@ -18,7 +19,7 @@ Binaries for Windows, MacOS, and Linux are available in the [Releases](https://g
|
||||
|
||||
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
||||
|
||||
(Psst. You can use `--websocket_port`(value), `--websocket_password`(value), and `--websocket_debug`(flag) on the command line to override the configured values.)
|
||||
(Psst. You can use `--websocket_port`(value), `--websocket_password`(value), `--websocket_debug`(flag) and `--websocket_ipv4_only`(flag) on the command line to override the configured values.)
|
||||
|
||||
### Possible use cases
|
||||
|
||||
@ -27,14 +28,17 @@ It is **highly recommended** to protect obs-websocket with a password against un
|
||||
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||
|
||||
### Client software
|
||||
|
||||
- (No known clients supporting 5.0.0 at the moment. Ping us in the Discord if you have one!)
|
||||
|
||||
### Client libraries (for developers)
|
||||
|
||||
Here's a list of available language APIs for obs-websocket:
|
||||
|
||||
- Python 3.7+ (Asyncio): [simpleobsws](https://github.com/IRLToolkit/simpleobsws/tree/master) by IRLToolkit
|
||||
- Rust: [obws](https://github.com/dnaka91/obws/tree/v5-api) by dnaka91
|
||||
- Godot 3.4.x: [obs-websocket-gd](https://github.com/you-win/obs-websocket-gd) by you-win
|
||||
- Javascript (Node and web): [obs-websocket-js](https://github.com/obs-websocket-community-projects/obs-websocket-js) by OBS Websocket Community
|
||||
|
||||
The 5.x server is a typical WebSocket server running by default on port 4455 (the port number can be changed in the Settings dialog under `Tools`).
|
||||
The protocol we use is documented in [PROTOCOL.md](docs/generated/protocol.md).
|
||||
|
265
docs/README.md
265
docs/README.md
@ -1,127 +1,138 @@
|
||||
# obs-websocket documentation
|
||||
|
||||
This is the documentation for obs-websocket. Run build_docs.sh to auto generate the latest docs from the `src` directory. There are 3 components to the docs generation:
|
||||
- `comments/comments.js`: Generates the `work/comments.json` file from the code comments in the src directory.
|
||||
- `docs/process_comments.py`: Processes `work/comments.json` to create `generated/protocol.json`, which is a machine-readable documentation format that can be used to create obs-websocket client libraries.
|
||||
- `docs/generate_md.py`: Processes `generated/protocol.json` to create `generated/protocol.md`, which is the actual human-readable documentation.
|
||||
|
||||
Some notes about documenting:
|
||||
- The `complexity` comment line is a suggestion to the user about how much knowledge about OBS's inner workings is required to safely use the associated feature. `1` for easy, `5` for megadeath-expert.
|
||||
- The `rpcVersion` comment line is used to specify the latest available version that the feature is available in. If a feature is deprecated, then the placeholder value of `-1` should be replaced with the last RPC version that the feature will be available in. Manually specifying an RPC version automatically adds the `Deprecated` line to the entry in `generated/protocol.md`.
|
||||
- The description can be multiple lines, but must be contained above the first documentation property (the lines starting with `@`).
|
||||
- Value types are in reference to JSON values. The only ones you should use are `Any`, `String`, `Boolean`, `Number`, `Array`, `Object`.
|
||||
- `Array` types follow this format: `Array<subtype>`, for example `Array<String>` to specify an array of strings.
|
||||
|
||||
Formatting notes:
|
||||
- Fields should have their columns aligned. So in a request, the columns of all `@requestField`s should be aligned.
|
||||
- We suggest looking at how other enums/events/requests have been documented before documenting one of your own, to get a general feel of how things have been formatted.
|
||||
|
||||
## Creating enum documentation
|
||||
|
||||
Enums follow this code comment format:
|
||||
```
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @enumIdentifier [identifier]
|
||||
* @enumValue [value]
|
||||
* @enumType [type]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @api enums
|
||||
*/
|
||||
```
|
||||
|
||||
Example code comment:
|
||||
```
|
||||
/**
|
||||
* The initial message sent by obs-websocket to newly connected clients.
|
||||
*
|
||||
* @enumIdentifier Hello
|
||||
* @enumValue 0
|
||||
* @enumType WebSocketOpCode
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api enums
|
||||
*/
|
||||
```
|
||||
- This is the documentation for the `WebSocketOpCode::Hello` enum identifier.
|
||||
|
||||
## Creating event documentation
|
||||
|
||||
Events follow this code comment format:
|
||||
```
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @dataField [field name] | [value type] | [field description]
|
||||
* [... more @dataField entries ...]
|
||||
*
|
||||
* @eventType [type]
|
||||
* @eventSubscription [EventSubscription requirement]
|
||||
* @complexity [complexity rating, 1-5]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @category [event category]
|
||||
* @api events
|
||||
*/
|
||||
```
|
||||
|
||||
Example code comment:
|
||||
```
|
||||
/**
|
||||
* Studio mode has been enabled or disabled.
|
||||
*
|
||||
* @dataField studioModeEnabled | Boolean | True == Enabled, False == Disabled
|
||||
*
|
||||
* @eventType StudioModeStateChanged
|
||||
* @eventSubscription General
|
||||
* @complexity 1
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category general
|
||||
* @api events
|
||||
*/
|
||||
```
|
||||
|
||||
## Creating request documentation
|
||||
|
||||
Requests follow this code comment format:
|
||||
```
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @requestField [optional flag][field name] | [value type] | [field description] | [value restrictions (only include if the value type is `Number`)] | [default behavior (only include if optional flag is set)]
|
||||
* [... more @requestField entries ...]
|
||||
*
|
||||
* @responseField [field name] | [value type] | [field description]
|
||||
* [... more @responseField entries ...]
|
||||
*
|
||||
* @requestType [type]
|
||||
* @complexity [complexity rating, 1-5]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @category [request category]
|
||||
* @api requests
|
||||
*/
|
||||
```
|
||||
- The optional flag is a `?` that prefixes the field name, telling the docs processor that the field is optionally specified.
|
||||
|
||||
Example code comment:
|
||||
```
|
||||
/**
|
||||
* Gets the value of a "slot" from the selected persistent data realm.
|
||||
*
|
||||
* @requestField realm | String | The data realm to select. `OBS_WEBSOCKET_DATA_REALM_GLOBAL` or `OBS_WEBSOCKET_DATA_REALM_PROFILE`
|
||||
* @requestField slotName | String | The name of the slot to retrieve data from
|
||||
*
|
||||
* @responseField slotValue | String | Value associated with the slot. `null` if not set
|
||||
*
|
||||
* @requestType GetPersistentData
|
||||
* @complexity 2
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
```
|
||||
# obs-websocket documentation
|
||||
|
||||
This is the documentation for obs-websocket. Run `build_docs.sh` to auto generate the latest docs from the `src` directory. There are 3 components to the docs generation:
|
||||
|
||||
- `comments/comments.js`: Generates the `work/comments.json` file from the code comments in the src directory.
|
||||
- `docs/process_comments.py`: Processes `work/comments.json` to create `generated/protocol.json`, which is a machine-readable documentation format that can be used to create obs-websocket client libraries.
|
||||
- `docs/generate_md.py`: Processes `generated/protocol.json` to create `generated/protocol.md`, which is the actual human-readable documentation.
|
||||
|
||||
Some notes about documenting:
|
||||
|
||||
- The `complexity` comment line is a suggestion to the user about how much knowledge about OBS's inner workings is required to safely use the associated feature. `1` for easy, `5` for megadeath-expert.
|
||||
- The `rpcVersion` comment line is used to specify the latest available version that the feature is available in. If a feature is deprecated, then the placeholder value of `-1` should be replaced with the last RPC version that the feature will be available in. Manually specifying an RPC version automatically adds the `Deprecated` line to the entry in `generated/protocol.md`.
|
||||
- The description can be multiple lines, but must be contained above the first documentation property (the lines starting with `@`).
|
||||
- Value types are in reference to JSON values. The only ones you should use are `Any`, `String`, `Boolean`, `Number`, `Array`, `Object`.
|
||||
- `Array` types follow this format: `Array<subtype>`, for example `Array<String>` to specify an array of strings.
|
||||
|
||||
Formatting notes:
|
||||
|
||||
- Fields should have their columns aligned. So in a request, the columns of all `@requestField`s should be aligned.
|
||||
- We suggest looking at how other enums/events/requests have been documented before documenting one of your own, to get a general feel of how things have been formatted.
|
||||
|
||||
## Creating enum documentation
|
||||
|
||||
Enums follow this code comment format:
|
||||
|
||||
```js
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @enumIdentifier [identifier]
|
||||
* @enumValue [value]
|
||||
* @enumType [type]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @api enums
|
||||
*/
|
||||
```
|
||||
|
||||
Example code comment:
|
||||
|
||||
```js
|
||||
/**
|
||||
* The initial message sent by obs-websocket to newly connected clients.
|
||||
*
|
||||
* @enumIdentifier Hello
|
||||
* @enumValue 0
|
||||
* @enumType WebSocketOpCode
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api enums
|
||||
*/
|
||||
```
|
||||
|
||||
- This is the documentation for the `WebSocketOpCode::Hello` enum identifier.
|
||||
|
||||
## Creating event documentation
|
||||
|
||||
Events follow this code comment format:
|
||||
|
||||
```js
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @dataField [field name] | [value type] | [field description]
|
||||
* [... more @dataField entries ...]
|
||||
*
|
||||
* @eventType [type]
|
||||
* @eventSubscription [EventSubscription requirement]
|
||||
* @complexity [complexity rating, 1-5]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @category [event category]
|
||||
* @api events
|
||||
*/
|
||||
```
|
||||
|
||||
Example code comment:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Studio mode has been enabled or disabled.
|
||||
*
|
||||
* @dataField studioModeEnabled | Boolean | True == Enabled, False == Disabled
|
||||
*
|
||||
* @eventType StudioModeStateChanged
|
||||
* @eventSubscription General
|
||||
* @complexity 1
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category general
|
||||
* @api events
|
||||
*/
|
||||
```
|
||||
|
||||
## Creating request documentation
|
||||
|
||||
Requests follow this code comment format:
|
||||
|
||||
```js
|
||||
/**
|
||||
* [description]
|
||||
*
|
||||
* @requestField [optional flag][field name] | [value type] | [field description] | [value restrictions (only include if the value type is `Number`)] | [default behavior (only include if optional flag is set)]
|
||||
* [... more @requestField entries ...]
|
||||
*
|
||||
* @responseField [field name] | [value type] | [field description]
|
||||
* [... more @responseField entries ...]
|
||||
*
|
||||
* @requestType [type]
|
||||
* @complexity [complexity rating, 1-5]
|
||||
* @rpcVersion [latest available RPC version, use `-1` unless deprecated.]
|
||||
* @initialVersion [first obs-websocket version this is found in]
|
||||
* @category [request category]
|
||||
* @api requests
|
||||
*/
|
||||
```
|
||||
|
||||
- The optional flag is a `?` that prefixes the field name, telling the docs processor that the field is optionally specified.
|
||||
|
||||
Example code comment:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Gets the value of a "slot" from the selected persistent data realm.
|
||||
*
|
||||
* @requestField realm | String | The data realm to select. `OBS_WEBSOCKET_DATA_REALM_GLOBAL` or `OBS_WEBSOCKET_DATA_REALM_PROFILE`
|
||||
* @requestField slotName | String | The name of the slot to retrieve data from
|
||||
*
|
||||
* @responseField slotValue | String | Value associated with the slot. `null` if not set
|
||||
*
|
||||
* @requestType GetPersistentData
|
||||
* @complexity 2
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
```
|
||||
|
@ -30,7 +30,6 @@ categoryOrder = [
|
||||
]
|
||||
|
||||
requestFieldHeader = """
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description | Value Restrictions | ?Default Behavior |
|
||||
@ -38,7 +37,6 @@ requestFieldHeader = """
|
||||
"""
|
||||
|
||||
responseFieldHeader = """
|
||||
|
||||
**Response Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
@ -46,7 +44,6 @@ responseFieldHeader = """
|
||||
"""
|
||||
|
||||
dataFieldHeader = """
|
||||
|
||||
**Data Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
@ -130,6 +127,8 @@ def get_enums(enums):
|
||||
ret += '- Added in v{}\n'.format(enumIdentifier['initialVersion'])
|
||||
if enumIdentifier != enum['enumIdentifiers'][-1]:
|
||||
ret += '\n---\n\n'
|
||||
else:
|
||||
ret += '\n'
|
||||
return ret
|
||||
|
||||
def get_requests_toc(requests):
|
||||
@ -143,7 +142,7 @@ def get_requests_toc(requests):
|
||||
if not len(requestsOut):
|
||||
continue
|
||||
categoryFragment = get_fragment(category, False)
|
||||
ret += '- [{}](#{})\n'.format(category, categoryFragment)
|
||||
ret += '- [{} Requests](#{}-requests)\n'.format(category, categoryFragment)
|
||||
for request in requestsOut:
|
||||
requestType = request['requestType']
|
||||
requestTypeFragment = get_fragment(requestType, False)
|
||||
@ -161,7 +160,7 @@ def get_requests(requests):
|
||||
if not len(requestsOut):
|
||||
continue
|
||||
categoryFragment = get_fragment(category)
|
||||
ret += '\n\n## {}\n\n'.format(category)
|
||||
ret += '## {} Requests\n\n'.format(category)
|
||||
for request in requestsOut:
|
||||
requestType = request['requestType']
|
||||
requestTypeFragment = get_fragment(requestType)
|
||||
@ -193,6 +192,8 @@ def get_requests(requests):
|
||||
|
||||
if request != requestsOut[-1]:
|
||||
ret += '\n---\n\n'
|
||||
else:
|
||||
ret += '\n'
|
||||
return ret
|
||||
|
||||
def get_events_toc(events):
|
||||
@ -206,7 +207,7 @@ def get_events_toc(events):
|
||||
if not len(eventsOut):
|
||||
continue
|
||||
categoryFragment = get_fragment(category, False)
|
||||
ret += '- [{}](#{})\n'.format(category, categoryFragment)
|
||||
ret += '- [{} Events](#{}-events)\n'.format(category, categoryFragment)
|
||||
for event in eventsOut:
|
||||
eventType = event['eventType']
|
||||
eventTypeFragment = get_fragment(eventType, False)
|
||||
@ -224,7 +225,7 @@ def get_events(events):
|
||||
if not len(eventsOut):
|
||||
continue
|
||||
categoryFragment = get_fragment(category)
|
||||
ret += '## {}\n\n'.format(category)
|
||||
ret += '## {} Events\n\n'.format(category)
|
||||
for event in eventsOut:
|
||||
eventType = event['eventType']
|
||||
eventTypeFragment = get_fragment(eventType)
|
||||
@ -247,6 +248,8 @@ def get_events(events):
|
||||
|
||||
if event != eventsOut[-1]:
|
||||
ret += '\n---\n\n'
|
||||
else:
|
||||
ret += '\n'
|
||||
return ret
|
||||
|
||||
# Actual code
|
||||
@ -264,40 +267,41 @@ except IOError:
|
||||
with open('../generated/protocol.json', 'r') as f:
|
||||
protocol = json.load(f)
|
||||
|
||||
output = "<!-- This file was automatically generated. Do not edit directly! -->\n\n"
|
||||
output = "<!-- This file was automatically generated. Do not edit directly! -->\n"
|
||||
output += "<!-- markdownlint-disable no-bare-urls -->\n"
|
||||
|
||||
# Insert introduction partial
|
||||
output += read_file('partials/introduction.md')
|
||||
logging.info('Inserted introduction section.')
|
||||
|
||||
output += '\n\n'
|
||||
output += '\n'
|
||||
|
||||
# Generate enums MD
|
||||
output += read_file('partials/enumsHeader.md')
|
||||
output += '\n'
|
||||
output += get_enums_toc(protocol['enums'])
|
||||
output += '\n\n'
|
||||
output += '\n'
|
||||
output += get_enums(protocol['enums'])
|
||||
logging.info('Inserted enums section.')
|
||||
|
||||
output += '\n\n'
|
||||
|
||||
# Generate events MD
|
||||
output += read_file('partials/eventsHeader.md')
|
||||
output += '\n'
|
||||
output += get_events_toc(protocol['events'])
|
||||
output += '\n\n'
|
||||
output += '\n'
|
||||
output += get_events(protocol['events'])
|
||||
logging.info('Inserted events section.')
|
||||
|
||||
output += '\n\n'
|
||||
|
||||
# Generate requests MD
|
||||
output += read_file('partials/requestsHeader.md')
|
||||
output += '\n'
|
||||
output += get_requests_toc(protocol['requests'])
|
||||
output += '\n\n'
|
||||
output += '\n'
|
||||
output += get_requests(protocol['requests'])
|
||||
logging.info('Inserted requests section.')
|
||||
|
||||
output += '\n\n'
|
||||
if output.endswith('\n\n'):
|
||||
output = output[:-1]
|
||||
|
||||
# Write new protocol MD
|
||||
with open('../generated/protocol.md', 'w') as f:
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Enums
|
||||
|
||||
These are enumeration declarations, which are referenced throughout obs-websocket's protocol.
|
||||
|
||||
### Enumerations Table of Contents
|
||||
## Enumerations Table of Contents
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Events
|
||||
|
||||
### Events Table of Contents
|
||||
## Events Table of Contents
|
||||
|
@ -1,28 +1,31 @@
|
||||
# Main Table of Contents
|
||||
- [obs-websocket 5.0.0 Protocol](#obs-websocket-500-protocol)
|
||||
- [Connecting to obs-websocket](#connecting-to-obs-websocket)
|
||||
- [Connection steps](#connection-steps)
|
||||
- [Creating an authentication string](#creating-an-authentication-string)
|
||||
- [Base message types](#message-types)
|
||||
- [OpCode 0 Hello](#hello-opcode-0)
|
||||
- [OpCode 1 Identify](#identify-opcode-1)
|
||||
- [OpCode 2 Identified](#identified-opcode-2)
|
||||
- [OpCode 3 Reidentify](#reidentify-opcode-3)
|
||||
- [OpCode 5 Event](#event-opcode-5)
|
||||
- [OpCode 6 Request](#request-opcode-6)
|
||||
- [OpCode 7 RequestResponse](#requestresponse-opcode-7)
|
||||
- [OpCode 8 RequestBatch](#requestbatch-opcode-8)
|
||||
- [OpCode 9 RequestBatchResponse](#requestbatchresponse-opcode-9)
|
||||
- [Enums](#enums)
|
||||
- [Events](#events)
|
||||
- [Requests](#requests)
|
||||
|
||||
# obs-websocket 5.0.0 Protocol
|
||||
|
||||
## Main Table of Contents
|
||||
|
||||
- [General Intro](#general-intro)
|
||||
- [Design Goals](#design-goals)
|
||||
- [Connecting to obs-websocket](#connecting-to-obs-websocket)
|
||||
- [Connection steps](#connection-steps)
|
||||
- [Connection Notes](#connection-notes)
|
||||
- [Creating an authentication string](#creating-an-authentication-string)
|
||||
- [Message Types (OpCodes)](#message-types-opcodes)
|
||||
- [Hello (OpCode 0)](#hello-opcode-0)
|
||||
- [Identify (OpCode 1)](#identify-opcode-1)
|
||||
- [Identified (OpCode 2)](#identified-opcode-2)
|
||||
- [Reidentify (OpCode 3)](#reidentify-opcode-3)
|
||||
- [Event (OpCode 5)](#event-opcode-5)
|
||||
- [Request (OpCode 6)](#request-opcode-6)
|
||||
- [RequestResponse (OpCode 7)](#requestresponse-opcode-7)
|
||||
- [RequestBatch (OpCode 8)](#requestbatch-opcode-8)
|
||||
- [RequestBatchResponse (OpCode 9)](#requestbatchresponse-opcode-9)
|
||||
|
||||
## General Intro
|
||||
|
||||
obs-websocket provides a feature-rich RPC communication protocol, giving access to much of OBS's feature set. This document contains everything you should know in order to make a connection and use obs-websocket's functionality to the fullest.
|
||||
|
||||
### Design Goals
|
||||
|
||||
- Abstraction of identification, events, requests, and batch requests into dedicated message types
|
||||
- Conformity of request naming using similar terms like `Get`, `Set`, `Get[x]List`, `Start[x]`, `Toggle[x]`
|
||||
- Conformity of OBS data field names like `sourceName`, `sourceKind`, `sourceType`, `sceneName`, `sceneItemName`
|
||||
@ -31,13 +34,14 @@ obs-websocket provides a feature-rich RPC communication protocol, giving access
|
||||
- PubSub system - Allow clients to specify which events they do or don't want to receive from OBS
|
||||
- RPC versioning - Client and server negotiate the latest version of the obs-websocket protocol to communicate with.
|
||||
|
||||
|
||||
## Connecting to obs-websocket
|
||||
|
||||
Here's info on how to connect to obs-websocket
|
||||
|
||||
---
|
||||
|
||||
### Connection steps
|
||||
|
||||
These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way.
|
||||
|
||||
- Initial HTTP request made to the obs-websocket server.
|
||||
@ -64,6 +68,7 @@ These steps should be followed precisely. Failure to connect to the server as in
|
||||
- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification.
|
||||
|
||||
#### Connection Notes
|
||||
|
||||
- If a binary frame is received when using the `obswebsocket.json` (default) subprotocol, or a text frame is received while using the `obswebsocket.msgpack` subprotocol, the connection is closed with `WebSocketCloseCode::MessageDecodeError`.
|
||||
- The obs-websocket server listens for any messages containing a `request-type` field in the first level JSON from unidentified clients. If a message matches, the connection is closed with `WebSocketCloseCode::UnsupportedRpcVersion` and a warning is logged.
|
||||
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is closed with `WebSocketCloseCode::UnknownOpCode`.
|
||||
@ -72,11 +77,13 @@ These steps should be followed precisely. Failure to connect to the server as in
|
||||
---
|
||||
|
||||
### Creating an authentication string
|
||||
|
||||
obs-websocket uses SHA256 to transmit authentication credentials. The server starts by sending an object in the `authentication` field of its `Hello` message data. The client processes the authentication challenge and responds via the `authentication` string in the `Identify` message data.
|
||||
|
||||
For this guide, we'll be using `supersecretpassword` as the password.
|
||||
|
||||
The `authentication` object in `Hello` looks like this (example):
|
||||
|
||||
```json
|
||||
{
|
||||
"challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=",
|
||||
@ -85,6 +92,7 @@ The `authentication` object in `Hello` looks like this (example):
|
||||
```
|
||||
|
||||
To generate the authentication string, follow these steps:
|
||||
|
||||
- Concatenate the websocket password with the `salt` provided by the server (`password + salt`)
|
||||
- Generate an SHA256 binary hash of the result and base64 encode it, known as a base64 secret.
|
||||
- Concatenate the base64 secret with the `challenge` sent by the server (`base64_secret + challenge`)
|
||||
@ -92,39 +100,45 @@ To generate the authentication string, follow these steps:
|
||||
|
||||
For real-world examples of the `authentication` string creation, refer to the obs-websocket client libraries listed on the [README](README.md).
|
||||
|
||||
|
||||
## Message Types (OpCodes)
|
||||
The following message types are the low-level message types which may be sent to and from obs-websocket.
|
||||
|
||||
The following message types are the low-level message types which may be sent to and from obs-websocket.
|
||||
|
||||
Messages sent from the obs-websocket server or client may contain these first-level fields, known as the base object:
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"op": number,
|
||||
"d": object
|
||||
}
|
||||
```
|
||||
|
||||
- `op` is a `WebSocketOpCode` OpCode.
|
||||
- `d` is an object of the data fields associated with the operation.
|
||||
|
||||
---
|
||||
|
||||
### Hello (OpCode 0)
|
||||
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Freshly connected websocket client
|
||||
- Description: First message sent from the server immediately on client connection. Contains authentication information if auth is required. Also contains RPC version for version negotiation.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"obsWebSocketVersion": string,
|
||||
"rpcVersion": number,
|
||||
"authentication": object(optional)
|
||||
}
|
||||
```
|
||||
|
||||
- `rpcVersion` is a version number which gets incremented on each **breaking change** to the obs-websocket protocol. Its usage in this context is to provide the current rpc version that the server would like to use.
|
||||
|
||||
**Example Messages:**
|
||||
Authentication is required
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 0,
|
||||
@ -140,6 +154,7 @@ Authentication is required
|
||||
```
|
||||
|
||||
Authentication is not required
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 0,
|
||||
@ -153,22 +168,26 @@ Authentication is not required
|
||||
---
|
||||
|
||||
### Identify (OpCode 1)
|
||||
|
||||
- Sent from: Freshly connected websocket client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Response to `Hello` message, should contain authentication string if authentication is required, along with PubSub subscriptions and other session parameters.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"rpcVersion": number,
|
||||
"authentication": string(optional),
|
||||
"eventSubscriptions": number(optional) = (EventSubscription::All)
|
||||
}
|
||||
```
|
||||
|
||||
- `rpcVersion` is the version number that the client would like the obs-websocket server to use.
|
||||
- `eventSubscriptions` is a bitmask of `EventSubscriptions` items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to.
|
||||
|
||||
**Example Message:**
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 1,
|
||||
@ -183,19 +202,23 @@ Authentication is not required
|
||||
---
|
||||
|
||||
### Identified (OpCode 2)
|
||||
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Freshly identified client
|
||||
- Description: The identify request was received and validated, and the connection is now ready for normal operation.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"negotiatedRpcVersion": number
|
||||
}
|
||||
```
|
||||
|
||||
- If rpc version negotiation succeeds, the server determines the RPC version to be used and gives it to the client as `negotiatedRpcVersion`
|
||||
|
||||
**Example Message:**
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 2,
|
||||
@ -208,36 +231,43 @@ Authentication is not required
|
||||
---
|
||||
|
||||
### Reidentify (OpCode 3)
|
||||
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Sent at any time after initial identification to update the provided session parameters.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"eventSubscriptions": number(optional) = (EventSubscription::All)
|
||||
}
|
||||
```
|
||||
|
||||
- Only the listed parameters may be changed after initial identification. To change a parameter not listed, you must reconnect to the obs-websocket server.
|
||||
|
||||
---
|
||||
|
||||
### Event (OpCode 5)
|
||||
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: All subscribed and identified clients
|
||||
- Description: An event coming from OBS has occured. Eg scene switched, source muted.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"eventType": string,
|
||||
"eventIntent": number,
|
||||
"eventData": object(optional)
|
||||
}
|
||||
```
|
||||
|
||||
- `eventIntent` is the original intent required to be subscribed to in order to receive the event.
|
||||
|
||||
**Example Message:**
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 5,
|
||||
@ -254,21 +284,24 @@ Authentication is not required
|
||||
---
|
||||
|
||||
### Request (OpCode 6)
|
||||
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Client is making a request to obs-websocket. Eg get current scene, create source.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"requestType": string,
|
||||
"requestId": string,
|
||||
"requestData": object(optional),
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**Example Message:**
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 6,
|
||||
@ -285,12 +318,14 @@ Authentication is not required
|
||||
---
|
||||
|
||||
### RequestResponse (OpCode 7)
|
||||
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Identified client which made the request
|
||||
- Description: obs-websocket is responding to a request coming from a client.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"requestType": string,
|
||||
"requestId": string,
|
||||
@ -298,22 +333,26 @@ Authentication is not required
|
||||
"responseData": object(optional)
|
||||
}
|
||||
```
|
||||
|
||||
- The `requestType` and `requestId` are simply mirrors of what was sent by the client.
|
||||
|
||||
`requestStatus` object:
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"result": bool,
|
||||
"code": number,
|
||||
"comment": string(optional)
|
||||
}
|
||||
```
|
||||
|
||||
- `result` is `true` if the request resulted in `RequestStatus::Success`. False if otherwise.
|
||||
- `code` is a `RequestStatus` code.
|
||||
- `comment` may be provided by the server on errors to offer further details on why a request failed.
|
||||
|
||||
**Example Messages:**
|
||||
Successful Response
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 7,
|
||||
@ -329,6 +368,7 @@ Successful Response
|
||||
```
|
||||
|
||||
Failure Response
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 7,
|
||||
@ -347,12 +387,14 @@ Failure Response
|
||||
---
|
||||
|
||||
### RequestBatch (OpCode 8)
|
||||
|
||||
- Sent from: Identified client
|
||||
- Sent to: obs-websocket
|
||||
- Description: Client is making a batch of requests for obs-websocket. Requests are processed serially (in order) by the server.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"requestId": string,
|
||||
"haltOnFailure": bool(optional) = false,
|
||||
@ -360,18 +402,21 @@ Failure Response
|
||||
"requests": array<object>
|
||||
}
|
||||
```
|
||||
|
||||
- When `haltOnFailure` is `true`, the processing of requests will be halted on first failure. Returns only the processed requests in [`RequestBatchResponse`](#requestbatchresponse-opcode-9).
|
||||
- Requests in the `requests` array follow the same structure as the `Request` payload data format, however `requestId` is an optional field.
|
||||
|
||||
---
|
||||
|
||||
### RequestBatchResponse (OpCode 9)
|
||||
|
||||
- Sent from: obs-websocket
|
||||
- Sent to: Identified client which made the request
|
||||
- Description: obs-websocket is responding to a request batch coming from the client.
|
||||
|
||||
**Data Keys:**
|
||||
```
|
||||
|
||||
```txt
|
||||
{
|
||||
"requestId": string,
|
||||
"results": array<object>
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Requests
|
||||
|
||||
### Requests Table of Contents
|
||||
## Requests Table of Contents
|
||||
|
@ -1443,6 +1443,42 @@
|
||||
],
|
||||
"responseFields": []
|
||||
},
|
||||
{
|
||||
"description": "Sets the enable state of a source filter.",
|
||||
"requestType": "SetSourceFilterEnabled",
|
||||
"complexity": 3,
|
||||
"rpcVersion": "1",
|
||||
"deprecated": false,
|
||||
"initialVersion": "5.0.0",
|
||||
"category": "filters",
|
||||
"requestFields": [
|
||||
{
|
||||
"valueName": "sourceName",
|
||||
"valueType": "String",
|
||||
"valueDescription": "Name of the source the filter is on",
|
||||
"valueRestrictions": null,
|
||||
"valueOptional": false,
|
||||
"valueOptionalBehavior": null
|
||||
},
|
||||
{
|
||||
"valueName": "filterName",
|
||||
"valueType": "String",
|
||||
"valueDescription": "Name of the filter",
|
||||
"valueRestrictions": null,
|
||||
"valueOptional": false,
|
||||
"valueOptionalBehavior": null
|
||||
},
|
||||
{
|
||||
"valueName": "filterEnabled",
|
||||
"valueType": "Boolean",
|
||||
"valueDescription": "New enable state of the filter",
|
||||
"valueRestrictions": null,
|
||||
"valueOptional": false,
|
||||
"valueOptionalBehavior": null
|
||||
}
|
||||
],
|
||||
"responseFields": []
|
||||
},
|
||||
{
|
||||
"description": "Gets data about the current plugin and RPC version.",
|
||||
"requestType": "GetVersion",
|
||||
@ -1477,6 +1513,16 @@
|
||||
"valueName": "supportedImageFormats",
|
||||
"valueType": "Array<String>",
|
||||
"valueDescription": "Image formats available in `GetSourceScreenshot` and `SaveSourceScreenshot` requests."
|
||||
},
|
||||
{
|
||||
"valueName": "platform",
|
||||
"valueType": "String",
|
||||
"valueDescription": "Name of the platform. Usually `windows`, `macos`, or `ubuntu` (linux flavor). Not guaranteed to be any of those"
|
||||
},
|
||||
{
|
||||
"valueName": "platformDescription",
|
||||
"valueType": "String",
|
||||
"valueDescription": "Description of the platform, like `Windows 10 (10.0)`"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2283,7 +2329,7 @@
|
||||
"responseFields": []
|
||||
},
|
||||
{
|
||||
"description": "Gets the audio monitor type of an input.\n\nThe available audio monitor types are:\n- `OBS_MONITORING_TYPE_NONE`\n- `OBS_MONITORING_TYPE_MONITOR_ONLY`\n- `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`",
|
||||
"description": "Gets the audio monitor type of an input.\n\nThe available audio monitor types are:\n\n- `OBS_MONITORING_TYPE_NONE`\n- `OBS_MONITORING_TYPE_MONITOR_ONLY`\n- `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`",
|
||||
"requestType": "GetInputAudioMonitorType",
|
||||
"complexity": 2,
|
||||
"rpcVersion": "1",
|
||||
@ -2453,7 +2499,7 @@
|
||||
"responseFields": []
|
||||
},
|
||||
{
|
||||
"description": "Gets the status of a media input.\n\nMedia States:\n- `OBS_MEDIA_STATE_NONE`\n- `OBS_MEDIA_STATE_PLAYING`\n- `OBS_MEDIA_STATE_OPENING`\n- `OBS_MEDIA_STATE_BUFFERING`\n- `OBS_MEDIA_STATE_PAUSED`\n- `OBS_MEDIA_STATE_STOPPED`\n- `OBS_MEDIA_STATE_ENDED`\n- `OBS_MEDIA_STATE_ERROR`",
|
||||
"description": "Gets the status of a media input.\n\nMedia States:\n\n- `OBS_MEDIA_STATE_NONE`\n- `OBS_MEDIA_STATE_PLAYING`\n- `OBS_MEDIA_STATE_OPENING`\n- `OBS_MEDIA_STATE_BUFFERING`\n- `OBS_MEDIA_STATE_PAUSED`\n- `OBS_MEDIA_STATE_STOPPED`\n- `OBS_MEDIA_STATE_ENDED`\n- `OBS_MEDIA_STATE_ERROR`",
|
||||
"requestType": "GetMediaInputStatus",
|
||||
"complexity": 2,
|
||||
"rpcVersion": "1",
|
||||
@ -2891,6 +2937,14 @@
|
||||
"valueRestrictions": null,
|
||||
"valueOptional": false,
|
||||
"valueOptionalBehavior": null
|
||||
},
|
||||
{
|
||||
"valueName": "searchOffset",
|
||||
"valueType": "Number",
|
||||
"valueDescription": "Number of matches to skip during search. >= 0 means first forward. -1 means last (top) item",
|
||||
"valueRestrictions": ">= -1",
|
||||
"valueOptional": true,
|
||||
"valueOptionalBehavior": "0"
|
||||
}
|
||||
],
|
||||
"responseFields": [
|
||||
@ -4183,6 +4237,23 @@
|
||||
}
|
||||
],
|
||||
"responseFields": []
|
||||
},
|
||||
{
|
||||
"description": "Gets a list of connected monitors and information about them.",
|
||||
"requestType": "GetMonitorList",
|
||||
"complexity": 2,
|
||||
"rpcVersion": "1",
|
||||
"deprecated": false,
|
||||
"initialVersion": "5.0.0",
|
||||
"category": "ui",
|
||||
"requestFields": [],
|
||||
"responseFields": [
|
||||
{
|
||||
"valueName": "monitors",
|
||||
"valueType": "Array<Object>",
|
||||
"valueDescription": "a list of detected monitors with some information"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@ -4675,7 +4746,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The monitor type of an input has changed.\n\nAvailable types are:\n- `OBS_MONITORING_TYPE_NONE`\n- `OBS_MONITORING_TYPE_MONITOR_ONLY`\n- `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`",
|
||||
"description": "The monitor type of an input has changed.\n\nAvailable types are:\n\n- `OBS_MONITORING_TYPE_NONE`\n- `OBS_MONITORING_TYPE_MONITOR_ONLY`\n- `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`",
|
||||
"eventType": "InputAudioMonitorTypeChanged",
|
||||
"eventSubscription": "Inputs",
|
||||
"complexity": 2,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,74 +1,74 @@
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
|
||||
#define MyAppPublisher "obs-websocket"
|
||||
#define MyAppURL "http://github.com/obsproject/obs-websocket"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={code:GetDirName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
DirExistsWarning=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\LICENSE"; Flags: dontcopy
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
|
||||
[Code]
|
||||
procedure InitializeWizard();
|
||||
var
|
||||
GPLText: AnsiString;
|
||||
Page: TOutputMsgMemoWizardPage;
|
||||
begin
|
||||
ExtractTemporaryFile('LICENSE');
|
||||
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
|
||||
Page := CreateOutputMsgMemoPage(wpWelcome,
|
||||
'License Information', 'Please review the license terms before installing obs-websocket',
|
||||
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
|
||||
String(GPLText)
|
||||
);
|
||||
end;
|
||||
|
||||
// Validate that obs-studio is installed before installing the plugin
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
Result := '';
|
||||
|
||||
if not DirExists(ExpandConstant('{app}\obs-plugins')) then
|
||||
begin
|
||||
Result := 'The selected install directory does not appear to be valid. Please install OBS Studio before installing {#MyAppName} or correct your install path.';
|
||||
end;
|
||||
end;
|
||||
|
||||
// credit where it's due :
|
||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
InstallPath: string;
|
||||
begin
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
Result := '{commonpf}\obs-studio';
|
||||
// query the first registry value; if this succeeds, return the obtained value
|
||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath
|
||||
end;
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
|
||||
#define MyAppPublisher "OBS Project"
|
||||
#define MyAppURL "http://github.com/obsproject/obs-websocket"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={code:GetDirName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
DirExistsWarning=no
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\LICENSE"; Flags: dontcopy
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
|
||||
[Code]
|
||||
procedure InitializeWizard();
|
||||
var
|
||||
GPLText: AnsiString;
|
||||
Page: TOutputMsgMemoWizardPage;
|
||||
begin
|
||||
ExtractTemporaryFile('LICENSE');
|
||||
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
|
||||
Page := CreateOutputMsgMemoPage(wpWelcome,
|
||||
'License Information', 'Please review the license terms before installing obs-websocket',
|
||||
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
|
||||
String(GPLText)
|
||||
);
|
||||
end;
|
||||
|
||||
// Validate that obs-studio is installed before installing the plugin
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
Result := '';
|
||||
|
||||
if not DirExists(ExpandConstant('{app}\obs-plugins')) then
|
||||
begin
|
||||
Result := 'The selected install directory does not appear to be valid. Please install OBS Studio before installing {#MyAppName} or correct your install path.';
|
||||
end;
|
||||
end;
|
||||
|
||||
// credit where it's due :
|
||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
InstallPath: string;
|
||||
begin
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
Result := '{commonpf}\obs-studio';
|
||||
// query the first registry value; if this succeeds, return the obtained value
|
||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath
|
||||
end;
|
||||
|
@ -1,40 +1,79 @@
|
||||
#include <obs-module.h>
|
||||
|
||||
#include "../obs-websocket-api.h"
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")
|
||||
|
||||
obs_websocket_vendor vendor;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "Example obs-websocket-api plugin loaded!");
|
||||
return true;
|
||||
}
|
||||
|
||||
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data);
|
||||
void obs_module_post_load(void)
|
||||
{
|
||||
vendor = obs_websocket_register_vendor("api_example_plugin");
|
||||
if (!vendor) {
|
||||
blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obs_websocket_vendor_register_request(vendor, "example_request", example_request_cb, NULL))
|
||||
blog(LOG_ERROR, "Failed to register `example_request` request with obs-websocket.");
|
||||
}
|
||||
|
||||
void obs_module_unload(void)
|
||||
{
|
||||
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!");
|
||||
}
|
||||
|
||||
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data)
|
||||
{
|
||||
if (obs_data_has_user_value(request_data, "ping"))
|
||||
obs_data_set_bool(response_data, "pong", true);
|
||||
|
||||
UNUSED_PARAMETER(priv_data);
|
||||
}
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Similar example code can be found in ../../src/obs-websocket.cpp
|
||||
// You can test that sample code by specifying -DPLUGIN_TESTS=TRUE
|
||||
|
||||
#include <obs-module.h>
|
||||
|
||||
#include "../obs-websocket-api.h"
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")
|
||||
|
||||
obs_websocket_vendor vendor;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "Example obs-websocket-api plugin loaded!");
|
||||
return true;
|
||||
}
|
||||
|
||||
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data);
|
||||
void obs_module_post_load(void)
|
||||
{
|
||||
vendor = obs_websocket_register_vendor("api_example_plugin");
|
||||
if (!vendor) {
|
||||
blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obs_websocket_vendor_register_request(vendor, "example_request", example_request_cb, NULL))
|
||||
blog(LOG_ERROR, "Failed to register `example_request` request with obs-websocket.");
|
||||
|
||||
uint api_version = obs_websocket_get_api_version();
|
||||
if (api_version == 0) {
|
||||
blog(LOG_ERROR, "Unable to fetch obs-websocket plugin API version.");
|
||||
return;
|
||||
} else if (api_version == 1) {
|
||||
blog(LOG_WARNING, "Unsupported obs-websocket plugin API version for calling requests.");
|
||||
return;
|
||||
}
|
||||
|
||||
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
|
||||
if (!response) {
|
||||
blog(LOG_ERROR, "Failed to call GetVersion due to obs-websocket not being installed.");
|
||||
return;
|
||||
}
|
||||
blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
|
||||
response->status_code, response->comment, response->response_data);
|
||||
obs_websocket_request_response_free(response);
|
||||
}
|
||||
|
||||
void obs_module_unload(void)
|
||||
{
|
||||
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!");
|
||||
}
|
||||
|
||||
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data)
|
||||
{
|
||||
if (obs_data_has_user_value(request_data, "ping"))
|
||||
obs_data_set_bool(response_data, "pong", true);
|
||||
|
||||
UNUSED_PARAMETER(priv_data);
|
||||
}
|
||||
|
@ -1,135 +1,216 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _OBS_WEBSOCKET_API_H
|
||||
#define _OBS_WEBSOCKET_API_H
|
||||
|
||||
#include <obs.h>
|
||||
|
||||
#define OBS_WEBSOCKET_API_VERSION 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* obs_websocket_vendor;
|
||||
typedef void (*obs_websocket_request_callback_function)(obs_data_t*, obs_data_t*, void*);
|
||||
|
||||
struct obs_websocket_request_callback {
|
||||
obs_websocket_request_callback_function callback;
|
||||
void *priv_data;
|
||||
};
|
||||
|
||||
inline proc_handler_t *ph;
|
||||
|
||||
static inline proc_handler_t *obs_websocket_get_ph(void)
|
||||
{
|
||||
proc_handler_t *global_ph = obs_get_proc_handler();
|
||||
assert(global_ph != NULL);
|
||||
|
||||
calldata_t cd = {0};
|
||||
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
|
||||
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
|
||||
proc_handler_t *ret = (proc_handler_t*)calldata_ptr(&cd, "ph");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
|
||||
{
|
||||
if (!ph || !vendor || !proc_name || !strlen(proc_name) || !cd)
|
||||
return false;
|
||||
|
||||
calldata_set_ptr(cd, "vendor", vendor);
|
||||
|
||||
proc_handler_call(ph, proc_name, cd);
|
||||
return calldata_bool(cd, "success");
|
||||
}
|
||||
|
||||
// ALWAYS CALL VIA `obs_module_post_load()` CALLBACK!
|
||||
// Registers a new "vendor" (Example: obs-ndi)
|
||||
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
|
||||
{
|
||||
ph = obs_websocket_get_ph();
|
||||
if (!ph)
|
||||
return NULL;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "name", vendor_name);
|
||||
|
||||
proc_handler_call(ph, "vendor_register", &cd);
|
||||
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Registers a new request for a vendor
|
||||
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type, obs_websocket_request_callback_function request_callback, void* priv_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
struct obs_websocket_request_callback cb = {};
|
||||
cb.callback = request_callback;
|
||||
cb.priv_data = priv_data;
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
calldata_set_ptr(&cd, "callback", &cb);
|
||||
|
||||
bool success = obs_websocket_run_simple_proc(vendor, "vendor_request_register", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Unregisters an existing vendor request
|
||||
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
|
||||
bool success = obs_websocket_run_simple_proc(vendor, "vendor_request_unregister", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Does not affect event_data refcount.
|
||||
// Emits an event under the vendor's name
|
||||
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", event_name);
|
||||
calldata_set_ptr(&cd, "data", (void*)event_data);
|
||||
|
||||
bool success = obs_websocket_run_simple_proc(vendor, "vendor_event_emit", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _OBS_WEBSOCKET_API_H
|
||||
#define _OBS_WEBSOCKET_API_H
|
||||
|
||||
#include <obs.h>
|
||||
|
||||
#define OBS_WEBSOCKET_API_VERSION 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *obs_websocket_vendor;
|
||||
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
|
||||
|
||||
struct obs_websocket_request_response {
|
||||
unsigned int status_code;
|
||||
char *comment;
|
||||
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
|
||||
};
|
||||
|
||||
/* ==================== INTERNAL DEFINITIONS ==================== */
|
||||
|
||||
struct obs_websocket_request_callback {
|
||||
obs_websocket_request_callback_function callback;
|
||||
void *priv_data;
|
||||
};
|
||||
|
||||
inline proc_handler_t *_ph;
|
||||
|
||||
/* ==================== INTERNAL API FUNCTIONS ==================== */
|
||||
|
||||
static inline proc_handler_t *obs_websocket_get_ph(void)
|
||||
{
|
||||
proc_handler_t *global_ph = obs_get_proc_handler();
|
||||
assert(global_ph != NULL);
|
||||
|
||||
calldata_t cd = {0};
|
||||
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
|
||||
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
|
||||
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_ensure_ph(void)
|
||||
{
|
||||
if (!_ph)
|
||||
_ph = obs_websocket_get_ph();
|
||||
return _ph != NULL;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return false;
|
||||
|
||||
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
|
||||
return false;
|
||||
|
||||
calldata_set_ptr(cd, "vendor", vendor);
|
||||
|
||||
proc_handler_call(_ph, proc_name, cd);
|
||||
return calldata_bool(cd, "success");
|
||||
}
|
||||
|
||||
/* ==================== GENERAL API FUNCTIONS ==================== */
|
||||
|
||||
// Gets the API version built with the obs-websocket plugin
|
||||
static inline unsigned int obs_websocket_get_api_version(void)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return 0;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
if (!proc_handler_call(_ph, "get_api_version", &cd))
|
||||
return 1; // API v1 does not include get_api_version
|
||||
|
||||
unsigned int ret = calldata_int(&cd, "version");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
|
||||
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
const char *request_data_string = NULL;
|
||||
if (request_data)
|
||||
request_data_string = obs_data_get_json(request_data);
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "request_type", request_type);
|
||||
calldata_set_string(&cd, "request_data", request_data_string);
|
||||
|
||||
proc_handler_call(_ph, "call_request", &cd);
|
||||
|
||||
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Free a request response object returned by `obs_websocket_call_request()`
|
||||
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
|
||||
{
|
||||
if (!response)
|
||||
return;
|
||||
|
||||
if (response->comment)
|
||||
bfree(response->comment);
|
||||
if (response->response_data)
|
||||
bfree(response->response_data);
|
||||
bfree(response);
|
||||
}
|
||||
|
||||
/* ==================== VENDOR API FUNCTIONS ==================== */
|
||||
|
||||
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
|
||||
// Registers a new "vendor" (Example: obs-ndi)
|
||||
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "name", vendor_name);
|
||||
|
||||
proc_handler_call(_ph, "vendor_register", &cd);
|
||||
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Registers a new request for a vendor
|
||||
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
|
||||
obs_websocket_request_callback_function request_callback, void *priv_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
struct obs_websocket_request_callback cb = {};
|
||||
cb.callback = request_callback;
|
||||
cb.priv_data = priv_data;
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
calldata_set_ptr(&cd, "callback", &cb);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Unregisters an existing vendor request
|
||||
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Does not affect event_data refcount.
|
||||
// Emits an event under the vendor's name
|
||||
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", event_name);
|
||||
calldata_set_ptr(&cd, "data", (void *)event_data);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* ==================== END API FUNCTIONS ==================== */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -33,26 +33,28 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#define PARAM_PASSWORD "ServerPassword"
|
||||
|
||||
#define CMDLINE_WEBSOCKET_PORT "websocket_port"
|
||||
#define CMDLINE_WEBSOCKET_IPV4_ONLY "websocket_ipv4_only"
|
||||
#define CMDLINE_WEBSOCKET_PASSWORD "websocket_password"
|
||||
#define CMDLINE_WEBSOCKET_DEBUG "websocket_debug"
|
||||
|
||||
Config::Config() :
|
||||
PortOverridden(false),
|
||||
PasswordOverridden(false),
|
||||
FirstLoad(true),
|
||||
ServerEnabled(true),
|
||||
ServerPort(4455),
|
||||
DebugEnabled(false),
|
||||
AlertsEnabled(false),
|
||||
AuthRequired(true),
|
||||
ServerPassword("")
|
||||
Config::Config()
|
||||
: PortOverridden(false),
|
||||
PasswordOverridden(false),
|
||||
FirstLoad(true),
|
||||
ServerEnabled(true),
|
||||
ServerPort(4455),
|
||||
Ipv4Only(false),
|
||||
DebugEnabled(false),
|
||||
AlertsEnabled(false),
|
||||
AuthRequired(true),
|
||||
ServerPassword("")
|
||||
{
|
||||
SetDefaultsToGlobalStore();
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
{
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
config_t *obsConfig = GetConfigStore();
|
||||
if (!obsConfig) {
|
||||
blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!");
|
||||
return;
|
||||
@ -93,6 +95,12 @@ void Config::Load()
|
||||
}
|
||||
}
|
||||
|
||||
// Process `--websocket_ipv4_only` override
|
||||
if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_IPV4_ONLY)) {
|
||||
blog(LOG_INFO, "[Config::Load] --websocket_ipv4_only passed. Binding only to IPv4 interfaces.");
|
||||
Ipv4Only = true;
|
||||
}
|
||||
|
||||
// Process `--websocket_password` override
|
||||
QString passwordArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PASSWORD);
|
||||
if (passwordArgument != "") {
|
||||
@ -112,7 +120,7 @@ void Config::Load()
|
||||
|
||||
void Config::Save()
|
||||
{
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
config_t *obsConfig = GetConfigStore();
|
||||
if (!obsConfig) {
|
||||
blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!");
|
||||
return;
|
||||
@ -134,7 +142,7 @@ void Config::Save()
|
||||
|
||||
void Config::SetDefaultsToGlobalStore()
|
||||
{
|
||||
config_t* obsConfig = GetConfigStore();
|
||||
config_t *obsConfig = GetConfigStore();
|
||||
if (!obsConfig) {
|
||||
blog(LOG_ERROR, "[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!");
|
||||
return;
|
||||
@ -148,7 +156,7 @@ void Config::SetDefaultsToGlobalStore()
|
||||
config_set_default_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword));
|
||||
}
|
||||
|
||||
config_t* Config::GetConfigStore()
|
||||
config_t *Config::GetConfigStore()
|
||||
{
|
||||
return obs_frontend_get_global_config();
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ struct Config {
|
||||
void Load();
|
||||
void Save();
|
||||
void SetDefaultsToGlobalStore();
|
||||
config_t* GetConfigStore();
|
||||
config_t *GetConfigStore();
|
||||
|
||||
std::atomic<bool> PortOverridden;
|
||||
std::atomic<bool> PasswordOverridden;
|
||||
@ -38,6 +38,7 @@ struct Config {
|
||||
std::atomic<bool> FirstLoad;
|
||||
std::atomic<bool> ServerEnabled;
|
||||
std::atomic<uint16_t> ServerPort;
|
||||
std::atomic<bool> Ipv4Only;
|
||||
std::atomic<bool> DebugEnabled;
|
||||
std::atomic<bool> AlertsEnabled;
|
||||
std::atomic<bool> AuthRequired;
|
||||
|
@ -1,7 +1,13 @@
|
||||
#include "WebSocketApi.h"
|
||||
#include "requesthandler/RequestHandler.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "utils/Json.h"
|
||||
|
||||
#define RETURN_STATUS(status) { calldata_set_bool(cd, "success", status); return; }
|
||||
#define RETURN_STATUS(status) \
|
||||
{ \
|
||||
calldata_set_bool(cd, "success", status); \
|
||||
return; \
|
||||
}
|
||||
#define RETURN_SUCCESS() RETURN_STATUS(true);
|
||||
#define RETURN_FAILURE() RETURN_STATUS(false);
|
||||
|
||||
@ -13,7 +19,7 @@ WebSocketApi::Vendor *get_vendor(calldata_t *cd)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<WebSocketApi::Vendor*>(voidVendor);
|
||||
return static_cast<WebSocketApi::Vendor *>(voidVendor);
|
||||
}
|
||||
|
||||
WebSocketApi::WebSocketApi()
|
||||
@ -22,10 +28,16 @@ WebSocketApi::WebSocketApi()
|
||||
|
||||
_procHandler = proc_handler_create();
|
||||
|
||||
proc_handler_add(_procHandler, "bool get_api_version(out int version)", &get_api_version, nullptr);
|
||||
proc_handler_add(_procHandler, "bool call_request(in string request_type, in string request_data, out ptr response)",
|
||||
&call_request, nullptr);
|
||||
proc_handler_add(_procHandler, "bool vendor_register(in string name, out ptr vendor)", &vendor_register_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_request_register(in ptr vendor, in string type, in ptr callback)", &vendor_request_register_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)", &vendor_request_unregister_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data)", &vendor_event_emit_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_request_register(in ptr vendor, in string type, in ptr callback)",
|
||||
&vendor_request_register_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)",
|
||||
&vendor_request_unregister_cb, this);
|
||||
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data)", &vendor_event_emit_cb,
|
||||
this);
|
||||
|
||||
proc_handler_t *ph = obs_get_proc_handler();
|
||||
assert(ph != NULL);
|
||||
@ -54,7 +66,8 @@ void WebSocketApi::SetEventCallback(EventCallback cb)
|
||||
_eventCallback = cb;
|
||||
}
|
||||
|
||||
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType, obs_data_t *requestData, obs_data_t *responseData)
|
||||
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType,
|
||||
obs_data_t *requestData, obs_data_t *responseData)
|
||||
{
|
||||
std::shared_lock l(_mutex);
|
||||
|
||||
@ -81,16 +94,59 @@ enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::str
|
||||
|
||||
void WebSocketApi::get_ph_cb(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<WebSocketApi*>(priv_data);
|
||||
auto c = static_cast<WebSocketApi *>(priv_data);
|
||||
|
||||
calldata_set_ptr(cd, "ph", (void*)c->_procHandler);
|
||||
calldata_set_ptr(cd, "ph", (void *)c->_procHandler);
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void WebSocketApi::get_api_version(void *, calldata_t *cd)
|
||||
{
|
||||
calldata_set_int(cd, "version", OBS_WEBSOCKET_API_VERSION);
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void WebSocketApi::call_request(void *, calldata_t *cd)
|
||||
{
|
||||
const char *request_type = calldata_string(cd, "request_type");
|
||||
const char *request_data = calldata_string(cd, "request_data");
|
||||
|
||||
if (!request_type)
|
||||
RETURN_FAILURE();
|
||||
|
||||
auto response = static_cast<obs_websocket_request_response *>(bzalloc(sizeof(struct obs_websocket_request_response)));
|
||||
if (!response)
|
||||
RETURN_FAILURE();
|
||||
|
||||
json requestData;
|
||||
if (request_data)
|
||||
requestData = json::parse(request_data);
|
||||
|
||||
RequestHandler requestHandler;
|
||||
Request request(request_type, requestData);
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
response->status_code = (unsigned int)requestResult.StatusCode;
|
||||
if (!requestResult.Comment.empty())
|
||||
response->comment = bstrdup(requestResult.Comment.c_str());
|
||||
if (requestResult.ResponseData.is_object()) {
|
||||
std::string responseData = requestResult.ResponseData.dump();
|
||||
response->response_data = bstrdup(responseData.c_str());
|
||||
}
|
||||
|
||||
calldata_set_ptr(cd, "response", response);
|
||||
|
||||
blog_debug("[WebSocketApi::call_request] Request %s called, response status code is %u", request_type,
|
||||
response->status_code);
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<WebSocketApi*>(priv_data);
|
||||
auto c = static_cast<WebSocketApi *>(priv_data);
|
||||
|
||||
const char *vendorName;
|
||||
if (!calldata_get_string(cd, "name", &vendorName) || strlen(vendorName) == 0) {
|
||||
@ -102,18 +158,19 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
|
||||
std::unique_lock l(c->_mutex);
|
||||
|
||||
if (c->_vendors.count(vendorName)) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_register_cb] Failed because `%s` is already a registered vendor.", vendorName);
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_register_cb] Failed because `%s` is already a registered vendor.",
|
||||
vendorName);
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
Vendor* v = new Vendor();
|
||||
Vendor *v = new Vendor();
|
||||
v->_name = vendorName;
|
||||
|
||||
c->_vendors[vendorName] = v;
|
||||
|
||||
blog_debug("[WebSocketApi::vendor_register_cb] [vendorName: %s] Registered new vendor.", v->_name.c_str());
|
||||
|
||||
calldata_set_ptr(cd, "vendor", static_cast<void*>(v));
|
||||
calldata_set_ptr(cd, "vendor", static_cast<void *>(v));
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
@ -126,28 +183,35 @@ void WebSocketApi::vendor_request_register_cb(void *, calldata_t *cd)
|
||||
|
||||
const char *requestType;
|
||||
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing or empty `type` string.", v->_name.c_str());
|
||||
blog(LOG_WARNING,
|
||||
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing or empty `type` string.",
|
||||
v->_name.c_str());
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
void *voidCallback;
|
||||
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing `callback` pointer.", v->_name.c_str());
|
||||
blog(LOG_WARNING,
|
||||
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing `callback` pointer.",
|
||||
v->_name.c_str());
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
auto cb = static_cast<obs_websocket_request_callback*>(voidCallback);
|
||||
auto cb = static_cast<obs_websocket_request_callback *>(voidCallback);
|
||||
|
||||
std::unique_lock l(v->_mutex);
|
||||
|
||||
if (v->_requests.count(requestType)) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is already a registered request.", v->_name.c_str(), requestType);
|
||||
blog(LOG_WARNING,
|
||||
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is already a registered request.",
|
||||
v->_name.c_str(), requestType);
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
v->_requests[requestType] = *cb;
|
||||
|
||||
blog_debug("[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Registered new vendor request: %s", v->_name.c_str(), requestType);
|
||||
blog_debug("[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Registered new vendor request: %s",
|
||||
v->_name.c_str(), requestType);
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
@ -160,27 +224,32 @@ void WebSocketApi::vendor_request_unregister_cb(void *, calldata_t *cd)
|
||||
|
||||
const char *requestType;
|
||||
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Failed due to missing `type` string.", v->_name.c_str());
|
||||
blog(LOG_WARNING,
|
||||
"[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Failed due to missing `type` string.",
|
||||
v->_name.c_str());
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
std::unique_lock l(v->_mutex);
|
||||
|
||||
if (!v->_requests.count(requestType)) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is not a registered request.", v->_name.c_str(), requestType);
|
||||
blog(LOG_WARNING,
|
||||
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is not a registered request.",
|
||||
v->_name.c_str(), requestType);
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
v->_requests.erase(requestType);
|
||||
|
||||
blog_debug("[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Unregistered vendor request: %s", v->_name.c_str(), requestType);
|
||||
blog_debug("[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Unregistered vendor request: %s",
|
||||
v->_name.c_str(), requestType);
|
||||
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<WebSocketApi*>(priv_data);
|
||||
auto c = static_cast<WebSocketApi *>(priv_data);
|
||||
|
||||
Vendor *v = get_vendor(cd);
|
||||
if (!v)
|
||||
@ -188,17 +257,19 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
|
||||
|
||||
const char *eventType;
|
||||
if (!calldata_get_string(cd, "type", &eventType) || strlen(eventType) == 0) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `type` string.", v->_name.c_str());
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `type` string.",
|
||||
v->_name.c_str());
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
void *voidEventData;
|
||||
if (!calldata_get_ptr(cd, "data", &voidEventData)) {
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `data` pointer.", v->_name.c_str());
|
||||
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `data` pointer.",
|
||||
v->_name.c_str());
|
||||
RETURN_FAILURE();
|
||||
}
|
||||
|
||||
auto eventData = static_cast<obs_data_t*>(voidEventData);
|
||||
auto eventData = static_cast<obs_data_t *>(voidEventData);
|
||||
|
||||
if (!c->_eventCallback)
|
||||
RETURN_FAILURE();
|
||||
|
@ -10,37 +10,40 @@
|
||||
#include "../lib/obs-websocket-api.h"
|
||||
|
||||
class WebSocketApi {
|
||||
public:
|
||||
enum RequestReturnCode {
|
||||
Normal,
|
||||
NoVendor,
|
||||
NoVendorRequest,
|
||||
};
|
||||
public:
|
||||
enum RequestReturnCode {
|
||||
Normal,
|
||||
NoVendor,
|
||||
NoVendorRequest,
|
||||
};
|
||||
|
||||
typedef std::function<void(std::string, std::string, obs_data_t*)> EventCallback;
|
||||
typedef std::function<void(std::string, std::string, obs_data_t *)> EventCallback;
|
||||
|
||||
struct Vendor {
|
||||
std::shared_mutex _mutex;
|
||||
std::string _name;
|
||||
std::map<std::string, obs_websocket_request_callback> _requests;
|
||||
};
|
||||
|
||||
WebSocketApi();
|
||||
~WebSocketApi();
|
||||
|
||||
void SetEventCallback(EventCallback cb);
|
||||
|
||||
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData);
|
||||
|
||||
static void get_ph_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_register_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_request_unregister_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
|
||||
|
||||
private:
|
||||
struct Vendor {
|
||||
std::shared_mutex _mutex;
|
||||
EventCallback _eventCallback;
|
||||
proc_handler_t *_procHandler;
|
||||
std::map<std::string, Vendor*> _vendors;
|
||||
std::string _name;
|
||||
std::map<std::string, obs_websocket_request_callback> _requests;
|
||||
};
|
||||
|
||||
WebSocketApi();
|
||||
~WebSocketApi();
|
||||
|
||||
void SetEventCallback(EventCallback cb);
|
||||
|
||||
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData,
|
||||
obs_data_t *responseData);
|
||||
|
||||
static void get_ph_cb(void *priv_data, calldata_t *cd);
|
||||
static void get_api_version(void *, calldata_t *cd);
|
||||
static void call_request(void *, calldata_t *cd);
|
||||
static void vendor_register_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_request_unregister_cb(void *priv_data, calldata_t *cd);
|
||||
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
|
||||
|
||||
private:
|
||||
std::shared_mutex _mutex;
|
||||
EventCallback _eventCallback;
|
||||
proc_handler_t *_procHandler;
|
||||
std::map<std::string, Vendor *> _vendors;
|
||||
};
|
||||
|
@ -19,18 +19,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "EventHandler.h"
|
||||
|
||||
EventHandler::EventHandler() :
|
||||
_obsLoaded(false),
|
||||
_inputVolumeMetersRef(0),
|
||||
_inputActiveStateChangedRef(0),
|
||||
_inputShowStateChangedRef(0),
|
||||
_sceneItemTransformChangedRef(0)
|
||||
EventHandler::EventHandler()
|
||||
: _obsLoaded(false),
|
||||
_inputVolumeMetersRef(0),
|
||||
_inputActiveStateChangedRef(0),
|
||||
_inputShowStateChangedRef(0),
|
||||
_sceneItemTransformChangedRef(0)
|
||||
{
|
||||
blog_debug("[EventHandler::EventHandler] Setting up...");
|
||||
|
||||
obs_frontend_add_event_callback(OnFrontendEvent, this);
|
||||
|
||||
signal_handler_t* coreSignalHandler = obs_get_signal_handler();
|
||||
signal_handler_t *coreSignalHandler = obs_get_signal_handler();
|
||||
if (coreSignalHandler) {
|
||||
signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
|
||||
signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
|
||||
@ -49,7 +49,7 @@ EventHandler::~EventHandler()
|
||||
|
||||
obs_frontend_remove_event_callback(OnFrontendEvent, this);
|
||||
|
||||
signal_handler_t* coreSignalHandler = obs_get_signal_handler();
|
||||
signal_handler_t *coreSignalHandler = obs_get_signal_handler();
|
||||
if (coreSignalHandler) {
|
||||
signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
|
||||
signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
|
||||
@ -80,7 +80,8 @@ void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
|
||||
if (_inputVolumeMetersHandler)
|
||||
blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
|
||||
else
|
||||
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1));
|
||||
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(
|
||||
std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
|
||||
@ -124,7 +125,7 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
|
||||
// Disconnect all existing signals from the source to prevent multiple connections
|
||||
DisconnectSourceSignals(source);
|
||||
|
||||
signal_handler_t* sh = obs_source_get_signal_handler(source);
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(source);
|
||||
|
||||
obs_source_type sourceType = obs_source_get_type(source);
|
||||
|
||||
@ -166,8 +167,8 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
|
||||
signal_handler_connect(sh, "reorder_filters", HandleSourceFilterListReindexed, this);
|
||||
signal_handler_connect(sh, "filter_add", FilterAddMultiHandler, this);
|
||||
signal_handler_connect(sh, "filter_remove", FilterRemoveMultiHandler, this);
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->ConnectSourceSignals(filter);
|
||||
};
|
||||
obs_source_enum_filters(source, enumFilters, this);
|
||||
@ -193,7 +194,7 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
signal_handler_t* sh = obs_source_get_signal_handler(source);
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(source);
|
||||
|
||||
obs_source_type sourceType = obs_source_get_type(source);
|
||||
|
||||
@ -235,8 +236,8 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
|
||||
signal_handler_disconnect(sh, "reorder_filters", HandleSourceFilterListReindexed, this);
|
||||
signal_handler_disconnect(sh, "filter_add", FilterAddMultiHandler, this);
|
||||
signal_handler_disconnect(sh, "filter_remove", FilterRemoveMultiHandler, this);
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->DisconnectSourceSignals(filter);
|
||||
};
|
||||
obs_source_enum_filters(source, enumFilters, this);
|
||||
@ -258,236 +259,233 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
|
||||
|
||||
void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(private_data);
|
||||
auto eventHandler = static_cast<EventHandler *>(private_data);
|
||||
|
||||
if (!eventHandler->_obsLoaded.load() && event != OBS_FRONTEND_EVENT_FINISHED_LOADING)
|
||||
return;
|
||||
|
||||
switch (event) {
|
||||
// General
|
||||
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
|
||||
blog_debug("[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events...");
|
||||
// General
|
||||
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
|
||||
blog_debug(
|
||||
"[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events...");
|
||||
|
||||
// Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging).
|
||||
eventHandler->_obsLoaded.store(true);
|
||||
// Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging).
|
||||
eventHandler->_obsLoaded.store(true);
|
||||
|
||||
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
|
||||
// Enumerate inputs and connect each one
|
||||
{
|
||||
auto enumInputs = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
eventHandler->ConnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumInputs, private_data);
|
||||
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
|
||||
// Enumerate inputs and connect each one
|
||||
{
|
||||
auto enumInputs = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->ConnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumInputs, private_data);
|
||||
}
|
||||
|
||||
// Enumerate scenes and connect each one
|
||||
{
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->ConnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_scenes(enumScenes, private_data);
|
||||
}
|
||||
|
||||
// Enumerate all scene transitions and connect each one
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t *transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
|
||||
// Enumerate scenes and connect each one
|
||||
{
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
eventHandler->ConnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_scenes(enumScenes, private_data);
|
||||
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
|
||||
|
||||
if (eventHandler->_obsLoadedCallback)
|
||||
eventHandler->_obsLoadedCallback();
|
||||
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_EXIT:
|
||||
eventHandler->HandleExitStarted();
|
||||
|
||||
blog_debug("[EventHandler::OnFrontendEvent] OBS is unloading. Disabling events...");
|
||||
// Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging).
|
||||
eventHandler->_obsLoaded.store(false);
|
||||
|
||||
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
|
||||
// Enumerate inputs and disconnect each one
|
||||
{
|
||||
auto enumInputs = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->DisconnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumInputs, private_data);
|
||||
}
|
||||
|
||||
// Enumerate scenes and disconnect each one
|
||||
{
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
eventHandler->DisconnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_scenes(enumScenes, private_data);
|
||||
}
|
||||
|
||||
// Enumerate all scene transitions and disconnect each one
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t *transition = transitions.sources.array[i];
|
||||
eventHandler->DisconnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
|
||||
// Enumerate all scene transitions and connect each one
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t* transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
|
||||
|
||||
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
|
||||
eventHandler->HandleStudioModeStateChanged(true);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED:
|
||||
eventHandler->HandleStudioModeStateChanged(false);
|
||||
break;
|
||||
|
||||
if (eventHandler->_obsLoadedCallback)
|
||||
eventHandler->_obsLoadedCallback();
|
||||
// Config
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: {
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t *transition = transitions.sources.array[i];
|
||||
eventHandler->DisconnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
eventHandler->HandleCurrentSceneCollectionChanging();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: {
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t *transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
eventHandler->HandleCurrentSceneCollectionChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
|
||||
eventHandler->HandleSceneCollectionListChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
|
||||
eventHandler->HandleCurrentProfileChanging();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
|
||||
eventHandler->HandleCurrentProfileChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED:
|
||||
eventHandler->HandleProfileListChanged();
|
||||
break;
|
||||
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_EXIT:
|
||||
eventHandler->HandleExitStarted();
|
||||
// Scenes
|
||||
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||
eventHandler->HandleCurrentProgramSceneChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
|
||||
eventHandler->HandleCurrentPreviewSceneChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED:
|
||||
eventHandler->HandleSceneListChanged();
|
||||
break;
|
||||
|
||||
blog_debug("[EventHandler::OnFrontendEvent] OBS is unloading. Disabling events...");
|
||||
// Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging).
|
||||
eventHandler->_obsLoaded.store(false);
|
||||
// Transitions
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
|
||||
eventHandler->HandleCurrentSceneTransitionChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: {
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t *transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
} break;
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED:
|
||||
eventHandler->HandleCurrentSceneTransitionDurationChanged();
|
||||
break;
|
||||
|
||||
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
|
||||
// Enumerate inputs and disconnect each one
|
||||
{
|
||||
auto enumInputs = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
eventHandler->DisconnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumInputs, private_data);
|
||||
}
|
||||
// Outputs
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTING:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_PAUSED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_RESUMED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED:
|
||||
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED:
|
||||
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
|
||||
eventHandler->HandleReplayBufferSaved();
|
||||
break;
|
||||
|
||||
// Enumerate scenes and disconnect each one
|
||||
{
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
eventHandler->DisconnectSourceSignals(source);
|
||||
return true;
|
||||
};
|
||||
obs_enum_scenes(enumScenes, private_data);
|
||||
}
|
||||
|
||||
// Enumerate all scene transitions and disconnect each one
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t* transition = transitions.sources.array[i];
|
||||
eventHandler->DisconnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
|
||||
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
|
||||
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
|
||||
eventHandler->HandleStudioModeStateChanged(true);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED:
|
||||
eventHandler->HandleStudioModeStateChanged(false);
|
||||
break;
|
||||
|
||||
// Config
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t* transition = transitions.sources.array[i];
|
||||
eventHandler->DisconnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
eventHandler->HandleCurrentSceneCollectionChanging();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t* transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
eventHandler->HandleCurrentSceneCollectionChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
|
||||
eventHandler->HandleSceneCollectionListChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
|
||||
eventHandler->HandleCurrentProfileChanging();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
|
||||
eventHandler->HandleCurrentProfileChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED:
|
||||
eventHandler->HandleProfileListChanged();
|
||||
break;
|
||||
|
||||
// Scenes
|
||||
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||
eventHandler->HandleCurrentProgramSceneChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
|
||||
eventHandler->HandleCurrentPreviewSceneChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED:
|
||||
eventHandler->HandleSceneListChanged();
|
||||
break;
|
||||
|
||||
// Transitions
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
|
||||
eventHandler->HandleCurrentSceneTransitionChanged();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
|
||||
{
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
obs_source_t* transition = transitions.sources.array[i];
|
||||
eventHandler->ConnectSourceSignals(transition);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
}
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED:
|
||||
eventHandler->HandleCurrentSceneTransitionDurationChanged();
|
||||
break;
|
||||
|
||||
// Outputs
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
||||
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTING:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_PAUSED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
|
||||
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_RESUMED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED:
|
||||
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED:
|
||||
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED:
|
||||
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED);
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
|
||||
eventHandler->HandleReplayBufferSaved();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only called for creation of a public source
|
||||
void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
// Don't react to signals until OBS has finished loading
|
||||
if (!eventHandler->_obsLoaded.load())
|
||||
@ -500,21 +498,22 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
|
||||
eventHandler->ConnectSourceSignals(source);
|
||||
|
||||
switch (obs_source_get_type(source)) {
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
eventHandler->HandleInputCreated(source);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
eventHandler->HandleSceneCreated(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
eventHandler->HandleInputCreated(source);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
eventHandler->HandleSceneCreated(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only called for destruction of a public source
|
||||
// Only called for destruction of a public sourcs
|
||||
// Used as a fallback if an input/scene is not explicitly removed
|
||||
void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
// We can't use any smart types here because releasing the source will cause infinite recursion
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
@ -529,20 +528,26 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
|
||||
return;
|
||||
|
||||
switch (obs_source_get_type(source)) {
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
// We have to call `InputRemoved` with source_destroy because source_removed is not called when an input's last scene item is removed
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
// Only emit removed if the input has not already been removed. This is the case when removing the last scene item of an input.
|
||||
if (!obs_source_removed(source))
|
||||
eventHandler->HandleInputRemoved(source);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
// Only emit removed if the scene has not already been removed.
|
||||
if (!obs_source_removed(source))
|
||||
eventHandler->HandleSceneRemoved(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We prefer remove signals over destroy signals because they are more time-accurate.
|
||||
// For example, if an input is "removed" but there is a dangling ref, you still want to know that it shouldn't exist, but it's not guaranteed to be destroyed.
|
||||
void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
if (!eventHandler->_obsLoaded.load())
|
||||
return;
|
||||
@ -552,20 +557,20 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
|
||||
return;
|
||||
|
||||
switch (obs_source_get_type(source)) {
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
// Scenes emit the `removed` signal when they are removed from OBS, as expected
|
||||
eventHandler->HandleSceneRemoved(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
eventHandler->HandleInputRemoved(source);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
eventHandler->HandleSceneRemoved(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
if (!eventHandler->_obsLoaded.load())
|
||||
return;
|
||||
@ -580,15 +585,15 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
|
||||
return;
|
||||
|
||||
switch (obs_source_get_type(source)) {
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_TRANSITION:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_INPUT:
|
||||
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName);
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_TRANSITION:
|
||||
break;
|
||||
case OBS_SOURCE_TYPE_SCENE:
|
||||
eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -29,123 +29,121 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../utils/Obs_VolumeMeter.h"
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
class EventHandler
|
||||
{
|
||||
public:
|
||||
EventHandler();
|
||||
~EventHandler();
|
||||
class EventHandler {
|
||||
public:
|
||||
EventHandler();
|
||||
~EventHandler();
|
||||
|
||||
typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback;
|
||||
void SetBroadcastCallback(BroadcastCallback cb);
|
||||
typedef std::function<void()> ObsLoadedCallback;
|
||||
void SetObsLoadedCallback(ObsLoadedCallback cb);
|
||||
typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback;
|
||||
void SetBroadcastCallback(BroadcastCallback cb);
|
||||
typedef std::function<void()> ObsLoadedCallback;
|
||||
void SetObsLoadedCallback(ObsLoadedCallback cb);
|
||||
|
||||
void ProcessSubscription(uint64_t eventSubscriptions);
|
||||
void ProcessUnsubscription(uint64_t eventSubscriptions);
|
||||
void ProcessSubscription(uint64_t eventSubscriptions);
|
||||
void ProcessUnsubscription(uint64_t eventSubscriptions);
|
||||
|
||||
private:
|
||||
BroadcastCallback _broadcastCallback;
|
||||
ObsLoadedCallback _obsLoadedCallback;
|
||||
private:
|
||||
BroadcastCallback _broadcastCallback;
|
||||
ObsLoadedCallback _obsLoadedCallback;
|
||||
|
||||
std::atomic<bool> _obsLoaded;
|
||||
std::atomic<bool> _obsLoaded;
|
||||
|
||||
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
|
||||
std::atomic<uint64_t> _inputVolumeMetersRef;
|
||||
std::atomic<uint64_t> _inputActiveStateChangedRef;
|
||||
std::atomic<uint64_t> _inputShowStateChangedRef;
|
||||
std::atomic<uint64_t> _sceneItemTransformChangedRef;
|
||||
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
|
||||
std::atomic<uint64_t> _inputVolumeMetersRef;
|
||||
std::atomic<uint64_t> _inputActiveStateChangedRef;
|
||||
std::atomic<uint64_t> _inputShowStateChangedRef;
|
||||
std::atomic<uint64_t> _sceneItemTransformChangedRef;
|
||||
|
||||
void ConnectSourceSignals(obs_source_t *source);
|
||||
void DisconnectSourceSignals(obs_source_t *source);
|
||||
void ConnectSourceSignals(obs_source_t *source);
|
||||
void DisconnectSourceSignals(obs_source_t *source);
|
||||
|
||||
void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0);
|
||||
void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0);
|
||||
|
||||
// Signal handler: frontend
|
||||
static void OnFrontendEvent(enum obs_frontend_event event, void *private_data);
|
||||
// Signal handler: frontend
|
||||
static void OnFrontendEvent(enum obs_frontend_event event, void *private_data);
|
||||
|
||||
// Signal handler: libobs
|
||||
static void SourceCreatedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceDestroyedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceRemovedMultiHandler(void *param, calldata_t *data);
|
||||
// Signal handler: libobs
|
||||
static void SourceCreatedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceDestroyedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceRemovedMultiHandler(void *param, calldata_t *data);
|
||||
|
||||
// Signal handler: source
|
||||
static void SourceRenamedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPauseMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPlayMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaRestartMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaStopMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaNextMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data);
|
||||
// Signal handler: source
|
||||
static void SourceRenamedMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPauseMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPlayMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaRestartMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaStopMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaNextMultiHandler(void *param, calldata_t *data);
|
||||
static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data);
|
||||
|
||||
// General
|
||||
void HandleExitStarted();
|
||||
void HandleStudioModeStateChanged(bool enabled);
|
||||
|
||||
// General
|
||||
void HandleExitStarted();
|
||||
void HandleStudioModeStateChanged(bool enabled);
|
||||
// Config
|
||||
void HandleCurrentSceneCollectionChanging();
|
||||
void HandleCurrentSceneCollectionChanged();
|
||||
void HandleSceneCollectionListChanged();
|
||||
void HandleCurrentProfileChanging();
|
||||
void HandleCurrentProfileChanged();
|
||||
void HandleProfileListChanged();
|
||||
|
||||
// Config
|
||||
void HandleCurrentSceneCollectionChanging();
|
||||
void HandleCurrentSceneCollectionChanged();
|
||||
void HandleSceneCollectionListChanged();
|
||||
void HandleCurrentProfileChanging();
|
||||
void HandleCurrentProfileChanged();
|
||||
void HandleProfileListChanged();
|
||||
// Scenes
|
||||
void HandleSceneCreated(obs_source_t *source);
|
||||
void HandleSceneRemoved(obs_source_t *source);
|
||||
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName);
|
||||
void HandleCurrentProgramSceneChanged();
|
||||
void HandleCurrentPreviewSceneChanged();
|
||||
void HandleSceneListChanged();
|
||||
|
||||
// Scenes
|
||||
void HandleSceneCreated(obs_source_t *source);
|
||||
void HandleSceneRemoved(obs_source_t *source);
|
||||
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName);
|
||||
void HandleCurrentProgramSceneChanged();
|
||||
void HandleCurrentPreviewSceneChanged();
|
||||
void HandleSceneListChanged();
|
||||
// Inputs
|
||||
void HandleInputCreated(obs_source_t *source);
|
||||
void HandleInputRemoved(obs_source_t *source);
|
||||
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName);
|
||||
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
|
||||
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
|
||||
|
||||
// Inputs
|
||||
void HandleInputCreated(obs_source_t *source);
|
||||
void HandleInputRemoved(obs_source_t *source);
|
||||
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName);
|
||||
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
|
||||
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
|
||||
// Transitions
|
||||
void HandleCurrentSceneTransitionChanged();
|
||||
void HandleCurrentSceneTransitionDurationChanged();
|
||||
static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneTransitionVideoEnded(void *param, calldata_t *data); // Direct callback
|
||||
|
||||
// Transitions
|
||||
void HandleCurrentSceneTransitionChanged();
|
||||
void HandleCurrentSceneTransitionDurationChanged();
|
||||
static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneTransitionVideoEnded(void *param, calldata_t *data); // Direct callback
|
||||
// Filters
|
||||
static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback
|
||||
static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback
|
||||
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter);
|
||||
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter);
|
||||
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
|
||||
// Filters
|
||||
static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback
|
||||
static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback
|
||||
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter);
|
||||
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter);
|
||||
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
// Outputs
|
||||
void HandleStreamStateChanged(ObsOutputState state);
|
||||
void HandleRecordStateChanged(ObsOutputState state);
|
||||
void HandleReplayBufferStateChanged(ObsOutputState state);
|
||||
void HandleVirtualcamStateChanged(ObsOutputState state);
|
||||
void HandleReplayBufferSaved();
|
||||
|
||||
// Outputs
|
||||
void HandleStreamStateChanged(ObsOutputState state);
|
||||
void HandleRecordStateChanged(ObsOutputState state);
|
||||
void HandleReplayBufferStateChanged(ObsOutputState state);
|
||||
void HandleVirtualcamStateChanged(ObsOutputState state);
|
||||
void HandleReplayBufferSaved();
|
||||
// Scene Items
|
||||
static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback
|
||||
|
||||
// Scene Items
|
||||
static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback
|
||||
|
||||
// Media Inputs
|
||||
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback
|
||||
void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action);
|
||||
// Media Inputs
|
||||
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
|
||||
static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback
|
||||
void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action);
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
|
||||
@ -36,7 +36,7 @@ void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::FilterRemoveMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
|
||||
@ -65,7 +65,7 @@ void EventHandler::FilterRemoveMultiHandler(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSourceFilterListReindexed(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -150,7 +150,7 @@ void EventHandler::HandleSourceFilterRemoved(obs_source_t *source, obs_source_t
|
||||
*/
|
||||
void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!filter)
|
||||
@ -180,7 +180,7 @@ void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSourceFilterEnableStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!filter)
|
||||
|
@ -111,7 +111,7 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
|
||||
*/
|
||||
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
if (!eventHandler->_inputActiveStateChangedRef.load())
|
||||
return;
|
||||
@ -147,7 +147,7 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
if (!eventHandler->_inputShowStateChangedRef.load())
|
||||
return;
|
||||
@ -181,7 +181,7 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -213,7 +213,7 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -252,7 +252,7 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -285,7 +285,7 @@ void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -318,7 +318,7 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
|
||||
*/
|
||||
void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -344,6 +344,7 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
|
||||
* The monitor type of an input has changed.
|
||||
*
|
||||
* Available types are:
|
||||
*
|
||||
* - `OBS_MONITORING_TYPE_NONE`
|
||||
* - `OBS_MONITORING_TYPE_MONITOR_ONLY`
|
||||
* - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`
|
||||
@ -361,7 +362,7 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -372,11 +373,9 @@ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *d
|
||||
|
||||
enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type");
|
||||
|
||||
std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType);
|
||||
|
||||
json eventData;
|
||||
eventData["inputName"] = obs_source_get_name(source);
|
||||
eventData["monitorType"] = monitorTypeString;
|
||||
eventData["monitorType"] = monitorType;
|
||||
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData);
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,14 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "EventHandler.h"
|
||||
|
||||
#define CASE(x) case x: return #x;
|
||||
#define CASE(x) \
|
||||
case x: \
|
||||
return #x;
|
||||
|
||||
std::string GetMediaInputActionString(ObsMediaInputAction action) {
|
||||
std::string GetMediaInputActionString(ObsMediaInputAction action)
|
||||
{
|
||||
switch (action) {
|
||||
default:
|
||||
default:
|
||||
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
|
||||
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
|
||||
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
|
||||
@ -35,7 +38,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) {
|
||||
|
||||
void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -49,7 +52,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -63,7 +66,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -77,7 +80,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -91,7 +94,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -105,7 +108,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
|
||||
|
||||
void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -132,7 +135,7 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
|
||||
*/
|
||||
void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -161,7 +164,7 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
|
||||
*/
|
||||
void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
|
@ -19,18 +19,19 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "EventHandler.h"
|
||||
|
||||
static bool GetOutputStateActive(ObsOutputState state) {
|
||||
switch(state) {
|
||||
case OBS_WEBSOCKET_OUTPUT_STARTED:
|
||||
case OBS_WEBSOCKET_OUTPUT_RESUMED:
|
||||
return true;
|
||||
case OBS_WEBSOCKET_OUTPUT_STARTING:
|
||||
case OBS_WEBSOCKET_OUTPUT_STOPPING:
|
||||
case OBS_WEBSOCKET_OUTPUT_STOPPED:
|
||||
case OBS_WEBSOCKET_OUTPUT_PAUSED:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
static bool GetOutputStateActive(ObsOutputState state)
|
||||
{
|
||||
switch (state) {
|
||||
case OBS_WEBSOCKET_OUTPUT_STARTED:
|
||||
case OBS_WEBSOCKET_OUTPUT_RESUMED:
|
||||
return true;
|
||||
case OBS_WEBSOCKET_OUTPUT_STARTING:
|
||||
case OBS_WEBSOCKET_OUTPUT_STOPPING:
|
||||
case OBS_WEBSOCKET_OUTPUT_STOPPED:
|
||||
case OBS_WEBSOCKET_OUTPUT_PAUSED:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +53,7 @@ void EventHandler::HandleStreamStateChanged(ObsOutputState state)
|
||||
{
|
||||
json eventData;
|
||||
eventData["outputActive"] = GetOutputStateActive(state);
|
||||
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
|
||||
eventData["outputState"] = state;
|
||||
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData);
|
||||
}
|
||||
|
||||
@ -61,6 +62,7 @@ void EventHandler::HandleStreamStateChanged(ObsOutputState state)
|
||||
*
|
||||
* @dataField outputActive | Boolean | Whether the output is active
|
||||
* @dataField outputState | String | The specific state of the output
|
||||
* @dataField outputPath | String | File name for the saved recording, if record stopped. `null` otherwise
|
||||
*
|
||||
* @eventType RecordStateChanged
|
||||
* @eventSubscription Outputs
|
||||
@ -74,7 +76,12 @@ void EventHandler::HandleRecordStateChanged(ObsOutputState state)
|
||||
{
|
||||
json eventData;
|
||||
eventData["outputActive"] = GetOutputStateActive(state);
|
||||
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
|
||||
eventData["outputState"] = state;
|
||||
if (state == OBS_WEBSOCKET_OUTPUT_STOPPED) {
|
||||
eventData["outputPath"] = Utils::Obs::StringHelper::GetLastRecordFileName();
|
||||
} else {
|
||||
eventData["outputPath"] = nullptr;
|
||||
}
|
||||
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData);
|
||||
}
|
||||
|
||||
@ -96,7 +103,7 @@ void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state)
|
||||
{
|
||||
json eventData;
|
||||
eventData["outputActive"] = GetOutputStateActive(state);
|
||||
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
|
||||
eventData["outputState"] = state;
|
||||
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData);
|
||||
}
|
||||
|
||||
@ -118,7 +125,7 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
|
||||
{
|
||||
json eventData;
|
||||
eventData["outputActive"] = GetOutputStateActive(state);
|
||||
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
|
||||
eventData["outputState"] = state;
|
||||
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData);
|
||||
}
|
||||
|
||||
@ -138,6 +145,6 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
|
||||
void EventHandler::HandleReplayBufferSaved()
|
||||
{
|
||||
json eventData;
|
||||
eventData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath();
|
||||
eventData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFileName();
|
||||
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferSaved", eventData);
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -74,7 +74,7 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -107,7 +107,7 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -136,7 +136,7 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -172,7 +172,7 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
|
||||
*/
|
||||
void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -207,7 +207,7 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
|
||||
*/
|
||||
void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
|
||||
if (!scene)
|
||||
@ -240,7 +240,7 @@ void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
if (!eventHandler->_sceneItemTransformChangedRef.load())
|
||||
return;
|
||||
|
@ -124,7 +124,7 @@ void EventHandler::HandleCurrentPreviewSceneChanged()
|
||||
{
|
||||
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene();
|
||||
|
||||
// This event may be called when OBS is not in studio mode, however retreiving the source while not in studio mode will return null.
|
||||
// This event may be called when OBS is not in studio mode, however retreiving the source while not in studio mode will return null.
|
||||
if (!currentPreviewScene)
|
||||
return;
|
||||
|
||||
|
@ -76,7 +76,7 @@ void EventHandler::HandleCurrentSceneTransitionDurationChanged()
|
||||
*/
|
||||
void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -104,7 +104,7 @@ void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
@ -135,7 +135,7 @@ void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
|
||||
*/
|
||||
void EventHandler::HandleSceneTransitionVideoEnded(void *param, calldata_t *data)
|
||||
{
|
||||
auto eventHandler = static_cast<EventHandler*>(param);
|
||||
auto eventHandler = static_cast<EventHandler *>(param);
|
||||
|
||||
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
|
||||
if (!source)
|
||||
|
@ -157,13 +157,15 @@ namespace EventSubscription {
|
||||
* Helper to receive all non-high-volume events.
|
||||
*
|
||||
* @enumIdentifier All
|
||||
* @enumValue (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors)
|
||||
* @enumValue (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors | Ui)
|
||||
* @enumType EventSubscription
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api enums
|
||||
*/
|
||||
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Ui | Vendors),
|
||||
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors |
|
||||
Ui),
|
||||
|
||||
/**
|
||||
* Subscription value to receive the `InputVolumeMeters` high-volume event.
|
||||
*
|
||||
|
@ -28,18 +28,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../Config.h"
|
||||
#include "../utils/Platform.h"
|
||||
|
||||
ConnectInfo::ConnectInfo(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::ConnectInfo)
|
||||
ConnectInfo::ConnectInfo(QWidget *parent) : QDialog(parent, Qt::Dialog), ui(new Ui::ConnectInfo)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->copyServerIpButton, &QPushButton::clicked,
|
||||
this, &ConnectInfo::CopyServerIpButtonClicked);
|
||||
connect(ui->copyServerPortButton, &QPushButton::clicked,
|
||||
this, &ConnectInfo::CopyServerPortButtonClicked);
|
||||
connect(ui->copyServerPasswordButton, &QPushButton::clicked,
|
||||
this, &ConnectInfo::CopyServerPasswordButtonClicked);
|
||||
connect(ui->copyServerIpButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerIpButtonClicked);
|
||||
connect(ui->copyServerPortButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerPortButtonClicked);
|
||||
connect(ui->copyServerPasswordButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerPasswordButtonClicked);
|
||||
}
|
||||
|
||||
ConnectInfo::~ConnectInfo()
|
||||
@ -113,14 +108,14 @@ void ConnectInfo::DrawQr(QString qrText)
|
||||
QPixmap map(230, 230);
|
||||
map.fill(Qt::white);
|
||||
QPainter painter(&map);
|
||||
|
||||
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(QT_TO_UTF8(qrText), qrcodegen::QrCode::Ecc::MEDIUM);
|
||||
const int s = qr.getSize() > 0 ? qr.getSize() : 1;
|
||||
const double w = map.width();
|
||||
const double h = map.height();
|
||||
const double aspect = w/h;
|
||||
const double aspect = w / h;
|
||||
const double size = ((aspect > 1.0) ? h : w);
|
||||
const double scale = size / (s+2);
|
||||
const double scale = size / (s + 2);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(Qt::black);
|
||||
|
||||
|
@ -25,12 +25,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "ui_ConnectInfo.h"
|
||||
|
||||
class ConnectInfo : public QDialog
|
||||
{
|
||||
class ConnectInfo : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConnectInfo(QWidget* parent = 0);
|
||||
explicit ConnectInfo(QWidget *parent = 0);
|
||||
~ConnectInfo();
|
||||
void showEvent(QShowEvent *event);
|
||||
void RefreshData();
|
||||
|
@ -37,12 +37,12 @@ QString GetToolTipIconHtml()
|
||||
return iconTemplate.arg(iconFile);
|
||||
}
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog),
|
||||
connectInfo(new ConnectInfo),
|
||||
sessionTableTimer(new QTimer),
|
||||
passwordManuallyEdited(false)
|
||||
SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
: QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog),
|
||||
connectInfo(new ConnectInfo),
|
||||
sessionTableTimer(new QTimer),
|
||||
passwordManuallyEdited(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->websocketSessionTable->horizontalHeader()->resizeSection(3, 100); // Resize Session Table column widths
|
||||
@ -54,18 +54,13 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
// Set the appropriate tooltip icon for the theme
|
||||
ui->enableDebugLoggingToolTipLabel->setText(GetToolTipIconHtml());
|
||||
|
||||
connect(sessionTableTimer, &QTimer::timeout,
|
||||
this, &SettingsDialog::FillSessionTable);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &SettingsDialog::DialogButtonClicked);
|
||||
connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged,
|
||||
this, &SettingsDialog::EnableAuthenticationCheckBoxChanged);
|
||||
connect(ui->generatePasswordButton, &QPushButton::clicked,
|
||||
this, &SettingsDialog::GeneratePasswordButtonClicked);
|
||||
connect(ui->showConnectInfoButton, &QPushButton::clicked,
|
||||
this, &SettingsDialog::ShowConnectInfoButtonClicked);
|
||||
connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited,
|
||||
this, &SettingsDialog::PasswordEdited);
|
||||
connect(sessionTableTimer, &QTimer::timeout, this, &SettingsDialog::FillSessionTable);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &SettingsDialog::DialogButtonClicked);
|
||||
connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged, this,
|
||||
&SettingsDialog::EnableAuthenticationCheckBoxChanged);
|
||||
connect(ui->generatePasswordButton, &QPushButton::clicked, this, &SettingsDialog::GeneratePasswordButtonClicked);
|
||||
connect(ui->showConnectInfoButton, &QPushButton::clicked, this, &SettingsDialog::ShowConnectInfoButtonClicked);
|
||||
connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited, this, &SettingsDialog::PasswordEdited);
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
@ -83,18 +78,8 @@ void SettingsDialog::showEvent(QShowEvent *)
|
||||
return;
|
||||
}
|
||||
|
||||
ui->enableWebSocketServerCheckBox->setChecked(conf->ServerEnabled);
|
||||
ui->enableSystemTrayAlertsCheckBox->setChecked(conf->AlertsEnabled);
|
||||
ui->enableDebugLoggingCheckBox->setChecked(conf->DebugEnabled);
|
||||
ui->enableAuthenticationCheckBox->setChecked(conf->AuthRequired);
|
||||
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
|
||||
ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired);
|
||||
ui->generatePasswordButton->setEnabled(conf->AuthRequired);
|
||||
ui->serverPortSpinBox->setValue(conf->ServerPort);
|
||||
|
||||
if (conf->PortOverridden) {
|
||||
if (conf->PortOverridden)
|
||||
ui->serverPortSpinBox->setEnabled(false);
|
||||
}
|
||||
|
||||
if (conf->PasswordOverridden) {
|
||||
ui->enableAuthenticationCheckBox->setEnabled(false);
|
||||
@ -104,7 +89,7 @@ void SettingsDialog::showEvent(QShowEvent *)
|
||||
|
||||
passwordManuallyEdited = false;
|
||||
|
||||
FillSessionTable();
|
||||
RefreshData();
|
||||
|
||||
sessionTableTimer->start(1000);
|
||||
}
|
||||
@ -125,6 +110,27 @@ void SettingsDialog::ToggleShowHide()
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::RefreshData()
|
||||
{
|
||||
auto conf = GetConfig();
|
||||
if (!conf) {
|
||||
blog(LOG_ERROR, "[SettingsDialog::RefreshData] Unable to retreive config!");
|
||||
return;
|
||||
}
|
||||
|
||||
ui->enableWebSocketServerCheckBox->setChecked(conf->ServerEnabled);
|
||||
ui->enableSystemTrayAlertsCheckBox->setChecked(conf->AlertsEnabled);
|
||||
ui->enableDebugLoggingCheckBox->setChecked(conf->DebugEnabled);
|
||||
ui->serverPortSpinBox->setValue(conf->ServerPort);
|
||||
ui->enableAuthenticationCheckBox->setChecked(conf->AuthRequired);
|
||||
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
|
||||
|
||||
ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired);
|
||||
ui->generatePasswordButton->setEnabled(conf->AuthRequired);
|
||||
|
||||
FillSessionTable();
|
||||
}
|
||||
|
||||
void SettingsDialog::DialogButtonClicked(QAbstractButton *button)
|
||||
{
|
||||
if (button == ui->buttonBox->button(QDialogButtonBox::Ok)) {
|
||||
@ -162,28 +168,30 @@ void SettingsDialog::SaveFormData()
|
||||
int ret = msgBox.exec();
|
||||
|
||||
switch (ret) {
|
||||
case QMessageBox::Yes:
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
default:
|
||||
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
|
||||
return;
|
||||
case QMessageBox::Yes:
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
default:
|
||||
ui->serverPasswordLineEdit->setText(conf->ServerPassword);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsRestart = (conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) ||
|
||||
(ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text()) ||
|
||||
(conf->ServerPort != ui->serverPortSpinBox->value());
|
||||
bool needsRestart =
|
||||
(conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) ||
|
||||
(conf->ServerPort != ui->serverPortSpinBox->value()) ||
|
||||
(ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text());
|
||||
|
||||
conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked();
|
||||
conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked();
|
||||
conf->DebugEnabled = ui->enableDebugLoggingCheckBox->isChecked();
|
||||
conf->ServerPort = ui->serverPortSpinBox->value();
|
||||
conf->AuthRequired = ui->enableAuthenticationCheckBox->isChecked();
|
||||
conf->ServerPassword = ui->serverPasswordLineEdit->text();
|
||||
conf->ServerPort = ui->serverPortSpinBox->value();
|
||||
|
||||
conf->Save();
|
||||
|
||||
RefreshData();
|
||||
connectInfo->RefreshData();
|
||||
|
||||
if (needsRestart) {
|
||||
@ -226,7 +234,8 @@ void SettingsDialog::FillSessionTable()
|
||||
QTableWidgetItem *durationItem = new QTableWidgetItem(QTime(0, 0, sessionDuration).toString("hh:mm:ss"));
|
||||
ui->websocketSessionTable->setItem(i, 1, durationItem);
|
||||
|
||||
QTableWidgetItem *statsItem = new QTableWidgetItem(QString("%1/%2").arg(session.incomingMessages).arg(session.outgoingMessages));
|
||||
QTableWidgetItem *statsItem =
|
||||
new QTableWidgetItem(QString("%1/%2").arg(session.incomingMessages).arg(session.outgoingMessages));
|
||||
ui->websocketSessionTable->setItem(i, 2, statsItem);
|
||||
|
||||
QLabel *identifiedLabel = new QLabel();
|
||||
@ -246,9 +255,7 @@ void SettingsDialog::FillSessionTable()
|
||||
invalidateButtonLayout->setContentsMargins(0, 0, 0, 0);
|
||||
invalidateButtonWidget->setLayout(invalidateButtonLayout);
|
||||
ui->websocketSessionTable->setCellWidget(i, 4, invalidateButtonWidget);
|
||||
connect(invalidateButton, &QPushButton::clicked, [=]() {
|
||||
webSocketServer->InvalidateSession(session.hdl);
|
||||
});
|
||||
connect(invalidateButton, &QPushButton::clicked, [=]() { webSocketServer->InvalidateSession(session.hdl); });
|
||||
|
||||
i++;
|
||||
}
|
||||
@ -285,11 +292,11 @@ void SettingsDialog::ShowConnectInfoButtonClicked()
|
||||
int ret = msgBox.exec();
|
||||
|
||||
switch (ret) {
|
||||
case QMessageBox::Yes:
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
default:
|
||||
return;
|
||||
case QMessageBox::Yes:
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,16 +27,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "ui_SettingsDialog.h"
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
class SettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget* parent = 0);
|
||||
explicit SettingsDialog(QWidget *parent = 0);
|
||||
~SettingsDialog();
|
||||
void showEvent(QShowEvent *event);
|
||||
void hideEvent(QHideEvent *event);
|
||||
void ToggleShowHide();
|
||||
void RefreshData();
|
||||
|
||||
private Q_SLOTS:
|
||||
void DialogButtonClicked(QAbstractButton *button);
|
||||
|
@ -32,10 +32,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
OBS_MODULE_AUTHOR("OBSProject")
|
||||
const char *obs_module_name(void) { return "obs-websocket"; }
|
||||
const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); }
|
||||
const char *obs_module_name(void)
|
||||
{
|
||||
return "obs-websocket";
|
||||
}
|
||||
const char *obs_module_description(void)
|
||||
{
|
||||
return obs_module_text("OBSWebSocket.Plugin.Description");
|
||||
}
|
||||
|
||||
os_cpu_usage_info_t* _cpuUsageInfo;
|
||||
os_cpu_usage_info_t *_cpuUsageInfo;
|
||||
ConfigPtr _config;
|
||||
EventHandlerPtr _eventHandler;
|
||||
WebSocketApiPtr _webSocketApi;
|
||||
@ -46,7 +52,8 @@ void WebSocketApiEventCallback(std::string vendorName, std::string eventType, ob
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION);
|
||||
blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION,
|
||||
OBS_WEBSOCKET_RPC_VERSION);
|
||||
blog(LOG_INFO, "[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s", QT_VERSION_STR, qVersion());
|
||||
blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d", ASIO_VERSION);
|
||||
|
||||
@ -69,13 +76,13 @@ bool obs_module_load(void)
|
||||
|
||||
// Initialize the settings dialog
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||
QMainWindow *mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
_settingsDialog = new SettingsDialog(mainWindow);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
// Add the settings dialog to the tools menu
|
||||
const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle");
|
||||
QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
|
||||
const char *menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle");
|
||||
QAction *menuAction = (QAction *)obs_frontend_add_tools_menu_qaction(menuActionText);
|
||||
QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); });
|
||||
|
||||
blog(LOG_INFO, "[obs_module_load] Module loaded.");
|
||||
@ -111,7 +118,7 @@ void obs_module_unload()
|
||||
blog(LOG_INFO, "[obs_module_unload] Finished shutting down.");
|
||||
}
|
||||
|
||||
os_cpu_usage_info_t* GetCpuUsageInfo()
|
||||
os_cpu_usage_info_t *GetCpuUsageInfo()
|
||||
{
|
||||
return _cpuUsageInfo;
|
||||
}
|
||||
@ -190,12 +197,26 @@ void obs_module_post_load()
|
||||
{
|
||||
blog(LOG_INFO, "[obs_module_post_load] Post load started.");
|
||||
|
||||
// Test plugin API version fetch
|
||||
uint apiVersion = obs_websocket_get_api_version();
|
||||
blog(LOG_INFO, "[obs_module_post_load] obs-websocket plugin API version: %u", apiVersion);
|
||||
|
||||
// Test calling obs-websocket requests
|
||||
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
|
||||
if (response) {
|
||||
blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
|
||||
response->status_code, response->comment, response->response_data);
|
||||
obs_websocket_request_response_free(response);
|
||||
}
|
||||
|
||||
// Test vendor creation
|
||||
auto vendor = obs_websocket_register_vendor("obs-websocket-test");
|
||||
if (!vendor) {
|
||||
blog(LOG_WARNING, "[obs_module_post_load] Failed to create vendor!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test vendor request registration
|
||||
if (!obs_websocket_vendor_register_request(vendor, "TestRequest", test_vendor_request_cb, vendor)) {
|
||||
blog(LOG_WARNING, "[obs_module_post_load] Failed to register vendor request!");
|
||||
return;
|
||||
|
@ -38,7 +38,7 @@ typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr;
|
||||
class WebSocketServer;
|
||||
typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr;
|
||||
|
||||
os_cpu_usage_info_t* GetCpuUsageInfo();
|
||||
os_cpu_usage_info_t *GetCpuUsageInfo();
|
||||
|
||||
ConfigPtr GetConfig();
|
||||
|
||||
|
@ -24,8 +24,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../utils/Compat.h"
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
struct SerialFrameBatch
|
||||
{
|
||||
struct SerialFrameBatch {
|
||||
RequestHandler &requestHandler;
|
||||
std::queue<RequestBatchRequest> requests;
|
||||
std::vector<RequestResult> results;
|
||||
@ -37,43 +36,46 @@ struct SerialFrameBatch
|
||||
std::mutex conditionMutex;
|
||||
std::condition_variable condition;
|
||||
|
||||
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure) :
|
||||
requestHandler(requestHandler),
|
||||
variables(variables),
|
||||
haltOnFailure(haltOnFailure),
|
||||
frameCount(0),
|
||||
sleepUntilFrame(0)
|
||||
{}
|
||||
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure)
|
||||
: requestHandler(requestHandler),
|
||||
variables(variables),
|
||||
haltOnFailure(haltOnFailure),
|
||||
frameCount(0),
|
||||
sleepUntilFrame(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct ParallelBatchResults
|
||||
{
|
||||
struct ParallelBatchResults {
|
||||
RequestHandler &requestHandler;
|
||||
std::vector<RequestResult> results;
|
||||
|
||||
std::mutex conditionMutex;
|
||||
std::condition_variable condition;
|
||||
|
||||
ParallelBatchResults(RequestHandler &requestHandler) :
|
||||
requestHandler(requestHandler)
|
||||
{}
|
||||
ParallelBatchResults(RequestHandler &requestHandler) : requestHandler(requestHandler) {}
|
||||
};
|
||||
|
||||
// `{"inputName": "inputNameVariable"}` is essentially `inputName = inputNameVariable`
|
||||
static void PreProcessVariables(const json &variables, RequestBatchRequest &request)
|
||||
{
|
||||
if (variables.empty() || !request.InputVariables.is_object() || request.InputVariables.empty() || !request.RequestData.is_object())
|
||||
if (variables.empty() || !request.InputVariables.is_object() || request.InputVariables.empty() ||
|
||||
!request.RequestData.is_object())
|
||||
return;
|
||||
|
||||
for (auto& [key, value] : request.InputVariables.items()) {
|
||||
for (auto &[key, value] : request.InputVariables.items()) {
|
||||
if (!value.is_string()) {
|
||||
blog_debug("[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `inputVariables `is not a string. Skipping!", key.c_str());
|
||||
blog_debug(
|
||||
"[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `inputVariables `is not a string. Skipping!",
|
||||
key.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string valueString = value;
|
||||
if (!variables.contains(valueString)) {
|
||||
blog_debug("[WebSocketServer::ProcessRequestBatch] `inputVariables` requested variable `%s`, but it does not exist. Skipping!", valueString.c_str());
|
||||
blog_debug(
|
||||
"[WebSocketServer::ProcessRequestBatch] `inputVariables` requested variable `%s`, but it does not exist. Skipping!",
|
||||
valueString.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -89,15 +91,19 @@ static void PostProcessVariables(json &variables, const RequestBatchRequest &req
|
||||
if (!request.OutputVariables.is_object() || request.OutputVariables.empty() || requestResult.ResponseData.empty())
|
||||
return;
|
||||
|
||||
for (auto& [key, value] : request.OutputVariables.items()) {
|
||||
for (auto &[key, value] : request.OutputVariables.items()) {
|
||||
if (!value.is_string()) {
|
||||
blog_debug("[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `outputVariables` is not a string. Skipping!", key.c_str());
|
||||
blog_debug(
|
||||
"[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `outputVariables` is not a string. Skipping!",
|
||||
key.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string valueString = value;
|
||||
if (!requestResult.ResponseData.contains(valueString)) {
|
||||
blog_debug("[WebSocketServer::ProcessRequestBatch] `outputVariables` requested responseData field `%s`, but it does not exist. Skipping!", valueString.c_str());
|
||||
blog_debug(
|
||||
"[WebSocketServer::ProcessRequestBatch] `outputVariables` requested responseData field `%s`, but it does not exist. Skipping!",
|
||||
valueString.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -109,7 +115,7 @@ static void ObsTickCallback(void *param, float)
|
||||
{
|
||||
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
|
||||
|
||||
auto serialFrameBatch = static_cast<SerialFrameBatch*>(param);
|
||||
auto serialFrameBatch = static_cast<SerialFrameBatch *>(param);
|
||||
|
||||
// Increment frame count
|
||||
serialFrameBatch->frameCount++;
|
||||
@ -156,7 +162,10 @@ static void ObsTickCallback(void *param, float)
|
||||
serialFrameBatch->condition.notify_one();
|
||||
}
|
||||
|
||||
std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session, RequestBatchExecutionType::RequestBatchExecutionType executionType, std::vector<RequestBatchRequest> &requests, json &variables, bool haltOnFailure)
|
||||
std::vector<RequestResult>
|
||||
RequestBatchHandler::ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session,
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType,
|
||||
std::vector<RequestBatchRequest> &requests, json &variables, bool haltOnFailure)
|
||||
{
|
||||
RequestHandler requestHandler(session);
|
||||
if (executionType == RequestBatchExecutionType::SerialRealtime) {
|
||||
@ -189,7 +198,7 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
|
||||
|
||||
// Wait until the graphics thread processes the last request in the queue
|
||||
std::unique_lock<std::mutex> lock(serialFrameBatch.conditionMutex);
|
||||
serialFrameBatch.condition.wait(lock, [&serialFrameBatch]{return serialFrameBatch.requests.empty();});
|
||||
serialFrameBatch.condition.wait(lock, [&serialFrameBatch] { return serialFrameBatch.requests.empty(); });
|
||||
|
||||
// Remove the created callback entry since we don't need it anymore
|
||||
obs_remove_tick_callback(ObsTickCallback, &serialFrameBatch);
|
||||
@ -215,7 +224,9 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
|
||||
|
||||
// Wait for the last request to finish processing
|
||||
size_t requestCount = requests.size();
|
||||
parallelResults.condition.wait(lock, [¶llelResults, requestCount]{return parallelResults.results.size() == requestCount;});
|
||||
parallelResults.condition.wait(lock, [¶llelResults, requestCount] {
|
||||
return parallelResults.results.size() == requestCount;
|
||||
});
|
||||
|
||||
return parallelResults.results;
|
||||
}
|
||||
|
@ -24,5 +24,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "rpc/RequestBatchRequest.h"
|
||||
|
||||
namespace RequestBatchHandler {
|
||||
std::vector<RequestResult> ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session, RequestBatchExecutionType::RequestBatchExecutionType executionType, std::vector<RequestBatchRequest> &requests, json &variables, bool haltOnFailure);
|
||||
std::vector<RequestResult> ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session,
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType,
|
||||
std::vector<RequestBatchRequest> &requests, json &variables,
|
||||
bool haltOnFailure);
|
||||
}
|
||||
|
@ -23,8 +23,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "RequestHandler.h"
|
||||
|
||||
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
|
||||
{
|
||||
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap{
|
||||
// General
|
||||
{"GetVersion", &RequestHandler::GetVersion},
|
||||
{"GetStats", &RequestHandler::GetStats},
|
||||
@ -138,6 +137,8 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
|
||||
{"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex},
|
||||
{"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode},
|
||||
{"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode},
|
||||
{"GetSceneItemPrivateSettings", &RequestHandler::GetSceneItemPrivateSettings},
|
||||
{"SetSceneItemPrivateSettings", &RequestHandler::SetSceneItemPrivateSettings},
|
||||
|
||||
// Outputs
|
||||
{"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus},
|
||||
@ -150,6 +151,13 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
|
||||
{"StopReplayBuffer", &RequestHandler::StopReplayBuffer},
|
||||
{"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer},
|
||||
{"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay},
|
||||
{"GetOutputList", &RequestHandler::GetOutputList},
|
||||
{"GetOutputStatus", &RequestHandler::GetOutputStatus},
|
||||
{"ToggleOutput", &RequestHandler::ToggleOutput},
|
||||
{"StartOutput", &RequestHandler::StartOutput},
|
||||
{"StopOutput", &RequestHandler::StopOutput},
|
||||
{"GetOutputSettings", &RequestHandler::GetOutputSettings},
|
||||
{"SetOutputSettings", &RequestHandler::SetOutputSettings},
|
||||
|
||||
// Stream
|
||||
{"GetStreamStatus", &RequestHandler::GetStreamStatus},
|
||||
@ -179,14 +187,14 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
|
||||
{"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog},
|
||||
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
|
||||
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
|
||||
{"GetMonitorList", &RequestHandler::GetMonitorList},
|
||||
{"OpenVideoMixProjector", &RequestHandler::OpenVideoMixProjector},
|
||||
{"OpenSourceProjector", &RequestHandler::OpenSourceProjector},
|
||||
};
|
||||
|
||||
RequestHandler::RequestHandler(SessionPtr session) :
|
||||
_session(session)
|
||||
{
|
||||
}
|
||||
RequestHandler::RequestHandler(SessionPtr session) : _session(session) {}
|
||||
|
||||
RequestResult RequestHandler::ProcessRequest(const Request& request)
|
||||
RequestResult RequestHandler::ProcessRequest(const Request &request)
|
||||
{
|
||||
#ifdef PLUGIN_TESTS
|
||||
ScopeProfiler prof{"obs_websocket_request_processing"};
|
||||
@ -196,12 +204,12 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object.");
|
||||
|
||||
if (request.RequestType.empty())
|
||||
return RequestResult::Error(RequestStatus::MissingRequestType, "Your request is missing a `requestType`");
|
||||
return RequestResult::Error(RequestStatus::MissingRequestType, "Your request's `requestType` may not be empty.");
|
||||
|
||||
RequestMethodHandler handler;
|
||||
try {
|
||||
handler = _handlerMap.at(request.RequestType);
|
||||
} catch (const std::out_of_range& oor) {
|
||||
} catch (const std::out_of_range &oor) {
|
||||
return RequestResult::Error(RequestStatus::UnknownRequestType, "Your request type is not valid.");
|
||||
}
|
||||
|
||||
@ -211,7 +219,7 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
|
||||
std::vector<std::string> RequestHandler::GetRequestList()
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
for (auto const& [key, val] : _handlerMap) {
|
||||
for (auto const &[key, val] : _handlerMap) {
|
||||
ret.push_back(key);
|
||||
}
|
||||
|
||||
|
@ -33,171 +33,183 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
class RequestHandler;
|
||||
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&);
|
||||
typedef RequestResult (RequestHandler::*RequestMethodHandler)(const Request &);
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
RequestHandler(SessionPtr session);
|
||||
public:
|
||||
RequestHandler(SessionPtr session = nullptr);
|
||||
|
||||
RequestResult ProcessRequest(const Request& request);
|
||||
std::vector<std::string> GetRequestList();
|
||||
RequestResult ProcessRequest(const Request &request);
|
||||
std::vector<std::string> GetRequestList();
|
||||
|
||||
private:
|
||||
// General
|
||||
RequestResult GetVersion(const Request&);
|
||||
RequestResult GetStats(const Request&);
|
||||
RequestResult BroadcastCustomEvent(const Request&);
|
||||
RequestResult CallVendorRequest(const Request&);
|
||||
RequestResult GetHotkeyList(const Request&);
|
||||
RequestResult TriggerHotkeyByName(const Request&);
|
||||
RequestResult TriggerHotkeyByKeySequence(const Request&);
|
||||
RequestResult Sleep(const Request&);
|
||||
private:
|
||||
// General
|
||||
RequestResult GetVersion(const Request &);
|
||||
RequestResult GetStats(const Request &);
|
||||
RequestResult BroadcastCustomEvent(const Request &);
|
||||
RequestResult CallVendorRequest(const Request &);
|
||||
RequestResult GetHotkeyList(const Request &);
|
||||
RequestResult TriggerHotkeyByName(const Request &);
|
||||
RequestResult TriggerHotkeyByKeySequence(const Request &);
|
||||
RequestResult Sleep(const Request &);
|
||||
|
||||
// Config
|
||||
RequestResult GetPersistentData(const Request&);
|
||||
RequestResult SetPersistentData(const Request&);
|
||||
RequestResult GetSceneCollectionList(const Request&);
|
||||
RequestResult SetCurrentSceneCollection(const Request&);
|
||||
RequestResult CreateSceneCollection(const Request&);
|
||||
RequestResult GetProfileList(const Request&);
|
||||
RequestResult SetCurrentProfile(const Request&);
|
||||
RequestResult CreateProfile(const Request&);
|
||||
RequestResult RemoveProfile(const Request&);
|
||||
RequestResult GetProfileParameter(const Request&);
|
||||
RequestResult SetProfileParameter(const Request&);
|
||||
RequestResult GetVideoSettings(const Request&);
|
||||
RequestResult SetVideoSettings(const Request&);
|
||||
RequestResult GetStreamServiceSettings(const Request&);
|
||||
RequestResult SetStreamServiceSettings(const Request&);
|
||||
RequestResult GetRecordDirectory(const Request&);
|
||||
// Config
|
||||
RequestResult GetPersistentData(const Request &);
|
||||
RequestResult SetPersistentData(const Request &);
|
||||
RequestResult GetSceneCollectionList(const Request &);
|
||||
RequestResult SetCurrentSceneCollection(const Request &);
|
||||
RequestResult CreateSceneCollection(const Request &);
|
||||
RequestResult GetProfileList(const Request &);
|
||||
RequestResult SetCurrentProfile(const Request &);
|
||||
RequestResult CreateProfile(const Request &);
|
||||
RequestResult RemoveProfile(const Request &);
|
||||
RequestResult GetProfileParameter(const Request &);
|
||||
RequestResult SetProfileParameter(const Request &);
|
||||
RequestResult GetVideoSettings(const Request &);
|
||||
RequestResult SetVideoSettings(const Request &);
|
||||
RequestResult GetStreamServiceSettings(const Request &);
|
||||
RequestResult SetStreamServiceSettings(const Request &);
|
||||
RequestResult GetRecordDirectory(const Request &);
|
||||
|
||||
// Sources
|
||||
RequestResult GetSourceActive(const Request&);
|
||||
RequestResult GetSourceScreenshot(const Request&);
|
||||
RequestResult SaveSourceScreenshot(const Request&);
|
||||
RequestResult GetSourcePrivateSettings(const Request&);
|
||||
RequestResult SetSourcePrivateSettings(const Request&);
|
||||
// Sources
|
||||
RequestResult GetSourceActive(const Request &);
|
||||
RequestResult GetSourceScreenshot(const Request &);
|
||||
RequestResult SaveSourceScreenshot(const Request &);
|
||||
RequestResult GetSourcePrivateSettings(const Request &);
|
||||
RequestResult SetSourcePrivateSettings(const Request &);
|
||||
|
||||
// Scenes
|
||||
RequestResult GetSceneList(const Request&);
|
||||
RequestResult GetGroupList(const Request&);
|
||||
RequestResult GetCurrentProgramScene(const Request&);
|
||||
RequestResult SetCurrentProgramScene(const Request&);
|
||||
RequestResult GetCurrentPreviewScene(const Request&);
|
||||
RequestResult SetCurrentPreviewScene(const Request&);
|
||||
RequestResult CreateScene(const Request&);
|
||||
RequestResult RemoveScene(const Request&);
|
||||
RequestResult SetSceneName(const Request&);
|
||||
RequestResult GetSceneSceneTransitionOverride(const Request&);
|
||||
RequestResult SetSceneSceneTransitionOverride(const Request&);
|
||||
// Scenes
|
||||
RequestResult GetSceneList(const Request &);
|
||||
RequestResult GetGroupList(const Request &);
|
||||
RequestResult GetCurrentProgramScene(const Request &);
|
||||
RequestResult SetCurrentProgramScene(const Request &);
|
||||
RequestResult GetCurrentPreviewScene(const Request &);
|
||||
RequestResult SetCurrentPreviewScene(const Request &);
|
||||
RequestResult CreateScene(const Request &);
|
||||
RequestResult RemoveScene(const Request &);
|
||||
RequestResult SetSceneName(const Request &);
|
||||
RequestResult GetSceneSceneTransitionOverride(const Request &);
|
||||
RequestResult SetSceneSceneTransitionOverride(const Request &);
|
||||
|
||||
// Inputs
|
||||
RequestResult GetInputList(const Request&);
|
||||
RequestResult GetInputKindList(const Request&);
|
||||
RequestResult GetSpecialInputs(const Request&);
|
||||
RequestResult CreateInput(const Request&);
|
||||
RequestResult RemoveInput(const Request&);
|
||||
RequestResult SetInputName(const Request&);
|
||||
RequestResult GetInputDefaultSettings(const Request&);
|
||||
RequestResult GetInputSettings(const Request&);
|
||||
RequestResult SetInputSettings(const Request&);
|
||||
RequestResult GetInputMute(const Request&);
|
||||
RequestResult SetInputMute(const Request&);
|
||||
RequestResult ToggleInputMute(const Request&);
|
||||
RequestResult GetInputVolume(const Request&);
|
||||
RequestResult SetInputVolume(const Request&);
|
||||
RequestResult GetInputAudioBalance(const Request&);
|
||||
RequestResult SetInputAudioBalance(const Request&);
|
||||
RequestResult GetInputAudioSyncOffset(const Request&);
|
||||
RequestResult SetInputAudioSyncOffset(const Request&);
|
||||
RequestResult GetInputAudioMonitorType(const Request&);
|
||||
RequestResult SetInputAudioMonitorType(const Request&);
|
||||
RequestResult GetInputAudioTracks(const Request&);
|
||||
RequestResult SetInputAudioTracks(const Request&);
|
||||
RequestResult GetInputPropertiesListPropertyItems(const Request&);
|
||||
RequestResult PressInputPropertiesButton(const Request&);
|
||||
// Inputs
|
||||
RequestResult GetInputList(const Request &);
|
||||
RequestResult GetInputKindList(const Request &);
|
||||
RequestResult GetSpecialInputs(const Request &);
|
||||
RequestResult CreateInput(const Request &);
|
||||
RequestResult RemoveInput(const Request &);
|
||||
RequestResult SetInputName(const Request &);
|
||||
RequestResult GetInputDefaultSettings(const Request &);
|
||||
RequestResult GetInputSettings(const Request &);
|
||||
RequestResult SetInputSettings(const Request &);
|
||||
RequestResult GetInputMute(const Request &);
|
||||
RequestResult SetInputMute(const Request &);
|
||||
RequestResult ToggleInputMute(const Request &);
|
||||
RequestResult GetInputVolume(const Request &);
|
||||
RequestResult SetInputVolume(const Request &);
|
||||
RequestResult GetInputAudioBalance(const Request &);
|
||||
RequestResult SetInputAudioBalance(const Request &);
|
||||
RequestResult GetInputAudioSyncOffset(const Request &);
|
||||
RequestResult SetInputAudioSyncOffset(const Request &);
|
||||
RequestResult GetInputAudioMonitorType(const Request &);
|
||||
RequestResult SetInputAudioMonitorType(const Request &);
|
||||
RequestResult GetInputAudioTracks(const Request &);
|
||||
RequestResult SetInputAudioTracks(const Request &);
|
||||
RequestResult GetInputPropertiesListPropertyItems(const Request &);
|
||||
RequestResult PressInputPropertiesButton(const Request &);
|
||||
|
||||
// Transitions
|
||||
RequestResult GetTransitionKindList(const Request&);
|
||||
RequestResult GetSceneTransitionList(const Request&);
|
||||
RequestResult GetCurrentSceneTransition(const Request&);
|
||||
RequestResult SetCurrentSceneTransition(const Request&);
|
||||
RequestResult SetCurrentSceneTransitionDuration(const Request&);
|
||||
RequestResult SetCurrentSceneTransitionSettings(const Request&);
|
||||
RequestResult GetCurrentSceneTransitionCursor(const Request&);
|
||||
RequestResult TriggerStudioModeTransition(const Request&);
|
||||
RequestResult SetTBarPosition(const Request&);
|
||||
// Transitions
|
||||
RequestResult GetTransitionKindList(const Request &);
|
||||
RequestResult GetSceneTransitionList(const Request &);
|
||||
RequestResult GetCurrentSceneTransition(const Request &);
|
||||
RequestResult SetCurrentSceneTransition(const Request &);
|
||||
RequestResult SetCurrentSceneTransitionDuration(const Request &);
|
||||
RequestResult SetCurrentSceneTransitionSettings(const Request &);
|
||||
RequestResult GetCurrentSceneTransitionCursor(const Request &);
|
||||
RequestResult TriggerStudioModeTransition(const Request &);
|
||||
RequestResult SetTBarPosition(const Request &);
|
||||
|
||||
// Filters
|
||||
RequestResult GetSourceFilterList(const Request&);
|
||||
RequestResult GetSourceFilterDefaultSettings(const Request&);
|
||||
RequestResult CreateSourceFilter(const Request&);
|
||||
RequestResult RemoveSourceFilter(const Request&);
|
||||
RequestResult SetSourceFilterName(const Request&);
|
||||
RequestResult GetSourceFilter(const Request&);
|
||||
RequestResult SetSourceFilterIndex(const Request&);
|
||||
RequestResult SetSourceFilterSettings(const Request&);
|
||||
RequestResult SetSourceFilterEnabled(const Request&);
|
||||
// Filters
|
||||
RequestResult GetSourceFilterList(const Request &);
|
||||
RequestResult GetSourceFilterDefaultSettings(const Request &);
|
||||
RequestResult CreateSourceFilter(const Request &);
|
||||
RequestResult RemoveSourceFilter(const Request &);
|
||||
RequestResult SetSourceFilterName(const Request &);
|
||||
RequestResult GetSourceFilter(const Request &);
|
||||
RequestResult SetSourceFilterIndex(const Request &);
|
||||
RequestResult SetSourceFilterSettings(const Request &);
|
||||
RequestResult SetSourceFilterEnabled(const Request &);
|
||||
|
||||
// Scene Items
|
||||
RequestResult GetSceneItemList(const Request&);
|
||||
RequestResult GetGroupSceneItemList(const Request&);
|
||||
RequestResult GetSceneItemId(const Request&);
|
||||
RequestResult CreateSceneItem(const Request&);
|
||||
RequestResult RemoveSceneItem(const Request&);
|
||||
RequestResult DuplicateSceneItem(const Request&);
|
||||
RequestResult GetSceneItemTransform(const Request&);
|
||||
RequestResult SetSceneItemTransform(const Request&);
|
||||
RequestResult GetSceneItemEnabled(const Request&);
|
||||
RequestResult SetSceneItemEnabled(const Request&);
|
||||
RequestResult GetSceneItemLocked(const Request&);
|
||||
RequestResult SetSceneItemLocked(const Request&);
|
||||
RequestResult GetSceneItemIndex(const Request&);
|
||||
RequestResult SetSceneItemIndex(const Request&);
|
||||
RequestResult GetSceneItemBlendMode(const Request&);
|
||||
RequestResult SetSceneItemBlendMode(const Request&);
|
||||
// Scene Items
|
||||
RequestResult GetSceneItemList(const Request &);
|
||||
RequestResult GetGroupSceneItemList(const Request &);
|
||||
RequestResult GetSceneItemId(const Request &);
|
||||
RequestResult CreateSceneItem(const Request &);
|
||||
RequestResult RemoveSceneItem(const Request &);
|
||||
RequestResult DuplicateSceneItem(const Request &);
|
||||
RequestResult GetSceneItemTransform(const Request &);
|
||||
RequestResult SetSceneItemTransform(const Request &);
|
||||
RequestResult GetSceneItemEnabled(const Request &);
|
||||
RequestResult SetSceneItemEnabled(const Request &);
|
||||
RequestResult GetSceneItemLocked(const Request &);
|
||||
RequestResult SetSceneItemLocked(const Request &);
|
||||
RequestResult GetSceneItemIndex(const Request &);
|
||||
RequestResult SetSceneItemIndex(const Request &);
|
||||
RequestResult GetSceneItemBlendMode(const Request &);
|
||||
RequestResult SetSceneItemBlendMode(const Request &);
|
||||
RequestResult GetSceneItemPrivateSettings(const Request &);
|
||||
RequestResult SetSceneItemPrivateSettings(const Request &);
|
||||
|
||||
// Outputs
|
||||
RequestResult GetVirtualCamStatus(const Request&);
|
||||
RequestResult ToggleVirtualCam(const Request&);
|
||||
RequestResult StartVirtualCam(const Request&);
|
||||
RequestResult StopVirtualCam(const Request&);
|
||||
RequestResult GetReplayBufferStatus(const Request&);
|
||||
RequestResult ToggleReplayBuffer(const Request&);
|
||||
RequestResult StartReplayBuffer(const Request&);
|
||||
RequestResult StopReplayBuffer(const Request&);
|
||||
RequestResult SaveReplayBuffer(const Request&);
|
||||
RequestResult GetLastReplayBufferReplay(const Request&);
|
||||
// Outputs
|
||||
RequestResult GetVirtualCamStatus(const Request &);
|
||||
RequestResult ToggleVirtualCam(const Request &);
|
||||
RequestResult StartVirtualCam(const Request &);
|
||||
RequestResult StopVirtualCam(const Request &);
|
||||
RequestResult GetReplayBufferStatus(const Request &);
|
||||
RequestResult ToggleReplayBuffer(const Request &);
|
||||
RequestResult StartReplayBuffer(const Request &);
|
||||
RequestResult StopReplayBuffer(const Request &);
|
||||
RequestResult SaveReplayBuffer(const Request &);
|
||||
RequestResult GetLastReplayBufferReplay(const Request &);
|
||||
RequestResult GetOutputList(const Request &);
|
||||
RequestResult GetOutputStatus(const Request &);
|
||||
RequestResult ToggleOutput(const Request &);
|
||||
RequestResult StartOutput(const Request &);
|
||||
RequestResult StopOutput(const Request &);
|
||||
RequestResult GetOutputSettings(const Request &);
|
||||
RequestResult SetOutputSettings(const Request &);
|
||||
|
||||
// Stream
|
||||
RequestResult GetStreamStatus(const Request&);
|
||||
RequestResult ToggleStream(const Request&);
|
||||
RequestResult StartStream(const Request&);
|
||||
RequestResult StopStream(const Request&);
|
||||
RequestResult SendStreamCaption(const Request&);
|
||||
// Stream
|
||||
RequestResult GetStreamStatus(const Request &);
|
||||
RequestResult ToggleStream(const Request &);
|
||||
RequestResult StartStream(const Request &);
|
||||
RequestResult StopStream(const Request &);
|
||||
RequestResult SendStreamCaption(const Request &);
|
||||
|
||||
// Record
|
||||
RequestResult GetRecordStatus(const Request&);
|
||||
RequestResult ToggleRecord(const Request&);
|
||||
RequestResult StartRecord(const Request&);
|
||||
RequestResult StopRecord(const Request&);
|
||||
RequestResult ToggleRecordPause(const Request&);
|
||||
RequestResult PauseRecord(const Request&);
|
||||
RequestResult ResumeRecord(const Request&);
|
||||
// Record
|
||||
RequestResult GetRecordStatus(const Request &);
|
||||
RequestResult ToggleRecord(const Request &);
|
||||
RequestResult StartRecord(const Request &);
|
||||
RequestResult StopRecord(const Request &);
|
||||
RequestResult ToggleRecordPause(const Request &);
|
||||
RequestResult PauseRecord(const Request &);
|
||||
RequestResult ResumeRecord(const Request &);
|
||||
|
||||
// Media Inputs
|
||||
RequestResult GetMediaInputStatus(const Request&);
|
||||
RequestResult SetMediaInputCursor(const Request&);
|
||||
RequestResult OffsetMediaInputCursor(const Request&);
|
||||
RequestResult TriggerMediaInputAction(const Request&);
|
||||
// Media Inputs
|
||||
RequestResult GetMediaInputStatus(const Request &);
|
||||
RequestResult SetMediaInputCursor(const Request &);
|
||||
RequestResult OffsetMediaInputCursor(const Request &);
|
||||
RequestResult TriggerMediaInputAction(const Request &);
|
||||
|
||||
// Ui
|
||||
RequestResult GetStudioModeEnabled(const Request&);
|
||||
RequestResult SetStudioModeEnabled(const Request&);
|
||||
RequestResult OpenInputPropertiesDialog(const Request&);
|
||||
RequestResult OpenInputFiltersDialog(const Request&);
|
||||
RequestResult OpenInputInteractDialog(const Request&);
|
||||
// Ui
|
||||
RequestResult GetStudioModeEnabled(const Request &);
|
||||
RequestResult SetStudioModeEnabled(const Request &);
|
||||
RequestResult OpenInputPropertiesDialog(const Request &);
|
||||
RequestResult OpenInputFiltersDialog(const Request &);
|
||||
RequestResult OpenInputInteractDialog(const Request &);
|
||||
RequestResult GetMonitorList(const Request &);
|
||||
RequestResult OpenVideoMixProjector(const Request &);
|
||||
RequestResult OpenSourceProjector(const Request &);
|
||||
|
||||
SessionPtr _session;
|
||||
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap;
|
||||
SessionPtr _session;
|
||||
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap;
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetPersistentData(const Request& request)
|
||||
RequestResult RequestHandler::GetPersistentData(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -53,7 +53,8 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
|
||||
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
|
||||
persistentDataPath += "/obsWebSocketPersistentData.json";
|
||||
else
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm.");
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound,
|
||||
"You have specified an invalid persistent data realm.");
|
||||
|
||||
json responseData;
|
||||
json persistentData;
|
||||
@ -79,11 +80,12 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetPersistentData(const Request& request)
|
||||
RequestResult RequestHandler::SetPersistentData(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!(request.ValidateString("realm", statusCode, comment) && request.ValidateString("slotName", statusCode, comment) && request.ValidateBasic("slotValue", statusCode, comment)))
|
||||
if (!(request.ValidateString("realm", statusCode, comment) && request.ValidateString("slotName", statusCode, comment) &&
|
||||
request.ValidateBasic("slotValue", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string realm = request.RequestData["realm"];
|
||||
@ -96,13 +98,15 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
|
||||
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
|
||||
persistentDataPath += "/obsWebSocketPersistentData.json";
|
||||
else
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm.");
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound,
|
||||
"You have specified an invalid persistent data realm.");
|
||||
|
||||
json persistentData = json::object();
|
||||
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
|
||||
persistentData[slotName] = slotValue;
|
||||
if (!Utils::Json::SetJsonFileContent(persistentDataPath, persistentData))
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to write persistent data. No permissions?");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"Unable to write persistent data. No permissions?");
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
@ -120,7 +124,7 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneCollectionList(const Request&)
|
||||
RequestResult RequestHandler::GetSceneCollectionList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection();
|
||||
@ -142,7 +146,7 @@ RequestResult RequestHandler::GetSceneCollectionList(const Request&)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentSceneCollection(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -158,9 +162,10 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
|
||||
std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection();
|
||||
// Avoid queueing tasks if nothing will change
|
||||
if (currentSceneCollectionName != sceneCollectionName) {
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
obs_frontend_set_current_scene_collection(static_cast<const char*>(param));
|
||||
}, (void*)sceneCollectionName.c_str(), true);
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
[](void *param) { obs_frontend_set_current_scene_collection(static_cast<const char *>(param)); },
|
||||
(void *)sceneCollectionName.c_str(), true);
|
||||
}
|
||||
|
||||
return RequestResult::Success();
|
||||
@ -180,7 +185,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::CreateSceneCollection(const Request& request)
|
||||
RequestResult RequestHandler::CreateSceneCollection(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -193,9 +198,10 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
|
||||
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
|
||||
|
||||
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||
QMainWindow *mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
bool success = false;
|
||||
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
|
||||
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success),
|
||||
Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
|
||||
if (!success)
|
||||
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene collection.");
|
||||
|
||||
@ -215,7 +221,7 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetProfileList(const Request&)
|
||||
RequestResult RequestHandler::GetProfileList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile();
|
||||
@ -235,7 +241,7 @@ RequestResult RequestHandler::GetProfileList(const Request&)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentProfile(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentProfile(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -251,9 +257,9 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
|
||||
std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile();
|
||||
// Avoid queueing tasks if nothing will change
|
||||
if (currentProfileName != profileName) {
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
obs_frontend_set_current_profile(static_cast<const char*>(param));
|
||||
}, (void*)profileName.c_str(), true);
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI, [](void *param) { obs_frontend_set_current_profile(static_cast<const char *>(param)); },
|
||||
(void *)profileName.c_str(), true);
|
||||
}
|
||||
|
||||
return RequestResult::Success();
|
||||
@ -271,7 +277,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::CreateProfile(const Request& request)
|
||||
RequestResult RequestHandler::CreateProfile(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -284,8 +290,9 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
|
||||
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
|
||||
|
||||
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
|
||||
QMainWindow *mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(profileName)));
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
@ -302,7 +309,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::RemoveProfile(const Request& request)
|
||||
RequestResult RequestHandler::RemoveProfile(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -318,8 +325,9 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
|
||||
if (profiles.size() < 2)
|
||||
return RequestResult::Error(RequestStatus::NotEnoughResources);
|
||||
|
||||
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
|
||||
QMainWindow *mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(profileName)));
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
@ -340,17 +348,18 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetProfileParameter(const Request& request)
|
||||
RequestResult RequestHandler::GetProfileParameter(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!(request.ValidateString("parameterCategory", statusCode, comment) && request.ValidateString("parameterName", statusCode, comment)))
|
||||
if (!(request.ValidateString("parameterCategory", statusCode, comment) &&
|
||||
request.ValidateString("parameterName", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string parameterCategory = request.RequestData["parameterCategory"];
|
||||
std::string parameterName = request.RequestData["parameterName"];
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
config_t *profile = obs_frontend_get_profile_config();
|
||||
|
||||
if (!profile)
|
||||
blog(LOG_ERROR, "[RequestHandler::GetProfileParameter] Profile is invalid.");
|
||||
@ -358,7 +367,8 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
|
||||
json responseData;
|
||||
if (config_has_default_value(profile, parameterCategory.c_str(), parameterName.c_str())) {
|
||||
responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str());
|
||||
responseData["defaultParameterValue"] = config_get_default_string(profile, parameterCategory.c_str(), parameterName.c_str());
|
||||
responseData["defaultParameterValue"] =
|
||||
config_get_default_string(profile, parameterCategory.c_str(), parameterName.c_str());
|
||||
} else if (config_has_user_value(profile, parameterCategory.c_str(), parameterName.c_str())) {
|
||||
responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str());
|
||||
responseData["defaultParameterValue"] = nullptr;
|
||||
@ -384,23 +394,24 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetProfileParameter(const Request& request)
|
||||
RequestResult RequestHandler::SetProfileParameter(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!(request.ValidateString("parameterCategory", statusCode, comment) &&
|
||||
request.ValidateString("parameterName", statusCode, comment)))
|
||||
request.ValidateString("parameterName", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string parameterCategory = request.RequestData["parameterCategory"];
|
||||
std::string parameterName = request.RequestData["parameterName"];
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
config_t *profile = obs_frontend_get_profile_config();
|
||||
|
||||
// Using check helpers here would just make the logic more complicated
|
||||
if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) {
|
||||
if (!config_remove_value(profile, parameterCategory.c_str(), parameterName.c_str()))
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "There are no existing instances of that profile parameter.");
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound,
|
||||
"There are no existing instances of that profile parameter.");
|
||||
} else if (request.RequestData["parameterValue"].is_string()) {
|
||||
std::string parameterValue = request.RequestData["parameterValue"];
|
||||
config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str());
|
||||
@ -432,7 +443,7 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetVideoSettings(const Request&)
|
||||
RequestResult RequestHandler::GetVideoSettings(const Request &)
|
||||
{
|
||||
struct obs_video_info ovi;
|
||||
if (!obs_get_video_info(&ovi))
|
||||
@ -468,23 +479,27 @@ RequestResult RequestHandler::GetVideoSettings(const Request&)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetVideoSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetVideoSettings(const Request &request)
|
||||
{
|
||||
if (obs_video_active())
|
||||
return RequestResult::Error(RequestStatus::OutputRunning, "Video settings cannot be changed while an output is active.");
|
||||
return RequestResult::Error(RequestStatus::OutputRunning,
|
||||
"Video settings cannot be changed while an output is active.");
|
||||
|
||||
RequestStatus::RequestStatus statusCode = RequestStatus::NoError;
|
||||
std::string comment;
|
||||
bool changeFps = (request.Contains("fpsNumerator") && request.Contains("fpsDenominator"));
|
||||
if (changeFps && !(request.ValidateOptionalNumber("fpsNumerator", statusCode, comment, 1) && request.ValidateOptionalNumber("fpsDenominator", statusCode, comment, 1)))
|
||||
if (changeFps && !(request.ValidateOptionalNumber("fpsNumerator", statusCode, comment, 1) &&
|
||||
request.ValidateOptionalNumber("fpsDenominator", statusCode, comment, 1)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
bool changeBaseRes = (request.Contains("baseWidth") && request.Contains("baseHeight"));
|
||||
if (changeBaseRes && !(request.ValidateOptionalNumber("baseWidth", statusCode, comment, 8, 4096) && request.ValidateOptionalNumber("baseHeight", statusCode, comment, 8, 4096)))
|
||||
if (changeBaseRes && !(request.ValidateOptionalNumber("baseWidth", statusCode, comment, 8, 4096) &&
|
||||
request.ValidateOptionalNumber("baseHeight", statusCode, comment, 8, 4096)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
bool changeOutputRes = (request.Contains("outputWidth") && request.Contains("outputHeight"));
|
||||
if (changeOutputRes && !(request.ValidateOptionalNumber("outputWidth", statusCode, comment, 8, 4096) && request.ValidateOptionalNumber("outputHeight", statusCode, comment, 8, 4096)))
|
||||
if (changeOutputRes && !(request.ValidateOptionalNumber("outputWidth", statusCode, comment, 8, 4096) &&
|
||||
request.ValidateOptionalNumber("outputHeight", statusCode, comment, 8, 4096)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
config_t *config = obs_frontend_get_profile_config();
|
||||
@ -527,7 +542,7 @@ RequestResult RequestHandler::SetVideoSettings(const Request& request)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
|
||||
RequestResult RequestHandler::GetStreamServiceSettings(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
|
||||
@ -554,21 +569,24 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
|
||||
* @category config
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetStreamServiceSettings(const Request &request)
|
||||
{
|
||||
if (obs_frontend_streaming_active())
|
||||
return RequestResult::Error(RequestStatus::OutputRunning, "You cannot change stream service settings while streaming.");
|
||||
return RequestResult::Error(RequestStatus::OutputRunning,
|
||||
"You cannot change stream service settings while streaming.");
|
||||
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!(request.ValidateString("streamServiceType", statusCode, comment) && request.ValidateObject("streamServiceSettings", statusCode, comment)))
|
||||
if (!(request.ValidateString("streamServiceType", statusCode, comment) &&
|
||||
request.ValidateObject("streamServiceSettings", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
OBSService currentStreamService = obs_frontend_get_streaming_service();
|
||||
|
||||
std::string streamServiceType = obs_service_get_type(currentStreamService);
|
||||
std::string requestedStreamServiceType = request.RequestData["streamServiceType"];
|
||||
OBSDataAutoRelease requestedStreamServiceSettings = Utils::Json::JsonToObsData(request.RequestData["streamServiceSettings"]);
|
||||
OBSDataAutoRelease requestedStreamServiceSettings =
|
||||
Utils::Json::JsonToObsData(request.RequestData["streamServiceSettings"]);
|
||||
|
||||
// Don't create a new service if the current service is the same type.
|
||||
if (streamServiceType == requestedStreamServiceType) {
|
||||
@ -582,10 +600,13 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
|
||||
obs_service_update(currentStreamService, newStreamServiceSettings);
|
||||
} else {
|
||||
// TODO: This leaks memory. I have no idea why.
|
||||
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, nullptr);
|
||||
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service",
|
||||
requestedStreamServiceSettings, nullptr);
|
||||
// TODO: Check service type here, instead of relying on service creation to fail.
|
||||
if (!newStreamService)
|
||||
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the stream service with the requested streamServiceType. It may be an invalid type.");
|
||||
return RequestResult::Error(
|
||||
RequestStatus::ResourceCreationFailed,
|
||||
"Failed to create the stream service with the requested streamServiceType. It may be an invalid type.");
|
||||
|
||||
obs_frontend_set_streaming_service(newStreamService);
|
||||
}
|
||||
@ -607,7 +628,7 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
|
||||
* @api requests
|
||||
* @category rconfig
|
||||
*/
|
||||
RequestResult RequestHandler::GetRecordDirectory(const Request&)
|
||||
RequestResult RequestHandler::GetRecordDirectory(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
|
||||
|
@ -33,12 +33,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::GetSourceFilterList(const Request& request)
|
||||
RequestResult RequestHandler::GetSourceFilterList(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
|
||||
if(!source)
|
||||
if (!source)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
json responseData;
|
||||
@ -61,7 +61,7 @@ RequestResult RequestHandler::GetSourceFilterList(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& request)
|
||||
RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -97,13 +97,14 @@ RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& requ
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::CreateSourceFilter(const Request& request)
|
||||
RequestResult RequestHandler::CreateSourceFilter(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
||||
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
|
||||
if (!(source && request.ValidateString("filterName", statusCode, comment) && request.ValidateString("filterKind", statusCode, comment)))
|
||||
if (!(source && request.ValidateString("filterName", statusCode, comment) &&
|
||||
request.ValidateString("filterKind", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string filterName = request.RequestData["filterName"];
|
||||
@ -114,7 +115,9 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
|
||||
std::string filterKind = request.RequestData["filterKind"];
|
||||
auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList();
|
||||
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end())
|
||||
return RequestResult::Error(RequestStatus::InvalidFilterKind, "Your specified filter kind is not supported by OBS. Check that any necessary plugins are loaded.");
|
||||
return RequestResult::Error(
|
||||
RequestStatus::InvalidFilterKind,
|
||||
"Your specified filter kind is not supported by OBS. Check that any necessary plugins are loaded.");
|
||||
|
||||
OBSDataAutoRelease filterSettings = nullptr;
|
||||
if (request.Contains("filterSettings")) {
|
||||
@ -126,7 +129,7 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
|
||||
|
||||
OBSSourceAutoRelease filter = Utils::Obs::ActionHelper::CreateSourceFilter(source, filterName, filterKind, filterSettings);
|
||||
|
||||
if(!filter)
|
||||
if (!filter)
|
||||
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the filter failed.");
|
||||
|
||||
return RequestResult::Success();
|
||||
@ -145,7 +148,7 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::RemoveSourceFilter(const Request& request)
|
||||
RequestResult RequestHandler::RemoveSourceFilter(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -172,7 +175,7 @@ RequestResult RequestHandler::RemoveSourceFilter(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::SetSourceFilterName(const Request& request)
|
||||
RequestResult RequestHandler::SetSourceFilterName(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -209,7 +212,7 @@ RequestResult RequestHandler::SetSourceFilterName(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::GetSourceFilter(const Request& request)
|
||||
RequestResult RequestHandler::GetSourceFilter(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -219,7 +222,8 @@ RequestResult RequestHandler::GetSourceFilter(const Request& request)
|
||||
|
||||
json responseData;
|
||||
responseData["filterEnabled"] = obs_source_enabled(pair.filter);
|
||||
responseData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(pair.source, pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe
|
||||
responseData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(
|
||||
pair.source, pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe
|
||||
responseData["filterKind"] = obs_source_get_id(pair.filter);
|
||||
|
||||
OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter);
|
||||
@ -242,7 +246,7 @@ RequestResult RequestHandler::GetSourceFilter(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::SetSourceFilterIndex(const Request& request)
|
||||
RequestResult RequestHandler::SetSourceFilterIndex(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -272,7 +276,7 @@ RequestResult RequestHandler::SetSourceFilterIndex(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetSourceFilterSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -292,7 +296,8 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
|
||||
|
||||
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]);
|
||||
if (!newSettings)
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"An internal data conversion operation failed. Please report this!");
|
||||
|
||||
if (overlay)
|
||||
obs_source_update(pair.filter, newSettings);
|
||||
@ -308,7 +313,7 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
|
||||
* Sets the enable state of a source filter.
|
||||
*
|
||||
* @requestField sourceName | String | Name of the source the filter is on
|
||||
* @requestField filterName | Number | Name of the filter
|
||||
* @requestField filterName | String | Name of the filter
|
||||
* @requestField filterEnabled | Boolean | New enable state of the filter
|
||||
*
|
||||
* @requestType SetSourceFilterEnabled
|
||||
@ -318,7 +323,7 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
|
||||
* @api requests
|
||||
* @category filters
|
||||
*/
|
||||
RequestResult RequestHandler::SetSourceFilterEnabled(const Request& request)
|
||||
RequestResult RequestHandler::SetSourceFilterEnabled(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
@ -18,6 +18,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QImageWriter>
|
||||
#include <QSysInfo>
|
||||
|
||||
#include "RequestHandler.h"
|
||||
#include "../websocketserver/WebSocketServer.h"
|
||||
@ -25,7 +26,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../WebSocketApi.h"
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
|
||||
/**
|
||||
* Gets data about the current plugin and RPC version.
|
||||
*
|
||||
@ -34,6 +34,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @responseField rpcVersion | Number | Current latest obs-websocket RPC version
|
||||
* @responseField availableRequests | Array<String> | Array of available RPC requests for the currently negotiated RPC version
|
||||
* @responseField supportedImageFormats | Array<String> | Image formats available in `GetSourceScreenshot` and `SaveSourceScreenshot` requests.
|
||||
* @responseField platform | String | Name of the platform. Usually `windows`, `macos`, or `ubuntu` (linux flavor). Not guaranteed to be any of those
|
||||
* @responseField platformDescription | String | Description of the platform, like `Windows 10 (10.0)`
|
||||
*
|
||||
* @requestType GetVersion
|
||||
* @complexity 1
|
||||
@ -42,7 +44,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetVersion(const Request&)
|
||||
RequestResult RequestHandler::GetVersion(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
|
||||
@ -52,11 +54,14 @@ RequestResult RequestHandler::GetVersion(const Request&)
|
||||
|
||||
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
|
||||
std::vector<std::string> supportedImageFormats;
|
||||
for (const QByteArray& format : imageWriterFormats) {
|
||||
for (const QByteArray &format : imageWriterFormats) {
|
||||
supportedImageFormats.push_back(format.toStdString());
|
||||
}
|
||||
responseData["supportedImageFormats"] = supportedImageFormats;
|
||||
|
||||
responseData["platform"] = QSysInfo::productType().toStdString();
|
||||
responseData["platformDescription"] = QSysInfo::prettyProductName().toStdString();
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
@ -82,12 +87,17 @@ RequestResult RequestHandler::GetVersion(const Request&)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetStats(const Request&)
|
||||
RequestResult RequestHandler::GetStats(const Request &)
|
||||
{
|
||||
json responseData = Utils::Obs::ObjectHelper::GetStats();
|
||||
|
||||
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages();
|
||||
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages();
|
||||
if (_session) {
|
||||
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages();
|
||||
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages();
|
||||
} else {
|
||||
responseData["webSocketSessionIncomingMessages"] = nullptr;
|
||||
responseData["webSocketSessionOutgoingMessages"] = nullptr;
|
||||
}
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
@ -104,7 +114,7 @@ RequestResult RequestHandler::GetStats(const Request&)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
|
||||
RequestResult RequestHandler::BroadcastCustomEvent(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -130,6 +140,8 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
|
||||
* @requestField requestType | String | The request type to call
|
||||
* @requestField ?requestData | Object | Object containing appropriate request data | {}
|
||||
*
|
||||
* @responseField vendorName | String | Echoed of `vendorName`
|
||||
* @responseField requestType | String | Echoed of `requestType`
|
||||
* @responseField responseData | Object | Object containing appropriate response data. {} if request does not provide any response data
|
||||
*
|
||||
* @requestType CallVendorRequest
|
||||
@ -139,11 +151,12 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::CallVendorRequest(const Request& request)
|
||||
RequestResult RequestHandler::CallVendorRequest(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!request.ValidateString("vendorName", statusCode, comment) || !request.ValidateString("requestType", statusCode, comment))
|
||||
if (!request.ValidateString("vendorName", statusCode, comment) ||
|
||||
!request.ValidateString("requestType", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string vendorName = request.RequestData["vendorName"];
|
||||
@ -161,20 +174,23 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
|
||||
|
||||
auto webSocketApi = GetWebSocketApi();
|
||||
if (!webSocketApi)
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to call request due to internal error.");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"Unable to call request due to internal error.");
|
||||
|
||||
auto ret = webSocketApi->PerformVendorRequest(vendorName, requestType, requestData, obsResponseData);
|
||||
switch (ret) {
|
||||
default:
|
||||
case WebSocketApi::RequestReturnCode::Normal:
|
||||
break;
|
||||
case WebSocketApi::RequestReturnCode::NoVendor:
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No vendor was found by that name.");
|
||||
case WebSocketApi::RequestReturnCode::NoVendorRequest:
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name.");
|
||||
default:
|
||||
case WebSocketApi::RequestReturnCode::Normal:
|
||||
break;
|
||||
case WebSocketApi::RequestReturnCode::NoVendor:
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No vendor was found by that name.");
|
||||
case WebSocketApi::RequestReturnCode::NoVendorRequest:
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name.");
|
||||
}
|
||||
|
||||
json responseData;
|
||||
responseData["vendorName"] = vendorName;
|
||||
responseData["requestType"] = requestType;
|
||||
responseData["responseData"] = Utils::Json::ObsDataToJson(obsResponseData);
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
@ -192,7 +208,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetHotkeyList(const Request&)
|
||||
RequestResult RequestHandler::GetHotkeyList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList();
|
||||
@ -211,7 +227,7 @@ RequestResult RequestHandler::GetHotkeyList(const Request&)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
|
||||
RequestResult RequestHandler::TriggerHotkeyByName(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -244,7 +260,7 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
|
||||
RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request &request)
|
||||
{
|
||||
obs_key_combination_t combo = {0};
|
||||
|
||||
@ -266,19 +282,23 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
|
||||
|
||||
const json keyModifiersJson = request.RequestData["keyModifiers"];
|
||||
uint32_t keyModifiers = 0;
|
||||
if (keyModifiersJson.contains("shift") && keyModifiersJson["shift"].is_boolean() && keyModifiersJson["shift"].get<bool>())
|
||||
if (keyModifiersJson.contains("shift") && keyModifiersJson["shift"].is_boolean() &&
|
||||
keyModifiersJson["shift"].get<bool>())
|
||||
keyModifiers |= INTERACT_SHIFT_KEY;
|
||||
if (keyModifiersJson.contains("control") && keyModifiersJson["control"].is_boolean() && keyModifiersJson["control"].get<bool>())
|
||||
if (keyModifiersJson.contains("control") && keyModifiersJson["control"].is_boolean() &&
|
||||
keyModifiersJson["control"].get<bool>())
|
||||
keyModifiers |= INTERACT_CONTROL_KEY;
|
||||
if (keyModifiersJson.contains("alt") && keyModifiersJson["alt"].is_boolean() && keyModifiersJson["alt"].get<bool>())
|
||||
keyModifiers |= INTERACT_ALT_KEY;
|
||||
if (keyModifiersJson.contains("command") && keyModifiersJson["command"].is_boolean() && keyModifiersJson["command"].get<bool>())
|
||||
if (keyModifiersJson.contains("command") && keyModifiersJson["command"].is_boolean() &&
|
||||
keyModifiersJson["command"].get<bool>())
|
||||
keyModifiers |= INTERACT_COMMAND_KEY;
|
||||
combo.modifiers = keyModifiers;
|
||||
}
|
||||
|
||||
if (!combo.modifiers && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE))
|
||||
return RequestResult::Error(RequestStatus::CannotAct, "Your provided request fields cannot be used to trigger a hotkey.");
|
||||
return RequestResult::Error(RequestStatus::CannotAct,
|
||||
"Your provided request fields cannot be used to trigger a hotkey.");
|
||||
|
||||
// Apparently things break when you don't start by setting the combo to false
|
||||
obs_hotkey_inject_event(combo, false);
|
||||
@ -301,7 +321,7 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
|
||||
* @category general
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::Sleep(const Request& request)
|
||||
RequestResult RequestHandler::Sleep(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
@ -33,7 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputList(const Request& request)
|
||||
RequestResult RequestHandler::GetInputList(const Request &request)
|
||||
{
|
||||
std::string inputKind;
|
||||
|
||||
@ -65,7 +65,7 @@ RequestResult RequestHandler::GetInputList(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputKindList(const Request& request)
|
||||
RequestResult RequestHandler::GetInputKindList(const Request &request)
|
||||
{
|
||||
bool unversioned = false;
|
||||
|
||||
@ -100,7 +100,7 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetSpecialInputs(const Request&)
|
||||
RequestResult RequestHandler::GetSpecialInputs(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
|
||||
@ -138,12 +138,13 @@ RequestResult RequestHandler::GetSpecialInputs(const Request&)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::CreateInput(const Request& request)
|
||||
RequestResult RequestHandler::CreateInput(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment);
|
||||
if (!(sceneSource && request.ValidateString("inputName", statusCode, comment) && request.ValidateString("inputKind", statusCode, comment)))
|
||||
if (!(sceneSource && request.ValidateString("inputName", statusCode, comment) &&
|
||||
request.ValidateString("inputKind", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string inputName = request.RequestData["inputName"];
|
||||
@ -154,7 +155,9 @@ RequestResult RequestHandler::CreateInput(const Request& request)
|
||||
std::string inputKind = request.RequestData["inputKind"];
|
||||
auto kinds = Utils::Obs::ArrayHelper::GetInputKindList();
|
||||
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end())
|
||||
return RequestResult::Error(RequestStatus::InvalidInputKind, "Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded.");
|
||||
return RequestResult::Error(
|
||||
RequestStatus::InvalidInputKind,
|
||||
"Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded.");
|
||||
|
||||
OBSDataAutoRelease inputSettings = nullptr;
|
||||
if (request.Contains("inputSettings")) {
|
||||
@ -175,7 +178,8 @@ RequestResult RequestHandler::CreateInput(const Request& request)
|
||||
}
|
||||
|
||||
// Create the input and add it as a scene item to the destination scene
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::Obs::ActionHelper::CreateInput(inputName, inputKind, inputSettings, scene, sceneItemEnabled);
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::Obs::ActionHelper::CreateInput(inputName, inputKind, inputSettings, scene, sceneItemEnabled);
|
||||
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the input or scene item failed.");
|
||||
@ -199,7 +203,7 @@ RequestResult RequestHandler::CreateInput(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::RemoveInput(const Request& request)
|
||||
RequestResult RequestHandler::RemoveInput(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -228,7 +232,7 @@ RequestResult RequestHandler::RemoveInput(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputName(const Request& request)
|
||||
RequestResult RequestHandler::SetInputName(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -240,7 +244,8 @@ RequestResult RequestHandler::SetInputName(const Request& request)
|
||||
|
||||
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str());
|
||||
if (existingSource)
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new input name.");
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists,
|
||||
"A source already exists by that new input name.");
|
||||
|
||||
obs_source_set_name(input, newInputName.c_str());
|
||||
|
||||
@ -261,7 +266,7 @@ RequestResult RequestHandler::SetInputName(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
|
||||
RequestResult RequestHandler::GetInputDefaultSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -299,7 +304,7 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputSettings(const Request& request)
|
||||
RequestResult RequestHandler::GetInputSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -329,7 +334,7 @@ RequestResult RequestHandler::GetInputSettings(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetInputSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -349,7 +354,8 @@ RequestResult RequestHandler::SetInputSettings(const Request& request)
|
||||
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["inputSettings"]);
|
||||
if (!newSettings)
|
||||
// This should never happen
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"An internal data conversion operation failed. Please report this!");
|
||||
|
||||
if (overlay)
|
||||
// Applies the new settings on top of the existing user settings
|
||||
@ -378,7 +384,7 @@ RequestResult RequestHandler::SetInputSettings(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputMute(const Request& request)
|
||||
RequestResult RequestHandler::GetInputMute(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -407,7 +413,7 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputMute(const Request& request)
|
||||
RequestResult RequestHandler::SetInputMute(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -437,7 +443,7 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleInputMute(const Request& request)
|
||||
RequestResult RequestHandler::ToggleInputMute(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -471,7 +477,7 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputVolume(const Request& request)
|
||||
RequestResult RequestHandler::GetInputVolume(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -507,7 +513,7 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputVolume(const Request& request)
|
||||
RequestResult RequestHandler::SetInputVolume(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -557,7 +563,7 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
|
||||
RequestResult RequestHandler::GetInputAudioBalance(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -587,7 +593,7 @@ RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
|
||||
RequestResult RequestHandler::SetInputAudioBalance(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -620,7 +626,7 @@ RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
|
||||
RequestResult RequestHandler::GetInputAudioSyncOffset(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -651,7 +657,7 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
|
||||
RequestResult RequestHandler::SetInputAudioSyncOffset(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -672,6 +678,7 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
|
||||
* Gets the audio monitor type of an input.
|
||||
*
|
||||
* The available audio monitor types are:
|
||||
*
|
||||
* - `OBS_MONITORING_TYPE_NONE`
|
||||
* - `OBS_MONITORING_TYPE_MONITOR_ONLY`
|
||||
* - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`
|
||||
@ -687,7 +694,7 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
|
||||
RequestResult RequestHandler::GetInputAudioMonitorType(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -699,7 +706,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
|
||||
|
||||
json responseData;
|
||||
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
|
||||
responseData["monitorType"] = obs_source_get_monitoring_type(input);
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
@ -717,7 +724,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
|
||||
RequestResult RequestHandler::SetInputAudioMonitorType(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -729,7 +736,8 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
|
||||
|
||||
if (!obs_audio_monitoring_available())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Audio monitoring is not available on this platform.");
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"Audio monitoring is not available on this platform.");
|
||||
|
||||
enum obs_monitoring_type monitorType;
|
||||
std::string monitorTypeString = request.RequestData["monitorType"];
|
||||
@ -740,7 +748,8 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
|
||||
else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT")
|
||||
monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT;
|
||||
else
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, std::string("Unknown monitor type: ") + monitorTypeString);
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
std::string("Unknown monitor type: ") + monitorTypeString);
|
||||
|
||||
obs_source_set_monitoring_type(input, monitorType);
|
||||
|
||||
@ -761,7 +770,7 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
|
||||
RequestResult RequestHandler::GetInputAudioTracks(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -798,7 +807,7 @@ RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
|
||||
RequestResult RequestHandler::SetInputAudioTracks(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -820,7 +829,8 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
|
||||
continue;
|
||||
|
||||
if (!inputAudioTracks[track].is_boolean())
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The value of one of your tracks is not a boolean.");
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestFieldType,
|
||||
"The value of one of your tracks is not a boolean.");
|
||||
|
||||
bool enabled = inputAudioTracks[track];
|
||||
|
||||
@ -853,7 +863,7 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request& request)
|
||||
RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -891,7 +901,7 @@ RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request&
|
||||
* @api requests
|
||||
* @category inputs
|
||||
*/
|
||||
RequestResult RequestHandler::PressInputPropertiesButton(const Request& request)
|
||||
RequestResult RequestHandler::PressInputPropertiesButton(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
@ -29,6 +29,7 @@ bool IsMediaTimeValid(obs_source_t *input)
|
||||
* Gets the status of a media input.
|
||||
*
|
||||
* Media States:
|
||||
*
|
||||
* - `OBS_MEDIA_STATE_NONE`
|
||||
* - `OBS_MEDIA_STATE_PLAYING`
|
||||
* - `OBS_MEDIA_STATE_OPENING`
|
||||
@ -51,7 +52,7 @@ bool IsMediaTimeValid(obs_source_t *input)
|
||||
* @api requests
|
||||
* @category media inputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
|
||||
RequestResult RequestHandler::GetMediaInputStatus(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -60,7 +61,8 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
json responseData;
|
||||
responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input);
|
||||
responseData["mediaState"] = obs_source_media_get_state(input);
|
||||
;
|
||||
|
||||
if (IsMediaTimeValid(input)) {
|
||||
responseData["mediaDuration"] = obs_source_media_get_duration(input);
|
||||
@ -88,7 +90,7 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
|
||||
* @api requests
|
||||
* @category media inputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
|
||||
RequestResult RequestHandler::SetMediaInputCursor(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -97,7 +99,8 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (!IsMediaTimeValid(input))
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"The media input must be playing or paused in order to set the cursor position.");
|
||||
|
||||
int64_t mediaCursor = request.RequestData["mediaCursor"];
|
||||
|
||||
@ -122,7 +125,7 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
|
||||
* @api requests
|
||||
* @category media inputs
|
||||
*/
|
||||
RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
|
||||
RequestResult RequestHandler::OffsetMediaInputCursor(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -131,7 +134,8 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (!IsMediaTimeValid(input))
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"The media input must be playing or paused in order to set the cursor position.");
|
||||
|
||||
int64_t mediaCursorOffset = request.RequestData["mediaCursorOffset"];
|
||||
int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset;
|
||||
@ -157,7 +161,7 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
|
||||
* @api requests
|
||||
* @category media inputs
|
||||
*/
|
||||
RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
|
||||
RequestResult RequestHandler::TriggerMediaInputAction(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -165,33 +169,33 @@ RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
|
||||
if (!(input && request.ValidateString("mediaAction", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string mediaActionString = request.RequestData["mediaAction"];
|
||||
auto mediaAction = Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString);
|
||||
enum ObsMediaInputAction mediaAction = request.RequestData["mediaAction"];
|
||||
|
||||
switch (mediaAction) {
|
||||
default:
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, "You have specified an invalid media input action.");
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
|
||||
// Shoutout to whoever implemented this API call like this
|
||||
obs_source_media_play_pause(input, false);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
|
||||
obs_source_media_play_pause(input, true);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
|
||||
obs_source_media_stop(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
|
||||
// I'm only implementing this because I'm nice. I think its a really dumb action.
|
||||
obs_source_media_restart(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
|
||||
obs_source_media_next(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
|
||||
obs_source_media_previous(input);
|
||||
break;
|
||||
default:
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"You have specified an invalid media input action.");
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
|
||||
// Shoutout to whoever implemented this API call like this
|
||||
obs_source_media_play_pause(input, false);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
|
||||
obs_source_media_play_pause(input, true);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
|
||||
obs_source_media_stop(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
|
||||
// I'm only implementing this because I'm nice. I think its a really dumb action.
|
||||
obs_source_media_restart(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
|
||||
obs_source_media_next(input);
|
||||
break;
|
||||
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
|
||||
obs_source_media_previous(input);
|
||||
break;
|
||||
}
|
||||
|
||||
return RequestResult::Success();
|
||||
|
@ -46,7 +46,7 @@ static bool ReplayBufferAvailable()
|
||||
* @category outputs
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
|
||||
RequestResult RequestHandler::GetVirtualCamStatus(const Request &)
|
||||
{
|
||||
if (!VirtualCamAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
|
||||
@ -68,7 +68,7 @@ RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
|
||||
* @category outputs
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleVirtualCam(const Request&)
|
||||
RequestResult RequestHandler::ToggleVirtualCam(const Request &)
|
||||
{
|
||||
if (!VirtualCamAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
|
||||
@ -95,7 +95,7 @@ RequestResult RequestHandler::ToggleVirtualCam(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StartVirtualCam(const Request&)
|
||||
RequestResult RequestHandler::StartVirtualCam(const Request &)
|
||||
{
|
||||
if (!VirtualCamAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
|
||||
@ -118,7 +118,7 @@ RequestResult RequestHandler::StartVirtualCam(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StopVirtualCam(const Request&)
|
||||
RequestResult RequestHandler::StopVirtualCam(const Request &)
|
||||
{
|
||||
if (!VirtualCamAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
|
||||
@ -143,7 +143,7 @@ RequestResult RequestHandler::StopVirtualCam(const Request&)
|
||||
* @category outputs
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
|
||||
RequestResult RequestHandler::GetReplayBufferStatus(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -165,7 +165,7 @@ RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
|
||||
* @category outputs
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
|
||||
RequestResult RequestHandler::ToggleReplayBuffer(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -192,7 +192,7 @@ RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StartReplayBuffer(const Request&)
|
||||
RequestResult RequestHandler::StartReplayBuffer(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -215,7 +215,7 @@ RequestResult RequestHandler::StartReplayBuffer(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StopReplayBuffer(const Request&)
|
||||
RequestResult RequestHandler::StopReplayBuffer(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -238,7 +238,7 @@ RequestResult RequestHandler::StopReplayBuffer(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::SaveReplayBuffer(const Request&)
|
||||
RequestResult RequestHandler::SaveReplayBuffer(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -263,7 +263,7 @@ RequestResult RequestHandler::SaveReplayBuffer(const Request&)
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&)
|
||||
RequestResult RequestHandler::GetLastReplayBufferReplay(const Request &)
|
||||
{
|
||||
if (!ReplayBufferAvailable())
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
|
||||
@ -272,6 +272,217 @@ RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&)
|
||||
return RequestResult::Error(RequestStatus::OutputNotRunning);
|
||||
|
||||
json responseData;
|
||||
responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath();
|
||||
responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFileName();
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of available outputs.
|
||||
*
|
||||
* @requestType GetOutputList
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetOutputList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["outputs"] = Utils::Obs::ArrayHelper::GetOutputList();
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
*
|
||||
* @responseField outputActive | Boolean | Whether the output is active
|
||||
* @responseField outputReconnecting | Boolean | Whether the output is reconnecting
|
||||
* @responseField outputTimecode | String | Current formatted timecode string for the output
|
||||
* @responseField outputDuration | Number | Current duration in milliseconds for the output
|
||||
* @responseField outputCongestion | Number | Congestion of the output
|
||||
* @responseField outputBytes | Number | Number of bytes sent by the output
|
||||
* @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process
|
||||
* @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process
|
||||
*
|
||||
* @requestType GetOutputStatus
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetOutputStatus(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!output)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(output);
|
||||
|
||||
json responseData;
|
||||
responseData["outputActive"] = obs_output_active(output);
|
||||
responseData["outputReconnecting"] = obs_output_reconnecting(output);
|
||||
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
|
||||
responseData["outputDuration"] = outputDuration;
|
||||
responseData["outputCongestion"] = obs_output_get_congestion(output);
|
||||
responseData["outputBytes"] = obs_output_get_total_bytes(output);
|
||||
responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(output);
|
||||
responseData["outputTotalFrames"] = obs_output_get_total_frames(output);
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the status of an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
*
|
||||
* @responseField outputActive | Boolean | Whether the output is active
|
||||
*
|
||||
* @requestType ToggleOutput
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleOutput(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!output)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
bool outputActive = obs_output_active(output);
|
||||
if (outputActive)
|
||||
obs_output_stop(output);
|
||||
else
|
||||
obs_output_start(output);
|
||||
|
||||
json responseData;
|
||||
responseData["outputActive"] = !outputActive;
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
*
|
||||
* @requestType StartOutput
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StartOutput(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!output)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (obs_output_active(output))
|
||||
return RequestResult::Error(RequestStatus::OutputRunning);
|
||||
|
||||
obs_output_start(output);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
*
|
||||
* @requestType StopOutput
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::StopOutput(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!output)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (!obs_output_active(output))
|
||||
return RequestResult::Error(RequestStatus::OutputNotRunning);
|
||||
|
||||
obs_output_stop(output);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the settings of an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
*
|
||||
* @responseField outputSettings | Object | Output settings
|
||||
*
|
||||
* @requestType GetOutputSettings
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::GetOutputSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!output)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
OBSDataAutoRelease outputSettings = obs_output_get_settings(output);
|
||||
|
||||
json responseData;
|
||||
responseData["outputSettings"] = Utils::Json::ObsDataToJson(outputSettings);
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the settings of an output.
|
||||
*
|
||||
* @requestField outputName | String | Output name
|
||||
* @requestField outputSettings | Object | Output settings
|
||||
*
|
||||
* @requestType SetOutputSettings
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @api requests
|
||||
* @category outputs
|
||||
*/
|
||||
RequestResult RequestHandler::SetOutputSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment);
|
||||
if (!(output && request.ValidateObject("outputSettings", statusCode, comment, true)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["outputSettings"]);
|
||||
if (!newSettings)
|
||||
// This should never happen
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"An internal data conversion operation failed. Please report this!");
|
||||
|
||||
obs_output_update(output, newSettings);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::GetRecordStatus(const Request&)
|
||||
RequestResult RequestHandler::GetRecordStatus(const Request &)
|
||||
{
|
||||
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
|
||||
|
||||
@ -61,7 +61,7 @@ RequestResult RequestHandler::GetRecordStatus(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleRecord(const Request&)
|
||||
RequestResult RequestHandler::ToggleRecord(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
if (obs_frontend_recording_active()) {
|
||||
@ -85,7 +85,7 @@ RequestResult RequestHandler::ToggleRecord(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::StartRecord(const Request&)
|
||||
RequestResult RequestHandler::StartRecord(const Request &)
|
||||
{
|
||||
if (obs_frontend_recording_active())
|
||||
return RequestResult::Error(RequestStatus::OutputRunning);
|
||||
@ -99,6 +99,8 @@ RequestResult RequestHandler::StartRecord(const Request&)
|
||||
/**
|
||||
* Stops the record output.
|
||||
*
|
||||
* @responseField outputPath | String | File name for the saved recording
|
||||
*
|
||||
* @requestType StopRecord
|
||||
* @complexity 1
|
||||
* @rpcVersion -1
|
||||
@ -106,7 +108,7 @@ RequestResult RequestHandler::StartRecord(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::StopRecord(const Request&)
|
||||
RequestResult RequestHandler::StopRecord(const Request &)
|
||||
{
|
||||
if (!obs_frontend_recording_active())
|
||||
return RequestResult::Error(RequestStatus::OutputNotRunning);
|
||||
@ -114,7 +116,10 @@ RequestResult RequestHandler::StopRecord(const Request&)
|
||||
// TODO: Call signal directly to perform blocking wait
|
||||
obs_frontend_recording_stop();
|
||||
|
||||
return RequestResult::Success();
|
||||
json responseData;
|
||||
responseData["outputPath"] = Utils::Obs::StringHelper::GetLastRecordFileName();
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,7 +132,7 @@ RequestResult RequestHandler::StopRecord(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleRecordPause(const Request&)
|
||||
RequestResult RequestHandler::ToggleRecordPause(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
if (obs_frontend_recording_paused()) {
|
||||
@ -151,7 +156,7 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::PauseRecord(const Request&)
|
||||
RequestResult RequestHandler::PauseRecord(const Request &)
|
||||
{
|
||||
if (obs_frontend_recording_paused())
|
||||
return RequestResult::Error(RequestStatus::OutputPaused);
|
||||
@ -172,7 +177,7 @@ RequestResult RequestHandler::PauseRecord(const Request&)
|
||||
* @api requests
|
||||
* @category record
|
||||
*/
|
||||
RequestResult RequestHandler::ResumeRecord(const Request&)
|
||||
RequestResult RequestHandler::ResumeRecord(const Request &)
|
||||
{
|
||||
if (!obs_frontend_recording_paused())
|
||||
return RequestResult::Error(RequestStatus::OutputNotPaused);
|
||||
|
@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemList(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemList(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -67,7 +67,7 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
|
||||
RequestResult RequestHandler::GetGroupSceneItemList(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -86,8 +86,9 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
|
||||
*
|
||||
* Scenes and Groups
|
||||
*
|
||||
* @requestField sceneName | String | Name of the scene or group to search in
|
||||
* @requestField sourceName | String | Name of the source to find
|
||||
* @requestField sceneName | String | Name of the scene or group to search in
|
||||
* @requestField sourceName | String | Name of the source to find
|
||||
* @requestField ?searchOffset | Number | Number of matches to skip during search. >= 0 means first forward. -1 means last (top) item | >= -1 | 0
|
||||
*
|
||||
* @responseField sceneItemId | Number | Numeric ID of the scene item
|
||||
*
|
||||
@ -98,19 +99,28 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemId(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemId(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneAutoRelease scene = request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneAutoRelease scene =
|
||||
request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(scene && request.ValidateString("sourceName", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string sourceName = request.RequestData["sourceName"];
|
||||
|
||||
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName);
|
||||
int offset = 0;
|
||||
if (request.Contains("searchOffset")) {
|
||||
if (!request.ValidateOptionalNumber("searchOffset", statusCode, comment, -1))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
offset = request.RequestData["searchOffset"];
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName, offset);
|
||||
if (!item)
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene items were found in the specified scene by that name.");
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound,
|
||||
"No scene items were found in the specified scene by that name or offset.");
|
||||
|
||||
json responseData;
|
||||
responseData["sceneItemId"] = obs_sceneitem_get_id(item);
|
||||
@ -136,7 +146,7 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::CreateSceneItem(const Request& request)
|
||||
RequestResult RequestHandler::CreateSceneItem(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -185,7 +195,7 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::RemoveSceneItem(const Request& request)
|
||||
RequestResult RequestHandler::RemoveSceneItem(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -217,7 +227,7 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
|
||||
RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -234,7 +244,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
|
||||
} else {
|
||||
destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem));
|
||||
if (!destinationScene)
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Internal error: Failed to get ref for scene of scene item.");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"Internal error: Failed to get ref for scene of scene item.");
|
||||
}
|
||||
|
||||
if (obs_sceneitem_is_group(sceneItem) && obs_sceneitem_get_scene(sceneItem) == destinationScene) {
|
||||
@ -251,7 +262,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
|
||||
obs_sceneitem_get_crop(sceneItem, &sceneItemCrop);
|
||||
|
||||
// Create the new item
|
||||
OBSSceneItemAutoRelease newSceneItem = Utils::Obs::ActionHelper::CreateSceneItem(sceneItemSource, destinationScene, sceneItemEnabled, &sceneItemTransform, &sceneItemCrop);
|
||||
OBSSceneItemAutoRelease newSceneItem = Utils::Obs::ActionHelper::CreateSceneItem(
|
||||
sceneItemSource, destinationScene, sceneItemEnabled, &sceneItemTransform, &sceneItemCrop);
|
||||
obs_scene_release(destinationScene);
|
||||
if (!newSceneItem)
|
||||
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene item.");
|
||||
@ -279,11 +291,12 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemTransform(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -307,11 +320,12 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(sceneItem && request.ValidateObject("sceneItemTransform", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -355,7 +369,8 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
|
||||
float scaleX = r.RequestData["scaleX"];
|
||||
float finalWidth = scaleX * sourceWidth;
|
||||
if (!(finalWidth > -90001.0 && finalWidth < 90001.0))
|
||||
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange, "The field scaleX is too small or large for the current source resolution.");
|
||||
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange,
|
||||
"The field `scaleX` is too small or large for the current source resolution.");
|
||||
sceneItemTransform.scale.x = scaleX;
|
||||
transformChanged = true;
|
||||
}
|
||||
@ -365,7 +380,8 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
|
||||
float scaleY = r.RequestData["scaleY"];
|
||||
float finalHeight = scaleY * sourceHeight;
|
||||
if (!(finalHeight > -90001.0 && finalHeight < 90001.0))
|
||||
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange, "The field scaleY is too small or large for the current source resolution.");
|
||||
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange,
|
||||
"The field `scaleY` is too small or large for the current source resolution.");
|
||||
sceneItemTransform.scale.y = scaleY;
|
||||
transformChanged = true;
|
||||
}
|
||||
@ -380,10 +396,10 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
|
||||
if (r.Contains("boundsType")) {
|
||||
if (!r.ValidateOptionalString("boundsType", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
std::string boundsTypeString = r.RequestData["boundsType"];
|
||||
enum obs_bounds_type boundsType = Utils::Obs::EnumHelper::GetSceneItemBoundsType(boundsTypeString);
|
||||
if (boundsType == OBS_BOUNDS_NONE && boundsTypeString != "OBS_BOUNDS_NONE")
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field boundsType has an invalid value.");
|
||||
enum obs_bounds_type boundsType = r.RequestData["boundsType"];
|
||||
if (boundsType == OBS_BOUNDS_NONE && r.RequestData["boundsType"] != "OBS_BOUNDS_NONE")
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"The field `boundsType` has an invalid value.");
|
||||
sceneItemTransform.bounds_type = boundsType;
|
||||
transformChanged = true;
|
||||
}
|
||||
@ -462,11 +478,12 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemEnabled(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -492,11 +509,12 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneItemEnabled(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(sceneItem && request.ValidateBoolean("sceneItemEnabled", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -524,11 +542,12 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemLocked(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -554,11 +573,12 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneItemLocked(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(sceneItem && request.ValidateBoolean("sceneItemLocked", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -588,11 +608,12 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemIndex(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -618,11 +639,12 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneItemIndex(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(sceneItem && request.ValidateNumber("sceneItemIndex", statusCode, comment, 0, 8192)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
@ -660,18 +682,19 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneItemBlendMode(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
auto blendMode = obs_sceneitem_get_blending_mode(sceneItem);
|
||||
|
||||
json responseData;
|
||||
responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode);
|
||||
responseData["sceneItemBlendMode"] = blendMode;
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
@ -692,21 +715,59 @@ RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
|
||||
* @api requests
|
||||
* @category scene items
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneItemBlendMode(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string blendModeString = request.RequestData["sceneItemBlendMode"];
|
||||
|
||||
auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString);
|
||||
if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL")
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value.");
|
||||
enum obs_blending_type blendMode = request.RequestData["sceneItemBlendMode"];
|
||||
if (blendMode == OBS_BLEND_NORMAL && request.RequestData["sceneItemBlendMode"] != "OBS_BLEND_NORMAL")
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"The field sceneItemBlendMode has an invalid value.");
|
||||
|
||||
obs_sceneitem_set_blending_mode(sceneItem, blendMode);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
// Intentionally undocumented
|
||||
RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem);
|
||||
|
||||
json responseData;
|
||||
responseData["sceneItemSettings"] = Utils::Json::ObsDataToJson(privateSettings);
|
||||
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
// Intentionally undocumented
|
||||
RequestResult RequestHandler::SetSceneItemPrivateSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment,
|
||||
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
|
||||
if (!sceneItem || !request.ValidateObject("sceneItemSettings", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem);
|
||||
|
||||
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sceneItemSettings"]);
|
||||
|
||||
// Always overlays to prevent destroying internal source unintentionally
|
||||
obs_data_apply(privateSettings, newSettings);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneList(const Request&)
|
||||
RequestResult RequestHandler::GetSceneList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
|
||||
@ -68,7 +68,7 @@ RequestResult RequestHandler::GetSceneList(const Request&)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::GetGroupList(const Request&)
|
||||
RequestResult RequestHandler::GetGroupList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
|
||||
@ -89,7 +89,7 @@ RequestResult RequestHandler::GetGroupList(const Request&)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::GetCurrentProgramScene(const Request&)
|
||||
RequestResult RequestHandler::GetCurrentProgramScene(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
OBSSourceAutoRelease currentProgramScene = obs_frontend_get_current_scene();
|
||||
@ -110,7 +110,7 @@ RequestResult RequestHandler::GetCurrentProgramScene(const Request&)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentProgramScene(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentProgramScene(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -137,7 +137,7 @@ RequestResult RequestHandler::SetCurrentProgramScene(const Request& request)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::GetCurrentPreviewScene(const Request&)
|
||||
RequestResult RequestHandler::GetCurrentPreviewScene(const Request &)
|
||||
{
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return RequestResult::Error(RequestStatus::StudioModeNotActive);
|
||||
@ -164,7 +164,7 @@ RequestResult RequestHandler::GetCurrentPreviewScene(const Request&)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentPreviewScene(const Request &request)
|
||||
{
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return RequestResult::Error(RequestStatus::StudioModeNotActive);
|
||||
@ -192,7 +192,7 @@ RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::CreateScene(const Request& request)
|
||||
RequestResult RequestHandler::CreateScene(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -226,7 +226,7 @@ RequestResult RequestHandler::CreateScene(const Request& request)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::RemoveScene(const Request& request)
|
||||
RequestResult RequestHandler::RemoveScene(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -235,7 +235,8 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (Utils::Obs::NumberHelper::GetSceneCount() < 2)
|
||||
return RequestResult::Error(RequestStatus::NotEnoughResources, "You cannot remove the last scene in the collection.");
|
||||
return RequestResult::Error(RequestStatus::NotEnoughResources,
|
||||
"You cannot remove the last scene in the collection.");
|
||||
|
||||
obs_source_remove(scene);
|
||||
|
||||
@ -255,7 +256,7 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneName(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneName(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -267,7 +268,8 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
|
||||
|
||||
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str());
|
||||
if (existingSource)
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new scene name.");
|
||||
return RequestResult::Error(RequestStatus::ResourceAlreadyExists,
|
||||
"A source already exists by that new scene name.");
|
||||
|
||||
obs_source_set_name(scene, newSceneName.c_str());
|
||||
|
||||
@ -289,7 +291,7 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& request)
|
||||
RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -328,7 +330,7 @@ RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& req
|
||||
* @api requests
|
||||
* @category scenes
|
||||
*/
|
||||
RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& request)
|
||||
RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -342,7 +344,8 @@ RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& req
|
||||
if (hasName && !request.RequestData["transitionName"].is_null()) {
|
||||
if (!request.ValidateOptionalString("transitionName", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(request.RequestData["transitionName"]);
|
||||
OBSSourceAutoRelease transition =
|
||||
Utils::Obs::SearchHelper::GetSceneTransitionByName(request.RequestData["transitionName"]);
|
||||
if (!transition)
|
||||
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name.");
|
||||
}
|
||||
@ -354,7 +357,8 @@ RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& req
|
||||
}
|
||||
|
||||
if (!hasName && !hasDuration)
|
||||
return RequestResult::Error(RequestStatus::MissingRequestField, "Your request data must include either `transitionName` or `transitionDuration`.");
|
||||
return RequestResult::Error(RequestStatus::MissingRequestField,
|
||||
"Your request data must include either `transitionName` or `transitionDuration`.");
|
||||
|
||||
if (hasName) {
|
||||
if (request.RequestData["transitionName"].is_null()) {
|
||||
|
@ -56,14 +56,14 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
|
||||
ret.fill(0);
|
||||
|
||||
// Video image buffer
|
||||
uint8_t* videoData = nullptr;
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
|
||||
// Enter graphics context
|
||||
obs_enter_graphics();
|
||||
|
||||
gs_texrender_t* texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
gs_stagesurf_t* stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA);
|
||||
gs_texrender_t *texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
gs_stagesurf_t *stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA);
|
||||
|
||||
success = false;
|
||||
gs_texrender_reset(texRender);
|
||||
@ -88,7 +88,7 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
|
||||
if (gs_stagesurface_map(stageSurface, &videoData, &videoLinesize)) {
|
||||
int lineSize = ret.bytesPerLine();
|
||||
for (uint y = 0; y < imgHeight; y++) {
|
||||
memcpy(ret.scanLine(y), videoData + (y * videoLinesize), lineSize);
|
||||
memcpy(ret.scanLine(y), videoData + (y * videoLinesize), lineSize);
|
||||
}
|
||||
gs_stagesurface_unmap(stageSurface);
|
||||
success = true;
|
||||
@ -126,7 +126,7 @@ bool IsImageFormatValid(std::string format)
|
||||
* @api requests
|
||||
* @category sources
|
||||
*/
|
||||
RequestResult RequestHandler::GetSourceActive(const Request& request)
|
||||
RequestResult RequestHandler::GetSourceActive(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -166,7 +166,7 @@ RequestResult RequestHandler::GetSourceActive(const Request& request)
|
||||
* @api requests
|
||||
* @category sources
|
||||
*/
|
||||
RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
|
||||
RequestResult RequestHandler::GetSourceScreenshot(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -180,7 +180,8 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
|
||||
std::string imageFormat = request.RequestData["imageFormat"];
|
||||
|
||||
if (!IsImageFormatValid(imageFormat))
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, "Your specified image format is invalid or not supported by this system.");
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"Your specified image format is invalid or not supported by this system.");
|
||||
|
||||
uint32_t requestedWidth{0};
|
||||
uint32_t requestedHeight{0};
|
||||
@ -253,12 +254,13 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
|
||||
* @api requests
|
||||
* @category sources
|
||||
*/
|
||||
RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
|
||||
RequestResult RequestHandler::SaveSourceScreenshot(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
|
||||
if (!(source && request.ValidateString("imageFormat", statusCode, comment) && request.ValidateString("imageFilePath", statusCode, comment)))
|
||||
if (!(source && request.ValidateString("imageFormat", statusCode, comment) &&
|
||||
request.ValidateString("imageFilePath", statusCode, comment)))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
|
||||
@ -268,7 +270,8 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
|
||||
std::string imageFilePath = request.RequestData["imageFilePath"];
|
||||
|
||||
if (!IsImageFormatValid(imageFormat))
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField, "Your specified image format is invalid or not supported by this system.");
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"Your specified image format is invalid or not supported by this system.");
|
||||
|
||||
QFileInfo filePathInfo(QString::fromStdString(imageFilePath));
|
||||
if (!filePathInfo.absoluteDir().exists())
|
||||
@ -314,7 +317,7 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
|
||||
}
|
||||
|
||||
// Intentionally undocumented
|
||||
RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request)
|
||||
RequestResult RequestHandler::GetSourcePrivateSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -331,7 +334,7 @@ RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request)
|
||||
}
|
||||
|
||||
// Intentionally undocumented
|
||||
RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetSourcePrivateSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
@ -26,6 +26,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @responseField outputReconnecting | Boolean | Whether the output is currently reconnecting
|
||||
* @responseField outputTimecode | String | Current formatted timecode string for the output
|
||||
* @responseField outputDuration | Number | Current duration in milliseconds for the output
|
||||
* @responseField outputCongestion | Number | Congestion of the output
|
||||
* @responseField outputBytes | Number | Number of bytes sent by the output
|
||||
* @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process
|
||||
* @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process
|
||||
@ -37,7 +38,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category stream
|
||||
*/
|
||||
RequestResult RequestHandler::GetStreamStatus(const Request&)
|
||||
RequestResult RequestHandler::GetStreamStatus(const Request &)
|
||||
{
|
||||
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
||||
|
||||
@ -48,6 +49,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
|
||||
responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput);
|
||||
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
|
||||
responseData["outputDuration"] = outputDuration;
|
||||
responseData["outputCongestion"] = obs_output_get_congestion(streamOutput);
|
||||
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput);
|
||||
responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput);
|
||||
responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput);
|
||||
@ -67,7 +69,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
|
||||
* @api requests
|
||||
* @category stream
|
||||
*/
|
||||
RequestResult RequestHandler::ToggleStream(const Request&)
|
||||
RequestResult RequestHandler::ToggleStream(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
if (obs_frontend_streaming_active()) {
|
||||
@ -91,7 +93,7 @@ RequestResult RequestHandler::ToggleStream(const Request&)
|
||||
* @api requests
|
||||
* @category stream
|
||||
*/
|
||||
RequestResult RequestHandler::StartStream(const Request&)
|
||||
RequestResult RequestHandler::StartStream(const Request &)
|
||||
{
|
||||
if (obs_frontend_streaming_active())
|
||||
return RequestResult::Error(RequestStatus::OutputRunning);
|
||||
@ -112,7 +114,7 @@ RequestResult RequestHandler::StartStream(const Request&)
|
||||
* @api requests
|
||||
* @category stream
|
||||
*/
|
||||
RequestResult RequestHandler::StopStream(const Request&)
|
||||
RequestResult RequestHandler::StopStream(const Request &)
|
||||
{
|
||||
if (!obs_frontend_streaming_active())
|
||||
return RequestResult::Error(RequestStatus::OutputNotRunning);
|
||||
@ -135,7 +137,7 @@ RequestResult RequestHandler::StopStream(const Request&)
|
||||
* @category stream
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SendStreamCaption(const Request& request)
|
||||
RequestResult RequestHandler::SendStreamCaption(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
|
@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::GetTransitionKindList(const Request&)
|
||||
RequestResult RequestHandler::GetTransitionKindList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["transitionKinds"] = Utils::Obs::ArrayHelper::GetTransitionKindList();
|
||||
@ -56,7 +56,7 @@ RequestResult RequestHandler::GetTransitionKindList(const Request&)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::GetSceneTransitionList(const Request&)
|
||||
RequestResult RequestHandler::GetSceneTransitionList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
|
||||
@ -91,11 +91,12 @@ RequestResult RequestHandler::GetSceneTransitionList(const Request&)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
|
||||
RequestResult RequestHandler::GetCurrentSceneTransition(const Request &)
|
||||
{
|
||||
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
|
||||
if (!transition)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"OBS does not currently have a scene transition set."); // This should not happen!
|
||||
|
||||
json responseData;
|
||||
responseData["transitionName"] = obs_source_get_name(transition);
|
||||
@ -135,7 +136,7 @@ RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentSceneTransition(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -165,7 +166,7 @@ RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -192,7 +193,7 @@ RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& r
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& request)
|
||||
RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -201,10 +202,12 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
|
||||
|
||||
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
|
||||
if (!transition)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"OBS does not currently have a scene transition set."); // This should not happen!
|
||||
|
||||
if (!obs_source_configurable(transition))
|
||||
return RequestResult::Error(RequestStatus::ResourceNotConfigurable, "The current transition does not support custom settings.");
|
||||
return RequestResult::Error(RequestStatus::ResourceNotConfigurable,
|
||||
"The current transition does not support custom settings.");
|
||||
|
||||
bool overlay = true;
|
||||
if (request.Contains("overlay")) {
|
||||
@ -216,7 +219,8 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
|
||||
|
||||
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["transitionSettings"]);
|
||||
if (!newSettings)
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!");
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed,
|
||||
"An internal data conversion operation failed. Please report this!");
|
||||
|
||||
if (overlay)
|
||||
obs_source_update(transition, newSettings);
|
||||
@ -242,11 +246,12 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&)
|
||||
RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request &)
|
||||
{
|
||||
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
|
||||
if (!transition)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"OBS does not currently have a scene transition set."); // This should not happen!
|
||||
|
||||
json responseData;
|
||||
responseData["transitionCursor"] = obs_transition_get_time(transition);
|
||||
@ -264,7 +269,7 @@ RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::TriggerStudioModeTransition(const Request&)
|
||||
RequestResult RequestHandler::TriggerStudioModeTransition(const Request &)
|
||||
{
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return RequestResult::Error(RequestStatus::StudioModeNotActive);
|
||||
@ -291,7 +296,7 @@ RequestResult RequestHandler::TriggerStudioModeTransition(const Request&)
|
||||
* @api requests
|
||||
* @category transitions
|
||||
*/
|
||||
RequestResult RequestHandler::SetTBarPosition(const Request& request)
|
||||
RequestResult RequestHandler::SetTBarPosition(const Request &request)
|
||||
{
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return RequestResult::Error(RequestStatus::StudioModeNotActive);
|
||||
@ -309,7 +314,8 @@ RequestResult RequestHandler::SetTBarPosition(const Request& request)
|
||||
|
||||
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
|
||||
if (!transition)
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"OBS does not currently have a scene transition set."); // This should not happen!
|
||||
|
||||
float position = request.RequestData["position"];
|
||||
|
||||
|
@ -17,6 +17,11 @@ You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include <QRect>
|
||||
#include <sstream>
|
||||
|
||||
#include "RequestHandler.h"
|
||||
|
||||
/**
|
||||
@ -31,7 +36,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
|
||||
RequestResult RequestHandler::GetStudioModeEnabled(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active();
|
||||
@ -50,7 +55,7 @@ RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
|
||||
RequestResult RequestHandler::SetStudioModeEnabled(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -62,10 +67,13 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
|
||||
// (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior
|
||||
bool studioModeEnabled = request.RequestData["studioModeEnabled"];
|
||||
// Queue the task inside of the UI thread to prevent race conditions
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
auto studioModeEnabled = (bool*)param;
|
||||
obs_frontend_set_preview_program_mode(*studioModeEnabled);
|
||||
}, &studioModeEnabled, true);
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
[](void *param) {
|
||||
auto studioModeEnabled = (bool *)param;
|
||||
obs_frontend_set_preview_program_mode(*studioModeEnabled);
|
||||
},
|
||||
&studioModeEnabled, true);
|
||||
}
|
||||
|
||||
return RequestResult::Success();
|
||||
@ -83,7 +91,7 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
|
||||
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -108,7 +116,7 @@ RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
|
||||
RequestResult RequestHandler::OpenInputFiltersDialog(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -133,7 +141,7 @@ RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
|
||||
RequestResult RequestHandler::OpenInputInteractDialog(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
@ -142,9 +150,155 @@ RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION))
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support interaction.");
|
||||
return RequestResult::Error(RequestStatus::InvalidResourceState,
|
||||
"The specified input does not support interaction.");
|
||||
|
||||
obs_frontend_open_source_interaction(input);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of connected monitors and information about them.
|
||||
*
|
||||
* @responseField monitors | Array<Object> | a list of detected monitors with some information
|
||||
*
|
||||
* @requestType GetMonitorList
|
||||
* @complexity 2
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetMonitorList(const Request &)
|
||||
{
|
||||
json responseData;
|
||||
std::vector<json> monitorsData;
|
||||
QList<QScreen *> screensList = QGuiApplication::screens();
|
||||
for (int screenIndex = 0; screenIndex < screensList.size(); screenIndex++) {
|
||||
json screenData;
|
||||
QScreen const *screen = screensList[screenIndex];
|
||||
std::stringstream nameAndIndex;
|
||||
nameAndIndex << screen->name().toStdString();
|
||||
nameAndIndex << '(' << screenIndex << ')';
|
||||
screenData["monitorName"] = nameAndIndex.str();
|
||||
screenData["monitorIndex"] = screenIndex;
|
||||
const QRect screenGeometry = screen->geometry();
|
||||
screenData["monitorWidth"] = screenGeometry.width();
|
||||
screenData["monitorHeight"] = screenGeometry.height();
|
||||
screenData["monitorPositionX"] = screenGeometry.x();
|
||||
screenData["monitorPositionY"] = screenGeometry.y();
|
||||
monitorsData.push_back(screenData);
|
||||
}
|
||||
responseData["monitors"] = monitorsData;
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a projector for a specific output video mix.
|
||||
*
|
||||
* Mix types:
|
||||
*
|
||||
* - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_PREVIEW`
|
||||
* - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM`
|
||||
* - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW`
|
||||
*
|
||||
* Note: This request serves to provide feature parity with 4.x. It is very likely to be changed/deprecated in a future release.
|
||||
*
|
||||
* @requestField videoMixType | String | Type of mix to open
|
||||
* @requestField ?monitorIndex | Number | Monitor index, use `GetMonitorList` to obtain index | None | -1: Opens projector in windowed mode
|
||||
* @requestField ?projectorGeometry | String | Size/Position data for a windowed projector, in Qt Base64 encoded format. Mutually exclusive with `monitorIndex` | N/A
|
||||
*
|
||||
* @requestType OpenVideoMixProjector
|
||||
* @complexity 3
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::OpenVideoMixProjector(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
if (!request.ValidateString("videoMixType", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
std::string videoMixType = request.RequestData["videoMixType"];
|
||||
const char *projectorType;
|
||||
if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PREVIEW")
|
||||
projectorType = "Preview";
|
||||
else if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM")
|
||||
projectorType = "Program";
|
||||
else if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW")
|
||||
projectorType = "Multiview";
|
||||
else
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"The field `videoMixType` has an invalid enum value.");
|
||||
|
||||
int monitorIndex = -1;
|
||||
if (request.Contains("monitorIndex")) {
|
||||
if (!request.ValidateOptionalNumber("monitorIndex", statusCode, comment, -1, 9))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
monitorIndex = request.RequestData["monitorIndex"];
|
||||
}
|
||||
|
||||
std::string projectorGeometry;
|
||||
if (request.Contains("projectorGeometry")) {
|
||||
if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
if (monitorIndex != -1)
|
||||
return RequestResult::Error(RequestStatus::TooManyRequestFields,
|
||||
"`monitorIndex` and `projectorGeometry` are mutually exclusive.");
|
||||
projectorGeometry = request.RequestData["projectorGeometry"];
|
||||
}
|
||||
|
||||
obs_frontend_open_projector(projectorType, monitorIndex, projectorGeometry.c_str(), nullptr);
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a projector for a source.
|
||||
*
|
||||
* Note: This request serves to provide feature parity with 4.x. It is very likely to be changed/deprecated in a future release.
|
||||
*
|
||||
* @requestField sourceName | String | Name of the source to open a projector for
|
||||
* @requestField ?monitorIndex | Number | Monitor index, use `GetMonitorList` to obtain index | None | -1: Opens projector in windowed mode
|
||||
* @requestField ?projectorGeometry | String | Size/Position data for a windowed projector, in Qt Base64 encoded format. Mutually exclusive with `monitorIndex` | N/A
|
||||
*
|
||||
* @requestType OpenSourceProjector
|
||||
* @complexity 3
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.0.0
|
||||
* @category ui
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::OpenSourceProjector(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
|
||||
if (!source)
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
int monitorIndex = -1;
|
||||
if (request.Contains("monitorIndex")) {
|
||||
if (!request.ValidateOptionalNumber("monitorIndex", statusCode, comment, -1, 9))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
monitorIndex = request.RequestData["monitorIndex"];
|
||||
}
|
||||
|
||||
std::string projectorGeometry;
|
||||
if (request.Contains("projectorGeometry")) {
|
||||
if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
if (monitorIndex != -1)
|
||||
return RequestResult::Error(RequestStatus::TooManyRequestFields,
|
||||
"`monitorIndex` and `projectorGeometry` are mutually exclusive.");
|
||||
projectorGeometry = request.RequestData["projectorGeometry"];
|
||||
}
|
||||
|
||||
obs_frontend_open_projector("Source", monitorIndex, projectorGeometry.c_str(), obs_source_get_name(source));
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
@ -29,11 +29,12 @@ json GetDefaultJsonObject(const json &requestData)
|
||||
return requestData;
|
||||
}
|
||||
|
||||
Request::Request(const std::string &requestType, const json &requestData, const RequestBatchExecutionType::RequestBatchExecutionType executionType) :
|
||||
RequestType(requestType),
|
||||
HasRequestData(requestData.is_object()),
|
||||
RequestData(GetDefaultJsonObject(requestData)),
|
||||
ExecutionType(executionType)
|
||||
Request::Request(const std::string &requestType, const json &requestData,
|
||||
const RequestBatchExecutionType::RequestBatchExecutionType executionType)
|
||||
: RequestType(requestType),
|
||||
HasRequestData(requestData.is_object()),
|
||||
RequestData(GetDefaultJsonObject(requestData)),
|
||||
ExecutionType(executionType)
|
||||
{
|
||||
}
|
||||
|
||||
@ -59,7 +60,8 @@ bool Request::ValidateBasic(const std::string &keyName, RequestStatus::RequestSt
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue, const double maxValue) const
|
||||
bool Request::ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const double minValue, const double maxValue) const
|
||||
{
|
||||
if (!RequestData[keyName].is_number()) {
|
||||
statusCode = RequestStatus::InvalidRequestFieldType;
|
||||
@ -70,19 +72,22 @@ bool Request::ValidateOptionalNumber(const std::string &keyName, RequestStatus::
|
||||
double value = RequestData[keyName];
|
||||
if (value < minValue) {
|
||||
statusCode = RequestStatus::RequestFieldOutOfRange;
|
||||
comment = std::string("The field value of `") + keyName + "` is below the minimum of `" + std::to_string(minValue) + "`";
|
||||
comment = std::string("The field value of `") + keyName + "` is below the minimum of `" + std::to_string(minValue) +
|
||||
"`";
|
||||
return false;
|
||||
}
|
||||
if (value > maxValue) {
|
||||
statusCode = RequestStatus::RequestFieldOutOfRange;
|
||||
comment = std::string("The field value of `") + keyName + "` is above the maximum of `" + std::to_string(maxValue) + "`";
|
||||
comment = std::string("The field value of `") + keyName + "` is above the maximum of `" + std::to_string(maxValue) +
|
||||
"`";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue, const double maxValue) const
|
||||
bool Request::ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const double minValue, const double maxValue) const
|
||||
{
|
||||
if (!ValidateBasic(keyName, statusCode, comment))
|
||||
return false;
|
||||
@ -93,7 +98,8 @@ bool Request::ValidateNumber(const std::string &keyName, RequestStatus::RequestS
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!RequestData[keyName].is_string()) {
|
||||
statusCode = RequestStatus::InvalidRequestFieldType;
|
||||
@ -110,7 +116,8 @@ bool Request::ValidateOptionalString(const std::string &keyName, RequestStatus::
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!ValidateBasic(keyName, statusCode, comment))
|
||||
return false;
|
||||
@ -121,7 +128,8 @@ bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestS
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
|
||||
bool Request::ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const
|
||||
{
|
||||
if (!RequestData[keyName].is_boolean()) {
|
||||
statusCode = RequestStatus::InvalidRequestFieldType;
|
||||
@ -143,7 +151,8 @@ bool Request::ValidateBoolean(const std::string &keyName, RequestStatus::Request
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!RequestData[keyName].is_object()) {
|
||||
statusCode = RequestStatus::InvalidRequestFieldType;
|
||||
@ -160,7 +169,8 @@ bool Request::ValidateOptionalObject(const std::string &keyName, RequestStatus::
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!ValidateBasic(keyName, statusCode, comment))
|
||||
return false;
|
||||
@ -171,7 +181,8 @@ bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestS
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!RequestData[keyName].is_array()) {
|
||||
statusCode = RequestStatus::InvalidRequestFieldType;
|
||||
@ -188,7 +199,8 @@ bool Request::ValidateOptionalArray(const std::string &keyName, RequestStatus::R
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const
|
||||
bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty) const
|
||||
{
|
||||
if (!ValidateBasic(keyName, statusCode, comment))
|
||||
return false;
|
||||
@ -199,7 +211,8 @@ bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestSt
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
|
||||
obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const
|
||||
{
|
||||
if (!ValidateString(keyName, statusCode, comment))
|
||||
return nullptr;
|
||||
@ -216,7 +229,8 @@ obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus:
|
||||
return ret;
|
||||
}
|
||||
|
||||
obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
|
||||
obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter) const
|
||||
{
|
||||
obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
|
||||
if (!ret)
|
||||
@ -245,7 +259,8 @@ obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::
|
||||
return ret;
|
||||
}
|
||||
|
||||
obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
|
||||
obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter) const
|
||||
{
|
||||
OBSSourceAutoRelease sceneSource = ValidateSource(keyName, statusCode, comment);
|
||||
if (!sceneSource)
|
||||
@ -275,7 +290,8 @@ obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
|
||||
obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const
|
||||
{
|
||||
obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
|
||||
if (!ret)
|
||||
@ -291,7 +307,8 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
|
||||
return ret;
|
||||
}
|
||||
|
||||
FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
|
||||
FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName,
|
||||
RequestStatus::RequestStatus &statusCode, std::string &comment) const
|
||||
{
|
||||
obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment);
|
||||
if (!source)
|
||||
@ -305,14 +322,17 @@ FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::
|
||||
obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str());
|
||||
if (!filter) {
|
||||
statusCode = RequestStatus::ResourceNotFound;
|
||||
comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get<std::string>() + "` with the name `" + filterName + "`.";
|
||||
comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get<std::string>() +
|
||||
"` with the name `" + filterName + "`.";
|
||||
return FilterPair{source, nullptr};
|
||||
}
|
||||
|
||||
return FilterPair{source, filter};
|
||||
}
|
||||
|
||||
obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
|
||||
obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName,
|
||||
RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter) const
|
||||
{
|
||||
OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter);
|
||||
if (!scene)
|
||||
@ -326,10 +346,29 @@ obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, con
|
||||
OBSSceneItem sceneItem = obs_scene_find_sceneitem_by_id(scene, sceneItemId);
|
||||
if (!sceneItem) {
|
||||
statusCode = RequestStatus::ResourceNotFound;
|
||||
comment = std::string("No scene items were found in scene `") + RequestData[sceneKeyName].get<std::string>() + "` with the ID `" + std::to_string(sceneItemId) + "`.";
|
||||
comment = std::string("No scene items were found in scene `") + RequestData[sceneKeyName].get<std::string>() +
|
||||
"` with the ID `" + std::to_string(sceneItemId) + "`.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
obs_sceneitem_addref(sceneItem);
|
||||
return sceneItem;
|
||||
}
|
||||
|
||||
obs_output_t *Request::ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const
|
||||
{
|
||||
if (!ValidateString(keyName, statusCode, comment))
|
||||
return nullptr;
|
||||
|
||||
std::string outputName = RequestData[keyName];
|
||||
|
||||
obs_output_t *ret = obs_get_output_by_name(outputName.c_str());
|
||||
if (!ret) {
|
||||
statusCode = RequestStatus::ResourceNotFound;
|
||||
comment = std::string("No output was found with the name `") + outputName + "`.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -35,32 +35,50 @@ struct FilterPair {
|
||||
OBSSourceAutoRelease filter;
|
||||
};
|
||||
|
||||
struct Request
|
||||
{
|
||||
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None);
|
||||
struct Request {
|
||||
Request(const std::string &requestType, const json &requestData = nullptr,
|
||||
const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None);
|
||||
|
||||
// Contains the key and is not null
|
||||
bool Contains(const std::string &keyName) const;
|
||||
|
||||
bool ValidateBasic(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
bool ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue = -INFINITY, const double maxValue = INFINITY) const;
|
||||
bool ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue = -INFINITY, const double maxValue = INFINITY) const;
|
||||
bool ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
bool ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const double minValue = -INFINITY, const double maxValue = INFINITY) const;
|
||||
bool ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const double minValue = -INFINITY, const double maxValue = INFINITY) const;
|
||||
bool ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
bool ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
bool ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const;
|
||||
bool ValidateBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
bool ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
|
||||
bool ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
bool ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
bool ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
bool ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const bool allowEmpty = false) const;
|
||||
|
||||
// All return values have incremented refcounts
|
||||
obs_source_t *ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_source_t *ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const;
|
||||
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const;
|
||||
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName,
|
||||
RequestStatus::RequestStatus &statusCode, std::string &comment) const;
|
||||
obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName,
|
||||
RequestStatus::RequestStatus &statusCode, std::string &comment,
|
||||
const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
|
||||
obs_output_t *ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
|
||||
std::string &comment) const;
|
||||
|
||||
std::string RequestType;
|
||||
bool HasRequestData;
|
||||
|
@ -18,9 +18,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "RequestBatchRequest.h"
|
||||
|
||||
RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData, RequestBatchExecutionType::RequestBatchExecutionType executionType, const json &inputVariables, const json &outputVariables) :
|
||||
Request(requestType, requestData, executionType),
|
||||
InputVariables(inputVariables),
|
||||
OutputVariables(outputVariables)
|
||||
RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData,
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType,
|
||||
const json &inputVariables, const json &outputVariables)
|
||||
: Request(requestType, requestData, executionType), InputVariables(inputVariables), OutputVariables(outputVariables)
|
||||
{
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "Request.h"
|
||||
|
||||
struct RequestBatchRequest : Request {
|
||||
RequestBatchRequest(const std::string &requestType, const json &requestData, RequestBatchExecutionType::RequestBatchExecutionType executionType, const json &inputVariables = nullptr, const json &outputVariables = nullptr);
|
||||
RequestBatchRequest(const std::string &requestType, const json &requestData,
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType,
|
||||
const json &inputVariables = nullptr, const json &outputVariables = nullptr);
|
||||
|
||||
json InputVariables;
|
||||
json OutputVariables;
|
||||
|
@ -19,11 +19,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include "RequestResult.h"
|
||||
|
||||
RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment) :
|
||||
StatusCode(statusCode),
|
||||
ResponseData(responseData),
|
||||
Comment(comment),
|
||||
SleepFrames(0)
|
||||
RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment)
|
||||
: StatusCode(statusCode), ResponseData(responseData), Comment(comment), SleepFrames(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../types/RequestStatus.h"
|
||||
#include "../../utils/Json.h"
|
||||
|
||||
struct RequestResult
|
||||
{
|
||||
RequestResult(RequestStatus::RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr, std::string comment = "");
|
||||
struct RequestResult {
|
||||
RequestResult(RequestStatus::RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr,
|
||||
std::string comment = "");
|
||||
static RequestResult Success(json responseData = nullptr);
|
||||
static RequestResult Error(RequestStatus::RequestStatus statusCode, std::string comment = "");
|
||||
RequestStatus::RequestStatus StatusCode;
|
||||
|
@ -22,7 +22,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RequestBatchExecutionType {
|
||||
enum RequestBatchExecutionType: int8_t {
|
||||
enum RequestBatchExecutionType : int8_t {
|
||||
/**
|
||||
* Not a request batch.
|
||||
*
|
||||
@ -77,8 +77,5 @@ namespace RequestBatchExecutionType {
|
||||
Parallel = 2,
|
||||
};
|
||||
|
||||
inline bool IsValid(int8_t executionType)
|
||||
{
|
||||
return executionType >= None && executionType <= Parallel;
|
||||
}
|
||||
inline bool IsValid(int8_t executionType) { return executionType >= None && executionType <= Parallel; }
|
||||
}
|
||||
|
@ -262,7 +262,6 @@ namespace RequestStatus {
|
||||
* @api enums
|
||||
*/
|
||||
StudioModeNotActive = 506,
|
||||
|
||||
|
||||
/**
|
||||
* The resource was not found.
|
||||
|
@ -1,35 +1,32 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "Compat.h"
|
||||
|
||||
Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(std::function<void()> func) :
|
||||
cb(std::move(func))
|
||||
{
|
||||
}
|
||||
|
||||
void Utils::Compat::StdFunctionRunnable::run()
|
||||
{
|
||||
cb();
|
||||
}
|
||||
|
||||
QRunnable *Utils::Compat::CreateFunctionRunnable(std::function<void()> func)
|
||||
{
|
||||
return new Utils::Compat::StdFunctionRunnable(std::move(func));
|
||||
}
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "Compat.h"
|
||||
|
||||
Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(std::function<void()> func) : cb(std::move(func)) {}
|
||||
|
||||
void Utils::Compat::StdFunctionRunnable::run()
|
||||
{
|
||||
cb();
|
||||
}
|
||||
|
||||
QRunnable *Utils::Compat::CreateFunctionRunnable(std::function<void()> func)
|
||||
{
|
||||
return new Utils::Compat::StdFunctionRunnable(std::move(func));
|
||||
}
|
||||
|
@ -27,9 +27,10 @@ namespace Utils {
|
||||
// Reimplement QRunnable for std::function. Retrocompatability for Qt < 5.15
|
||||
class StdFunctionRunnable : public QRunnable {
|
||||
std::function<void()> cb;
|
||||
public:
|
||||
StdFunctionRunnable(std::function<void()> func);
|
||||
void run() override;
|
||||
|
||||
public:
|
||||
StdFunctionRunnable(std::function<void()> func);
|
||||
void run() override;
|
||||
};
|
||||
|
||||
QRunnable *CreateFunctionRunnable(std::function<void()> func);
|
||||
|
@ -63,10 +63,7 @@ bool Utils::Crypto::CheckAuthenticationString(std::string secret, std::string ch
|
||||
secretAndChallenge += QString::fromStdString(challenge);
|
||||
|
||||
// Generate a SHA256 hash of secretAndChallenge
|
||||
auto hash = QCryptographicHash::hash(
|
||||
secretAndChallenge.toUtf8(),
|
||||
QCryptographicHash::Algorithm::Sha256
|
||||
);
|
||||
auto hash = QCryptographicHash::hash(secretAndChallenge.toUtf8(), QCryptographicHash::Algorithm::Sha256);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
std::string expectedAuthenticationString = hash.toBase64().toStdString();
|
||||
|
@ -45,7 +45,7 @@ void obs_data_set_json_array(obs_data_t *d, const char *key, json j)
|
||||
{
|
||||
obs_data_array_t *array = obs_data_array_create();
|
||||
|
||||
for (auto& [key, value] : j.items()) {
|
||||
for (auto &[key, value] : j.items()) {
|
||||
if (!value.is_object())
|
||||
continue;
|
||||
|
||||
@ -61,7 +61,7 @@ void obs_data_set_json_array(obs_data_t *d, const char *key, json j)
|
||||
|
||||
void obs_data_set_json_object_item(obs_data_t *d, json j)
|
||||
{
|
||||
for (auto& [key, value] : j.items()) {
|
||||
for (auto &[key, value] : j.items()) {
|
||||
if (value.is_object()) {
|
||||
obs_data_set_json_object(d, key.c_str(), value);
|
||||
} else if (value.is_array()) {
|
||||
@ -153,23 +153,22 @@ json Utils::Json::ObsDataToJson(obs_data_t *d, bool includeDefault)
|
||||
continue;
|
||||
|
||||
switch (type) {
|
||||
case OBS_DATA_STRING:
|
||||
set_json_string(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_NUMBER:
|
||||
set_json_number(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_BOOLEAN:
|
||||
set_json_bool(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_OBJECT:
|
||||
set_json_object(&j, name, item, includeDefault);
|
||||
break;
|
||||
case OBS_DATA_ARRAY:
|
||||
set_json_array(&j, name, item, includeDefault);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
case OBS_DATA_STRING:
|
||||
set_json_string(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_NUMBER:
|
||||
set_json_number(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_BOOLEAN:
|
||||
set_json_bool(&j, name, item);
|
||||
break;
|
||||
case OBS_DATA_OBJECT:
|
||||
set_json_object(&j, name, item, includeDefault);
|
||||
break;
|
||||
case OBS_DATA_ARRAY:
|
||||
set_json_array(&j, name, item, includeDefault);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,8 +183,8 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
|
||||
|
||||
try {
|
||||
content = json::parse(textContent);
|
||||
} catch (json::parse_error& e) {
|
||||
blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what());
|
||||
} catch (json::parse_error &e) {
|
||||
blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -25,6 +25,51 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(obs_source_type, {
|
||||
{OBS_SOURCE_TYPE_INPUT, "OBS_SOURCE_TYPE_INPUT"},
|
||||
{OBS_SOURCE_TYPE_FILTER, "OBS_SOURCE_TYPE_FILTER"},
|
||||
{OBS_SOURCE_TYPE_TRANSITION, "OBS_SOURCE_TYPE_TRANSITION"},
|
||||
{OBS_SOURCE_TYPE_SCENE, "OBS_SOURCE_TYPE_SCENE"},
|
||||
})
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(obs_monitoring_type,
|
||||
{
|
||||
{OBS_MONITORING_TYPE_NONE, "OBS_MONITORING_TYPE_NONE"},
|
||||
{OBS_MONITORING_TYPE_MONITOR_ONLY, "OBS_MONITORING_TYPE_MONITOR_ONLY"},
|
||||
{OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT, "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT"},
|
||||
})
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(obs_media_state, {
|
||||
{OBS_MEDIA_STATE_NONE, "OBS_MEDIA_STATE_NONE"},
|
||||
{OBS_MEDIA_STATE_PLAYING, "OBS_MEDIA_STATE_PLAYING"},
|
||||
{OBS_MEDIA_STATE_OPENING, "OBS_MEDIA_STATE_OPENING"},
|
||||
{OBS_MEDIA_STATE_BUFFERING, "OBS_MEDIA_STATE_BUFFERING"},
|
||||
{OBS_MEDIA_STATE_PAUSED, "OBS_MEDIA_STATE_PAUSED"},
|
||||
{OBS_MEDIA_STATE_STOPPED, "OBS_MEDIA_STATE_STOPPED"},
|
||||
{OBS_MEDIA_STATE_ENDED, "OBS_MEDIA_STATE_ENDED"},
|
||||
{OBS_MEDIA_STATE_ERROR, "OBS_MEDIA_STATE_ERROR"},
|
||||
})
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(obs_bounds_type, {
|
||||
{OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE"},
|
||||
{OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH"},
|
||||
{OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER"},
|
||||
{OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER"},
|
||||
{OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH"},
|
||||
{OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT"},
|
||||
{OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY"},
|
||||
})
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(obs_blending_type, {
|
||||
{OBS_BLEND_NORMAL, "OBS_BLEND_NORMAL"},
|
||||
{OBS_BLEND_ADDITIVE, "OBS_BLEND_ADDITIVE"},
|
||||
{OBS_BLEND_SUBTRACT, "OBS_BLEND_SUBTRACT"},
|
||||
{OBS_BLEND_SCREEN, "OBS_BLEND_SCREEN"},
|
||||
{OBS_BLEND_MULTIPLY, "OBS_BLEND_MULTIPLY"},
|
||||
{OBS_BLEND_LIGHTEN, "OBS_BLEND_LIGHTEN"},
|
||||
{OBS_BLEND_DARKEN, "OBS_BLEND_DARKEN"},
|
||||
})
|
||||
|
||||
namespace Utils {
|
||||
namespace Json {
|
||||
bool JsonArrayIsValidObsArray(const json &j);
|
||||
|
101
src/utils/Obs.h
101
src/utils/Obs.h
@ -26,43 +26,44 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "Json.h"
|
||||
|
||||
// Autorelease object definitions
|
||||
inline void ___properties_dummy_addref(obs_properties_t*){}
|
||||
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>;
|
||||
inline void ___properties_dummy_addref(obs_properties_t *) {}
|
||||
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t *, ___properties_dummy_addref, obs_properties_destroy>;
|
||||
|
||||
#if !defined(OBS_AUTORELEASE)
|
||||
inline void ___source_dummy_addref(obs_source_t*){}
|
||||
inline void ___scene_dummy_addref(obs_scene_t*){}
|
||||
inline void ___sceneitem_dummy_addref(obs_sceneitem_t*){}
|
||||
inline void ___data_dummy_addref(obs_data_t*){}
|
||||
inline void ___data_array_dummy_addref(obs_data_array_t*){}
|
||||
inline void ___output_dummy_addref(obs_output_t*){}
|
||||
inline void ___encoder_dummy_addref(obs_encoder_t *){}
|
||||
inline void ___service_dummy_addref(obs_service_t *){}
|
||||
inline void ___source_dummy_addref(obs_source_t *) {}
|
||||
inline void ___scene_dummy_addref(obs_scene_t *) {}
|
||||
inline void ___sceneitem_dummy_addref(obs_sceneitem_t *) {}
|
||||
inline void ___data_dummy_addref(obs_data_t *) {}
|
||||
inline void ___data_array_dummy_addref(obs_data_array_t *) {}
|
||||
inline void ___output_dummy_addref(obs_output_t *) {}
|
||||
inline void ___encoder_dummy_addref(obs_encoder_t *) {}
|
||||
inline void ___service_dummy_addref(obs_service_t *) {}
|
||||
|
||||
inline void ___weak_source_dummy_addref(obs_weak_source_t*){}
|
||||
inline void ___weak_output_dummy_addref(obs_weak_output_t *){}
|
||||
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *){}
|
||||
inline void ___weak_service_dummy_addref(obs_weak_service_t *){}
|
||||
inline void ___weak_source_dummy_addref(obs_weak_source_t *) {}
|
||||
inline void ___weak_output_dummy_addref(obs_weak_output_t *) {}
|
||||
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *) {}
|
||||
inline void ___weak_service_dummy_addref(obs_weak_service_t *) {}
|
||||
|
||||
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
|
||||
using OBSSceneAutoRelease = OBSRef<obs_scene_t*, ___scene_dummy_addref, obs_scene_release>;
|
||||
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
|
||||
using OBSDataAutoRelease = OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;
|
||||
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>;
|
||||
using OBSOutputAutoRelease = OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
|
||||
using OBSSourceAutoRelease = OBSRef<obs_source_t *, ___source_dummy_addref, obs_source_release>;
|
||||
using OBSSceneAutoRelease = OBSRef<obs_scene_t *, ___scene_dummy_addref, obs_scene_release>;
|
||||
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t *, ___sceneitem_dummy_addref, obs_sceneitem_release>;
|
||||
using OBSDataAutoRelease = OBSRef<obs_data_t *, ___data_dummy_addref, obs_data_release>;
|
||||
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t *, ___data_array_dummy_addref, obs_data_array_release>;
|
||||
using OBSOutputAutoRelease = OBSRef<obs_output_t *, ___output_dummy_addref, obs_output_release>;
|
||||
using OBSEncoderAutoRelease = OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_release>;
|
||||
using OBSServiceAutoRelease = OBSRef<obs_service_t *, ___service_dummy_addref, obs_service_release>;
|
||||
|
||||
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t*, ___weak_source_dummy_addref, obs_weak_source_release>;
|
||||
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t *, ___weak_source_dummy_addref, obs_weak_source_release>;
|
||||
using OBSWeakOutputAutoRelease = OBSRef<obs_weak_output_t *, ___weak_output_dummy_addref, obs_weak_output_release>;
|
||||
using OBSWeakEncoderAutoRelease = OBSRef<obs_weak_encoder_t *, ___weak_encoder_dummy_addref, obs_weak_encoder_release>;
|
||||
using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref, obs_weak_service_release>;
|
||||
#endif
|
||||
|
||||
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
|
||||
template<typename T> T *GetCalldataPointer(const calldata_t *data, const char *name)
|
||||
{
|
||||
void *ptr = nullptr;
|
||||
calldata_get_ptr(data, name, &ptr);
|
||||
return static_cast<T*>(ptr);
|
||||
return static_cast<T *>(ptr);
|
||||
}
|
||||
|
||||
enum ObsOutputState {
|
||||
@ -76,6 +77,16 @@ enum ObsOutputState {
|
||||
OBS_WEBSOCKET_OUTPUT_RESUMED,
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ObsOutputState, {
|
||||
{OBS_WEBSOCKET_OUTPUT_UNKNOWN, "OBS_WEBSOCKET_OUTPUT_UNKNOWN"},
|
||||
{OBS_WEBSOCKET_OUTPUT_STARTING, "OBS_WEBSOCKET_OUTPUT_STARTING"},
|
||||
{OBS_WEBSOCKET_OUTPUT_STARTED, "OBS_WEBSOCKET_OUTPUT_STARTED"},
|
||||
{OBS_WEBSOCKET_OUTPUT_STOPPING, "OBS_WEBSOCKET_OUTPUT_STOPPING"},
|
||||
{OBS_WEBSOCKET_OUTPUT_STOPPED, "OBS_WEBSOCKET_OUTPUT_STOPPED"},
|
||||
{OBS_WEBSOCKET_OUTPUT_PAUSED, "OBS_WEBSOCKET_OUTPUT_PAUSED"},
|
||||
{OBS_WEBSOCKET_OUTPUT_RESUMED, "OBS_WEBSOCKET_OUTPUT_RESUMED"},
|
||||
})
|
||||
|
||||
enum ObsMediaInputAction {
|
||||
/**
|
||||
* No action.
|
||||
@ -149,6 +160,17 @@ enum ObsMediaInputAction {
|
||||
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS,
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ObsMediaInputAction,
|
||||
{
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT"},
|
||||
{OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS"},
|
||||
})
|
||||
|
||||
namespace Utils {
|
||||
namespace Obs {
|
||||
namespace StringHelper {
|
||||
@ -157,21 +179,9 @@ namespace Utils {
|
||||
std::string GetCurrentProfile();
|
||||
std::string GetCurrentProfilePath();
|
||||
std::string GetCurrentRecordOutputPath();
|
||||
std::string GetSourceType(obs_source_t *source);
|
||||
std::string GetInputMonitorType(enum obs_monitoring_type monitorType);
|
||||
std::string GetInputMonitorType(obs_source_t *input);
|
||||
std::string GetMediaInputState(obs_source_t *input);
|
||||
std::string GetLastReplayBufferFilePath();
|
||||
std::string GetSceneItemBoundsType(enum obs_bounds_type type);
|
||||
std::string GetSceneItemBlendMode(enum obs_blending_type mode);
|
||||
std::string GetLastRecordFileName();
|
||||
std::string GetLastReplayBufferFileName();
|
||||
std::string DurationToTimecode(uint64_t);
|
||||
std::string GetOutputState(ObsOutputState state);
|
||||
}
|
||||
|
||||
namespace EnumHelper {
|
||||
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
|
||||
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
|
||||
enum obs_blending_type GetSceneItemBlendMode(std::string mode);
|
||||
}
|
||||
|
||||
namespace NumberHelper {
|
||||
@ -195,6 +205,7 @@ namespace Utils {
|
||||
std::vector<json> GetSceneTransitionList();
|
||||
std::vector<json> GetSourceFilterList(obs_source_t *source);
|
||||
std::vector<std::string> GetFilterKindList();
|
||||
std::vector<json> GetOutputList();
|
||||
}
|
||||
|
||||
namespace ObjectHelper {
|
||||
@ -205,13 +216,21 @@ namespace Utils {
|
||||
namespace SearchHelper {
|
||||
obs_hotkey_t *GetHotkeyByName(std::string name);
|
||||
obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease
|
||||
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name,
|
||||
int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
}
|
||||
|
||||
namespace ActionHelper {
|
||||
obs_sceneitem_t *CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true, obs_transform_info *sceneItemTransform = nullptr, obs_sceneitem_crop *sceneItemCrop = nullptr); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease
|
||||
obs_sceneitem_t *
|
||||
CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true,
|
||||
obs_transform_info *sceneItemTransform = nullptr,
|
||||
obs_sceneitem_crop *sceneItemCrop = nullptr); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings,
|
||||
obs_scene_t *scene,
|
||||
bool sceneItemEnabled = true); // Increments ref. Use OBSSceneItemAutoRelease
|
||||
obs_source_t *
|
||||
CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind,
|
||||
obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease
|
||||
void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index);
|
||||
}
|
||||
}
|
||||
|
@ -20,16 +20,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
struct CreateSceneItemData {
|
||||
obs_source_t *source; // In
|
||||
bool sceneItemEnabled; // In
|
||||
obs_source_t *source; // In
|
||||
bool sceneItemEnabled; // In
|
||||
obs_transform_info *sceneItemTransform = nullptr; // In
|
||||
obs_sceneitem_crop *sceneItemCrop = nullptr; // In
|
||||
OBSSceneItem sceneItem; // Out
|
||||
obs_sceneitem_crop *sceneItemCrop = nullptr; // In
|
||||
OBSSceneItem sceneItem; // Out
|
||||
};
|
||||
|
||||
void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
|
||||
static void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
|
||||
{
|
||||
auto *data = static_cast<CreateSceneItemData*>(_data);
|
||||
auto *data = static_cast<CreateSceneItemData *>(_data);
|
||||
data->sceneItem = obs_scene_add(scene, data->source);
|
||||
|
||||
if (data->sceneItemTransform)
|
||||
@ -41,7 +41,9 @@ void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
|
||||
obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled);
|
||||
}
|
||||
|
||||
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled, obs_transform_info *sceneItemTransform, obs_sceneitem_crop *sceneItemCrop)
|
||||
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled,
|
||||
obs_transform_info *sceneItemTransform,
|
||||
obs_sceneitem_crop *sceneItemCrop)
|
||||
{
|
||||
// Sanity check for valid scene
|
||||
if (!(source && scene))
|
||||
@ -64,7 +66,8 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source,
|
||||
return data.sceneItem;
|
||||
}
|
||||
|
||||
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled)
|
||||
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings,
|
||||
obs_scene_t *scene, bool sceneItemEnabled)
|
||||
{
|
||||
// Create the input
|
||||
OBSSourceAutoRelease input = obs_source_create(inputKind.c_str(), inputName.c_str(), inputSettings, nullptr);
|
||||
@ -88,7 +91,8 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, st
|
||||
return ret;
|
||||
}
|
||||
|
||||
obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings)
|
||||
obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind,
|
||||
obs_data_t *filterSettings)
|
||||
{
|
||||
obs_source_t *filter = obs_source_create_private(filterKind.c_str(), filterName.c_str(), filterSettings);
|
||||
|
||||
@ -105,7 +109,7 @@ void Utils::Obs::ActionHelper::SetSourceFilterIndex(obs_source_t *source, obs_so
|
||||
size_t currentIndex = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
|
||||
obs_order_movement direction = index > currentIndex ? OBS_ORDER_MOVE_DOWN : OBS_ORDER_MOVE_UP;
|
||||
|
||||
while(currentIndex != index) {
|
||||
while (currentIndex != index) {
|
||||
obs_source_filter_set_order(source, filter, direction);
|
||||
|
||||
if (direction == OBS_ORDER_MOVE_DOWN)
|
||||
|
@ -29,7 +29,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
|
||||
return ret;
|
||||
|
||||
size_t index = 0;
|
||||
char* value = nullptr;
|
||||
char *value = nullptr;
|
||||
do {
|
||||
value = array[index];
|
||||
if (value)
|
||||
@ -42,7 +42,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
|
||||
|
||||
std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
|
||||
{
|
||||
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||
char **sceneCollections = obs_frontend_get_scene_collections();
|
||||
auto ret = ConvertStringArray(sceneCollections);
|
||||
bfree(sceneCollections);
|
||||
return ret;
|
||||
@ -50,7 +50,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
|
||||
|
||||
std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList()
|
||||
{
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
char **profiles = obs_frontend_get_profiles();
|
||||
auto ret = ConvertStringArray(profiles);
|
||||
bfree(profiles);
|
||||
return ret;
|
||||
@ -60,13 +60,15 @@ std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
|
||||
{
|
||||
std::vector<obs_hotkey_t *> ret;
|
||||
|
||||
obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) {
|
||||
auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) {
|
||||
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data);
|
||||
|
||||
ret->push_back(hotkey);
|
||||
|
||||
return true;
|
||||
}, &ret);
|
||||
};
|
||||
|
||||
obs_enum_hotkeys(cb, &ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -112,7 +114,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
|
||||
std::vector<std::string> ret;
|
||||
|
||||
auto cb = [](void *priv_data, obs_source_t *scene) {
|
||||
auto ret = static_cast<std::vector<std::string>*>(priv_data);
|
||||
auto ret = static_cast<std::vector<std::string> *>(priv_data);
|
||||
|
||||
if (!obs_source_is_group(scene))
|
||||
return true;
|
||||
@ -132,17 +134,23 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
|
||||
std::pair<std::vector<json>, bool> enumData;
|
||||
enumData.second = basic;
|
||||
|
||||
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) {
|
||||
auto enumData = static_cast<std::pair<std::vector<json>, bool>*>(param);
|
||||
auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
|
||||
auto enumData = static_cast<std::pair<std::vector<json>, bool> *>(param);
|
||||
|
||||
// TODO: Make ObjectHelper util for scene items
|
||||
|
||||
json item;
|
||||
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
|
||||
// Should be slightly faster than calling obs_sceneitem_get_order_position()
|
||||
item["sceneItemIndex"] = enumData->first.size();
|
||||
item["sceneItemIndex"] =
|
||||
enumData->first.size(); // Should be slightly faster than calling obs_sceneitem_get_order_position()
|
||||
if (!enumData->second) {
|
||||
item["sceneItemEnabled"] = obs_sceneitem_visible(sceneItem);
|
||||
item["sceneItemLocked"] = obs_sceneitem_locked(sceneItem);
|
||||
item["sceneItemTransform"] = ObjectHelper::GetSceneItemTransform(sceneItem);
|
||||
item["sceneItemBlendMode"] = obs_sceneitem_get_blending_mode(sceneItem);
|
||||
OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
|
||||
item["sourceName"] = obs_source_get_name(itemSource);
|
||||
item["sourceType"] = StringHelper::GetSourceType(itemSource);
|
||||
item["sourceType"] = obs_source_get_type(itemSource);
|
||||
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
|
||||
item["inputKind"] = obs_source_get_id(itemSource);
|
||||
else
|
||||
@ -156,7 +164,9 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
|
||||
enumData->first.push_back(item);
|
||||
|
||||
return true;
|
||||
}, &enumData);
|
||||
};
|
||||
|
||||
obs_scene_enum_items(scene, cb, &enumData);
|
||||
|
||||
return enumData.first;
|
||||
}
|
||||
@ -171,12 +181,12 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
|
||||
EnumInputInfo inputInfo;
|
||||
inputInfo.inputKind = inputKind;
|
||||
|
||||
auto inputEnumProc = [](void *param, obs_source_t *input) {
|
||||
auto cb = [](void *param, obs_source_t *input) {
|
||||
// Sanity check in case the API changes
|
||||
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
|
||||
return true;
|
||||
|
||||
auto inputInfo = static_cast<EnumInputInfo*>(param);
|
||||
auto inputInfo = static_cast<EnumInputInfo *>(param);
|
||||
|
||||
std::string inputKind = obs_source_get_id(input);
|
||||
|
||||
@ -191,8 +201,9 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
|
||||
inputInfo->inputs.push_back(inputJson);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Actually enumerates only public inputs, despite the name
|
||||
obs_enum_sources(inputEnumProc, &inputInfo);
|
||||
obs_enum_sources(cb, &inputInfo);
|
||||
|
||||
return inputInfo.inputs;
|
||||
}
|
||||
@ -287,7 +298,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetFilterKindList()
|
||||
|
||||
size_t idx = 0;
|
||||
const char *kind;
|
||||
while(obs_enum_filter_types(idx++, &kind))
|
||||
while (obs_enum_filter_types(idx++, &kind))
|
||||
ret.push_back(kind);
|
||||
|
||||
return ret;
|
||||
@ -297,8 +308,8 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *sou
|
||||
{
|
||||
std::vector<json> filters;
|
||||
|
||||
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
|
||||
auto filters = reinterpret_cast<std::vector<json>*>(param);
|
||||
auto cb = [](obs_source_t *, obs_source_t *filter, void *param) {
|
||||
auto filters = reinterpret_cast<std::vector<json> *>(param);
|
||||
|
||||
json filterJson;
|
||||
filterJson["filterEnabled"] = obs_source_enabled(filter);
|
||||
@ -311,7 +322,40 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *sou
|
||||
|
||||
filters->push_back(filterJson);
|
||||
};
|
||||
obs_source_enum_filters(source, enumFilters, &filters);
|
||||
|
||||
obs_source_enum_filters(source, cb, &filters);
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
std::vector<json> Utils::Obs::ArrayHelper::GetOutputList()
|
||||
{
|
||||
std::vector<json> outputs;
|
||||
|
||||
auto cb = [](void *param, obs_output_t *output) {
|
||||
auto outputs = reinterpret_cast<std::vector<json> *>(param);
|
||||
|
||||
auto rawFlags = obs_output_get_flags(output);
|
||||
json flags;
|
||||
flags["OBS_OUTPUT_AUDIO"] = !!(rawFlags & OBS_OUTPUT_AUDIO);
|
||||
flags["OBS_OUTPUT_VIDEO"] = !!(rawFlags & OBS_OUTPUT_VIDEO);
|
||||
flags["OBS_OUTPUT_ENCODED"] = !!(rawFlags & OBS_OUTPUT_ENCODED);
|
||||
flags["OBS_OUTPUT_MULTI_TRACK"] = !!(rawFlags & OBS_OUTPUT_MULTI_TRACK);
|
||||
flags["OBS_OUTPUT_SERVICE"] = !!(rawFlags & OBS_OUTPUT_SERVICE);
|
||||
|
||||
json outputJson;
|
||||
outputJson["outputName"] = obs_output_get_name(output);
|
||||
outputJson["outputKind"] = obs_output_get_id(output);
|
||||
outputJson["outputWidth"] = obs_output_get_width(output);
|
||||
outputJson["outputHeight"] = obs_output_get_height(output);
|
||||
outputJson["outputActive"] = obs_output_active(output);
|
||||
outputJson["outputFlags"] = flags;
|
||||
|
||||
outputs->push_back(outputJson);
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_enum_outputs(cb, &outputs);
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "Obs.h"
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
#define RET_COMPARE(str, x) if (str == #x) return x;
|
||||
|
||||
enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType)
|
||||
{
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_NONE)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_INNER)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_OUTER)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_WIDTH)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_HEIGHT)
|
||||
RET_COMPARE(boundsType, OBS_BOUNDS_MAX_ONLY)
|
||||
|
||||
return OBS_BOUNDS_NONE;
|
||||
}
|
||||
|
||||
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
|
||||
{
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP)
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT)
|
||||
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS)
|
||||
|
||||
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
|
||||
}
|
||||
|
||||
enum obs_blending_type Utils::Obs::EnumHelper::GetSceneItemBlendMode(std::string mode)
|
||||
{
|
||||
RET_COMPARE(mode, OBS_BLEND_NORMAL)
|
||||
RET_COMPARE(mode, OBS_BLEND_ADDITIVE)
|
||||
RET_COMPARE(mode, OBS_BLEND_SUBTRACT)
|
||||
RET_COMPARE(mode, OBS_BLEND_SCREEN)
|
||||
RET_COMPARE(mode, OBS_BLEND_MULTIPLY)
|
||||
RET_COMPARE(mode, OBS_BLEND_LIGHTEN)
|
||||
RET_COMPARE(mode, OBS_BLEND_DARKEN)
|
||||
|
||||
return OBS_BLEND_NORMAL;
|
||||
}
|
@ -28,7 +28,7 @@ uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
|
||||
if (!output || !obs_output_active(output))
|
||||
return 0;
|
||||
|
||||
video_t* video = obs_output_video(output);
|
||||
video_t *video = obs_output_video(output);
|
||||
uint64_t frameTimeNs = video_output_get_frame_time(video);
|
||||
int totalFrames = obs_output_get_total_frames(output);
|
||||
|
||||
@ -39,7 +39,7 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
|
||||
{
|
||||
size_t ret;
|
||||
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
|
||||
auto ret = static_cast<size_t*>(param);
|
||||
auto ret = static_cast<size_t *>(param);
|
||||
|
||||
if (obs_source_is_group(scene))
|
||||
return true;
|
||||
@ -62,7 +62,7 @@ size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source, obs_
|
||||
};
|
||||
|
||||
auto search = [](obs_source_t *, obs_source_t *filter, void *priv_data) {
|
||||
auto filterSearch = static_cast<FilterSearch*>(priv_data);
|
||||
auto filterSearch = static_cast<FilterSearch *>(priv_data);
|
||||
|
||||
if (filter == filterSearch->filter)
|
||||
filterSearch->found = true;
|
||||
|
@ -29,7 +29,7 @@ json Utils::Obs::ObjectHelper::GetStats()
|
||||
|
||||
std::string outputPath = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
|
||||
|
||||
video_t* video = obs_get_video();
|
||||
video_t *video = obs_get_video();
|
||||
|
||||
ret["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo());
|
||||
ret["memoryUsage"] = (double)os_get_proc_resident_size() / (1024.0 * 1024.0);
|
||||
@ -54,8 +54,8 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
|
||||
obs_sceneitem_get_crop(item, &crop);
|
||||
|
||||
OBSSource source = obs_sceneitem_get_source(item);
|
||||
float sourceWidth = float(obs_source_get_width(source));
|
||||
float sourceHeight = float(obs_source_get_height(source));
|
||||
float sourceWidth = (float)obs_source_get_width(source);
|
||||
float sourceHeight = (float)obs_source_get_height(source);
|
||||
|
||||
ret["sourceWidth"] = sourceWidth;
|
||||
ret["sourceHeight"] = sourceHeight;
|
||||
@ -73,15 +73,15 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
|
||||
|
||||
ret["alignment"] = osi.alignment;
|
||||
|
||||
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type);
|
||||
ret["boundsType"] = osi.bounds_type;
|
||||
ret["boundsAlignment"] = osi.bounds_alignment;
|
||||
ret["boundsWidth"] = osi.bounds.x;
|
||||
ret["boundsHeight"] = osi.bounds.y;
|
||||
|
||||
ret["cropLeft"] = int(crop.left);
|
||||
ret["cropRight"] = int(crop.right);
|
||||
ret["cropTop"] = int(crop.top);
|
||||
ret["cropBottom"] = int(crop.bottom);
|
||||
ret["cropLeft"] = (int)crop.left;
|
||||
ret["cropRight"] = (int)crop.right;
|
||||
ret["cropTop"] = (int)crop.top;
|
||||
ret["cropBottom"] = (int)crop.bottom;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -54,15 +54,44 @@ obs_source_t *Utils::Obs::SearchHelper::GetSceneTransitionByName(std::string nam
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct SceneItemSearchData {
|
||||
std::string name;
|
||||
int offset;
|
||||
obs_sceneitem_t *ret = nullptr;
|
||||
};
|
||||
|
||||
// Increments item ref. Use OBSSceneItemAutoRelease
|
||||
obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene, std::string name)
|
||||
obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene, std::string name, int offset)
|
||||
{
|
||||
if (name.empty())
|
||||
return nullptr;
|
||||
|
||||
// Finds first matching scene item in scene, search starts at index 0
|
||||
OBSSceneItem ret = obs_scene_find_source(scene, name.c_str());
|
||||
obs_sceneitem_addref(ret);
|
||||
SceneItemSearchData enumData;
|
||||
enumData.name = name;
|
||||
enumData.offset = offset;
|
||||
|
||||
return ret;
|
||||
auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
|
||||
auto enumData = static_cast<SceneItemSearchData *>(param);
|
||||
|
||||
OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
|
||||
std::string sourceName = obs_source_get_name(itemSource);
|
||||
if (sourceName == enumData->name) {
|
||||
if (enumData->offset > 0) {
|
||||
enumData->offset--;
|
||||
} else {
|
||||
if (enumData->ret) // Release existing selection in the case of last match selection
|
||||
obs_sceneitem_release(enumData->ret);
|
||||
obs_sceneitem_addref(sceneItem);
|
||||
enumData->ret = sceneItem;
|
||||
if (enumData->offset == 0) // Only break if in normal selection mode (not offset == -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_scene_enum_items(scene, cb, &enumData);
|
||||
|
||||
return enumData.ret;
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "Obs.h"
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
#define CASE(x) case x: return #x;
|
||||
#define CASE(x) \
|
||||
case x: \
|
||||
return #x;
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetObsVersion()
|
||||
{
|
||||
@ -70,54 +72,27 @@ std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
|
||||
std::string Utils::Obs::StringHelper::GetLastRecordFileName()
|
||||
{
|
||||
obs_source_type sourceType = obs_source_get_type(source);
|
||||
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
|
||||
if (!output)
|
||||
return "";
|
||||
|
||||
switch (sourceType) {
|
||||
default:
|
||||
CASE(OBS_SOURCE_TYPE_INPUT)
|
||||
CASE(OBS_SOURCE_TYPE_FILTER)
|
||||
CASE(OBS_SOURCE_TYPE_TRANSITION)
|
||||
CASE(OBS_SOURCE_TYPE_SCENE)
|
||||
OBSDataAutoRelease outputSettings = obs_output_get_settings(output);
|
||||
|
||||
obs_data_item_t *item = obs_data_item_byname(outputSettings, "url");
|
||||
if (!item) {
|
||||
item = obs_data_item_byname(outputSettings, "path");
|
||||
if (!item)
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ret = obs_data_item_get_string(item);
|
||||
obs_data_item_release(&item);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetInputMonitorType(enum obs_monitoring_type monitorType)
|
||||
{
|
||||
switch (monitorType) {
|
||||
default:
|
||||
CASE(OBS_MONITORING_TYPE_NONE)
|
||||
CASE(OBS_MONITORING_TYPE_MONITOR_ONLY)
|
||||
CASE(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)
|
||||
}
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetInputMonitorType(obs_source_t *input)
|
||||
{
|
||||
obs_monitoring_type monitorType = obs_source_get_monitoring_type(input);
|
||||
|
||||
return GetInputMonitorType(monitorType);
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
|
||||
{
|
||||
obs_media_state mediaState = obs_source_media_get_state(input);
|
||||
|
||||
switch (mediaState) {
|
||||
default:
|
||||
CASE(OBS_MEDIA_STATE_NONE)
|
||||
CASE(OBS_MEDIA_STATE_PLAYING)
|
||||
CASE(OBS_MEDIA_STATE_OPENING)
|
||||
CASE(OBS_MEDIA_STATE_BUFFERING)
|
||||
CASE(OBS_MEDIA_STATE_PAUSED)
|
||||
CASE(OBS_MEDIA_STATE_STOPPED)
|
||||
CASE(OBS_MEDIA_STATE_ENDED)
|
||||
CASE(OBS_MEDIA_STATE_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
|
||||
std::string Utils::Obs::StringHelper::GetLastReplayBufferFileName()
|
||||
{
|
||||
OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output();
|
||||
if (!output)
|
||||
@ -135,34 +110,6 @@ std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
|
||||
return savedReplayPath;
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
|
||||
{
|
||||
switch (type) {
|
||||
default:
|
||||
CASE(OBS_BOUNDS_NONE)
|
||||
CASE(OBS_BOUNDS_STRETCH)
|
||||
CASE(OBS_BOUNDS_SCALE_INNER)
|
||||
CASE(OBS_BOUNDS_SCALE_OUTER)
|
||||
CASE(OBS_BOUNDS_SCALE_TO_WIDTH)
|
||||
CASE(OBS_BOUNDS_SCALE_TO_HEIGHT)
|
||||
CASE(OBS_BOUNDS_MAX_ONLY)
|
||||
}
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetSceneItemBlendMode(enum obs_blending_type mode)
|
||||
{
|
||||
switch (mode) {
|
||||
default:
|
||||
CASE(OBS_BLEND_NORMAL)
|
||||
CASE(OBS_BLEND_ADDITIVE)
|
||||
CASE(OBS_BLEND_SUBTRACT)
|
||||
CASE(OBS_BLEND_SCREEN)
|
||||
CASE(OBS_BLEND_MULTIPLY)
|
||||
CASE(OBS_BLEND_LIGHTEN)
|
||||
CASE(OBS_BLEND_DARKEN)
|
||||
}
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
|
||||
{
|
||||
uint64_t secs = ms / 1000ULL;
|
||||
@ -173,20 +120,7 @@ std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
|
||||
uint64_t secsPart = secs % 60ULL;
|
||||
uint64_t msPart = ms % 1000ULL;
|
||||
|
||||
QString formatted = QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
|
||||
QString formatted =
|
||||
QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
|
||||
return formatted.toStdString();
|
||||
}
|
||||
|
||||
std::string Utils::Obs::StringHelper::GetOutputState(ObsOutputState state)
|
||||
{
|
||||
switch (state) {
|
||||
default:
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_UNKNOWN)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_STARTING)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_STARTED)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_STOPPING)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_STOPPED)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_PAUSED)
|
||||
CASE(OBS_WEBSOCKET_OUTPUT_RESUMED)
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "Obs_VolumeMeter_Helpers.h"
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
|
||||
PeakMeterType(SAMPLE_PEAK_METER),
|
||||
_input(obs_source_get_weak_source(input)),
|
||||
_channels(0),
|
||||
_lastUpdate(0),
|
||||
_volume(obs_source_get_volume(input))
|
||||
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input)
|
||||
: PeakMeterType(SAMPLE_PEAK_METER),
|
||||
_input(obs_source_get_weak_source(input)),
|
||||
_channels(0),
|
||||
_lastUpdate(0),
|
||||
_volume(obs_source_get_volume(input))
|
||||
{
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(input);
|
||||
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
|
||||
@ -45,7 +45,8 @@ Utils::Obs::VolumeMeter::Meter::~Meter()
|
||||
{
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
|
||||
blog(LOG_WARNING,
|
||||
"[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -68,7 +69,8 @@ json Utils::Obs::VolumeMeter::Meter::GetMeterData()
|
||||
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
|
||||
blog(LOG_WARNING,
|
||||
"[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -129,7 +131,7 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
|
||||
int channelNumber = 0;
|
||||
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
float *samples = (float *)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
@ -143,41 +145,41 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
|
||||
|
||||
float peak;
|
||||
switch (PeakMeterType) {
|
||||
default:
|
||||
case SAMPLE_PEAK_METER:
|
||||
peak = GetSamplePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
case TRUE_PEAK_METER:
|
||||
peak = GetTruePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
default:
|
||||
case SAMPLE_PEAK_METER:
|
||||
peak = GetSamplePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
case TRUE_PEAK_METER:
|
||||
peak = GetTruePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sampleCount) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 2:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 3:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
default:
|
||||
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 2:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 3:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
default:
|
||||
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
}
|
||||
|
||||
_peak[channelNumber] = peak;
|
||||
@ -196,7 +198,7 @@ void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *d
|
||||
|
||||
int channelNumber = 0;
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
float *samples = (float *)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
@ -212,9 +214,10 @@ void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *d
|
||||
}
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
|
||||
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data,
|
||||
bool muted)
|
||||
{
|
||||
auto c = static_cast<Meter*>(priv_data);
|
||||
auto c = static_cast<Meter *>(priv_data);
|
||||
|
||||
std::unique_lock<std::mutex> l(c->_mutex);
|
||||
|
||||
@ -228,22 +231,20 @@ void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data,
|
||||
|
||||
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Meter*>(priv_data);
|
||||
auto c = static_cast<Meter *>(priv_data);
|
||||
|
||||
c->_volume = (float)calldata_float(cd, "volume");
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
|
||||
_updateCallback(cb),
|
||||
_updatePeriod(updatePeriod),
|
||||
_running(false)
|
||||
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod)
|
||||
: _updateCallback(cb), _updatePeriod(updatePeriod), _running(false)
|
||||
{
|
||||
signal_handler_t *sh = obs_get_signal_handler();
|
||||
if (!sh)
|
||||
return;
|
||||
|
||||
auto enumProc = [](void *priv_data, obs_source_t *input) {
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
auto c = static_cast<Handler *>(priv_data);
|
||||
|
||||
if (!obs_source_active(input))
|
||||
return true;
|
||||
@ -293,7 +294,7 @@ void Utils::Obs::VolumeMeter::Handler::UpdateThread()
|
||||
while (_running) {
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
|
||||
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this] { return !_running; }))
|
||||
break;
|
||||
}
|
||||
|
||||
@ -313,7 +314,7 @@ void Utils::Obs::VolumeMeter::Handler::UpdateThread()
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
auto c = static_cast<Handler *>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
@ -332,7 +333,7 @@ void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, ca
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
auto c = static_cast<Handler *>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
|
@ -36,37 +36,38 @@ namespace Utils {
|
||||
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
|
||||
// Keeps a running tally of the current audio levels, for a specific input
|
||||
class Meter {
|
||||
public:
|
||||
Meter(obs_source_t *input);
|
||||
~Meter();
|
||||
public:
|
||||
Meter(obs_source_t *input);
|
||||
~Meter();
|
||||
|
||||
bool InputValid();
|
||||
obs_weak_source_t *GetWeakInput() { return _input; }
|
||||
json GetMeterData();
|
||||
bool InputValid();
|
||||
obs_weak_source_t *GetWeakInput() { return _input; }
|
||||
json GetMeterData();
|
||||
|
||||
std::atomic<enum obs_peak_meter_type> PeakMeterType;
|
||||
std::atomic<enum obs_peak_meter_type> PeakMeterType;
|
||||
|
||||
private:
|
||||
OBSWeakSourceAutoRelease _input;
|
||||
private:
|
||||
OBSWeakSourceAutoRelease _input;
|
||||
|
||||
// All values in mul
|
||||
std::mutex _mutex;
|
||||
bool _muted;
|
||||
int _channels;
|
||||
float _magnitude[MAX_AUDIO_CHANNELS];
|
||||
float _peak[MAX_AUDIO_CHANNELS];
|
||||
float _previousSamples[MAX_AUDIO_CHANNELS][4];
|
||||
// All values in mul
|
||||
std::mutex _mutex;
|
||||
bool _muted;
|
||||
int _channels;
|
||||
float _magnitude[MAX_AUDIO_CHANNELS];
|
||||
float _peak[MAX_AUDIO_CHANNELS];
|
||||
float _previousSamples[MAX_AUDIO_CHANNELS][4];
|
||||
|
||||
std::atomic<uint64_t> _lastUpdate;
|
||||
std::atomic<float> _volume;
|
||||
std::atomic<uint64_t> _lastUpdate;
|
||||
std::atomic<float> _volume;
|
||||
|
||||
void ResetAudioLevels();
|
||||
void ProcessAudioChannels(const struct audio_data *data);
|
||||
void ProcessPeak(const struct audio_data *data);
|
||||
void ProcessMagnitude(const struct audio_data *data);
|
||||
void ResetAudioLevels();
|
||||
void ProcessAudioChannels(const struct audio_data *data);
|
||||
void ProcessPeak(const struct audio_data *data);
|
||||
void ProcessMagnitude(const struct audio_data *data);
|
||||
|
||||
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
|
||||
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
|
||||
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source,
|
||||
const struct audio_data *data, bool muted);
|
||||
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
|
||||
// Maintains an array of active inputs
|
||||
@ -74,25 +75,25 @@ namespace Utils {
|
||||
typedef std::function<void(std::vector<json>)> UpdateCallback;
|
||||
typedef std::unique_ptr<Meter> MeterPtr;
|
||||
|
||||
public:
|
||||
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
|
||||
~Handler();
|
||||
public:
|
||||
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
|
||||
~Handler();
|
||||
|
||||
private:
|
||||
UpdateCallback _updateCallback;
|
||||
private:
|
||||
UpdateCallback _updateCallback;
|
||||
|
||||
std::mutex _meterMutex;
|
||||
std::vector<MeterPtr> _meters;
|
||||
uint64_t _updatePeriod;
|
||||
std::mutex _meterMutex;
|
||||
std::vector<MeterPtr> _meters;
|
||||
uint64_t _updatePeriod;
|
||||
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::atomic<bool> _running;
|
||||
std::thread _updateThread;
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::atomic<bool> _running;
|
||||
std::thread _updateThread;
|
||||
|
||||
void UpdateThread();
|
||||
static void InputActivateCallback(void *priv_data, calldata_t *cd);
|
||||
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
|
||||
void UpdateThread();
|
||||
static void InputActivateCallback(void *priv_data, calldata_t *cd);
|
||||
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
// It should probably be imported as a libobs util someday though.
|
||||
|
||||
#define SHIFT_RIGHT_2PS(msb, lsb) \
|
||||
{ \
|
||||
__m128 tmp = \
|
||||
_mm_shuffle_ps(lsb, msb, _MM_SHUFFLE(0, 0, 3, 3)); \
|
||||
lsb = _mm_shuffle_ps(lsb, tmp, _MM_SHUFFLE(2, 1, 2, 1)); \
|
||||
msb = _mm_shuffle_ps(msb, msb, _MM_SHUFFLE(3, 3, 2, 1)); \
|
||||
#define SHIFT_RIGHT_2PS(msb, lsb) \
|
||||
{ \
|
||||
__m128 tmp = _mm_shuffle_ps(lsb, msb, _MM_SHUFFLE(0, 0, 3, 3)); \
|
||||
lsb = _mm_shuffle_ps(lsb, tmp, _MM_SHUFFLE(2, 1, 2, 1)); \
|
||||
msb = _mm_shuffle_ps(msb, msb, _MM_SHUFFLE(3, 3, 2, 1)); \
|
||||
}
|
||||
|
||||
#define abs_ps(v) _mm_andnot_ps(_mm_set1_ps(-0.f), v)
|
||||
|
@ -53,8 +53,12 @@ std::string Utils::Platform::GetLocalAddress()
|
||||
std::vector<std::pair<QString, uint8_t>> preferredAddresses;
|
||||
for (auto address : validAddresses) {
|
||||
// Attribute a priority (0 is best) to the address to choose the best picks
|
||||
if (address.startsWith("192.168.1.") || address.startsWith("192.168.0.")) { // Prefer common consumer router network prefixes
|
||||
preferredAddresses.push_back(std::make_pair(address, 0));
|
||||
if (address.startsWith("192.168.1.") ||
|
||||
address.startsWith("192.168.0.")) { // Prefer common consumer router network prefixes
|
||||
if (address.startsWith("192.168.56."))
|
||||
preferredAddresses.push_back(std::make_pair(address, 255)); // Ignore virtualbox default
|
||||
else
|
||||
preferredAddresses.push_back(std::make_pair(address, 0));
|
||||
} else if (address.startsWith("172.16.")) { // Slightly less common consumer router network prefixes
|
||||
preferredAddresses.push_back(std::make_pair(address, 1));
|
||||
} else if (address.startsWith("10.")) { // Even less common consumer router network prefixes
|
||||
@ -65,9 +69,8 @@ std::string Utils::Platform::GetLocalAddress()
|
||||
}
|
||||
|
||||
// Sort by priority
|
||||
std::sort(preferredAddresses.begin(), preferredAddresses.end(), [=](std::pair<QString, uint8_t> a, std::pair<QString, uint8_t> b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
std::sort(preferredAddresses.begin(), preferredAddresses.end(),
|
||||
[=](std::pair<QString, uint8_t> a, std::pair<QString, uint8_t> b) { return a.second < b.second; });
|
||||
|
||||
// Return highest priority address
|
||||
return preferredAddresses[0].first.toStdString();
|
||||
@ -109,14 +112,17 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS
|
||||
|
||||
SystemTrayNotification *notification = new SystemTrayNotification{icon, title, body};
|
||||
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
void *systemTrayPtr = obs_frontend_get_system_tray();
|
||||
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr);
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
[](void *param) {
|
||||
void *systemTrayPtr = obs_frontend_get_system_tray();
|
||||
auto systemTray = static_cast<QSystemTrayIcon *>(systemTrayPtr);
|
||||
|
||||
auto notification = static_cast<SystemTrayNotification*>(param);
|
||||
systemTray->showMessage(notification->title, notification->body, notification->icon);
|
||||
delete notification;
|
||||
}, (void*)notification, false);
|
||||
auto notification = static_cast<SystemTrayNotification *>(param);
|
||||
systemTray->showMessage(notification->title, notification->body, notification->icon);
|
||||
delete notification;
|
||||
},
|
||||
(void *)notification, false);
|
||||
}
|
||||
|
||||
bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &content)
|
||||
|
@ -31,9 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../utils/Platform.h"
|
||||
#include "../utils/Compat.h"
|
||||
|
||||
WebSocketServer::WebSocketServer() :
|
||||
QObject(nullptr),
|
||||
_sessions()
|
||||
WebSocketServer::WebSocketServer() : QObject(nullptr), _sessions()
|
||||
{
|
||||
_server.get_alog().clear_channels(websocketpp::log::alevel::all);
|
||||
_server.get_elog().clear_channels(websocketpp::log::elevel::all);
|
||||
@ -44,38 +42,17 @@ WebSocketServer::WebSocketServer() :
|
||||
#endif
|
||||
|
||||
_server.set_validate_handler(
|
||||
websocketpp::lib::bind(
|
||||
&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1
|
||||
)
|
||||
);
|
||||
_server.set_open_handler(
|
||||
websocketpp::lib::bind(
|
||||
&WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1
|
||||
)
|
||||
);
|
||||
_server.set_close_handler(
|
||||
websocketpp::lib::bind(
|
||||
&WebSocketServer::onClose, this, websocketpp::lib::placeholders::_1
|
||||
)
|
||||
);
|
||||
_server.set_message_handler(
|
||||
websocketpp::lib::bind(
|
||||
&WebSocketServer::onMessage, this, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2
|
||||
)
|
||||
);
|
||||
websocketpp::lib::bind(&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1));
|
||||
_server.set_open_handler(websocketpp::lib::bind(&WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1));
|
||||
_server.set_close_handler(websocketpp::lib::bind(&WebSocketServer::onClose, this, websocketpp::lib::placeholders::_1));
|
||||
_server.set_message_handler(websocketpp::lib::bind(&WebSocketServer::onMessage, this, websocketpp::lib::placeholders::_1,
|
||||
websocketpp::lib::placeholders::_2));
|
||||
|
||||
auto eventHandler = GetEventHandler();
|
||||
eventHandler->SetBroadcastCallback(
|
||||
std::bind(
|
||||
&WebSocketServer::BroadcastEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4
|
||||
)
|
||||
);
|
||||
eventHandler->SetBroadcastCallback(std::bind(&WebSocketServer::BroadcastEvent, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
|
||||
eventHandler->SetObsLoadedCallback(
|
||||
std::bind(
|
||||
&WebSocketServer::onObsLoaded, this
|
||||
)
|
||||
);
|
||||
eventHandler->SetObsLoadedCallback(std::bind(&WebSocketServer::onObsLoaded, this));
|
||||
}
|
||||
|
||||
WebSocketServer::~WebSocketServer()
|
||||
@ -89,9 +66,9 @@ void WebSocketServer::ServerRunner()
|
||||
blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread started.");
|
||||
try {
|
||||
_server.run();
|
||||
} catch (websocketpp::exception const & e) {
|
||||
} catch (websocketpp::exception const &e) {
|
||||
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what());
|
||||
} catch (const std::exception & e) {
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what());
|
||||
} catch (...) {
|
||||
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error");
|
||||
@ -118,7 +95,8 @@ void WebSocketServer::Start()
|
||||
// Set log levels if debug is enabled
|
||||
if (IsDebugEnabled()) {
|
||||
_server.get_alog().set_channels(websocketpp::log::alevel::all);
|
||||
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control);
|
||||
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
_server.get_elog().set_channels(websocketpp::log::elevel::all);
|
||||
_server.get_alog().clear_channels(websocketpp::log::elevel::devel | websocketpp::log::elevel::library);
|
||||
} else {
|
||||
@ -129,7 +107,13 @@ void WebSocketServer::Start()
|
||||
_server.reset();
|
||||
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_server.listen(websocketpp::lib::asio::ip::tcp::v4(), conf->ServerPort, errorCode);
|
||||
if (conf->Ipv4Only) {
|
||||
blog(LOG_INFO, "[WebSocketServer::Start] Locked to IPv4 bindings");
|
||||
_server.listen(websocketpp::lib::asio::ip::tcp::v4(), conf->ServerPort, errorCode);
|
||||
} else {
|
||||
blog(LOG_INFO, "[WebSocketServer::Start] Not locked to IPv4 bindings");
|
||||
_server.listen(conf->ServerPort, errorCode);
|
||||
}
|
||||
|
||||
if (errorCode) {
|
||||
std::string errorCodeMessage = errorCode.message();
|
||||
@ -141,7 +125,8 @@ void WebSocketServer::Start()
|
||||
|
||||
_serverThread = std::thread(&WebSocketServer::ServerRunner, this);
|
||||
|
||||
blog(LOG_INFO, "[WebSocketServer::Start] Server started successfully on port %d. Possible connect address: %s", conf->ServerPort.load(), Utils::Platform::GetLocalAddress().c_str());
|
||||
blog(LOG_INFO, "[WebSocketServer::Start] Server started successfully on port %d. Possible connect address: %s",
|
||||
conf->ServerPort.load(), Utils::Platform::GetLocalAddress().c_str());
|
||||
}
|
||||
|
||||
void WebSocketServer::Stop()
|
||||
@ -154,7 +139,7 @@ void WebSocketServer::Stop()
|
||||
_server.stop_listening();
|
||||
|
||||
std::unique_lock<std::mutex> lock(_sessionMutex);
|
||||
for (auto const& [hdl, session] : _sessions) {
|
||||
for (auto const &[hdl, session] : _sessions) {
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_server.pause_reading(hdl, errorCode);
|
||||
if (errorCode) {
|
||||
@ -205,14 +190,15 @@ std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocke
|
||||
std::vector<WebSocketServer::WebSocketSessionState> webSocketSessions;
|
||||
|
||||
std::unique_lock<std::mutex> lock(_sessionMutex);
|
||||
for (auto & [hdl, session] : _sessions) {
|
||||
for (auto &[hdl, session] : _sessions) {
|
||||
uint64_t connectedAt = session->ConnectedAt();
|
||||
uint64_t incomingMessages = session->IncomingMessages();
|
||||
uint64_t outgoingMessages = session->OutgoingMessages();
|
||||
std::string remoteAddress = session->RemoteAddress();
|
||||
bool isIdentified = session->IsIdentified();
|
||||
|
||||
webSocketSessions.emplace_back(WebSocketSessionState{hdl, remoteAddress, connectedAt, incomingMessages, outgoingMessages, isIdentified});
|
||||
|
||||
webSocketSessions.emplace_back(
|
||||
WebSocketSessionState{hdl, remoteAddress, connectedAt, incomingMessages, outgoingMessages, isIdentified});
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
@ -358,7 +344,8 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
|
||||
emit ClientDisconnected(state, conn->get_local_close_code());
|
||||
|
||||
// Log disconnection
|
||||
blog(LOG_INFO, "[WebSocketServer::onClose] WebSocket client %s has disconnected", remoteAddress.c_str());
|
||||
blog(LOG_INFO, "[WebSocketServer::onClose] WebSocket client `%s` has disconnected with code `%d` and reason: %s",
|
||||
remoteAddress.c_str(), conn->get_local_close_code(), conn->get_local_close_reason().c_str());
|
||||
|
||||
// Get config for tray notification
|
||||
auto conf = GetConfig();
|
||||
@ -370,12 +357,14 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
|
||||
// If previously identified, not going away, and notifications enabled, send a tray notification
|
||||
if (isIdentified && (conn->get_local_close_code() != websocketpp::close::status::going_away) && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Body")).arg(QString::fromStdString(remoteAddress));
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Body"))
|
||||
.arg(QString::fromStdString(remoteAddress));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message)
|
||||
void WebSocketServer::onMessage(websocketpp::connection_hdl hdl,
|
||||
websocketpp::server<websocketpp::config::asio>::message_ptr message)
|
||||
{
|
||||
auto opCode = message->get_opcode();
|
||||
std::string payload = message->get_payload();
|
||||
@ -384,7 +373,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
|
||||
SessionPtr session;
|
||||
try {
|
||||
session = _sessions.at(hdl);
|
||||
} catch (const std::out_of_range& oor) {
|
||||
} catch (const std::out_of_range &oor) {
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
@ -398,26 +387,32 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
|
||||
uint8_t sessionEncoding = session->Encoding();
|
||||
if (sessionEncoding == WebSocketEncoding::Json) {
|
||||
if (opCode != websocketpp::frame::opcode::text) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, "Your session encoding is set to Json, but a binary message was received.", errorCode);
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError,
|
||||
"Your session encoding is set to Json, but a binary message was received.",
|
||||
errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
incomingMessage = json::parse(payload);
|
||||
} catch (json::parse_error& e) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode Json: ") + e.what(), errorCode);
|
||||
} catch (json::parse_error &e) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError,
|
||||
std::string("Unable to decode Json: ") + e.what(), errorCode);
|
||||
return;
|
||||
}
|
||||
} else if (sessionEncoding == WebSocketEncoding::MsgPack) {
|
||||
if (opCode != websocketpp::frame::opcode::binary) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, "Your session encoding is set to MsgPack, but a text message was received.", errorCode);
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError,
|
||||
"Your session encoding is set to MsgPack, but a text message was received.",
|
||||
errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
incomingMessage = json::from_msgpack(payload);
|
||||
} catch (json::parse_error& e) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode MsgPack: ") + e.what(), errorCode);
|
||||
} catch (json::parse_error &e) {
|
||||
_server.close(hdl, WebSocketCloseCode::MessageDecodeError,
|
||||
std::string("Unable to decode MsgPack: ") + e.what(), errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -435,9 +430,11 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
|
||||
|
||||
// Disconnect client if 4.x protocol is detected
|
||||
if (!session->IsIdentified() && incomingMessage.contains("request-type")) {
|
||||
blog(LOG_WARNING, "[WebSocketServer::onMessage] Client %s appears to be running a pre-5.0.0 protocol.", session->RemoteAddress().c_str());
|
||||
blog(LOG_WARNING, "[WebSocketServer::onMessage] Client %s appears to be running a pre-5.0.0 protocol.",
|
||||
session->RemoteAddress().c_str());
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion;
|
||||
ret.closeReason = "You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated.";
|
||||
ret.closeReason =
|
||||
"You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated.";
|
||||
goto skipProcessing;
|
||||
}
|
||||
|
||||
@ -450,7 +447,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
|
||||
|
||||
ProcessMessage(session, ret, incomingMessage["op"], incomingMessage["d"]);
|
||||
|
||||
skipProcessing:
|
||||
skipProcessing:
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
websocketpp::lib::error_code errorCode;
|
||||
_server.close(hdl, ret.closeCode, ret.closeReason, errorCode);
|
||||
@ -472,7 +469,8 @@ skipProcessing:
|
||||
blog_debug("[WebSocketServer::onMessage] Outgoing message:\n%s", ret.result.dump(2).c_str());
|
||||
|
||||
if (errorCode)
|
||||
blog(LOG_WARNING, "[WebSocketServer::onMessage] Sending message to client failed: %s", errorCode.message().c_str());
|
||||
blog(LOG_WARNING, "[WebSocketServer::onMessage] Sending message to client failed: %s",
|
||||
errorCode.message().c_str());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -34,73 +34,66 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "../requesthandler/rpc/Request.h"
|
||||
#include "../plugin-macros.generated.h"
|
||||
|
||||
class WebSocketServer : QObject
|
||||
{
|
||||
class WebSocketServer : QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum WebSocketEncoding {
|
||||
Json,
|
||||
MsgPack
|
||||
};
|
||||
public:
|
||||
enum WebSocketEncoding { Json, MsgPack };
|
||||
|
||||
struct WebSocketSessionState {
|
||||
websocketpp::connection_hdl hdl;
|
||||
std::string remoteAddress;
|
||||
uint64_t connectedAt;
|
||||
uint64_t incomingMessages;
|
||||
uint64_t outgoingMessages;
|
||||
bool isIdentified;
|
||||
};
|
||||
struct WebSocketSessionState {
|
||||
websocketpp::connection_hdl hdl;
|
||||
std::string remoteAddress;
|
||||
uint64_t connectedAt;
|
||||
uint64_t incomingMessages;
|
||||
uint64_t outgoingMessages;
|
||||
bool isIdentified;
|
||||
};
|
||||
|
||||
WebSocketServer();
|
||||
~WebSocketServer();
|
||||
WebSocketServer();
|
||||
~WebSocketServer();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
void InvalidateSession(websocketpp::connection_hdl hdl);
|
||||
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr, uint8_t rpcVersion = 0);
|
||||
void Start();
|
||||
void Stop();
|
||||
void InvalidateSession(websocketpp::connection_hdl hdl);
|
||||
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr,
|
||||
uint8_t rpcVersion = 0);
|
||||
|
||||
bool IsListening() {
|
||||
return _server.is_listening();
|
||||
}
|
||||
bool IsListening() { return _server.is_listening(); }
|
||||
|
||||
std::vector<WebSocketSessionState> GetWebSocketSessions();
|
||||
std::vector<WebSocketSessionState> GetWebSocketSessions();
|
||||
|
||||
QThreadPool *GetThreadPool() {
|
||||
return &_threadPool;
|
||||
}
|
||||
QThreadPool *GetThreadPool() { return &_threadPool; }
|
||||
|
||||
signals:
|
||||
void ClientConnected(WebSocketSessionState state);
|
||||
void ClientDisconnected(WebSocketSessionState state, uint16_t closeCode);
|
||||
signals:
|
||||
void ClientConnected(WebSocketSessionState state);
|
||||
void ClientDisconnected(WebSocketSessionState state, uint16_t closeCode);
|
||||
|
||||
private:
|
||||
struct ProcessResult {
|
||||
WebSocketCloseCode::WebSocketCloseCode closeCode = WebSocketCloseCode::DontClose;
|
||||
std::string closeReason;
|
||||
json result;
|
||||
};
|
||||
private:
|
||||
struct ProcessResult {
|
||||
WebSocketCloseCode::WebSocketCloseCode closeCode = WebSocketCloseCode::DontClose;
|
||||
std::string closeReason;
|
||||
json result;
|
||||
};
|
||||
|
||||
void ServerRunner();
|
||||
void ServerRunner();
|
||||
|
||||
void onObsLoaded();
|
||||
bool onValidate(websocketpp::connection_hdl hdl);
|
||||
void onOpen(websocketpp::connection_hdl hdl);
|
||||
void onClose(websocketpp::connection_hdl hdl);
|
||||
void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message);
|
||||
void onObsLoaded();
|
||||
bool onValidate(websocketpp::connection_hdl hdl);
|
||||
void onOpen(websocketpp::connection_hdl hdl);
|
||||
void onClose(websocketpp::connection_hdl hdl);
|
||||
void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message);
|
||||
|
||||
static void SetSessionParameters(SessionPtr session, WebSocketServer::ProcessResult &ret, const json &payloadData);
|
||||
void ProcessMessage(SessionPtr session, ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData);
|
||||
static void SetSessionParameters(SessionPtr session, WebSocketServer::ProcessResult &ret, const json &payloadData);
|
||||
void ProcessMessage(SessionPtr session, ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData);
|
||||
|
||||
QThreadPool _threadPool;
|
||||
QThreadPool _threadPool;
|
||||
|
||||
std::thread _serverThread;
|
||||
websocketpp::server<websocketpp::config::asio> _server;
|
||||
std::thread _serverThread;
|
||||
websocketpp::server<websocketpp::config::asio> _server;
|
||||
|
||||
std::string _authenticationSecret;
|
||||
std::string _authenticationSalt;
|
||||
std::string _authenticationSecret;
|
||||
std::string _authenticationSalt;
|
||||
|
||||
std::mutex _sessionMutex;
|
||||
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions;
|
||||
std::mutex _sessionMutex;
|
||||
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions;
|
||||
};
|
||||
|
@ -44,10 +44,7 @@ static json ConstructRequestResult(RequestResult requestResult, const json &requ
|
||||
if (requestJson.contains("requestId") && !requestJson["requestId"].is_null())
|
||||
ret["requestId"] = requestJson["requestId"];
|
||||
|
||||
ret["requestStatus"] = {
|
||||
{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}
|
||||
};
|
||||
ret["requestStatus"] = {{"result", requestResult.StatusCode == RequestStatus::Success}, {"code", requestResult.StatusCode}};
|
||||
|
||||
if (!requestResult.Comment.empty())
|
||||
ret["requestStatus"]["comment"] = requestResult.Comment;
|
||||
@ -70,7 +67,8 @@ void WebSocketServer::SetSessionParameters(SessionPtr session, ProcessResult &re
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData)
|
||||
void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::ProcessResult &ret,
|
||||
WebSocketOpCode::WebSocketOpCode opCode, json &payloadData)
|
||||
{
|
||||
if (!payloadData.is_object()) {
|
||||
if (payloadData.is_null()) {
|
||||
@ -91,218 +89,253 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
|
||||
}
|
||||
|
||||
switch (opCode) {
|
||||
case WebSocketOpCode::Identify: { // Identify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
if (session->IsIdentified()) {
|
||||
ret.closeCode = WebSocketCloseCode::AlreadyIdentified;
|
||||
ret.closeReason = "You are already Identified with the obs-websocket server.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->AuthenticationRequired()) {
|
||||
if (!payloadData.contains("authentication")) {
|
||||
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Your payload's data is missing an `authentication` string, however authentication is required.";
|
||||
return;
|
||||
}
|
||||
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), payloadData["authentication"])) {
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body);
|
||||
}
|
||||
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Authentication failed.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!payloadData.contains("rpcVersion")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload's data is missing an `rpcVersion`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData["rpcVersion"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `rpcVersion` is not an unsigned number.";
|
||||
}
|
||||
|
||||
uint8_t requestedRpcVersion = payloadData["rpcVersion"];
|
||||
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion;
|
||||
ret.closeReason = "Your requested RPC version is not supported by this server.";
|
||||
return;
|
||||
}
|
||||
session->SetRpcVersion(requestedRpcVersion);
|
||||
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment refs for event subscriptions
|
||||
auto eventHandler = GetEventHandler();
|
||||
eventHandler->ProcessSubscription(session->EventSubscriptions());
|
||||
|
||||
// Mark session as identified
|
||||
session->SetIsIdentified(true);
|
||||
|
||||
// Send desktop notification. TODO: Move to UI code
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body")).arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
|
||||
}
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::Identified;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
} return;
|
||||
case WebSocketOpCode::Reidentify: { // Reidentify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
|
||||
// Decrement refs for current subscriptions
|
||||
auto eventHandler = GetEventHandler();
|
||||
eventHandler->ProcessUnsubscription(session->EventSubscriptions());
|
||||
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment refs for new subscriptions
|
||||
eventHandler->ProcessSubscription(session->EventSubscriptions());
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::Identified;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
} return;
|
||||
case WebSocketOpCode::Request: { // Request
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
return;
|
||||
}
|
||||
|
||||
RequestHandler requestHandler(session);
|
||||
Request request(payloadData["requestType"], payloadData["requestData"]);
|
||||
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
json resultPayloadData;
|
||||
resultPayloadData["requestType"] = payloadData["requestType"];
|
||||
resultPayloadData["requestId"] = payloadData["requestId"];
|
||||
resultPayloadData["requestStatus"] = {
|
||||
{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}
|
||||
};
|
||||
if (!requestResult.Comment.empty())
|
||||
resultPayloadData["requestStatus"]["comment"] = requestResult.Comment;
|
||||
if (requestResult.ResponseData.is_object())
|
||||
resultPayloadData["responseData"] = requestResult.ResponseData;
|
||||
ret.result["op"] = WebSocketOpCode::RequestResponse;
|
||||
ret.result["d"] = resultPayloadData;
|
||||
} return;
|
||||
case WebSocketOpCode::RequestBatch: { // RequestBatch
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData.contains("requests")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requests`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData["requests"].is_array()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `requests` is not an array.";
|
||||
return;
|
||||
}
|
||||
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::SerialRealtime;
|
||||
if (payloadData.contains("executionType") && !payloadData["executionType"].is_null()) {
|
||||
if (!payloadData["executionType"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `executionType` is not a number.";
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t requestedExecutionType = payloadData["executionType"];
|
||||
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue;
|
||||
ret.closeReason = "Your `executionType` has an invalid value.";
|
||||
return;
|
||||
}
|
||||
|
||||
// The thread pool must support 2 or more threads else parallel requests will deadlock.
|
||||
if (requestedExecutionType == RequestBatchExecutionType::Parallel && _threadPool.maxThreadCount() < 2) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
|
||||
ret.closeReason = "Parallel request batch processing is not available on this system due to limited core count.";
|
||||
return;
|
||||
}
|
||||
|
||||
executionType = (RequestBatchExecutionType::RequestBatchExecutionType)requestedExecutionType;
|
||||
}
|
||||
|
||||
if (payloadData.contains("variables") && !payloadData["variables"].is_null()) {
|
||||
if (!payloadData.is_object()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `variables` is not an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (executionType == RequestBatchExecutionType::Parallel) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
|
||||
ret.closeReason = "Variables are not supported in Parallel mode.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool haltOnFailure = false;
|
||||
if (payloadData.contains("haltOnFailure") && !payloadData["haltOnFailure"].is_null()) {
|
||||
if (!payloadData["haltOnFailure"].is_boolean()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `haltOnFailure` is not a boolean.";
|
||||
return;
|
||||
}
|
||||
|
||||
haltOnFailure = payloadData["haltOnFailure"];
|
||||
}
|
||||
|
||||
std::vector<json> requests = payloadData["requests"];
|
||||
|
||||
std::vector<RequestBatchRequest> requestsVector;
|
||||
for (auto &requestJson : requests)
|
||||
requestsVector.emplace_back(requestJson["requestType"], requestJson["requestData"], executionType, requestJson["inputVariables"], requestJson["outputVariables"]);
|
||||
|
||||
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(_threadPool, session, executionType, requestsVector, payloadData["variables"], haltOnFailure);
|
||||
|
||||
size_t i = 0;
|
||||
std::vector<json> results;
|
||||
for (auto &requestResult : resultsVector) {
|
||||
results.push_back(ConstructRequestResult(requestResult, requests[i]));
|
||||
i++;
|
||||
}
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::RequestBatchResponse;
|
||||
ret.result["d"]["requestId"] = payloadData["requestId"];
|
||||
ret.result["d"]["results"] = results;
|
||||
} return;
|
||||
default:
|
||||
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
|
||||
ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode);
|
||||
case WebSocketOpCode::Identify: { // Identify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
if (session->IsIdentified()) {
|
||||
ret.closeCode = WebSocketCloseCode::AlreadyIdentified;
|
||||
ret.closeReason = "You are already Identified with the obs-websocket server.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->AuthenticationRequired()) {
|
||||
if (!payloadData.contains("authentication")) {
|
||||
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason =
|
||||
"Your payload's data is missing an `authentication` string, however authentication is required.";
|
||||
return;
|
||||
}
|
||||
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(),
|
||||
payloadData["authentication"])) {
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
|
||||
QString body =
|
||||
QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body"))
|
||||
.arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body);
|
||||
}
|
||||
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
|
||||
ret.closeReason = "Authentication failed.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!payloadData.contains("rpcVersion")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload's data is missing an `rpcVersion`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData["rpcVersion"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `rpcVersion` is not an unsigned number.";
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t requestedRpcVersion = payloadData["rpcVersion"];
|
||||
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion;
|
||||
ret.closeReason = "Your requested RPC version is not supported by this server.";
|
||||
return;
|
||||
}
|
||||
session->SetRpcVersion(requestedRpcVersion);
|
||||
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment refs for event subscriptions
|
||||
auto eventHandler = GetEventHandler();
|
||||
eventHandler->ProcessSubscription(session->EventSubscriptions());
|
||||
|
||||
// Mark session as identified
|
||||
session->SetIsIdentified(true);
|
||||
|
||||
// Send desktop notification. TODO: Move to UI code
|
||||
auto conf = GetConfig();
|
||||
if (conf && conf->AlertsEnabled) {
|
||||
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title");
|
||||
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body"))
|
||||
.arg(QString::fromStdString(session->RemoteAddress()));
|
||||
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
|
||||
}
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::Identified;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
}
|
||||
return;
|
||||
case WebSocketOpCode::Reidentify: { // Reidentify
|
||||
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
|
||||
|
||||
// Decrement refs for current subscriptions
|
||||
auto eventHandler = GetEventHandler();
|
||||
eventHandler->ProcessUnsubscription(session->EventSubscriptions());
|
||||
|
||||
SetSessionParameters(session, ret, payloadData);
|
||||
if (ret.closeCode != WebSocketCloseCode::DontClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment refs for new subscriptions
|
||||
eventHandler->ProcessSubscription(session->EventSubscriptions());
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::Identified;
|
||||
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
|
||||
}
|
||||
return;
|
||||
case WebSocketOpCode::Request: { // Request
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData.contains("requestType")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload's data is missing an `requestType`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData["requestType"].is_string()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `requestType` is not a string.";
|
||||
return;
|
||||
}
|
||||
|
||||
RequestHandler requestHandler(session);
|
||||
|
||||
std::string requestType = payloadData["requestType"];
|
||||
json requestData = payloadData["requestData"];
|
||||
Request request(requestType, requestData);
|
||||
|
||||
RequestResult requestResult = requestHandler.ProcessRequest(request);
|
||||
|
||||
json resultPayloadData;
|
||||
resultPayloadData["requestType"] = requestType;
|
||||
resultPayloadData["requestId"] = payloadData["requestId"];
|
||||
resultPayloadData["requestStatus"] = {{"result", requestResult.StatusCode == RequestStatus::Success},
|
||||
{"code", requestResult.StatusCode}};
|
||||
if (!requestResult.Comment.empty())
|
||||
resultPayloadData["requestStatus"]["comment"] = requestResult.Comment;
|
||||
if (requestResult.ResponseData.is_object())
|
||||
resultPayloadData["responseData"] = requestResult.ResponseData;
|
||||
ret.result["op"] = WebSocketOpCode::RequestResponse;
|
||||
ret.result["d"] = resultPayloadData;
|
||||
}
|
||||
return;
|
||||
case WebSocketOpCode::RequestBatch: { // RequestBatch
|
||||
// RequestID checking has to be done here where we are able to close the connection.
|
||||
if (!payloadData.contains("requestId")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requestId`.";
|
||||
return;
|
||||
}
|
||||
|
||||
RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::SerialRealtime;
|
||||
if (payloadData.contains("executionType") && !payloadData["executionType"].is_null()) {
|
||||
if (!payloadData["executionType"].is_number_unsigned()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `executionType` is not a number.";
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t requestedExecutionType = payloadData["executionType"];
|
||||
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) ||
|
||||
requestedExecutionType == RequestBatchExecutionType::None) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue;
|
||||
ret.closeReason = "Your `executionType` has an invalid value.";
|
||||
return;
|
||||
}
|
||||
|
||||
// The thread pool must support 2 or more threads else parallel requests will deadlock.
|
||||
if (requestedExecutionType == RequestBatchExecutionType::Parallel && _threadPool.maxThreadCount() < 2) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
|
||||
ret.closeReason =
|
||||
"Parallel request batch processing is not available on this system due to limited core count.";
|
||||
return;
|
||||
}
|
||||
|
||||
executionType = (RequestBatchExecutionType::RequestBatchExecutionType)requestedExecutionType;
|
||||
}
|
||||
|
||||
if (payloadData.contains("variables") && !payloadData["variables"].is_null()) {
|
||||
if (!payloadData.is_object()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `variables` is not an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (executionType == RequestBatchExecutionType::Parallel) {
|
||||
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
|
||||
ret.closeReason = "Variables are not supported in Parallel mode.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool haltOnFailure = false;
|
||||
if (payloadData.contains("haltOnFailure") && !payloadData["haltOnFailure"].is_null()) {
|
||||
if (!payloadData["haltOnFailure"].is_boolean()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `haltOnFailure` is not a boolean.";
|
||||
return;
|
||||
}
|
||||
|
||||
haltOnFailure = payloadData["haltOnFailure"];
|
||||
}
|
||||
|
||||
if (!payloadData.contains("requests")) {
|
||||
ret.closeCode = WebSocketCloseCode::MissingDataField;
|
||||
ret.closeReason = "Your payload data is missing a `requests`.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloadData["requests"].is_array()) {
|
||||
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
|
||||
ret.closeReason = "Your `requests` is not an array.";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<json> requests = payloadData["requests"];
|
||||
|
||||
std::vector<RequestBatchRequest> requestsVector;
|
||||
for (auto &requestJson : requests) {
|
||||
if (!requestJson["requestType"].is_string())
|
||||
requestJson["requestType"] =
|
||||
""; // Workaround for what would otherwise be extensive additional logic for a rare edge case
|
||||
std::string requestType = requestJson["requestType"];
|
||||
json requestData = requestJson["requestData"];
|
||||
json inputVariables = requestJson["inputVariables"];
|
||||
json outputVariables = requestJson["outputVariables"];
|
||||
requestsVector.emplace_back(requestType, requestData, executionType, inputVariables, outputVariables);
|
||||
}
|
||||
|
||||
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(_threadPool, session, executionType, requestsVector,
|
||||
payloadData["variables"], haltOnFailure);
|
||||
|
||||
size_t i = 0;
|
||||
std::vector<json> results;
|
||||
for (auto &requestResult : resultsVector) {
|
||||
results.push_back(ConstructRequestResult(requestResult, requests[i]));
|
||||
i++;
|
||||
}
|
||||
|
||||
ret.result["op"] = WebSocketOpCode::RequestBatchResponse;
|
||||
ret.result["d"]["requestId"] = payloadData["requestId"];
|
||||
ret.result["d"]["results"] = results;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
|
||||
ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It isn't consistent to directly call the WebSocketServer from the events system, but it would also be dumb to make it unnecessarily complicated.
|
||||
void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData, uint8_t rpcVersion)
|
||||
void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData,
|
||||
uint8_t rpcVersion)
|
||||
{
|
||||
if (!_server.is_listening())
|
||||
return;
|
||||
@ -322,7 +355,7 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
|
||||
|
||||
// Recurse connected sessions and send the event to suitable sessions.
|
||||
std::unique_lock<std::mutex> lock(_sessionMutex);
|
||||
for (auto & it : _sessions) {
|
||||
for (auto &it : _sessions) {
|
||||
if (!it.second->IsIdentified()) {
|
||||
continue;
|
||||
}
|
||||
@ -332,24 +365,27 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
|
||||
if ((it.second->EventSubscriptions() & requiredIntent) != 0) {
|
||||
websocketpp::lib::error_code errorCode;
|
||||
switch (it.second->Encoding()) {
|
||||
case WebSocketEncoding::Json:
|
||||
if (messageJson.empty()) {
|
||||
messageJson = eventMessage.dump();
|
||||
}
|
||||
_server.send((websocketpp::connection_hdl)it.first, messageJson, websocketpp::frame::opcode::text, errorCode);
|
||||
it.second->IncrementOutgoingMessages();
|
||||
break;
|
||||
case WebSocketEncoding::MsgPack:
|
||||
if (messageMsgPack.empty()) {
|
||||
auto msgPackData = json::to_msgpack(eventMessage);
|
||||
messageMsgPack = std::string(msgPackData.begin(), msgPackData.end());
|
||||
}
|
||||
_server.send((websocketpp::connection_hdl)it.first, messageMsgPack, websocketpp::frame::opcode::binary, errorCode);
|
||||
it.second->IncrementOutgoingMessages();
|
||||
break;
|
||||
case WebSocketEncoding::Json:
|
||||
if (messageJson.empty()) {
|
||||
messageJson = eventMessage.dump();
|
||||
}
|
||||
_server.send((websocketpp::connection_hdl)it.first, messageJson,
|
||||
websocketpp::frame::opcode::text, errorCode);
|
||||
it.second->IncrementOutgoingMessages();
|
||||
break;
|
||||
case WebSocketEncoding::MsgPack:
|
||||
if (messageMsgPack.empty()) {
|
||||
auto msgPackData = json::to_msgpack(eventMessage);
|
||||
messageMsgPack = std::string(msgPackData.begin(), msgPackData.end());
|
||||
}
|
||||
_server.send((websocketpp::connection_hdl)it.first, messageMsgPack,
|
||||
websocketpp::frame::opcode::binary, errorCode);
|
||||
it.second->IncrementOutgoingMessages();
|
||||
break;
|
||||
}
|
||||
if (errorCode)
|
||||
blog(LOG_ERROR, "[WebSocketServer::BroadcastEvent] Error sending event message: %s", errorCode.message().c_str());
|
||||
blog(LOG_ERROR, "[WebSocketServer::BroadcastEvent] Error sending event message: %s",
|
||||
errorCode.message().c_str());
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
|
@ -20,16 +20,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "WebSocketSession.h"
|
||||
#include "../../eventhandler/types/EventSubscription.h"
|
||||
|
||||
WebSocketSession::WebSocketSession() :
|
||||
_remoteAddress(""),
|
||||
_connectedAt(0),
|
||||
_incomingMessages(0),
|
||||
_outgoingMessages(0),
|
||||
_encoding(0),
|
||||
_challenge(""),
|
||||
_rpcVersion(OBS_WEBSOCKET_RPC_VERSION),
|
||||
_isIdentified(false),
|
||||
_eventSubscriptions(EventSubscription::All)
|
||||
WebSocketSession::WebSocketSession()
|
||||
: _remoteAddress(""),
|
||||
_connectedAt(0),
|
||||
_incomingMessages(0),
|
||||
_outgoingMessages(0),
|
||||
_encoding(0),
|
||||
_challenge(""),
|
||||
_rpcVersion(OBS_WEBSOCKET_RPC_VERSION),
|
||||
_isIdentified(false),
|
||||
_eventSubscriptions(EventSubscription::All)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -29,59 +29,58 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
class WebSocketSession;
|
||||
typedef std::shared_ptr<WebSocketSession> SessionPtr;
|
||||
|
||||
class WebSocketSession
|
||||
{
|
||||
public:
|
||||
WebSocketSession();
|
||||
class WebSocketSession {
|
||||
public:
|
||||
WebSocketSession();
|
||||
|
||||
std::string RemoteAddress();
|
||||
void SetRemoteAddress(std::string address);
|
||||
std::string RemoteAddress();
|
||||
void SetRemoteAddress(std::string address);
|
||||
|
||||
uint64_t ConnectedAt();
|
||||
void SetConnectedAt(uint64_t at);
|
||||
uint64_t ConnectedAt();
|
||||
void SetConnectedAt(uint64_t at);
|
||||
|
||||
uint64_t IncomingMessages();
|
||||
void IncrementIncomingMessages();
|
||||
uint64_t IncomingMessages();
|
||||
void IncrementIncomingMessages();
|
||||
|
||||
uint64_t OutgoingMessages();
|
||||
void IncrementOutgoingMessages();
|
||||
uint64_t OutgoingMessages();
|
||||
void IncrementOutgoingMessages();
|
||||
|
||||
uint8_t Encoding();
|
||||
void SetEncoding(uint8_t encoding);
|
||||
uint8_t Encoding();
|
||||
void SetEncoding(uint8_t encoding);
|
||||
|
||||
bool AuthenticationRequired();
|
||||
void SetAuthenticationRequired(bool required);
|
||||
bool AuthenticationRequired();
|
||||
void SetAuthenticationRequired(bool required);
|
||||
|
||||
std::string Secret();
|
||||
void SetSecret(std::string secret);
|
||||
std::string Secret();
|
||||
void SetSecret(std::string secret);
|
||||
|
||||
std::string Challenge();
|
||||
void SetChallenge(std::string challenge);
|
||||
std::string Challenge();
|
||||
void SetChallenge(std::string challenge);
|
||||
|
||||
uint8_t RpcVersion();
|
||||
void SetRpcVersion(uint8_t version);
|
||||
uint8_t RpcVersion();
|
||||
void SetRpcVersion(uint8_t version);
|
||||
|
||||
bool IsIdentified();
|
||||
void SetIsIdentified(bool identified);
|
||||
bool IsIdentified();
|
||||
void SetIsIdentified(bool identified);
|
||||
|
||||
uint64_t EventSubscriptions();
|
||||
void SetEventSubscriptions(uint64_t subscriptions);
|
||||
uint64_t EventSubscriptions();
|
||||
void SetEventSubscriptions(uint64_t subscriptions);
|
||||
|
||||
std::mutex OperationMutex;
|
||||
std::mutex OperationMutex;
|
||||
|
||||
private:
|
||||
std::mutex _remoteAddressMutex;
|
||||
std::string _remoteAddress;
|
||||
std::atomic<uint64_t> _connectedAt;
|
||||
std::atomic<uint64_t> _incomingMessages;
|
||||
std::atomic<uint64_t> _outgoingMessages;
|
||||
std::atomic<uint8_t> _encoding;
|
||||
std::atomic<bool> _authenticationRequired;
|
||||
std::mutex _secretMutex;
|
||||
std::string _secret;
|
||||
std::mutex _challengeMutex;
|
||||
std::string _challenge;
|
||||
std::atomic<uint8_t> _rpcVersion;
|
||||
std::atomic<bool> _isIdentified;
|
||||
std::atomic<uint64_t> _eventSubscriptions;
|
||||
private:
|
||||
std::mutex _remoteAddressMutex;
|
||||
std::string _remoteAddress;
|
||||
std::atomic<uint64_t> _connectedAt;
|
||||
std::atomic<uint64_t> _incomingMessages;
|
||||
std::atomic<uint64_t> _outgoingMessages;
|
||||
std::atomic<uint8_t> _encoding;
|
||||
std::atomic<bool> _authenticationRequired;
|
||||
std::mutex _secretMutex;
|
||||
std::string _secret;
|
||||
std::mutex _challengeMutex;
|
||||
std::string _challenge;
|
||||
std::atomic<uint8_t> _rpcVersion;
|
||||
std::atomic<bool> _isIdentified;
|
||||
std::atomic<uint64_t> _eventSubscriptions;
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#pragma once
|
||||
|
||||
namespace WebSocketOpCode {
|
||||
enum WebSocketOpCode: uint8_t {
|
||||
enum WebSocketOpCode : uint8_t {
|
||||
/**
|
||||
* The initial message sent by obs-websocket to newly connected clients.
|
||||
*
|
||||
@ -122,8 +122,5 @@ namespace WebSocketOpCode {
|
||||
RequestBatchResponse = 9,
|
||||
};
|
||||
|
||||
inline bool IsValid(uint8_t opCode)
|
||||
{
|
||||
return opCode >= Hello && opCode <= RequestBatchResponse;
|
||||
}
|
||||
inline bool IsValid(uint8_t opCode) { return opCode >= Hello && opCode <= RequestBatchResponse; }
|
||||
}
|
||||
|
Reference in New Issue
Block a user