Compare commits

...

29 Commits
5.4.0 ... 5.0.1

Author SHA1 Message Date
d465dd3110 CI: Disable VST on Windows
Since master doesn't have the submodule, switching from master to
27.2.4 doesn't download the submodule. We can just disable it
2022-08-02 14:52:49 -07:00
a89f47e720 CI: Force OBS version to 27.2.4
The recent tag messed up CI
2022-08-02 14:43:43 -07:00
952a1f914e base: Update version to 5.0.1 2022-08-02 14:35:13 -07:00
816c1b278b requesthandler: Allow empty object in Set*PrivateSettings requests
Requested by #958

Closes #958
2022-08-02 14:33:35 -07:00
2daba572e8 eventhandler: Provide outputPath on OUTPUT_STARTED too
4.x apparently included this path during output start along with stop.
It appears safe to include this on output start.

Closes #963
2022-08-02 14:33:25 -07:00
02ccd0b76f websocketserver: Validate op field type
Could cause a crash by assuming `op` is always a number.

Closes #965
2022-08-02 14:33:15 -07:00
f95865c2c4 base: Fix MSVC warning about misdeclared Config struct 2022-08-02 14:33:06 -07:00
4dc5229c49 requesthandler: Fix OpenVideoMixProjector program option
It was stuck opening preview regardless of setting.
2022-08-02 14:32:58 -07:00
a25427c7cc base: Final changes before release 2022-07-02 11:08:18 -07:00
99f93c4bdd websocketserver: Log disconnect code and reason on disconnects
No clue why I wasn't doing this before.
2022-07-02 10:53:46 -07:00
6af3af61d5 requesthandler: Add Outputs requests
Co-authored-by: Ruggero Tomaselli <ruggerotomaselli@gmail.com>
Co-authored-by: tt2468 <tt2468@irltoolkit.com>
2022-07-02 10:31:47 -07:00
14a1547e11 docs: Fix lint formatting error 2022-07-02 08:30:02 -07:00
60ade46481 requesthandler: Fix oopsies in OpenVideoMixProjector 2022-07-02 08:27:32 -07:00
7c21e5732e requesthandler: Add projector creation requests
I didn't think I'd be able to make remotely usable requests using OBS'
existing projector API, but I'm actually pretty happy with how it
turned out.

Closes #929

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
2022-07-02 08:23:03 -07:00
f5db53f217 requesthandler: Echo request details in response of CallVendorRequest
I generally don't like to echo data provided to obs-websocket in
requests, but since we do that for the request type in base requests,
this particular case seems fair.

Closes #919
2022-07-02 07:11:44 -07:00
3400f88665 CI: Exclude qr from code format too 2022-07-02 07:03:46 -07:00
ad3cb5dcfd requesthandler, eventhandler: Add outputPath fields when record stops
Closes #934
2022-07-02 07:00:51 -07:00
5ecda806bd requesthandler: Rework and fix a few data consistency checks
Some stuff led to possible crashes, other stuff simply didn't work.
Should be much better now.

Closes #942
2022-07-02 05:57:35 -07:00
af3f29169c utils: Clean up a few iteration functions 2022-07-02 05:57:25 -07:00
af97978841 utils: Move from explicit enum converters to nlohmann macros 2022-07-02 05:57:08 -07:00
4f89378c45 utils: A few casting nitpicks 2022-07-02 05:50:37 -07:00
4fc8a3aecc CI: Fix deps formatting excludes 2022-07-02 05:49:57 -07:00
1626ae5546 Revert "Config, websocketserver: Add feature to bind to loopback (default)"
This reverts commit 1da0214201.
2022-07-02 05:49:02 -07:00
8b7fd3dd46 Revert "forms: Add configuration to enable external access"
This reverts commit 1cd12c1023.
2022-07-02 05:44:08 -07:00
73848a7370 docs: Fix EventSubscription::All subscribes 2022-07-02 05:43:57 -07:00
2e48dd24c4 base: Format code 2022-07-02 05:42:20 -07:00
ad1f28480c base: Update a few files with updated info 2022-07-02 05:36:52 -07:00
91cabe1202 websocketapi: Fix build on Windows 64 bit 2022-05-13 19:16:18 -07:00
f869f3df76 CI: Update to new branch name for release 2022-05-13 18:49:17 -07:00
84 changed files with 3011 additions and 2389 deletions

107
.clang-format Normal file
View 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

View File

@ -27,10 +27,11 @@ body:
- macOS 10.15 - macOS 10.15
- macOS 10.14 - macOS 10.14
- macOS 10.13 - macOS 10.13
- Ubuntu 22.04 LTS
- Ubuntu 21.04 - Ubuntu 21.04
- Ubuntu 20.10 - Ubuntu 20.10
- Ubuntu 20.04 - Ubuntu 20.04 LTS
- Ubuntu 18.04 - Ubuntu 18.04 LTS
- Other - Other
validations: validations:
required: true required: true
@ -48,6 +49,12 @@ body:
label: OBS Studio Version label: OBS Studio Version
description: What version of OBS Studio are you using? description: What version of OBS Studio are you using?
options: options:
- 28.0.0
- 27.2.4
- 27.2.3
- 27.2.2
- 27.2.1
- 27.2.0
- 27.1.3 - 27.1.3
- 27.1.1 - 27.1.1
- 27.1.0 - 27.1.0
@ -69,11 +76,9 @@ body:
label: obs-websocket Version label: obs-websocket Version
description: What version of obs-websocket are you using? description: What version of obs-websocket are you using?
options: options:
- 5.0.1
- 5.0.0
- 5.0.0-beta1 - 5.0.0-beta1
- 5.0.0-alpha3
- 5.0.0-alpha2
- 4.9.1
- 4.9.0
- Git - Git
validations: validations:
required: true required: true

View File

@ -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! --> <!--- 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). - [ ] 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. - [ ] 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. - [ ] The code has been tested.
- [ ] I have included updates to all appropriate documentation. - [ ] I have included updates to all appropriate documentation.

View File

@ -1,27 +1,27 @@
name: 'Generate docs' name: 'Generate docs'
on: on:
push: push:
paths-ignore: paths-ignore:
- 'docs/generated/**' - 'docs/generated/**'
branches: branches:
- master - release/5.0.0
jobs: jobs:
docs-build: docs-build:
name: 'Generate docs [Ubuntu]' name: 'Generate docs [Ubuntu]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/master' }} if: ${{ github.ref == 'refs/heads/master' }}
env: env:
CHECKOUT_REF: ${{ github.ref }} CHECKOUT_REF: ${{ github.ref }}
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
IS_CI: "true" IS_CI: "true"
steps: steps:
- name: 'Checkout' - name: 'Checkout'
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: ${{ github.workspace }}/obs-websocket path: ${{ github.workspace }}/obs-websocket
- name: 'Generate docs' - name: 'Generate docs'
working-directory: ${{ github.workspace }}/obs-websocket working-directory: ${{ github.workspace }}/obs-websocket
run: | run: |
./CI/generate-docs.sh ./CI/generate-docs.sh

View File

@ -3,10 +3,10 @@ name: Code Quality
on: on:
push: push:
branches: branches:
- master - release/5.0.0
pull_request: pull_request:
branches: branches:
- master - release/5.0.0
jobs: jobs:
markdown: markdown:

View File

@ -5,7 +5,7 @@ on:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
branches: branches:
- master - release/5.0.0
tags: tags:
- '[45].[0-9]+.[0-9]+*' - '[45].[0-9]+.[0-9]+*'
pull_request: pull_request:
@ -13,7 +13,7 @@ on:
- 'docs/**' - 'docs/**'
- '**.md' - '**.md'
branches: branches:
- master - release/5.0.0
jobs: jobs:
windows: windows:
@ -47,7 +47,8 @@ jobs:
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV echo "OBS_GIT_TAG=27.2.4" >> $GITHUB_ENV
# echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
@ -110,7 +111,7 @@ jobs:
run: | run: |
if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32} if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32}
cd .\build32 cd .\build32
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF -DBUILD_VST=OFF ..
- name: 'Build OBS Studio 32-bit' - name: 'Build OBS Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true' if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
@ -131,7 +132,7 @@ jobs:
run: | run: |
if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64} if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64}
cd .\build64 cd .\build64
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF -DBUILD_VST=OFF ..
- name: 'Build OBS Studio 64-bit' - name: 'Build OBS Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true' if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
@ -201,7 +202,8 @@ jobs:
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV echo "OBS_GIT_TAG=27.2.4" >> $GITHUB_ENV
# echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
@ -334,9 +336,9 @@ jobs:
cd ./build cd ./build
sudo checkinstall -y --type=debian --fstrans=no -nodoc \ sudo checkinstall -y --type=debian --fstrans=no -nodoc \
--backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion="$CHECKINSTALL_VERSION" \ --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 }}" \ --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" --pakdir="../package"
sudo chmod ao+r ../package/* sudo chmod ao+r ../package/*
sudo mv ../package/* ../package/${{ env.LINUX_FILENAME }} sudo mv ../package/* ../package/${{ env.LINUX_FILENAME }}
@ -388,7 +390,8 @@ jobs:
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV echo "OBS_GIT_TAG=27.2.4" >> $GITHUB_ENV
# echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio

57
CI/check-format.sh Executable file
View 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

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16...3.20) cmake_minimum_required(VERSION 3.16...3.20)
# Version variables # Version variables
project(obs-websocket VERSION 5.0.0) project(obs-websocket VERSION 5.0.1)
set(OBS_WEBSOCKET_RPC_VERSION 1) set(OBS_WEBSOCKET_RPC_VERSION 1)
@ -85,7 +85,7 @@ configure_file(
) )
# Inlude sources # Include sources
set(obs-websocket_SOURCES set(obs-websocket_SOURCES
src/obs-websocket.cpp src/obs-websocket.cpp
src/Config.cpp src/Config.cpp
@ -129,7 +129,6 @@ set(obs-websocket_SOURCES
src/utils/Json.cpp src/utils/Json.cpp
src/utils/Obs.cpp src/utils/Obs.cpp
src/utils/Obs_StringHelper.cpp src/utils/Obs_StringHelper.cpp
src/utils/Obs_EnumHelper.cpp
src/utils/Obs_NumberHelper.cpp src/utils/Obs_NumberHelper.cpp
src/utils/Obs_ArrayHelper.cpp src/utils/Obs_ArrayHelper.cpp
src/utils/Obs_ObjectHelper.cpp src/utils/Obs_ObjectHelper.cpp

View File

@ -8,7 +8,6 @@
WebSocket API for OBS Studio. WebSocket API for OBS Studio.
[![CI Multiplatform Build](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml)
[![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A) [![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A)
[![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket-dev/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket-dev) [![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket-dev/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket-dev)

View File

@ -9,14 +9,11 @@ OBSWebSocket.Settings.DebugEnable="Enable Debug Logging"
OBSWebSocket.Settings.DebugEnableHoverText="Enables debug logging for the current instance of OBS. Does not persist on load.\nUse --websocket_debug to enable on load." OBSWebSocket.Settings.DebugEnableHoverText="Enables debug logging for the current instance of OBS. Does not persist on load.\nUse --websocket_debug to enable on load."
OBSWebSocket.Settings.ServerSettingsTitle="Server Settings" OBSWebSocket.Settings.ServerSettingsTitle="Server Settings"
OBSWebSocket.Settings.ServerPort="Server Port"
OBSWebSocket.Settings.AllowExternal="Allow External Access"
OBSWebSocket.Settings.AllowExternalHoverText="Allows clients from outside this computer to connect to obs-websocket."
OBSWebSocket.Settings.AuthRequired="Enable Authentication" OBSWebSocket.Settings.AuthRequired="Enable Authentication"
OBSWebSocket.Settings.Password="Server Password" OBSWebSocket.Settings.Password="Server Password"
OBSWebSocket.Settings.GeneratePassword="Generate Password" OBSWebSocket.Settings.GeneratePassword="Generate Password"
OBSWebSocket.Settings.ServerPort="Server Port"
OBSWebSocket.Settings.ShowConnectInfo="Show Connect Info" OBSWebSocket.Settings.ShowConnectInfo="Show Connect Info"
OBSWebSocket.Settings.ShowConnectInfoHoverText="Connect Info is not available if external connections are disabled."
OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Warning: Currently Live" OBSWebSocket.Settings.ShowConnectInfoWarningTitle="Warning: Currently Live"
OBSWebSocket.Settings.ShowConnectInfoWarningMessage="It appears that an output (stream, recording, etc.) is currently active." OBSWebSocket.Settings.ShowConnectInfoWarningMessage="It appears that an output (stream, recording, etc.) is currently active."
OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Are you sure that you want to show your connect info?" OBSWebSocket.Settings.ShowConnectInfoWarningInfoText="Are you sure that you want to show your connect info?"

View File

@ -1,5 +1,5 @@
# obs-websocket 5.0.0 Protocol # obs-websocket 5.0.1 Protocol
## Main Table of Contents ## Main Table of Contents
@ -143,7 +143,7 @@ Authentication is required
{ {
"op": 0, "op": 0,
"d": { "d": {
"obsWebSocketVersion": "5.0.0", "obsWebSocketVersion": "5.0.1",
"rpcVersion": 1, "rpcVersion": 1,
"authentication": { "authentication": {
"challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=", "challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=",
@ -159,7 +159,7 @@ Authentication is not required
{ {
"op": 0, "op": 0,
"d": { "d": {
"obsWebSocketVersion": "5.0.0", "obsWebSocketVersion": "5.0.1",
"rpcVersion": 1 "rpcVersion": 1
} }
} }

View File

@ -1,74 +1,74 @@
#define MyAppName "obs-websocket" #define MyAppName "obs-websocket"
#define MyAppVersion "@OBS_WEBSOCKET_VERSION@" #define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
#define MyAppPublisher "obs-websocket" #define MyAppPublisher "OBS Project"
#define MyAppURL "http://github.com/obsproject/obs-websocket" #define MyAppURL "http://github.com/obsproject/obs-websocket"
[Setup] [Setup]
; NOTE: The value of AppId uniquely identifies this application. ; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications. ; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35} AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
AppName={#MyAppName} AppName={#MyAppName}
AppVersion={#MyAppVersion} AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher} AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL} AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL} AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL} AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName} DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
DirExistsWarning=no DirExistsWarning=no
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
[Files] [Files]
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\LICENSE"; Flags: dontcopy Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
[Code] [Code]
procedure InitializeWizard(); procedure InitializeWizard();
var var
GPLText: AnsiString; GPLText: AnsiString;
Page: TOutputMsgMemoWizardPage; Page: TOutputMsgMemoWizardPage;
begin begin
ExtractTemporaryFile('LICENSE'); ExtractTemporaryFile('LICENSE');
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText); LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
Page := CreateOutputMsgMemoPage(wpWelcome, Page := CreateOutputMsgMemoPage(wpWelcome,
'License Information', 'Please review the license terms before installing obs-websocket', '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.', 'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
String(GPLText) String(GPLText)
); );
end; end;
// Validate that obs-studio is installed before installing the plugin // Validate that obs-studio is installed before installing the plugin
function PrepareToInstall(var NeedsRestart: Boolean): String; function PrepareToInstall(var NeedsRestart: Boolean): String;
begin begin
Result := ''; Result := '';
if not DirExists(ExpandConstant('{app}\obs-plugins')) then if not DirExists(ExpandConstant('{app}\obs-plugins')) then
begin begin
Result := 'The selected install directory does not appear to be valid. Please install OBS Studio before installing {#MyAppName} or correct your install path.'; 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;
end; end;
// credit where it's due : // 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 // 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; function GetDirName(Value: string): string;
var var
InstallPath: string; InstallPath: string;
begin begin
// initialize default path, which will be returned when the following registry // initialize default path, which will be returned when the following registry
// key queries fail due to missing keys or for some different reason // key queries fail due to missing keys or for some different reason
Result := '{commonpf}\obs-studio'; Result := '{commonpf}\obs-studio';
// query the first registry value; if this succeeds, return the obtained value // query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath Result := InstallPath
end; end;

View File

@ -1,78 +1,79 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
// Similar example code can be found in ../../src/obs-websocket.cpp // Similar example code can be found in ../../src/obs-websocket.cpp
// You can test that sample code by specifying -DPLUGIN_TESTS=TRUE // You can test that sample code by specifying -DPLUGIN_TESTS=TRUE
#include <obs-module.h> #include <obs-module.h>
#include "../obs-websocket-api.h" #include "../obs-websocket-api.h"
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")
obs_websocket_vendor vendor; obs_websocket_vendor vendor;
bool obs_module_load(void) bool obs_module_load(void)
{ {
blog(LOG_INFO, "Example obs-websocket-api plugin loaded!"); blog(LOG_INFO, "Example obs-websocket-api plugin loaded!");
return true; return true;
} }
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data); void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data);
void obs_module_post_load(void) void obs_module_post_load(void)
{ {
vendor = obs_websocket_register_vendor("api_example_plugin"); vendor = obs_websocket_register_vendor("api_example_plugin");
if (!vendor) { if (!vendor) {
blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)"); blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
return; return;
} }
if (!obs_websocket_vendor_register_request(vendor, "example_request", example_request_cb, NULL)) 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."); blog(LOG_ERROR, "Failed to register `example_request` request with obs-websocket.");
uint api_version = obs_websocket_get_api_version(); uint api_version = obs_websocket_get_api_version();
if (api_version == 0) { if (api_version == 0) {
blog(LOG_ERROR, "Unable to fetch obs-websocket plugin API version."); blog(LOG_ERROR, "Unable to fetch obs-websocket plugin API version.");
return; return;
} else if (api_version == 1) { } else if (api_version == 1) {
blog(LOG_WARNING, "Unsupported obs-websocket plugin API version for calling requests."); blog(LOG_WARNING, "Unsupported obs-websocket plugin API version for calling requests.");
return; return;
} }
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion"); struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
if (!response) { if (!response) {
blog(LOG_ERROR, "Failed to call GetVersion due to obs-websocket not being installed."); blog(LOG_ERROR, "Failed to call GetVersion due to obs-websocket not being installed.");
return; 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); blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
obs_websocket_request_response_free(response); response->status_code, response->comment, response->response_data);
} obs_websocket_request_response_free(response);
}
void obs_module_unload(void)
{ void obs_module_unload(void)
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!"); {
} 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)
{ 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); if (obs_data_has_user_value(request_data, "ping"))
obs_data_set_bool(response_data, "pong", true);
UNUSED_PARAMETER(priv_data);
} UNUSED_PARAMETER(priv_data);
}

View File

@ -1,215 +1,216 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef _OBS_WEBSOCKET_API_H #ifndef _OBS_WEBSOCKET_API_H
#define _OBS_WEBSOCKET_API_H #define _OBS_WEBSOCKET_API_H
#include <obs.h> #include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 2 #define OBS_WEBSOCKET_API_VERSION 2
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
typedef void* obs_websocket_vendor; typedef void *obs_websocket_vendor;
typedef void (*obs_websocket_request_callback_function)(obs_data_t*, obs_data_t*, void*); typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
struct obs_websocket_request_response { struct obs_websocket_request_response {
unsigned int status_code; unsigned int status_code;
char *comment; char *comment;
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API. char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
}; };
/* ==================== INTERNAL DEFINITIONS ==================== */ /* ==================== INTERNAL DEFINITIONS ==================== */
struct obs_websocket_request_callback { struct obs_websocket_request_callback {
obs_websocket_request_callback_function callback; obs_websocket_request_callback_function callback;
void *priv_data; void *priv_data;
}; };
inline proc_handler_t *_ph; inline proc_handler_t *_ph;
/* ==================== INTERNAL API FUNCTIONS ==================== */ /* ==================== INTERNAL API FUNCTIONS ==================== */
static inline proc_handler_t *obs_websocket_get_ph(void) static inline proc_handler_t *obs_websocket_get_ph(void)
{ {
proc_handler_t *global_ph = obs_get_proc_handler(); proc_handler_t *global_ph = obs_get_proc_handler();
assert(global_ph != NULL); assert(global_ph != NULL);
calldata_t cd = {0}; calldata_t cd = {0};
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd)) 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?"); 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"); proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
calldata_free(&cd); calldata_free(&cd);
return ret; return ret;
} }
static inline bool obs_websocket_ensure_ph(void) static inline bool obs_websocket_ensure_ph(void)
{ {
if (!_ph) if (!_ph)
_ph = obs_websocket_get_ph(); _ph = obs_websocket_get_ph();
return _ph != NULL; return _ph != NULL;
} }
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd) 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()) if (!obs_websocket_ensure_ph())
return false; return false;
if (!vendor || !proc_name || !strlen(proc_name) || !cd) if (!vendor || !proc_name || !strlen(proc_name) || !cd)
return false; return false;
calldata_set_ptr(cd, "vendor", vendor); calldata_set_ptr(cd, "vendor", vendor);
proc_handler_call(_ph, proc_name, cd); proc_handler_call(_ph, proc_name, cd);
return calldata_bool(cd, "success"); return calldata_bool(cd, "success");
} }
/* ==================== GENERAL API FUNCTIONS ==================== */ /* ==================== GENERAL API FUNCTIONS ==================== */
// Gets the API version built with the obs-websocket plugin // Gets the API version built with the obs-websocket plugin
static inline unsigned int obs_websocket_get_api_version(void) static inline unsigned int obs_websocket_get_api_version(void)
{ {
if (!obs_websocket_ensure_ph()) if (!obs_websocket_ensure_ph())
return 0; return 0;
calldata_t cd = {0}; calldata_t cd = {0};
if (!proc_handler_call(_ph, "get_api_version", &cd)) if (!proc_handler_call(_ph, "get_api_version", &cd))
return 1; // API v1 does not include get_api_version return 1; // API v1 does not include get_api_version
unsigned int ret = calldata_int(&cd, "version"); unsigned int ret = calldata_int(&cd, "version");
calldata_free(&cd); calldata_free(&cd);
return ret; return ret;
} }
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()` // 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) 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()) if (!obs_websocket_ensure_ph())
return NULL; return NULL;
const char *request_data_string = NULL; const char *request_data_string = NULL;
if (request_data) if (request_data)
request_data_string = obs_data_get_json(request_data); request_data_string = obs_data_get_json(request_data);
calldata_t cd = {0}; calldata_t cd = {0};
calldata_set_string(&cd, "request_type", request_type); calldata_set_string(&cd, "request_type", request_type);
calldata_set_string(&cd, "request_data", request_data_string); calldata_set_string(&cd, "request_data", request_data_string);
proc_handler_call(_ph, "call_request", &cd); proc_handler_call(_ph, "call_request", &cd);
auto ret = (struct obs_websocket_request_response*)calldata_ptr(&cd, "response"); auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
calldata_free(&cd); calldata_free(&cd);
return ret; return ret;
} }
// Free a request response object returned by `obs_websocket_call_request()` // 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) static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
{ {
if (!response) if (!response)
return; return;
if (response->comment) if (response->comment)
bfree(response->comment); bfree(response->comment);
if (response->response_data) if (response->response_data)
bfree(response->response_data); bfree(response->response_data);
bfree(response); bfree(response);
} }
/* ==================== VENDOR API FUNCTIONS ==================== */ /* ==================== VENDOR API FUNCTIONS ==================== */
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK! // ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
// Registers a new "vendor" (Example: obs-ndi) // Registers a new "vendor" (Example: obs-ndi)
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name) static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
{ {
if (!obs_websocket_ensure_ph()) if (!obs_websocket_ensure_ph())
return NULL; return NULL;
calldata_t cd = {0}; calldata_t cd = {0};
calldata_set_string(&cd, "name", vendor_name); calldata_set_string(&cd, "name", vendor_name);
proc_handler_call(_ph, "vendor_register", &cd); proc_handler_call(_ph, "vendor_register", &cd);
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor"); obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
calldata_free(&cd); calldata_free(&cd);
return ret; return ret;
} }
// Registers a new request for a vendor // 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) 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}; {
calldata_t cd = {0};
struct obs_websocket_request_callback cb = {};
cb.callback = request_callback; struct obs_websocket_request_callback cb = {};
cb.priv_data = priv_data; cb.callback = request_callback;
cb.priv_data = priv_data;
calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb); 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); bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
calldata_free(&cd);
return success;
} return success;
}
// Unregisters an existing vendor request
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type) // 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_t cd = {0};
calldata_set_string(&cd, "type", request_type);
calldata_set_string(&cd, "type", request_type);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
calldata_free(&cd); bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
calldata_free(&cd);
return success;
} return success;
}
// Does not affect event_data refcount.
// Emits an event under the vendor's name // Does not affect event_data refcount.
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data) // 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_t cd = {0};
calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void*)event_data); 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); bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
calldata_free(&cd);
return success;
} return success;
}
/* ==================== END API FUNCTIONS ==================== */
/* ==================== END API FUNCTIONS ==================== */
#ifdef __cplusplus
} #ifdef __cplusplus
#endif }
#endif
#endif
#endif

View File

@ -28,7 +28,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define PARAM_FIRSTLOAD "FirstLoad" #define PARAM_FIRSTLOAD "FirstLoad"
#define PARAM_ENABLED "ServerEnabled" #define PARAM_ENABLED "ServerEnabled"
#define PARAM_PORT "ServerPort" #define PARAM_PORT "ServerPort"
#define PARAM_BINDLOOPBACK "BindLoopback"
#define PARAM_ALERTS "AlertsEnabled" #define PARAM_ALERTS "AlertsEnabled"
#define PARAM_AUTHREQUIRED "AuthRequired" #define PARAM_AUTHREQUIRED "AuthRequired"
#define PARAM_PASSWORD "ServerPassword" #define PARAM_PASSWORD "ServerPassword"
@ -38,25 +37,24 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define CMDLINE_WEBSOCKET_PASSWORD "websocket_password" #define CMDLINE_WEBSOCKET_PASSWORD "websocket_password"
#define CMDLINE_WEBSOCKET_DEBUG "websocket_debug" #define CMDLINE_WEBSOCKET_DEBUG "websocket_debug"
Config::Config() : Config::Config()
PortOverridden(false), : PortOverridden(false),
PasswordOverridden(false), PasswordOverridden(false),
FirstLoad(true), FirstLoad(true),
ServerEnabled(true), ServerEnabled(true),
ServerPort(4455), ServerPort(4455),
BindLoopback(true), Ipv4Only(false),
Ipv4Only(false), DebugEnabled(false),
DebugEnabled(false), AlertsEnabled(false),
AlertsEnabled(false), AuthRequired(true),
AuthRequired(true), ServerPassword("")
ServerPassword("")
{ {
SetDefaultsToGlobalStore(); SetDefaultsToGlobalStore();
} }
void Config::Load() void Config::Load()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!"); blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!");
return; return;
@ -66,7 +64,6 @@ void Config::Load()
ServerEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED); ServerEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED);
AlertsEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS); AlertsEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS);
ServerPort = config_get_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT); ServerPort = config_get_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT);
BindLoopback = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK);
AuthRequired = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED); AuthRequired = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED);
ServerPassword = config_get_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD); ServerPassword = config_get_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD);
@ -123,7 +120,7 @@ void Config::Load()
void Config::Save() void Config::Save()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!"); blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!");
return; return;
@ -134,7 +131,6 @@ void Config::Save()
if (!PortOverridden) { if (!PortOverridden) {
config_set_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort); config_set_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort);
} }
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK, BindLoopback);
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled); config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled);
if (!PasswordOverridden) { if (!PasswordOverridden) {
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
@ -146,7 +142,7 @@ void Config::Save()
void Config::SetDefaultsToGlobalStore() void Config::SetDefaultsToGlobalStore()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!"); blog(LOG_ERROR, "[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!");
return; return;
@ -155,13 +151,12 @@ void Config::SetDefaultsToGlobalStore()
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled);
config_set_default_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort); config_set_default_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK, BindLoopback);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword)); 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(); return obs_frontend_get_global_config();
} }

View File

@ -30,7 +30,7 @@ struct Config {
void Load(); void Load();
void Save(); void Save();
void SetDefaultsToGlobalStore(); void SetDefaultsToGlobalStore();
config_t* GetConfigStore(); config_t *GetConfigStore();
std::atomic<bool> PortOverridden; std::atomic<bool> PortOverridden;
std::atomic<bool> PasswordOverridden; std::atomic<bool> PasswordOverridden;
@ -38,7 +38,6 @@ struct Config {
std::atomic<bool> FirstLoad; std::atomic<bool> FirstLoad;
std::atomic<bool> ServerEnabled; std::atomic<bool> ServerEnabled;
std::atomic<uint16_t> ServerPort; std::atomic<uint16_t> ServerPort;
std::atomic<bool> BindLoopback;
std::atomic<bool> Ipv4Only; std::atomic<bool> Ipv4Only;
std::atomic<bool> DebugEnabled; std::atomic<bool> DebugEnabled;
std::atomic<bool> AlertsEnabled; std::atomic<bool> AlertsEnabled;

View File

@ -3,7 +3,11 @@
#include "obs-websocket.h" #include "obs-websocket.h"
#include "utils/Json.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_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false); #define RETURN_FAILURE() RETURN_STATUS(false);
@ -15,7 +19,7 @@ WebSocketApi::Vendor *get_vendor(calldata_t *cd)
return nullptr; return nullptr;
} }
return static_cast<WebSocketApi::Vendor*>(voidVendor); return static_cast<WebSocketApi::Vendor *>(voidVendor);
} }
WebSocketApi::WebSocketApi() WebSocketApi::WebSocketApi()
@ -25,11 +29,15 @@ WebSocketApi::WebSocketApi()
_procHandler = proc_handler_create(); _procHandler = proc_handler_create();
proc_handler_add(_procHandler, "bool get_api_version(out int version)", &get_api_version, nullptr); 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 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_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_register(in ptr vendor, in string type, in ptr callback)",
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)", &vendor_request_unregister_cb, this); &vendor_request_register_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_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(); proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL); assert(ph != NULL);
@ -58,7 +66,8 @@ void WebSocketApi::SetEventCallback(EventCallback cb)
_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); std::shared_lock l(_mutex);
@ -85,9 +94,9 @@ enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::str
void WebSocketApi::get_ph_cb(void *priv_data, calldata_t *cd) 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(); RETURN_SUCCESS();
} }
@ -107,7 +116,7 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
if (!request_type) if (!request_type)
RETURN_FAILURE(); RETURN_FAILURE();
auto response = static_cast<obs_websocket_request_response*>(bzalloc(sizeof(struct obs_websocket_request_response))); auto response = static_cast<obs_websocket_request_response *>(bzalloc(sizeof(struct obs_websocket_request_response)));
if (!response) if (!response)
RETURN_FAILURE(); RETURN_FAILURE();
@ -119,7 +128,7 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
Request request(request_type, requestData); Request request(request_type, requestData);
RequestResult requestResult = requestHandler.ProcessRequest(request); RequestResult requestResult = requestHandler.ProcessRequest(request);
response->status_code = (uint)requestResult.StatusCode; response->status_code = (unsigned int)requestResult.StatusCode;
if (!requestResult.Comment.empty()) if (!requestResult.Comment.empty())
response->comment = bstrdup(requestResult.Comment.c_str()); response->comment = bstrdup(requestResult.Comment.c_str());
if (requestResult.ResponseData.is_object()) { if (requestResult.ResponseData.is_object()) {
@ -129,14 +138,15 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
calldata_set_ptr(cd, "response", response); calldata_set_ptr(cd, "response", response);
blog_debug("[WebSocketApi::call_request] Request %s called, response status code is %u", request_type, response->status_code); blog_debug("[WebSocketApi::call_request] Request %s called, response status code is %u", request_type,
response->status_code);
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd) 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; const char *vendorName;
if (!calldata_get_string(cd, "name", &vendorName) || strlen(vendorName) == 0) { if (!calldata_get_string(cd, "name", &vendorName) || strlen(vendorName) == 0) {
@ -148,18 +158,19 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
std::unique_lock l(c->_mutex); std::unique_lock l(c->_mutex);
if (c->_vendors.count(vendorName)) { 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(); RETURN_FAILURE();
} }
Vendor* v = new Vendor(); Vendor *v = new Vendor();
v->_name = vendorName; v->_name = vendorName;
c->_vendors[vendorName] = v; c->_vendors[vendorName] = v;
blog_debug("[WebSocketApi::vendor_register_cb] [vendorName: %s] Registered new vendor.", v->_name.c_str()); 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(); RETURN_SUCCESS();
} }
@ -172,28 +183,35 @@ void WebSocketApi::vendor_request_register_cb(void *, calldata_t *cd)
const char *requestType; const char *requestType;
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) { 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(); RETURN_FAILURE();
} }
void *voidCallback; void *voidCallback;
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !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(); 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); std::unique_lock l(v->_mutex);
if (v->_requests.count(requestType)) { 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(); RETURN_FAILURE();
} }
v->_requests[requestType] = *cb; 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(); RETURN_SUCCESS();
} }
@ -206,27 +224,32 @@ void WebSocketApi::vendor_request_unregister_cb(void *, calldata_t *cd)
const char *requestType; const char *requestType;
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) { 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(); RETURN_FAILURE();
} }
std::unique_lock l(v->_mutex); std::unique_lock l(v->_mutex);
if (!v->_requests.count(requestType)) { 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(); RETURN_FAILURE();
} }
v->_requests.erase(requestType); 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(); RETURN_SUCCESS();
} }
void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd) 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); Vendor *v = get_vendor(cd);
if (!v) if (!v)
@ -234,17 +257,19 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
const char *eventType; const char *eventType;
if (!calldata_get_string(cd, "type", &eventType) || strlen(eventType) == 0) { 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(); RETURN_FAILURE();
} }
void *voidEventData; void *voidEventData;
if (!calldata_get_ptr(cd, "data", &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(); RETURN_FAILURE();
} }
auto eventData = static_cast<obs_data_t*>(voidEventData); auto eventData = static_cast<obs_data_t *>(voidEventData);
if (!c->_eventCallback) if (!c->_eventCallback)
RETURN_FAILURE(); RETURN_FAILURE();

View File

@ -10,39 +10,40 @@
#include "../lib/obs-websocket-api.h" #include "../lib/obs-websocket-api.h"
class WebSocketApi { class WebSocketApi {
public: public:
enum RequestReturnCode { enum RequestReturnCode {
Normal, Normal,
NoVendor, NoVendor,
NoVendorRequest, 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 { 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 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; std::shared_mutex _mutex;
EventCallback _eventCallback; std::string _name;
proc_handler_t *_procHandler; std::map<std::string, obs_websocket_request_callback> _requests;
std::map<std::string, Vendor*> _vendors; };
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;
}; };

View File

@ -19,18 +19,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
EventHandler::EventHandler() : EventHandler::EventHandler()
_obsLoaded(false), : _obsLoaded(false),
_inputVolumeMetersRef(0), _inputVolumeMetersRef(0),
_inputActiveStateChangedRef(0), _inputActiveStateChangedRef(0),
_inputShowStateChangedRef(0), _inputShowStateChangedRef(0),
_sceneItemTransformChangedRef(0) _sceneItemTransformChangedRef(0)
{ {
blog_debug("[EventHandler::EventHandler] Setting up..."); blog_debug("[EventHandler::EventHandler] Setting up...");
obs_frontend_add_event_callback(OnFrontendEvent, this); 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) { if (coreSignalHandler) {
signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
@ -49,7 +49,7 @@ EventHandler::~EventHandler()
obs_frontend_remove_event_callback(OnFrontendEvent, this); 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) { if (coreSignalHandler) {
signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this);
@ -80,7 +80,8 @@ void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
if (_inputVolumeMetersHandler) if (_inputVolumeMetersHandler)
blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!"); blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
else 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) 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 // Disconnect all existing signals from the source to prevent multiple connections
DisconnectSourceSignals(source); 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); 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, "reorder_filters", HandleSourceFilterListReindexed, this);
signal_handler_connect(sh, "filter_add", FilterAddMultiHandler, this); signal_handler_connect(sh, "filter_add", FilterAddMultiHandler, this);
signal_handler_connect(sh, "filter_remove", FilterRemoveMultiHandler, this); signal_handler_connect(sh, "filter_remove", FilterRemoveMultiHandler, this);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(filter); eventHandler->ConnectSourceSignals(filter);
}; };
obs_source_enum_filters(source, enumFilters, this); obs_source_enum_filters(source, enumFilters, this);
@ -193,7 +194,7 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
if (!source) if (!source)
return; 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); 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, "reorder_filters", HandleSourceFilterListReindexed, this);
signal_handler_disconnect(sh, "filter_add", FilterAddMultiHandler, this); signal_handler_disconnect(sh, "filter_add", FilterAddMultiHandler, this);
signal_handler_disconnect(sh, "filter_remove", FilterRemoveMultiHandler, this); signal_handler_disconnect(sh, "filter_remove", FilterRemoveMultiHandler, this);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(filter); eventHandler->DisconnectSourceSignals(filter);
}; };
obs_source_enum_filters(source, enumFilters, this); 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) 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) if (!eventHandler->_obsLoaded.load() && event != OBS_FRONTEND_EVENT_FINISHED_LOADING)
return; return;
switch (event) { switch (event) {
// General // General
case OBS_FRONTEND_EVENT_FINISHED_LOADING: case OBS_FRONTEND_EVENT_FINISHED_LOADING:
blog_debug("[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events..."); 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). // Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging).
eventHandler->_obsLoaded.store(true); eventHandler->_obsLoaded.store(true);
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()` // In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one // Enumerate inputs and connect each one
{ {
auto enumInputs = [](void *param, obs_source_t *source) { auto enumInputs = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(source); eventHandler->ConnectSourceSignals(source);
return true; return true;
}; };
obs_enum_sources(enumInputs, private_data); 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 blog_debug("[EventHandler::OnFrontendEvent] Finished.");
{
auto enumScenes = [](void *param, obs_source_t *source) { if (eventHandler->_obsLoadedCallback)
auto eventHandler = static_cast<EventHandler*>(param); eventHandler->_obsLoadedCallback();
eventHandler->ConnectSourceSignals(source);
return true; break;
}; case OBS_FRONTEND_EVENT_EXIT:
obs_enum_scenes(enumScenes, private_data); 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 blog_debug("[EventHandler::OnFrontendEvent] Finished.");
{
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."); 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) // Config
eventHandler->_obsLoadedCallback(); 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; // Scenes
case OBS_FRONTEND_EVENT_EXIT: case OBS_FRONTEND_EVENT_SCENE_CHANGED:
eventHandler->HandleExitStarted(); 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..."); // Transitions
// Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging). case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
eventHandler->_obsLoaded.store(false); 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()` // Outputs
// Enumerate inputs and disconnect each one case OBS_FRONTEND_EVENT_STREAMING_STARTING:
{ eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING);
auto enumInputs = [](void *param, obs_source_t *source) { break;
auto eventHandler = static_cast<EventHandler*>(param); case OBS_FRONTEND_EVENT_STREAMING_STARTED:
eventHandler->DisconnectSourceSignals(source); eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED);
return true; break;
}; case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
obs_enum_sources(enumInputs, private_data); 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 default:
{ break;
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;
} }
} }
// Only called for creation of a public source // Only called for creation of a public source
void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) 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 // Don't react to signals until OBS has finished loading
if (!eventHandler->_obsLoaded.load()) if (!eventHandler->_obsLoaded.load())
@ -500,14 +498,14 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
eventHandler->ConnectSourceSignals(source); eventHandler->ConnectSourceSignals(source);
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputCreated(source); eventHandler->HandleInputCreated(source);
break; break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneCreated(source); eventHandler->HandleSceneCreated(source);
break; break;
default: default:
break; break;
} }
} }
@ -515,7 +513,7 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
// Used as a fallback if an input/scene is not explicitly removed // Used as a fallback if an input/scene is not explicitly removed
void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) 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 // 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
@ -530,18 +528,18 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
return; return;
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: 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. // 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)) if (!obs_source_removed(source))
eventHandler->HandleInputRemoved(source); eventHandler->HandleInputRemoved(source);
break; break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
// Only emit removed if the scene has not already been removed. // Only emit removed if the scene has not already been removed.
if (!obs_source_removed(source)) if (!obs_source_removed(source))
eventHandler->HandleSceneRemoved(source); eventHandler->HandleSceneRemoved(source);
break; break;
default: default:
break; break;
} }
} }
@ -549,7 +547,7 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
// 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. // 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) void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_obsLoaded.load()) if (!eventHandler->_obsLoaded.load())
return; return;
@ -559,20 +557,20 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
return; return;
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputRemoved(source); eventHandler->HandleInputRemoved(source);
break; break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneRemoved(source); eventHandler->HandleSceneRemoved(source);
break; break;
default: default:
break; break;
} }
} }
void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data) void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_obsLoaded.load()) if (!eventHandler->_obsLoaded.load())
return; return;
@ -587,15 +585,15 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
return; return;
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName); eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName);
break; break;
case OBS_SOURCE_TYPE_TRANSITION: case OBS_SOURCE_TYPE_TRANSITION:
break; break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName); eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName);
break; break;
default: default:
break; break;
} }
} }

View File

@ -29,123 +29,121 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Obs_VolumeMeter.h" #include "../utils/Obs_VolumeMeter.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class EventHandler class EventHandler {
{ public:
public: EventHandler();
EventHandler(); ~EventHandler();
~EventHandler();
typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback; typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback;
void SetBroadcastCallback(BroadcastCallback cb); void SetBroadcastCallback(BroadcastCallback cb);
typedef std::function<void()> ObsLoadedCallback; typedef std::function<void()> ObsLoadedCallback;
void SetObsLoadedCallback(ObsLoadedCallback cb); void SetObsLoadedCallback(ObsLoadedCallback cb);
void ProcessSubscription(uint64_t eventSubscriptions); void ProcessSubscription(uint64_t eventSubscriptions);
void ProcessUnsubscription(uint64_t eventSubscriptions); void ProcessUnsubscription(uint64_t eventSubscriptions);
private: private:
BroadcastCallback _broadcastCallback; BroadcastCallback _broadcastCallback;
ObsLoadedCallback _obsLoadedCallback; ObsLoadedCallback _obsLoadedCallback;
std::atomic<bool> _obsLoaded; std::atomic<bool> _obsLoaded;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler; std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
std::atomic<uint64_t> _inputVolumeMetersRef; std::atomic<uint64_t> _inputVolumeMetersRef;
std::atomic<uint64_t> _inputActiveStateChangedRef; std::atomic<uint64_t> _inputActiveStateChangedRef;
std::atomic<uint64_t> _inputShowStateChangedRef; std::atomic<uint64_t> _inputShowStateChangedRef;
std::atomic<uint64_t> _sceneItemTransformChangedRef; std::atomic<uint64_t> _sceneItemTransformChangedRef;
void ConnectSourceSignals(obs_source_t *source); void ConnectSourceSignals(obs_source_t *source);
void DisconnectSourceSignals(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 // Signal handler: frontend
static void OnFrontendEvent(enum obs_frontend_event event, void *private_data); static void OnFrontendEvent(enum obs_frontend_event event, void *private_data);
// Signal handler: libobs // Signal handler: libobs
static void SourceCreatedMultiHandler(void *param, calldata_t *data); static void SourceCreatedMultiHandler(void *param, calldata_t *data);
static void SourceDestroyedMultiHandler(void *param, calldata_t *data); static void SourceDestroyedMultiHandler(void *param, calldata_t *data);
static void SourceRemovedMultiHandler(void *param, calldata_t *data); static void SourceRemovedMultiHandler(void *param, calldata_t *data);
// Signal handler: source // Signal handler: source
static void SourceRenamedMultiHandler(void *param, calldata_t *data); static void SourceRenamedMultiHandler(void *param, calldata_t *data);
static void SourceMediaPauseMultiHandler(void *param, calldata_t *data); static void SourceMediaPauseMultiHandler(void *param, calldata_t *data);
static void SourceMediaPlayMultiHandler(void *param, calldata_t *data); static void SourceMediaPlayMultiHandler(void *param, calldata_t *data);
static void SourceMediaRestartMultiHandler(void *param, calldata_t *data); static void SourceMediaRestartMultiHandler(void *param, calldata_t *data);
static void SourceMediaStopMultiHandler(void *param, calldata_t *data); static void SourceMediaStopMultiHandler(void *param, calldata_t *data);
static void SourceMediaNextMultiHandler(void *param, calldata_t *data); static void SourceMediaNextMultiHandler(void *param, calldata_t *data);
static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data); static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data);
// General
void HandleExitStarted();
void HandleStudioModeStateChanged(bool enabled);
// General // Config
void HandleExitStarted(); void HandleCurrentSceneCollectionChanging();
void HandleStudioModeStateChanged(bool enabled); void HandleCurrentSceneCollectionChanged();
void HandleSceneCollectionListChanged();
void HandleCurrentProfileChanging();
void HandleCurrentProfileChanged();
void HandleProfileListChanged();
// Config // Scenes
void HandleCurrentSceneCollectionChanging(); void HandleSceneCreated(obs_source_t *source);
void HandleCurrentSceneCollectionChanged(); void HandleSceneRemoved(obs_source_t *source);
void HandleSceneCollectionListChanged(); void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName);
void HandleCurrentProfileChanging(); void HandleCurrentProgramSceneChanged();
void HandleCurrentProfileChanged(); void HandleCurrentPreviewSceneChanged();
void HandleProfileListChanged(); void HandleSceneListChanged();
// Scenes // Inputs
void HandleSceneCreated(obs_source_t *source); void HandleInputCreated(obs_source_t *source);
void HandleSceneRemoved(obs_source_t *source); void HandleInputRemoved(obs_source_t *source);
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName); void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName);
void HandleCurrentProgramSceneChanged(); void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
void HandleCurrentPreviewSceneChanged(); static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback
void HandleSceneListChanged(); 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 // Transitions
void HandleInputCreated(obs_source_t *source); void HandleCurrentSceneTransitionChanged();
void HandleInputRemoved(obs_source_t *source); void HandleCurrentSceneTransitionDurationChanged();
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName); static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback static void HandleSceneTransitionVideoEnded(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 // Filters
void HandleCurrentSceneTransitionChanged(); static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback
void HandleCurrentSceneTransitionDurationChanged(); static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback
static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback
static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter);
static void HandleSceneTransitionVideoEnded(void *param, calldata_t *data); // Direct callback 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 // Outputs
static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback void HandleStreamStateChanged(ObsOutputState state);
static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback void HandleRecordStateChanged(ObsOutputState state);
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback void HandleReplayBufferStateChanged(ObsOutputState state);
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter); void HandleVirtualcamStateChanged(ObsOutputState state);
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter); void HandleReplayBufferSaved();
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
// Outputs // Scene Items
void HandleStreamStateChanged(ObsOutputState state); static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback
void HandleRecordStateChanged(ObsOutputState state); static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback
void HandleReplayBufferStateChanged(ObsOutputState state); static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback
void HandleVirtualcamStateChanged(ObsOutputState state); static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback
void HandleReplayBufferSaved(); 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 // Media Inputs
static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action);
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);
}; };

View File

@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data) 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 *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter"); 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) 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 *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter"); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!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) 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"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter) if (!filter)
@ -180,7 +180,7 @@ void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSourceFilterEnableStateChanged(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"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter) if (!filter)

View File

@ -111,7 +111,7 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
*/ */
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data) void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_inputActiveStateChangedRef.load()) if (!eventHandler->_inputActiveStateChangedRef.load())
return; return;
@ -147,7 +147,7 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputShowStateChanged(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()) if (!eventHandler->_inputShowStateChangedRef.load())
return; return;
@ -181,7 +181,7 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputMuteStateChanged(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -213,7 +213,7 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputVolumeChanged(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -252,7 +252,7 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputAudioBalanceChanged(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -285,7 +285,7 @@ void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputAudioSyncOffsetChanged(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -318,7 +318,7 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
*/ */
void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -362,7 +362,7 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputAudioMonitorTypeChanged(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -373,11 +373,9 @@ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *d
enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type"); enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type");
std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType);
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["monitorType"] = monitorTypeString; eventData["monitorType"] = monitorType;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData);
} }

View File

@ -19,11 +19,14 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #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) { switch (action) {
default: default:
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
@ -35,7 +38,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) {
void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -49,7 +52,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPlayMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -63,7 +66,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaRestartMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -77,7 +80,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaStopMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -91,7 +94,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaNextMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -105,7 +108,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPreviousMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -132,7 +135,7 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
*/ */
void EventHandler::HandleMediaInputPlaybackStarted(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -161,7 +164,7 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
*/ */
void EventHandler::HandleMediaInputPlaybackEnded(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)

View File

@ -19,18 +19,19 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
static bool GetOutputStateActive(ObsOutputState state) { static bool GetOutputStateActive(ObsOutputState state)
switch(state) { {
case OBS_WEBSOCKET_OUTPUT_STARTED: switch (state) {
case OBS_WEBSOCKET_OUTPUT_RESUMED: case OBS_WEBSOCKET_OUTPUT_STARTED:
return true; case OBS_WEBSOCKET_OUTPUT_RESUMED:
case OBS_WEBSOCKET_OUTPUT_STARTING: return true;
case OBS_WEBSOCKET_OUTPUT_STOPPING: case OBS_WEBSOCKET_OUTPUT_STARTING:
case OBS_WEBSOCKET_OUTPUT_STOPPED: case OBS_WEBSOCKET_OUTPUT_STOPPING:
case OBS_WEBSOCKET_OUTPUT_PAUSED: case OBS_WEBSOCKET_OUTPUT_STOPPED:
return false; case OBS_WEBSOCKET_OUTPUT_PAUSED:
default: return false;
return false; default:
return false;
} }
} }
@ -52,7 +53,7 @@ void EventHandler::HandleStreamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] = state;
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData); BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData);
} }
@ -61,6 +62,7 @@ void EventHandler::HandleStreamStateChanged(ObsOutputState state)
* *
* @dataField outputActive | Boolean | Whether the output is active * @dataField outputActive | Boolean | Whether the output is active
* @dataField outputState | String | The specific state of the output * @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 * @eventType RecordStateChanged
* @eventSubscription Outputs * @eventSubscription Outputs
@ -74,7 +76,12 @@ void EventHandler::HandleRecordStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] = state;
if (state == OBS_WEBSOCKET_OUTPUT_STOPPED || state == OBS_WEBSOCKET_OUTPUT_STARTED) {
eventData["outputPath"] = Utils::Obs::StringHelper::GetLastRecordFileName();
} else {
eventData["outputPath"] = nullptr;
}
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData); BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData);
} }
@ -96,7 +103,7 @@ void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] = state;
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData); BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData);
} }
@ -118,7 +125,7 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] = state;
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData); BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData);
} }
@ -138,6 +145,6 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
void EventHandler::HandleReplayBufferSaved() void EventHandler::HandleReplayBufferSaved()
{ {
json eventData; json eventData;
eventData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath(); eventData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFileName();
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferSaved", eventData); BroadcastEvent(EventSubscription::Outputs, "ReplayBufferSaved", eventData);
} }

View File

@ -37,7 +37,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -74,7 +74,7 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemRemoved(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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -107,7 +107,7 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemListReindexed(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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -136,7 +136,7 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemEnableStateChanged(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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -172,7 +172,7 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
*/ */
void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -207,7 +207,7 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
*/ */
void EventHandler::HandleSceneItemSelected(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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -240,7 +240,7 @@ void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemTransformChanged(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()) if (!eventHandler->_sceneItemTransformChangedRef.load())
return; return;

View File

@ -124,7 +124,7 @@ void EventHandler::HandleCurrentPreviewSceneChanged()
{ {
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); 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) if (!currentPreviewScene)
return; return;

View File

@ -76,7 +76,7 @@ void EventHandler::HandleCurrentSceneTransitionDurationChanged()
*/ */
void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -104,7 +104,7 @@ void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneTransitionEnded(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -135,7 +135,7 @@ void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneTransitionVideoEnded(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)

View File

@ -157,13 +157,15 @@ namespace EventSubscription {
* Helper to receive all non-high-volume events. * Helper to receive all non-high-volume events.
* *
* @enumIdentifier All * @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 * @enumType EventSubscription
* @rpcVersion -1 * @rpcVersion -1
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @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. * Subscription value to receive the `InputVolumeMeters` high-volume event.
* *

View File

@ -28,18 +28,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../Config.h" #include "../Config.h"
#include "../utils/Platform.h" #include "../utils/Platform.h"
ConnectInfo::ConnectInfo(QWidget* parent) : ConnectInfo::ConnectInfo(QWidget *parent) : QDialog(parent, Qt::Dialog), ui(new Ui::ConnectInfo)
QDialog(parent, Qt::Dialog),
ui(new Ui::ConnectInfo)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->copyServerIpButton, &QPushButton::clicked, connect(ui->copyServerIpButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerIpButtonClicked);
this, &ConnectInfo::CopyServerIpButtonClicked); connect(ui->copyServerPortButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerPortButtonClicked);
connect(ui->copyServerPortButton, &QPushButton::clicked, connect(ui->copyServerPasswordButton, &QPushButton::clicked, this, &ConnectInfo::CopyServerPasswordButtonClicked);
this, &ConnectInfo::CopyServerPortButtonClicked);
connect(ui->copyServerPasswordButton, &QPushButton::clicked,
this, &ConnectInfo::CopyServerPasswordButtonClicked);
} }
ConnectInfo::~ConnectInfo() ConnectInfo::~ConnectInfo()
@ -113,14 +108,14 @@ void ConnectInfo::DrawQr(QString qrText)
QPixmap map(230, 230); QPixmap map(230, 230);
map.fill(Qt::white); map.fill(Qt::white);
QPainter painter(&map); QPainter painter(&map);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(QT_TO_UTF8(qrText), qrcodegen::QrCode::Ecc::MEDIUM); qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(QT_TO_UTF8(qrText), qrcodegen::QrCode::Ecc::MEDIUM);
const int s = qr.getSize() > 0 ? qr.getSize() : 1; const int s = qr.getSize() > 0 ? qr.getSize() : 1;
const double w = map.width(); const double w = map.width();
const double h = map.height(); 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 size = ((aspect > 1.0) ? h : w);
const double scale = size / (s+2); const double scale = size / (s + 2);
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black); painter.setBrush(Qt::black);

View File

@ -25,12 +25,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "ui_ConnectInfo.h" #include "ui_ConnectInfo.h"
class ConnectInfo : public QDialog class ConnectInfo : public QDialog {
{
Q_OBJECT Q_OBJECT
public: public:
explicit ConnectInfo(QWidget* parent = 0); explicit ConnectInfo(QWidget *parent = 0);
~ConnectInfo(); ~ConnectInfo();
void showEvent(QShowEvent *event); void showEvent(QShowEvent *event);
void RefreshData(); void RefreshData();

View File

@ -37,12 +37,12 @@ QString GetToolTipIconHtml()
return iconTemplate.arg(iconFile); return iconTemplate.arg(iconFile);
} }
SettingsDialog::SettingsDialog(QWidget* parent) : SettingsDialog::SettingsDialog(QWidget *parent)
QDialog(parent, Qt::Dialog), : QDialog(parent, Qt::Dialog),
ui(new Ui::SettingsDialog), ui(new Ui::SettingsDialog),
connectInfo(new ConnectInfo), connectInfo(new ConnectInfo),
sessionTableTimer(new QTimer), sessionTableTimer(new QTimer),
passwordManuallyEdited(false) passwordManuallyEdited(false)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->websocketSessionTable->horizontalHeader()->resizeSection(3, 100); // Resize Session Table column widths ui->websocketSessionTable->horizontalHeader()->resizeSection(3, 100); // Resize Session Table column widths
@ -52,22 +52,15 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Set the appropriate tooltip icon for the theme // Set the appropriate tooltip icon for the theme
QString toolTipHtml = GetToolTipIconHtml(); ui->enableDebugLoggingToolTipLabel->setText(GetToolTipIconHtml());
ui->enableDebugLoggingToolTipLabel->setText(toolTipHtml);
ui->allowExternalToolTipLabel->setText(toolTipHtml);
connect(sessionTableTimer, &QTimer::timeout, connect(sessionTableTimer, &QTimer::timeout, this, &SettingsDialog::FillSessionTable);
this, &SettingsDialog::FillSessionTable); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &SettingsDialog::DialogButtonClicked);
connect(ui->buttonBox, &QDialogButtonBox::clicked, connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged, this,
this, &SettingsDialog::DialogButtonClicked); &SettingsDialog::EnableAuthenticationCheckBoxChanged);
connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged, connect(ui->generatePasswordButton, &QPushButton::clicked, this, &SettingsDialog::GeneratePasswordButtonClicked);
this, &SettingsDialog::EnableAuthenticationCheckBoxChanged); connect(ui->showConnectInfoButton, &QPushButton::clicked, this, &SettingsDialog::ShowConnectInfoButtonClicked);
connect(ui->generatePasswordButton, &QPushButton::clicked, connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited, this, &SettingsDialog::PasswordEdited);
this, &SettingsDialog::GeneratePasswordButtonClicked);
connect(ui->showConnectInfoButton, &QPushButton::clicked,
this, &SettingsDialog::ShowConnectInfoButtonClicked);
connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited,
this, &SettingsDialog::PasswordEdited);
} }
SettingsDialog::~SettingsDialog() SettingsDialog::~SettingsDialog()
@ -129,16 +122,12 @@ void SettingsDialog::RefreshData()
ui->enableSystemTrayAlertsCheckBox->setChecked(conf->AlertsEnabled); ui->enableSystemTrayAlertsCheckBox->setChecked(conf->AlertsEnabled);
ui->enableDebugLoggingCheckBox->setChecked(conf->DebugEnabled); ui->enableDebugLoggingCheckBox->setChecked(conf->DebugEnabled);
ui->serverPortSpinBox->setValue(conf->ServerPort); ui->serverPortSpinBox->setValue(conf->ServerPort);
ui->allowExternalCheckBox->setChecked(!conf->BindLoopback);
ui->enableAuthenticationCheckBox->setChecked(conf->AuthRequired); ui->enableAuthenticationCheckBox->setChecked(conf->AuthRequired);
ui->serverPasswordLineEdit->setText(conf->ServerPassword); ui->serverPasswordLineEdit->setText(conf->ServerPassword);
ui->showConnectInfoButton->setEnabled(!conf->BindLoopback);
ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired); ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired);
ui->generatePasswordButton->setEnabled(conf->AuthRequired); ui->generatePasswordButton->setEnabled(conf->AuthRequired);
ui->showConnectInfoButton->setToolTip(ui->allowExternalCheckBox->isChecked() ? "" : obs_module_text("OBSWebSocket.Settings.ShowConnectInfoHoverText"));
FillSessionTable(); FillSessionTable();
} }
@ -179,25 +168,24 @@ void SettingsDialog::SaveFormData()
int ret = msgBox.exec(); int ret = msgBox.exec();
switch (ret) { switch (ret) {
case QMessageBox::Yes: case QMessageBox::Yes:
break; break;
case QMessageBox::No: case QMessageBox::No:
default: default:
ui->serverPasswordLineEdit->setText(conf->ServerPassword); ui->serverPasswordLineEdit->setText(conf->ServerPassword);
return; return;
} }
} }
bool needsRestart = (conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) || bool needsRestart =
(ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text()) || (conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) ||
(conf->BindLoopback == ui->allowExternalCheckBox->isChecked()) || (conf->ServerPort != ui->serverPortSpinBox->value()) ||
(conf->ServerPort != ui->serverPortSpinBox->value()); (ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text());
conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked(); conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked();
conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked(); conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked();
conf->DebugEnabled = ui->enableDebugLoggingCheckBox->isChecked(); conf->DebugEnabled = ui->enableDebugLoggingCheckBox->isChecked();
conf->ServerPort = ui->serverPortSpinBox->value(); conf->ServerPort = ui->serverPortSpinBox->value();
conf->BindLoopback = !ui->allowExternalCheckBox->isChecked();
conf->AuthRequired = ui->enableAuthenticationCheckBox->isChecked(); conf->AuthRequired = ui->enableAuthenticationCheckBox->isChecked();
conf->ServerPassword = ui->serverPasswordLineEdit->text(); conf->ServerPassword = ui->serverPasswordLineEdit->text();
@ -246,7 +234,8 @@ void SettingsDialog::FillSessionTable()
QTableWidgetItem *durationItem = new QTableWidgetItem(QTime(0, 0, sessionDuration).toString("hh:mm:ss")); QTableWidgetItem *durationItem = new QTableWidgetItem(QTime(0, 0, sessionDuration).toString("hh:mm:ss"));
ui->websocketSessionTable->setItem(i, 1, durationItem); 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); ui->websocketSessionTable->setItem(i, 2, statsItem);
QLabel *identifiedLabel = new QLabel(); QLabel *identifiedLabel = new QLabel();
@ -266,9 +255,7 @@ void SettingsDialog::FillSessionTable()
invalidateButtonLayout->setContentsMargins(0, 0, 0, 0); invalidateButtonLayout->setContentsMargins(0, 0, 0, 0);
invalidateButtonWidget->setLayout(invalidateButtonLayout); invalidateButtonWidget->setLayout(invalidateButtonLayout);
ui->websocketSessionTable->setCellWidget(i, 4, invalidateButtonWidget); ui->websocketSessionTable->setCellWidget(i, 4, invalidateButtonWidget);
connect(invalidateButton, &QPushButton::clicked, [=]() { connect(invalidateButton, &QPushButton::clicked, [=]() { webSocketServer->InvalidateSession(session.hdl); });
webSocketServer->InvalidateSession(session.hdl);
});
i++; i++;
} }
@ -305,11 +292,11 @@ void SettingsDialog::ShowConnectInfoButtonClicked()
int ret = msgBox.exec(); int ret = msgBox.exec();
switch (ret) { switch (ret) {
case QMessageBox::Yes: case QMessageBox::Yes:
break; break;
case QMessageBox::No: case QMessageBox::No:
default: default:
return; return;
} }
} }

View File

@ -27,12 +27,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "ui_SettingsDialog.h" #include "ui_SettingsDialog.h"
class SettingsDialog : public QDialog class SettingsDialog : public QDialog {
{
Q_OBJECT Q_OBJECT
public: public:
explicit SettingsDialog(QWidget* parent = 0); explicit SettingsDialog(QWidget *parent = 0);
~SettingsDialog(); ~SettingsDialog();
void showEvent(QShowEvent *event); void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event); void hideEvent(QHideEvent *event);

View File

@ -155,21 +155,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="1" column="1">
<widget class="QCheckBox" name="enableAuthenticationCheckBox"> <widget class="QCheckBox" name="enableAuthenticationCheckBox">
<property name="text"> <property name="text">
<string>OBSWebSocket.Settings.AuthRequired</string> <string>OBSWebSocket.Settings.AuthRequired</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="2" column="0">
<widget class="QLabel" name="serverPasswordLabel"> <widget class="QLabel" name="serverPasswordLabel">
<property name="text"> <property name="text">
<string>OBSWebSocket.Settings.Password</string> <string>OBSWebSocket.Settings.Password</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLineEdit" name="serverPasswordLineEdit"> <widget class="QLineEdit" name="serverPasswordLineEdit">
@ -187,7 +187,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="5" column="0"> <item row="3" column="0">
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -203,47 +203,13 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="5" column="1"> <item row="3" column="1">
<widget class="QPushButton" name="showConnectInfoButton"> <widget class="QPushButton" name="showConnectInfoButton">
<property name="text"> <property name="text">
<string>OBSWebSocket.Settings.ShowConnectInfo</string> <string>OBSWebSocket.Settings.ShowConnectInfo</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="allowExternalCheckBox">
<property name="text">
<string>OBSWebSocket.Settings.AllowExternal</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="allowExternalToolTipLabel">
<property name="toolTip">
<string>OBSWebSocket.Settings.AllowExternalHoverText</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -32,10 +32,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
OBS_MODULE_AUTHOR("OBSProject") OBS_MODULE_AUTHOR("OBSProject")
const char *obs_module_name(void) { return "obs-websocket"; } const char *obs_module_name(void)
const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); } {
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; ConfigPtr _config;
EventHandlerPtr _eventHandler; EventHandlerPtr _eventHandler;
WebSocketApiPtr _webSocketApi; WebSocketApiPtr _webSocketApi;
@ -46,7 +52,8 @@ void WebSocketApiEventCallback(std::string vendorName, std::string eventType, ob
bool obs_module_load(void) 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] 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); 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 // Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string); 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); _settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation(); obs_frontend_pop_ui_translation();
// Add the settings dialog to the tools menu // Add the settings dialog to the tools menu
const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle"); const char *menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle");
QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText); QAction *menuAction = (QAction *)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); }); QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); });
blog(LOG_INFO, "[obs_module_load] Module loaded."); 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."); blog(LOG_INFO, "[obs_module_unload] Finished shutting down.");
} }
os_cpu_usage_info_t* GetCpuUsageInfo() os_cpu_usage_info_t *GetCpuUsageInfo()
{ {
return _cpuUsageInfo; return _cpuUsageInfo;
} }
@ -197,7 +204,8 @@ void obs_module_post_load()
// Test calling obs-websocket requests // Test calling obs-websocket requests
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion"); struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
if (response) { 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); 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); obs_websocket_request_response_free(response);
} }

View File

@ -26,7 +26,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "utils/Obs.h" #include "utils/Obs.h"
#include "plugin-macros.generated.h" #include "plugin-macros.generated.h"
class Config; struct Config;
typedef std::shared_ptr<Config> ConfigPtr; typedef std::shared_ptr<Config> ConfigPtr;
class EventHandler; class EventHandler;
@ -38,7 +38,7 @@ typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr;
class WebSocketServer; class WebSocketServer;
typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr; typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr;
os_cpu_usage_info_t* GetCpuUsageInfo(); os_cpu_usage_info_t *GetCpuUsageInfo();
ConfigPtr GetConfig(); ConfigPtr GetConfig();

View File

@ -24,8 +24,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Compat.h" #include "../utils/Compat.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
struct SerialFrameBatch struct SerialFrameBatch {
{
RequestHandler &requestHandler; RequestHandler &requestHandler;
std::queue<RequestBatchRequest> requests; std::queue<RequestBatchRequest> requests;
std::vector<RequestResult> results; std::vector<RequestResult> results;
@ -37,43 +36,46 @@ struct SerialFrameBatch
std::mutex conditionMutex; std::mutex conditionMutex;
std::condition_variable condition; std::condition_variable condition;
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure) : SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure)
requestHandler(requestHandler), : requestHandler(requestHandler),
variables(variables), variables(variables),
haltOnFailure(haltOnFailure), haltOnFailure(haltOnFailure),
frameCount(0), frameCount(0),
sleepUntilFrame(0) sleepUntilFrame(0)
{} {
}
}; };
struct ParallelBatchResults struct ParallelBatchResults {
{
RequestHandler &requestHandler; RequestHandler &requestHandler;
std::vector<RequestResult> results; std::vector<RequestResult> results;
std::mutex conditionMutex; std::mutex conditionMutex;
std::condition_variable condition; std::condition_variable condition;
ParallelBatchResults(RequestHandler &requestHandler) : ParallelBatchResults(RequestHandler &requestHandler) : requestHandler(requestHandler) {}
requestHandler(requestHandler)
{}
}; };
// `{"inputName": "inputNameVariable"}` is essentially `inputName = inputNameVariable` // `{"inputName": "inputNameVariable"}` is essentially `inputName = inputNameVariable`
static void PreProcessVariables(const json &variables, RequestBatchRequest &request) 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; return;
for (auto& [key, value] : request.InputVariables.items()) { for (auto &[key, value] : request.InputVariables.items()) {
if (!value.is_string()) { 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; continue;
} }
std::string valueString = value; std::string valueString = value;
if (!variables.contains(valueString)) { 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; continue;
} }
@ -89,15 +91,19 @@ static void PostProcessVariables(json &variables, const RequestBatchRequest &req
if (!request.OutputVariables.is_object() || request.OutputVariables.empty() || requestResult.ResponseData.empty()) if (!request.OutputVariables.is_object() || request.OutputVariables.empty() || requestResult.ResponseData.empty())
return; return;
for (auto& [key, value] : request.OutputVariables.items()) { for (auto &[key, value] : request.OutputVariables.items()) {
if (!value.is_string()) { 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; continue;
} }
std::string valueString = value; std::string valueString = value;
if (!requestResult.ResponseData.contains(valueString)) { 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; continue;
} }
@ -109,7 +115,7 @@ static void ObsTickCallback(void *param, float)
{ {
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"}; ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
auto serialFrameBatch = static_cast<SerialFrameBatch*>(param); auto serialFrameBatch = static_cast<SerialFrameBatch *>(param);
// Increment frame count // Increment frame count
serialFrameBatch->frameCount++; serialFrameBatch->frameCount++;
@ -156,7 +162,10 @@ static void ObsTickCallback(void *param, float)
serialFrameBatch->condition.notify_one(); 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); RequestHandler requestHandler(session);
if (executionType == RequestBatchExecutionType::SerialRealtime) { 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 // Wait until the graphics thread processes the last request in the queue
std::unique_lock<std::mutex> lock(serialFrameBatch.conditionMutex); 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 // Remove the created callback entry since we don't need it anymore
obs_remove_tick_callback(ObsTickCallback, &serialFrameBatch); obs_remove_tick_callback(ObsTickCallback, &serialFrameBatch);
@ -215,7 +224,9 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
// Wait for the last request to finish processing // Wait for the last request to finish processing
size_t requestCount = requests.size(); size_t requestCount = requests.size();
parallelResults.condition.wait(lock, [&parallelResults, requestCount]{return parallelResults.results.size() == requestCount;}); parallelResults.condition.wait(lock, [&parallelResults, requestCount] {
return parallelResults.results.size() == requestCount;
});
return parallelResults.results; return parallelResults.results;
} }

View File

@ -24,5 +24,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "rpc/RequestBatchRequest.h" #include "rpc/RequestBatchRequest.h"
namespace RequestBatchHandler { 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);
} }

View File

@ -23,8 +23,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h" #include "RequestHandler.h"
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap{
{
// General // General
{"GetVersion", &RequestHandler::GetVersion}, {"GetVersion", &RequestHandler::GetVersion},
{"GetStats", &RequestHandler::GetStats}, {"GetStats", &RequestHandler::GetStats},
@ -152,6 +151,13 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"StopReplayBuffer", &RequestHandler::StopReplayBuffer}, {"StopReplayBuffer", &RequestHandler::StopReplayBuffer},
{"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer}, {"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer},
{"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay}, {"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 // Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus}, {"GetStreamStatus", &RequestHandler::GetStreamStatus},
@ -182,14 +188,13 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog}, {"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog}, {"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
{"GetMonitorList", &RequestHandler::GetMonitorList}, {"GetMonitorList", &RequestHandler::GetMonitorList},
{"OpenVideoMixProjector", &RequestHandler::OpenVideoMixProjector},
{"OpenSourceProjector", &RequestHandler::OpenSourceProjector},
}; };
RequestHandler::RequestHandler(SessionPtr session) : RequestHandler::RequestHandler(SessionPtr session) : _session(session) {}
_session(session)
{
}
RequestResult RequestHandler::ProcessRequest(const Request& request) RequestResult RequestHandler::ProcessRequest(const Request &request)
{ {
#ifdef PLUGIN_TESTS #ifdef PLUGIN_TESTS
ScopeProfiler prof{"obs_websocket_request_processing"}; ScopeProfiler prof{"obs_websocket_request_processing"};
@ -199,12 +204,12 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object."); return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object.");
if (request.RequestType.empty()) 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; RequestMethodHandler handler;
try { try {
handler = _handlerMap.at(request.RequestType); 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."); return RequestResult::Error(RequestStatus::UnknownRequestType, "Your request type is not valid.");
} }
@ -214,7 +219,7 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
std::vector<std::string> RequestHandler::GetRequestList() std::vector<std::string> RequestHandler::GetRequestList()
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
for (auto const& [key, val] : _handlerMap) { for (auto const &[key, val] : _handlerMap) {
ret.push_back(key); ret.push_back(key);
} }

View File

@ -33,174 +33,183 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class RequestHandler; class RequestHandler;
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&); typedef RequestResult (RequestHandler::*RequestMethodHandler)(const Request &);
class RequestHandler { class RequestHandler {
public: public:
RequestHandler(SessionPtr session = nullptr); RequestHandler(SessionPtr session = nullptr);
RequestResult ProcessRequest(const Request& request); RequestResult ProcessRequest(const Request &request);
std::vector<std::string> GetRequestList(); std::vector<std::string> GetRequestList();
private: private:
// General // General
RequestResult GetVersion(const Request&); RequestResult GetVersion(const Request &);
RequestResult GetStats(const Request&); RequestResult GetStats(const Request &);
RequestResult BroadcastCustomEvent(const Request&); RequestResult BroadcastCustomEvent(const Request &);
RequestResult CallVendorRequest(const Request&); RequestResult CallVendorRequest(const Request &);
RequestResult GetHotkeyList(const Request&); RequestResult GetHotkeyList(const Request &);
RequestResult TriggerHotkeyByName(const Request&); RequestResult TriggerHotkeyByName(const Request &);
RequestResult TriggerHotkeyByKeySequence(const Request&); RequestResult TriggerHotkeyByKeySequence(const Request &);
RequestResult Sleep(const Request&); RequestResult Sleep(const Request &);
// Config // Config
RequestResult GetPersistentData(const Request&); RequestResult GetPersistentData(const Request &);
RequestResult SetPersistentData(const Request&); RequestResult SetPersistentData(const Request &);
RequestResult GetSceneCollectionList(const Request&); RequestResult GetSceneCollectionList(const Request &);
RequestResult SetCurrentSceneCollection(const Request&); RequestResult SetCurrentSceneCollection(const Request &);
RequestResult CreateSceneCollection(const Request&); RequestResult CreateSceneCollection(const Request &);
RequestResult GetProfileList(const Request&); RequestResult GetProfileList(const Request &);
RequestResult SetCurrentProfile(const Request&); RequestResult SetCurrentProfile(const Request &);
RequestResult CreateProfile(const Request&); RequestResult CreateProfile(const Request &);
RequestResult RemoveProfile(const Request&); RequestResult RemoveProfile(const Request &);
RequestResult GetProfileParameter(const Request&); RequestResult GetProfileParameter(const Request &);
RequestResult SetProfileParameter(const Request&); RequestResult SetProfileParameter(const Request &);
RequestResult GetVideoSettings(const Request&); RequestResult GetVideoSettings(const Request &);
RequestResult SetVideoSettings(const Request&); RequestResult SetVideoSettings(const Request &);
RequestResult GetStreamServiceSettings(const Request&); RequestResult GetStreamServiceSettings(const Request &);
RequestResult SetStreamServiceSettings(const Request&); RequestResult SetStreamServiceSettings(const Request &);
RequestResult GetRecordDirectory(const Request&); RequestResult GetRecordDirectory(const Request &);
// Sources // Sources
RequestResult GetSourceActive(const Request&); RequestResult GetSourceActive(const Request &);
RequestResult GetSourceScreenshot(const Request&); RequestResult GetSourceScreenshot(const Request &);
RequestResult SaveSourceScreenshot(const Request&); RequestResult SaveSourceScreenshot(const Request &);
RequestResult GetSourcePrivateSettings(const Request&); RequestResult GetSourcePrivateSettings(const Request &);
RequestResult SetSourcePrivateSettings(const Request&); RequestResult SetSourcePrivateSettings(const Request &);
// Scenes // Scenes
RequestResult GetSceneList(const Request&); RequestResult GetSceneList(const Request &);
RequestResult GetGroupList(const Request&); RequestResult GetGroupList(const Request &);
RequestResult GetCurrentProgramScene(const Request&); RequestResult GetCurrentProgramScene(const Request &);
RequestResult SetCurrentProgramScene(const Request&); RequestResult SetCurrentProgramScene(const Request &);
RequestResult GetCurrentPreviewScene(const Request&); RequestResult GetCurrentPreviewScene(const Request &);
RequestResult SetCurrentPreviewScene(const Request&); RequestResult SetCurrentPreviewScene(const Request &);
RequestResult CreateScene(const Request&); RequestResult CreateScene(const Request &);
RequestResult RemoveScene(const Request&); RequestResult RemoveScene(const Request &);
RequestResult SetSceneName(const Request&); RequestResult SetSceneName(const Request &);
RequestResult GetSceneSceneTransitionOverride(const Request&); RequestResult GetSceneSceneTransitionOverride(const Request &);
RequestResult SetSceneSceneTransitionOverride(const Request&); RequestResult SetSceneSceneTransitionOverride(const Request &);
// Inputs // Inputs
RequestResult GetInputList(const Request&); RequestResult GetInputList(const Request &);
RequestResult GetInputKindList(const Request&); RequestResult GetInputKindList(const Request &);
RequestResult GetSpecialInputs(const Request&); RequestResult GetSpecialInputs(const Request &);
RequestResult CreateInput(const Request&); RequestResult CreateInput(const Request &);
RequestResult RemoveInput(const Request&); RequestResult RemoveInput(const Request &);
RequestResult SetInputName(const Request&); RequestResult SetInputName(const Request &);
RequestResult GetInputDefaultSettings(const Request&); RequestResult GetInputDefaultSettings(const Request &);
RequestResult GetInputSettings(const Request&); RequestResult GetInputSettings(const Request &);
RequestResult SetInputSettings(const Request&); RequestResult SetInputSettings(const Request &);
RequestResult GetInputMute(const Request&); RequestResult GetInputMute(const Request &);
RequestResult SetInputMute(const Request&); RequestResult SetInputMute(const Request &);
RequestResult ToggleInputMute(const Request&); RequestResult ToggleInputMute(const Request &);
RequestResult GetInputVolume(const Request&); RequestResult GetInputVolume(const Request &);
RequestResult SetInputVolume(const Request&); RequestResult SetInputVolume(const Request &);
RequestResult GetInputAudioBalance(const Request&); RequestResult GetInputAudioBalance(const Request &);
RequestResult SetInputAudioBalance(const Request&); RequestResult SetInputAudioBalance(const Request &);
RequestResult GetInputAudioSyncOffset(const Request&); RequestResult GetInputAudioSyncOffset(const Request &);
RequestResult SetInputAudioSyncOffset(const Request&); RequestResult SetInputAudioSyncOffset(const Request &);
RequestResult GetInputAudioMonitorType(const Request&); RequestResult GetInputAudioMonitorType(const Request &);
RequestResult SetInputAudioMonitorType(const Request&); RequestResult SetInputAudioMonitorType(const Request &);
RequestResult GetInputAudioTracks(const Request&); RequestResult GetInputAudioTracks(const Request &);
RequestResult SetInputAudioTracks(const Request&); RequestResult SetInputAudioTracks(const Request &);
RequestResult GetInputPropertiesListPropertyItems(const Request&); RequestResult GetInputPropertiesListPropertyItems(const Request &);
RequestResult PressInputPropertiesButton(const Request&); RequestResult PressInputPropertiesButton(const Request &);
// Transitions // Transitions
RequestResult GetTransitionKindList(const Request&); RequestResult GetTransitionKindList(const Request &);
RequestResult GetSceneTransitionList(const Request&); RequestResult GetSceneTransitionList(const Request &);
RequestResult GetCurrentSceneTransition(const Request&); RequestResult GetCurrentSceneTransition(const Request &);
RequestResult SetCurrentSceneTransition(const Request&); RequestResult SetCurrentSceneTransition(const Request &);
RequestResult SetCurrentSceneTransitionDuration(const Request&); RequestResult SetCurrentSceneTransitionDuration(const Request &);
RequestResult SetCurrentSceneTransitionSettings(const Request&); RequestResult SetCurrentSceneTransitionSettings(const Request &);
RequestResult GetCurrentSceneTransitionCursor(const Request&); RequestResult GetCurrentSceneTransitionCursor(const Request &);
RequestResult TriggerStudioModeTransition(const Request&); RequestResult TriggerStudioModeTransition(const Request &);
RequestResult SetTBarPosition(const Request&); RequestResult SetTBarPosition(const Request &);
// Filters // Filters
RequestResult GetSourceFilterList(const Request&); RequestResult GetSourceFilterList(const Request &);
RequestResult GetSourceFilterDefaultSettings(const Request&); RequestResult GetSourceFilterDefaultSettings(const Request &);
RequestResult CreateSourceFilter(const Request&); RequestResult CreateSourceFilter(const Request &);
RequestResult RemoveSourceFilter(const Request&); RequestResult RemoveSourceFilter(const Request &);
RequestResult SetSourceFilterName(const Request&); RequestResult SetSourceFilterName(const Request &);
RequestResult GetSourceFilter(const Request&); RequestResult GetSourceFilter(const Request &);
RequestResult SetSourceFilterIndex(const Request&); RequestResult SetSourceFilterIndex(const Request &);
RequestResult SetSourceFilterSettings(const Request&); RequestResult SetSourceFilterSettings(const Request &);
RequestResult SetSourceFilterEnabled(const Request&); RequestResult SetSourceFilterEnabled(const Request &);
// Scene Items // Scene Items
RequestResult GetSceneItemList(const Request&); RequestResult GetSceneItemList(const Request &);
RequestResult GetGroupSceneItemList(const Request&); RequestResult GetGroupSceneItemList(const Request &);
RequestResult GetSceneItemId(const Request&); RequestResult GetSceneItemId(const Request &);
RequestResult CreateSceneItem(const Request&); RequestResult CreateSceneItem(const Request &);
RequestResult RemoveSceneItem(const Request&); RequestResult RemoveSceneItem(const Request &);
RequestResult DuplicateSceneItem(const Request&); RequestResult DuplicateSceneItem(const Request &);
RequestResult GetSceneItemTransform(const Request&); RequestResult GetSceneItemTransform(const Request &);
RequestResult SetSceneItemTransform(const Request&); RequestResult SetSceneItemTransform(const Request &);
RequestResult GetSceneItemEnabled(const Request&); RequestResult GetSceneItemEnabled(const Request &);
RequestResult SetSceneItemEnabled(const Request&); RequestResult SetSceneItemEnabled(const Request &);
RequestResult GetSceneItemLocked(const Request&); RequestResult GetSceneItemLocked(const Request &);
RequestResult SetSceneItemLocked(const Request&); RequestResult SetSceneItemLocked(const Request &);
RequestResult GetSceneItemIndex(const Request&); RequestResult GetSceneItemIndex(const Request &);
RequestResult SetSceneItemIndex(const Request&); RequestResult SetSceneItemIndex(const Request &);
RequestResult GetSceneItemBlendMode(const Request&); RequestResult GetSceneItemBlendMode(const Request &);
RequestResult SetSceneItemBlendMode(const Request&); RequestResult SetSceneItemBlendMode(const Request &);
RequestResult GetSceneItemPrivateSettings(const Request&); RequestResult GetSceneItemPrivateSettings(const Request &);
RequestResult SetSceneItemPrivateSettings(const Request&); RequestResult SetSceneItemPrivateSettings(const Request &);
// Outputs // Outputs
RequestResult GetVirtualCamStatus(const Request&); RequestResult GetVirtualCamStatus(const Request &);
RequestResult ToggleVirtualCam(const Request&); RequestResult ToggleVirtualCam(const Request &);
RequestResult StartVirtualCam(const Request&); RequestResult StartVirtualCam(const Request &);
RequestResult StopVirtualCam(const Request&); RequestResult StopVirtualCam(const Request &);
RequestResult GetReplayBufferStatus(const Request&); RequestResult GetReplayBufferStatus(const Request &);
RequestResult ToggleReplayBuffer(const Request&); RequestResult ToggleReplayBuffer(const Request &);
RequestResult StartReplayBuffer(const Request&); RequestResult StartReplayBuffer(const Request &);
RequestResult StopReplayBuffer(const Request&); RequestResult StopReplayBuffer(const Request &);
RequestResult SaveReplayBuffer(const Request&); RequestResult SaveReplayBuffer(const Request &);
RequestResult GetLastReplayBufferReplay(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 // Stream
RequestResult GetStreamStatus(const Request&); RequestResult GetStreamStatus(const Request &);
RequestResult ToggleStream(const Request&); RequestResult ToggleStream(const Request &);
RequestResult StartStream(const Request&); RequestResult StartStream(const Request &);
RequestResult StopStream(const Request&); RequestResult StopStream(const Request &);
RequestResult SendStreamCaption(const Request&); RequestResult SendStreamCaption(const Request &);
// Record // Record
RequestResult GetRecordStatus(const Request&); RequestResult GetRecordStatus(const Request &);
RequestResult ToggleRecord(const Request&); RequestResult ToggleRecord(const Request &);
RequestResult StartRecord(const Request&); RequestResult StartRecord(const Request &);
RequestResult StopRecord(const Request&); RequestResult StopRecord(const Request &);
RequestResult ToggleRecordPause(const Request&); RequestResult ToggleRecordPause(const Request &);
RequestResult PauseRecord(const Request&); RequestResult PauseRecord(const Request &);
RequestResult ResumeRecord(const Request&); RequestResult ResumeRecord(const Request &);
// Media Inputs // Media Inputs
RequestResult GetMediaInputStatus(const Request&); RequestResult GetMediaInputStatus(const Request &);
RequestResult SetMediaInputCursor(const Request&); RequestResult SetMediaInputCursor(const Request &);
RequestResult OffsetMediaInputCursor(const Request&); RequestResult OffsetMediaInputCursor(const Request &);
RequestResult TriggerMediaInputAction(const Request&); RequestResult TriggerMediaInputAction(const Request &);
// Ui // Ui
RequestResult GetStudioModeEnabled(const Request&); RequestResult GetStudioModeEnabled(const Request &);
RequestResult SetStudioModeEnabled(const Request&); RequestResult SetStudioModeEnabled(const Request &);
RequestResult OpenInputPropertiesDialog(const Request&); RequestResult OpenInputPropertiesDialog(const Request &);
RequestResult OpenInputFiltersDialog(const Request&); RequestResult OpenInputFiltersDialog(const Request &);
RequestResult OpenInputInteractDialog(const Request&); RequestResult OpenInputInteractDialog(const Request &);
RequestResult GetMonitorList(const Request&); RequestResult GetMonitorList(const Request &);
RequestResult OpenVideoMixProjector(const Request &);
RequestResult OpenSourceProjector(const Request &);
SessionPtr _session; SessionPtr _session;
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap; static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap;
}; };

View File

@ -37,7 +37,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetPersistentData(const Request& request) RequestResult RequestHandler::GetPersistentData(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -53,7 +53,8 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else 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 responseData;
json persistentData; json persistentData;
@ -79,11 +80,12 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetPersistentData(const Request& request) RequestResult RequestHandler::SetPersistentData(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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); return RequestResult::Error(statusCode, comment);
std::string realm = request.RequestData["realm"]; std::string realm = request.RequestData["realm"];
@ -96,13 +98,15 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else 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(); json persistentData = json::object();
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData); Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
persistentData[slotName] = slotValue; persistentData[slotName] = slotValue;
if (!Utils::Json::SetJsonFileContent(persistentDataPath, persistentData)) 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(); return RequestResult::Success();
} }
@ -120,7 +124,7 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetSceneCollectionList(const Request&) RequestResult RequestHandler::GetSceneCollectionList(const Request &)
{ {
json responseData; json responseData;
responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection();
@ -142,7 +146,7 @@ RequestResult RequestHandler::GetSceneCollectionList(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request) RequestResult RequestHandler::SetCurrentSceneCollection(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -158,9 +162,10 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection(); std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentSceneCollectionName != sceneCollectionName) { if (currentSceneCollectionName != sceneCollectionName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
obs_frontend_set_current_scene_collection(static_cast<const char*>(param)); OBS_TASK_UI,
}, (void*)sceneCollectionName.c_str(), true); [](void *param) { obs_frontend_set_current_scene_collection(static_cast<const char *>(param)); },
(void *)sceneCollectionName.c_str(), true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -180,7 +185,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CreateSceneCollection(const Request& request) RequestResult RequestHandler::CreateSceneCollection(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -193,9 +198,10 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end()) if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); 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; 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) if (!success)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene collection."); return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene collection.");
@ -215,7 +221,7 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetProfileList(const Request&) RequestResult RequestHandler::GetProfileList(const Request &)
{ {
json responseData; json responseData;
responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile();
@ -235,7 +241,7 @@ RequestResult RequestHandler::GetProfileList(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetCurrentProfile(const Request& request) RequestResult RequestHandler::SetCurrentProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -251,9 +257,9 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile(); std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentProfileName != profileName) { if (currentProfileName != profileName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
obs_frontend_set_current_profile(static_cast<const char*>(param)); OBS_TASK_UI, [](void *param) { obs_frontend_set_current_profile(static_cast<const char *>(param)); },
}, (void*)profileName.c_str(), true); (void *)profileName.c_str(), true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -271,7 +277,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CreateProfile(const Request& request) RequestResult RequestHandler::CreateProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -284,8 +290,9 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); 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());
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection,
Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); return RequestResult::Success();
} }
@ -302,7 +309,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::RemoveProfile(const Request& request) RequestResult RequestHandler::RemoveProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -318,8 +325,9 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
if (profiles.size() < 2) if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources); return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow *mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection,
Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); return RequestResult::Success();
} }
@ -340,17 +348,18 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetProfileParameter(const Request& request) RequestResult RequestHandler::GetProfileParameter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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); return RequestResult::Error(statusCode, comment);
std::string parameterCategory = request.RequestData["parameterCategory"]; std::string parameterCategory = request.RequestData["parameterCategory"];
std::string parameterName = request.RequestData["parameterName"]; std::string parameterName = request.RequestData["parameterName"];
config_t* profile = obs_frontend_get_profile_config(); config_t *profile = obs_frontend_get_profile_config();
if (!profile) if (!profile)
blog(LOG_ERROR, "[RequestHandler::GetProfileParameter] Profile is invalid."); blog(LOG_ERROR, "[RequestHandler::GetProfileParameter] Profile is invalid.");
@ -358,7 +367,8 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
json responseData; json responseData;
if (config_has_default_value(profile, parameterCategory.c_str(), parameterName.c_str())) { 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["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())) { } 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["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str());
responseData["defaultParameterValue"] = nullptr; responseData["defaultParameterValue"] = nullptr;
@ -384,23 +394,24 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetProfileParameter(const Request& request) RequestResult RequestHandler::SetProfileParameter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("parameterCategory", statusCode, comment) && if (!(request.ValidateString("parameterCategory", statusCode, comment) &&
request.ValidateString("parameterName", statusCode, comment))) request.ValidateString("parameterName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string parameterCategory = request.RequestData["parameterCategory"]; std::string parameterCategory = request.RequestData["parameterCategory"];
std::string parameterName = request.RequestData["parameterName"]; 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 // Using check helpers here would just make the logic more complicated
if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) { if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) {
if (!config_remove_value(profile, parameterCategory.c_str(), parameterName.c_str())) 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()) { } else if (request.RequestData["parameterValue"].is_string()) {
std::string parameterValue = request.RequestData["parameterValue"]; std::string parameterValue = request.RequestData["parameterValue"];
config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str()); 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 * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVideoSettings(const Request&) RequestResult RequestHandler::GetVideoSettings(const Request &)
{ {
struct obs_video_info ovi; struct obs_video_info ovi;
if (!obs_get_video_info(&ovi)) if (!obs_get_video_info(&ovi))
@ -468,23 +479,27 @@ RequestResult RequestHandler::GetVideoSettings(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetVideoSettings(const Request& request) RequestResult RequestHandler::SetVideoSettings(const Request &request)
{ {
if (obs_video_active()) 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; RequestStatus::RequestStatus statusCode = RequestStatus::NoError;
std::string comment; std::string comment;
bool changeFps = (request.Contains("fpsNumerator") && request.Contains("fpsDenominator")); 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); return RequestResult::Error(statusCode, comment);
bool changeBaseRes = (request.Contains("baseWidth") && request.Contains("baseHeight")); 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); return RequestResult::Error(statusCode, comment);
bool changeOutputRes = (request.Contains("outputWidth") && request.Contains("outputHeight")); 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); return RequestResult::Error(statusCode, comment);
config_t *config = obs_frontend_get_profile_config(); config_t *config = obs_frontend_get_profile_config();
@ -527,7 +542,7 @@ RequestResult RequestHandler::SetVideoSettings(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStreamServiceSettings(const Request&) RequestResult RequestHandler::GetStreamServiceSettings(const Request &)
{ {
json responseData; json responseData;
@ -554,21 +569,24 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetStreamServiceSettings(const Request& request) RequestResult RequestHandler::SetStreamServiceSettings(const Request &request)
{ {
if (obs_frontend_streaming_active()) 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; RequestStatus::RequestStatus statusCode;
std::string comment; 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); return RequestResult::Error(statusCode, comment);
OBSService currentStreamService = obs_frontend_get_streaming_service(); OBSService currentStreamService = obs_frontend_get_streaming_service();
std::string streamServiceType = obs_service_get_type(currentStreamService); std::string streamServiceType = obs_service_get_type(currentStreamService);
std::string requestedStreamServiceType = request.RequestData["streamServiceType"]; 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. // Don't create a new service if the current service is the same type.
if (streamServiceType == requestedStreamServiceType) { if (streamServiceType == requestedStreamServiceType) {
@ -582,10 +600,13 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
obs_service_update(currentStreamService, newStreamServiceSettings); obs_service_update(currentStreamService, newStreamServiceSettings);
} else { } else {
// TODO: This leaks memory. I have no idea why. // 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. // TODO: Check service type here, instead of relying on service creation to fail.
if (!newStreamService) 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); obs_frontend_set_streaming_service(newStreamService);
} }
@ -607,7 +628,7 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
* @api requests * @api requests
* @category rconfig * @category rconfig
*/ */
RequestResult RequestHandler::GetRecordDirectory(const Request&) RequestResult RequestHandler::GetRecordDirectory(const Request &)
{ {
json responseData; json responseData;
responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();

View File

@ -33,12 +33,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilterList(const Request& request) RequestResult RequestHandler::GetSourceFilterList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if(!source) if (!source)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
@ -61,7 +61,7 @@ RequestResult RequestHandler::GetSourceFilterList(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& request) RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -97,13 +97,14 @@ RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& requ
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::CreateSourceFilter(const Request& request) RequestResult RequestHandler::CreateSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, 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); return RequestResult::Error(statusCode, comment);
std::string filterName = request.RequestData["filterName"]; std::string filterName = request.RequestData["filterName"];
@ -114,7 +115,9 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
std::string filterKind = request.RequestData["filterKind"]; std::string filterKind = request.RequestData["filterKind"];
auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList(); auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList();
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end()) 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; OBSDataAutoRelease filterSettings = nullptr;
if (request.Contains("filterSettings")) { if (request.Contains("filterSettings")) {
@ -126,7 +129,7 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
OBSSourceAutoRelease filter = Utils::Obs::ActionHelper::CreateSourceFilter(source, filterName, filterKind, filterSettings); 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::Error(RequestStatus::ResourceCreationFailed, "Creation of the filter failed.");
return RequestResult::Success(); return RequestResult::Success();
@ -145,7 +148,7 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::RemoveSourceFilter(const Request& request) RequestResult RequestHandler::RemoveSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -172,7 +175,7 @@ RequestResult RequestHandler::RemoveSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterName(const Request& request) RequestResult RequestHandler::SetSourceFilterName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -209,7 +212,7 @@ RequestResult RequestHandler::SetSourceFilterName(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilter(const Request& request) RequestResult RequestHandler::GetSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -219,7 +222,8 @@ RequestResult RequestHandler::GetSourceFilter(const Request& request)
json responseData; json responseData;
responseData["filterEnabled"] = obs_source_enabled(pair.filter); 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); responseData["filterKind"] = obs_source_get_id(pair.filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter); OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter);
@ -242,7 +246,7 @@ RequestResult RequestHandler::GetSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterIndex(const Request& request) RequestResult RequestHandler::SetSourceFilterIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -272,7 +276,7 @@ RequestResult RequestHandler::SetSourceFilterIndex(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterSettings(const Request& request) RequestResult RequestHandler::SetSourceFilterSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -292,7 +296,8 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]);
if (!newSettings) 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) if (overlay)
obs_source_update(pair.filter, newSettings); obs_source_update(pair.filter, newSettings);
@ -318,7 +323,7 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterEnabled(const Request& request) RequestResult RequestHandler::SetSourceFilterEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;

View File

@ -26,7 +26,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../WebSocketApi.h" #include "../WebSocketApi.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
/** /**
* Gets data about the current plugin and RPC version. * Gets data about the current plugin and RPC version.
* *
@ -45,7 +44,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVersion(const Request&) RequestResult RequestHandler::GetVersion(const Request &)
{ {
json responseData; json responseData;
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion(); responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
@ -55,7 +54,7 @@ RequestResult RequestHandler::GetVersion(const Request&)
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats(); QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
std::vector<std::string> supportedImageFormats; std::vector<std::string> supportedImageFormats;
for (const QByteArray& format : imageWriterFormats) { for (const QByteArray &format : imageWriterFormats) {
supportedImageFormats.push_back(format.toStdString()); supportedImageFormats.push_back(format.toStdString());
} }
responseData["supportedImageFormats"] = supportedImageFormats; responseData["supportedImageFormats"] = supportedImageFormats;
@ -88,7 +87,7 @@ RequestResult RequestHandler::GetVersion(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStats(const Request&) RequestResult RequestHandler::GetStats(const Request &)
{ {
json responseData = Utils::Obs::ObjectHelper::GetStats(); json responseData = Utils::Obs::ObjectHelper::GetStats();
@ -115,7 +114,7 @@ RequestResult RequestHandler::GetStats(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::BroadcastCustomEvent(const Request& request) RequestResult RequestHandler::BroadcastCustomEvent(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -141,6 +140,8 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
* @requestField requestType | String | The request type to call * @requestField requestType | String | The request type to call
* @requestField ?requestData | Object | Object containing appropriate request data | {} * @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 * @responseField responseData | Object | Object containing appropriate response data. {} if request does not provide any response data
* *
* @requestType CallVendorRequest * @requestType CallVendorRequest
@ -150,11 +151,12 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CallVendorRequest(const Request& request) RequestResult RequestHandler::CallVendorRequest(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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); return RequestResult::Error(statusCode, comment);
std::string vendorName = request.RequestData["vendorName"]; std::string vendorName = request.RequestData["vendorName"];
@ -172,20 +174,23 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
auto webSocketApi = GetWebSocketApi(); auto webSocketApi = GetWebSocketApi();
if (!webSocketApi) 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); auto ret = webSocketApi->PerformVendorRequest(vendorName, requestType, requestData, obsResponseData);
switch (ret) { switch (ret) {
default: default:
case WebSocketApi::RequestReturnCode::Normal: case WebSocketApi::RequestReturnCode::Normal:
break; break;
case WebSocketApi::RequestReturnCode::NoVendor: case WebSocketApi::RequestReturnCode::NoVendor:
return RequestResult::Error(RequestStatus::ResourceNotFound, "No vendor was found by that name."); return RequestResult::Error(RequestStatus::ResourceNotFound, "No vendor was found by that name.");
case WebSocketApi::RequestReturnCode::NoVendorRequest: case WebSocketApi::RequestReturnCode::NoVendorRequest:
return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name."); return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name.");
} }
json responseData; json responseData;
responseData["vendorName"] = vendorName;
responseData["requestType"] = requestType;
responseData["responseData"] = Utils::Json::ObsDataToJson(obsResponseData); responseData["responseData"] = Utils::Json::ObsDataToJson(obsResponseData);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
@ -203,7 +208,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetHotkeyList(const Request&) RequestResult RequestHandler::GetHotkeyList(const Request &)
{ {
json responseData; json responseData;
responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList(); responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList();
@ -222,7 +227,7 @@ RequestResult RequestHandler::GetHotkeyList(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::TriggerHotkeyByName(const Request& request) RequestResult RequestHandler::TriggerHotkeyByName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -255,7 +260,7 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request) RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request &request)
{ {
obs_key_combination_t combo = {0}; obs_key_combination_t combo = {0};
@ -277,19 +282,23 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
const json keyModifiersJson = request.RequestData["keyModifiers"]; const json keyModifiersJson = request.RequestData["keyModifiers"];
uint32_t keyModifiers = 0; 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; 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; keyModifiers |= INTERACT_CONTROL_KEY;
if (keyModifiersJson.contains("alt") && keyModifiersJson["alt"].is_boolean() && keyModifiersJson["alt"].get<bool>()) if (keyModifiersJson.contains("alt") && keyModifiersJson["alt"].is_boolean() && keyModifiersJson["alt"].get<bool>())
keyModifiers |= INTERACT_ALT_KEY; 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; keyModifiers |= INTERACT_COMMAND_KEY;
combo.modifiers = keyModifiers; combo.modifiers = keyModifiers;
} }
if (!combo.modifiers && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE)) 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 // Apparently things break when you don't start by setting the combo to false
obs_hotkey_inject_event(combo, false); obs_hotkey_inject_event(combo, false);
@ -312,7 +321,7 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::Sleep(const Request& request) RequestResult RequestHandler::Sleep(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;

View File

@ -33,7 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputList(const Request& request) RequestResult RequestHandler::GetInputList(const Request &request)
{ {
std::string inputKind; std::string inputKind;
@ -65,7 +65,7 @@ RequestResult RequestHandler::GetInputList(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputKindList(const Request& request) RequestResult RequestHandler::GetInputKindList(const Request &request)
{ {
bool unversioned = false; bool unversioned = false;
@ -100,7 +100,7 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetSpecialInputs(const Request&) RequestResult RequestHandler::GetSpecialInputs(const Request &)
{ {
json responseData; json responseData;
@ -138,12 +138,13 @@ RequestResult RequestHandler::GetSpecialInputs(const Request&)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::CreateInput(const Request& request) RequestResult RequestHandler::CreateInput(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, 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); return RequestResult::Error(statusCode, comment);
std::string inputName = request.RequestData["inputName"]; std::string inputName = request.RequestData["inputName"];
@ -154,7 +155,9 @@ RequestResult RequestHandler::CreateInput(const Request& request)
std::string inputKind = request.RequestData["inputKind"]; std::string inputKind = request.RequestData["inputKind"];
auto kinds = Utils::Obs::ArrayHelper::GetInputKindList(); auto kinds = Utils::Obs::ArrayHelper::GetInputKindList();
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end()) 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; OBSDataAutoRelease inputSettings = nullptr;
if (request.Contains("inputSettings")) { 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 // 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) if (!sceneItem)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the input or scene item failed."); 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 * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::RemoveInput(const Request& request) RequestResult RequestHandler::RemoveInput(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -228,7 +232,7 @@ RequestResult RequestHandler::RemoveInput(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputName(const Request& request) RequestResult RequestHandler::SetInputName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -240,7 +244,8 @@ RequestResult RequestHandler::SetInputName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str()); OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str());
if (existingSource) 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()); obs_source_set_name(input, newInputName.c_str());
@ -261,7 +266,7 @@ RequestResult RequestHandler::SetInputName(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputDefaultSettings(const Request& request) RequestResult RequestHandler::GetInputDefaultSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -299,7 +304,7 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputSettings(const Request& request) RequestResult RequestHandler::GetInputSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -329,7 +334,7 @@ RequestResult RequestHandler::GetInputSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputSettings(const Request& request) RequestResult RequestHandler::SetInputSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -349,7 +354,8 @@ RequestResult RequestHandler::SetInputSettings(const Request& request)
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["inputSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["inputSettings"]);
if (!newSettings) if (!newSettings)
// This should never happen // 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) if (overlay)
// Applies the new settings on top of the existing user settings // Applies the new settings on top of the existing user settings
@ -378,7 +384,7 @@ RequestResult RequestHandler::SetInputSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputMute(const Request& request) RequestResult RequestHandler::GetInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -407,7 +413,7 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputMute(const Request& request) RequestResult RequestHandler::SetInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -437,7 +443,7 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::ToggleInputMute(const Request& request) RequestResult RequestHandler::ToggleInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -471,7 +477,7 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputVolume(const Request& request) RequestResult RequestHandler::GetInputVolume(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -507,7 +513,7 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputVolume(const Request& request) RequestResult RequestHandler::SetInputVolume(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -557,7 +563,7 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioBalance(const Request& request) RequestResult RequestHandler::GetInputAudioBalance(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -587,7 +593,7 @@ RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioBalance(const Request& request) RequestResult RequestHandler::SetInputAudioBalance(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -620,7 +626,7 @@ RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request) RequestResult RequestHandler::GetInputAudioSyncOffset(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -651,7 +657,7 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request) RequestResult RequestHandler::SetInputAudioSyncOffset(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -688,7 +694,7 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request) RequestResult RequestHandler::GetInputAudioMonitorType(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -700,7 +706,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData; json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input); responseData["monitorType"] = obs_source_get_monitoring_type(input);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -718,7 +724,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request) RequestResult RequestHandler::SetInputAudioMonitorType(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -730,7 +736,8 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
if (!obs_audio_monitoring_available()) 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; enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"]; std::string monitorTypeString = request.RequestData["monitorType"];
@ -741,7 +748,8 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT") else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT")
monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT; monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT;
else 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); obs_source_set_monitoring_type(input, monitorType);
@ -762,7 +770,7 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioTracks(const Request& request) RequestResult RequestHandler::GetInputAudioTracks(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -799,7 +807,7 @@ RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioTracks(const Request& request) RequestResult RequestHandler::SetInputAudioTracks(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -821,7 +829,8 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
continue; continue;
if (!inputAudioTracks[track].is_boolean()) 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]; bool enabled = inputAudioTracks[track];
@ -854,7 +863,7 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request& request) RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -892,7 +901,7 @@ RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request&
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::PressInputPropertiesButton(const Request& request) RequestResult RequestHandler::PressInputPropertiesButton(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;

View File

@ -52,7 +52,7 @@ bool IsMediaTimeValid(obs_source_t *input)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::GetMediaInputStatus(const Request& request) RequestResult RequestHandler::GetMediaInputStatus(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -61,7 +61,8 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input); responseData["mediaState"] = obs_source_media_get_state(input);
;
if (IsMediaTimeValid(input)) { if (IsMediaTimeValid(input)) {
responseData["mediaDuration"] = obs_source_media_get_duration(input); responseData["mediaDuration"] = obs_source_media_get_duration(input);
@ -89,7 +90,7 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::SetMediaInputCursor(const Request& request) RequestResult RequestHandler::SetMediaInputCursor(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -98,7 +99,8 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input)) 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"]; int64_t mediaCursor = request.RequestData["mediaCursor"];
@ -123,7 +125,7 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request) RequestResult RequestHandler::OffsetMediaInputCursor(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -132,7 +134,8 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input)) 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 mediaCursorOffset = request.RequestData["mediaCursorOffset"];
int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset; int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset;
@ -158,7 +161,7 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::TriggerMediaInputAction(const Request& request) RequestResult RequestHandler::TriggerMediaInputAction(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -166,33 +169,33 @@ RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
if (!(input && request.ValidateString("mediaAction", statusCode, comment))) if (!(input && request.ValidateString("mediaAction", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string mediaActionString = request.RequestData["mediaAction"]; enum ObsMediaInputAction mediaAction = request.RequestData["mediaAction"];
auto mediaAction = Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString);
switch (mediaAction) { switch (mediaAction) {
default: default:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE: case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
return RequestResult::Error(RequestStatus::InvalidRequestField, "You have specified an invalid media input action."); return RequestResult::Error(RequestStatus::InvalidRequestField,
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY: "You have specified an invalid media input action.");
// Shoutout to whoever implemented this API call like this case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
obs_source_media_play_pause(input, false); // Shoutout to whoever implemented this API call like this
break; obs_source_media_play_pause(input, false);
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE: break;
obs_source_media_play_pause(input, true); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
break; obs_source_media_play_pause(input, true);
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP: break;
obs_source_media_stop(input); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
break; obs_source_media_stop(input);
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART: break;
// I'm only implementing this because I'm nice. I think its a really dumb action. case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
obs_source_media_restart(input); // I'm only implementing this because I'm nice. I think its a really dumb action.
break; obs_source_media_restart(input);
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT: break;
obs_source_media_next(input); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
break; obs_source_media_next(input);
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS: break;
obs_source_media_previous(input); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
break; obs_source_media_previous(input);
break;
} }
return RequestResult::Success(); return RequestResult::Success();

View File

@ -46,7 +46,7 @@ static bool ReplayBufferAvailable()
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVirtualCamStatus(const Request&) RequestResult RequestHandler::GetVirtualCamStatus(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
@ -68,7 +68,7 @@ RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::ToggleVirtualCam(const Request&) RequestResult RequestHandler::ToggleVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
@ -95,7 +95,7 @@ RequestResult RequestHandler::ToggleVirtualCam(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StartVirtualCam(const Request&) RequestResult RequestHandler::StartVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
@ -118,7 +118,7 @@ RequestResult RequestHandler::StartVirtualCam(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StopVirtualCam(const Request&) RequestResult RequestHandler::StopVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
@ -143,7 +143,7 @@ RequestResult RequestHandler::StopVirtualCam(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetReplayBufferStatus(const Request&) RequestResult RequestHandler::GetReplayBufferStatus(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -165,7 +165,7 @@ RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::ToggleReplayBuffer(const Request&) RequestResult RequestHandler::ToggleReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -192,7 +192,7 @@ RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StartReplayBuffer(const Request&) RequestResult RequestHandler::StartReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -215,7 +215,7 @@ RequestResult RequestHandler::StartReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StopReplayBuffer(const Request&) RequestResult RequestHandler::StopReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -238,7 +238,7 @@ RequestResult RequestHandler::StopReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::SaveReplayBuffer(const Request&) RequestResult RequestHandler::SaveReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -263,7 +263,7 @@ RequestResult RequestHandler::SaveReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&) RequestResult RequestHandler::GetLastReplayBufferReplay(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
@ -272,6 +272,217 @@ RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&)
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
json responseData; json responseData;
responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath(); responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFileName();
return RequestResult::Success(responseData); 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();
}

View File

@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::GetRecordStatus(const Request&) RequestResult RequestHandler::GetRecordStatus(const Request &)
{ {
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
@ -61,7 +61,7 @@ RequestResult RequestHandler::GetRecordStatus(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ToggleRecord(const Request&) RequestResult RequestHandler::ToggleRecord(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_recording_active()) { if (obs_frontend_recording_active()) {
@ -85,7 +85,7 @@ RequestResult RequestHandler::ToggleRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::StartRecord(const Request&) RequestResult RequestHandler::StartRecord(const Request &)
{ {
if (obs_frontend_recording_active()) if (obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -99,6 +99,8 @@ RequestResult RequestHandler::StartRecord(const Request&)
/** /**
* Stops the record output. * Stops the record output.
* *
* @responseField outputPath | String | File name for the saved recording
*
* @requestType StopRecord * @requestType StopRecord
* @complexity 1 * @complexity 1
* @rpcVersion -1 * @rpcVersion -1
@ -106,7 +108,7 @@ RequestResult RequestHandler::StartRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::StopRecord(const Request&) RequestResult RequestHandler::StopRecord(const Request &)
{ {
if (!obs_frontend_recording_active()) if (!obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -114,7 +116,10 @@ RequestResult RequestHandler::StopRecord(const Request&)
// TODO: Call signal directly to perform blocking wait // TODO: Call signal directly to perform blocking wait
obs_frontend_recording_stop(); 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 * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ToggleRecordPause(const Request&) RequestResult RequestHandler::ToggleRecordPause(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_recording_paused()) { if (obs_frontend_recording_paused()) {
@ -151,7 +156,7 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::PauseRecord(const Request&) RequestResult RequestHandler::PauseRecord(const Request &)
{ {
if (obs_frontend_recording_paused()) if (obs_frontend_recording_paused())
return RequestResult::Error(RequestStatus::OutputPaused); return RequestResult::Error(RequestStatus::OutputPaused);
@ -172,7 +177,7 @@ RequestResult RequestHandler::PauseRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ResumeRecord(const Request&) RequestResult RequestHandler::ResumeRecord(const Request &)
{ {
if (!obs_frontend_recording_paused()) if (!obs_frontend_recording_paused())
return RequestResult::Error(RequestStatus::OutputNotPaused); return RequestResult::Error(RequestStatus::OutputNotPaused);

View File

@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemList(const Request& request) RequestResult RequestHandler::GetSceneItemList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -67,7 +67,7 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetGroupSceneItemList(const Request& request) RequestResult RequestHandler::GetGroupSceneItemList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -99,11 +99,12 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemId(const Request& request) RequestResult RequestHandler::GetSceneItemId(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(scene && request.ValidateString("sourceName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -118,7 +119,8 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName, offset); OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName, offset);
if (!item) if (!item)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene items were found in the specified scene by that name or offset."); return RequestResult::Error(RequestStatus::ResourceNotFound,
"No scene items were found in the specified scene by that name or offset.");
json responseData; json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(item); responseData["sceneItemId"] = obs_sceneitem_get_id(item);
@ -144,7 +146,7 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::CreateSceneItem(const Request& request) RequestResult RequestHandler::CreateSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -193,7 +195,7 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::RemoveSceneItem(const Request& request) RequestResult RequestHandler::RemoveSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -225,7 +227,7 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::DuplicateSceneItem(const Request& request) RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -242,7 +244,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
} else { } else {
destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem)); destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem));
if (!destinationScene) 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) { if (obs_sceneitem_is_group(sceneItem) && obs_sceneitem_get_scene(sceneItem) == destinationScene) {
@ -259,7 +262,8 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
obs_sceneitem_get_crop(sceneItem, &sceneItemCrop); obs_sceneitem_get_crop(sceneItem, &sceneItemCrop);
// Create the new item // 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); obs_scene_release(destinationScene);
if (!newSceneItem) if (!newSceneItem)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene item."); return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene item.");
@ -287,11 +291,12 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemTransform(const Request& request) RequestResult RequestHandler::GetSceneItemTransform(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -315,11 +320,12 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemTransform(const Request& request) RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(sceneItem && request.ValidateObject("sceneItemTransform", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -363,7 +369,8 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
float scaleX = r.RequestData["scaleX"]; float scaleX = r.RequestData["scaleX"];
float finalWidth = scaleX * sourceWidth; float finalWidth = scaleX * sourceWidth;
if (!(finalWidth > -90001.0 && finalWidth < 90001.0)) 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; sceneItemTransform.scale.x = scaleX;
transformChanged = true; transformChanged = true;
} }
@ -373,7 +380,8 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
float scaleY = r.RequestData["scaleY"]; float scaleY = r.RequestData["scaleY"];
float finalHeight = scaleY * sourceHeight; float finalHeight = scaleY * sourceHeight;
if (!(finalHeight > -90001.0 && finalHeight < 90001.0)) 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; sceneItemTransform.scale.y = scaleY;
transformChanged = true; transformChanged = true;
} }
@ -388,10 +396,10 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
if (r.Contains("boundsType")) { if (r.Contains("boundsType")) {
if (!r.ValidateOptionalString("boundsType", statusCode, comment)) if (!r.ValidateOptionalString("boundsType", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string boundsTypeString = r.RequestData["boundsType"]; enum obs_bounds_type boundsType = r.RequestData["boundsType"];
enum obs_bounds_type boundsType = Utils::Obs::EnumHelper::GetSceneItemBoundsType(boundsTypeString); if (boundsType == OBS_BOUNDS_NONE && r.RequestData["boundsType"] != "OBS_BOUNDS_NONE")
if (boundsType == OBS_BOUNDS_NONE && boundsTypeString != "OBS_BOUNDS_NONE") return RequestResult::Error(RequestStatus::InvalidRequestField,
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field boundsType has an invalid value."); "The field `boundsType` has an invalid value.");
sceneItemTransform.bounds_type = boundsType; sceneItemTransform.bounds_type = boundsType;
transformChanged = true; transformChanged = true;
} }
@ -470,11 +478,12 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemEnabled(const Request& request) RequestResult RequestHandler::GetSceneItemEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -500,11 +509,12 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemEnabled(const Request& request) RequestResult RequestHandler::SetSceneItemEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(sceneItem && request.ValidateBoolean("sceneItemEnabled", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -532,11 +542,12 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemLocked(const Request& request) RequestResult RequestHandler::GetSceneItemLocked(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -562,11 +573,12 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemLocked(const Request& request) RequestResult RequestHandler::SetSceneItemLocked(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(sceneItem && request.ValidateBoolean("sceneItemLocked", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -596,11 +608,12 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemIndex(const Request& request) RequestResult RequestHandler::GetSceneItemIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -626,11 +639,12 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemIndex(const Request& request) RequestResult RequestHandler::SetSceneItemIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(sceneItem && request.ValidateNumber("sceneItemIndex", statusCode, comment, 0, 8192)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -668,18 +682,19 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request) RequestResult RequestHandler::GetSceneItemBlendMode(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
auto blendMode = obs_sceneitem_get_blending_mode(sceneItem); auto blendMode = obs_sceneitem_get_blending_mode(sceneItem);
json responseData; json responseData;
responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode); responseData["sceneItemBlendMode"] = blendMode;
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -700,19 +715,19 @@ RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request) RequestResult RequestHandler::SetSceneItemBlendMode(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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))) if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string blendModeString = request.RequestData["sceneItemBlendMode"]; enum obs_blending_type blendMode = request.RequestData["sceneItemBlendMode"];
if (blendMode == OBS_BLEND_NORMAL && request.RequestData["sceneItemBlendMode"] != "OBS_BLEND_NORMAL")
auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString); return RequestResult::Error(RequestStatus::InvalidRequestField,
if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL") "The field sceneItemBlendMode has an invalid value.");
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value.");
obs_sceneitem_set_blending_mode(sceneItem, blendMode); obs_sceneitem_set_blending_mode(sceneItem, blendMode);
@ -720,11 +735,12 @@ RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request)
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request& request) RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -737,12 +753,13 @@ RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request& request
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::SetSceneItemPrivateSettings(const Request& request) RequestResult RequestHandler::SetSceneItemPrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; 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,
if (!sceneItem || !request.ValidateObject("sceneItemSettings", statusCode, comment)) OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem || !request.ValidateObject("sceneItemSettings", statusCode, comment, true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem); OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem);

View File

@ -33,7 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetSceneList(const Request&) RequestResult RequestHandler::GetSceneList(const Request &)
{ {
json responseData; json responseData;
@ -68,7 +68,7 @@ RequestResult RequestHandler::GetSceneList(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetGroupList(const Request&) RequestResult RequestHandler::GetGroupList(const Request &)
{ {
json responseData; json responseData;
@ -89,7 +89,7 @@ RequestResult RequestHandler::GetGroupList(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetCurrentProgramScene(const Request&) RequestResult RequestHandler::GetCurrentProgramScene(const Request &)
{ {
json responseData; json responseData;
OBSSourceAutoRelease currentProgramScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentProgramScene = obs_frontend_get_current_scene();
@ -110,7 +110,7 @@ RequestResult RequestHandler::GetCurrentProgramScene(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetCurrentProgramScene(const Request& request) RequestResult RequestHandler::SetCurrentProgramScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -137,7 +137,7 @@ RequestResult RequestHandler::SetCurrentProgramScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetCurrentPreviewScene(const Request&) RequestResult RequestHandler::GetCurrentPreviewScene(const Request &)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
@ -164,7 +164,7 @@ RequestResult RequestHandler::GetCurrentPreviewScene(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request) RequestResult RequestHandler::SetCurrentPreviewScene(const Request &request)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
@ -192,7 +192,7 @@ RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::CreateScene(const Request& request) RequestResult RequestHandler::CreateScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -226,7 +226,7 @@ RequestResult RequestHandler::CreateScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::RemoveScene(const Request& request) RequestResult RequestHandler::RemoveScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -235,7 +235,8 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (Utils::Obs::NumberHelper::GetSceneCount() < 2) 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); obs_source_remove(scene);
@ -255,7 +256,7 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetSceneName(const Request& request) RequestResult RequestHandler::SetSceneName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -267,7 +268,8 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str()); OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str());
if (existingSource) 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()); obs_source_set_name(scene, newSceneName.c_str());
@ -289,7 +291,7 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& request) RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -328,7 +330,7 @@ RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& req
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& request) RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -342,7 +344,8 @@ RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& req
if (hasName && !request.RequestData["transitionName"].is_null()) { if (hasName && !request.RequestData["transitionName"].is_null()) {
if (!request.ValidateOptionalString("transitionName", statusCode, comment)) if (!request.ValidateOptionalString("transitionName", statusCode, comment))
return RequestResult::Error(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) if (!transition)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name."); 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) 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 (hasName) {
if (request.RequestData["transitionName"].is_null()) { if (request.RequestData["transitionName"].is_null()) {

View File

@ -56,14 +56,14 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
ret.fill(0); ret.fill(0);
// Video image buffer // Video image buffer
uint8_t* videoData = nullptr; uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0; uint32_t videoLinesize = 0;
// Enter graphics context // Enter graphics context
obs_enter_graphics(); obs_enter_graphics();
gs_texrender_t* texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); gs_texrender_t *texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
gs_stagesurf_t* stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA); gs_stagesurf_t *stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA);
success = false; success = false;
gs_texrender_reset(texRender); 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)) { if (gs_stagesurface_map(stageSurface, &videoData, &videoLinesize)) {
int lineSize = ret.bytesPerLine(); int lineSize = ret.bytesPerLine();
for (uint y = 0; y < imgHeight; y++) { 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); gs_stagesurface_unmap(stageSurface);
success = true; success = true;
@ -126,7 +126,7 @@ bool IsImageFormatValid(std::string format)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::GetSourceActive(const Request& request) RequestResult RequestHandler::GetSourceActive(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -166,7 +166,7 @@ RequestResult RequestHandler::GetSourceActive(const Request& request)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::GetSourceScreenshot(const Request& request) RequestResult RequestHandler::GetSourceScreenshot(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -180,7 +180,8 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
std::string imageFormat = request.RequestData["imageFormat"]; std::string imageFormat = request.RequestData["imageFormat"];
if (!IsImageFormatValid(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 requestedWidth{0};
uint32_t requestedHeight{0}; uint32_t requestedHeight{0};
@ -253,12 +254,13 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::SaveSourceScreenshot(const Request& request) RequestResult RequestHandler::SaveSourceScreenshot(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, 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); return RequestResult::Error(statusCode, comment);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) 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"]; std::string imageFilePath = request.RequestData["imageFilePath"];
if (!IsImageFormatValid(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.");
QFileInfo filePathInfo(QString::fromStdString(imageFilePath)); QFileInfo filePathInfo(QString::fromStdString(imageFilePath));
if (!filePathInfo.absoluteDir().exists()) if (!filePathInfo.absoluteDir().exists())
@ -314,7 +317,7 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request) RequestResult RequestHandler::GetSourcePrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -331,12 +334,12 @@ RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request)
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request) RequestResult RequestHandler::SetSourcePrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source || !request.ValidateObject("sourceSettings", statusCode, comment)) if (!source || !request.ValidateObject("sourceSettings", statusCode, comment, true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source); OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source);

View File

@ -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 outputReconnecting | Boolean | Whether the output is currently reconnecting
* @responseField outputTimecode | String | Current formatted timecode string for the output * @responseField outputTimecode | String | Current formatted timecode string for the output
* @responseField outputDuration | Number | Current duration in milliseconds 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 outputBytes | Number | Number of bytes sent by the output
* @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process * @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 * @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 * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::GetStreamStatus(const Request&) RequestResult RequestHandler::GetStreamStatus(const Request &)
{ {
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -48,6 +49,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput); responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration); responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
responseData["outputDuration"] = outputDuration; responseData["outputDuration"] = outputDuration;
responseData["outputCongestion"] = obs_output_get_congestion(streamOutput);
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput); responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput);
responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput); responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput);
responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput); responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput);
@ -67,7 +69,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::ToggleStream(const Request&) RequestResult RequestHandler::ToggleStream(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_streaming_active()) { if (obs_frontend_streaming_active()) {
@ -91,7 +93,7 @@ RequestResult RequestHandler::ToggleStream(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::StartStream(const Request&) RequestResult RequestHandler::StartStream(const Request &)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -112,7 +114,7 @@ RequestResult RequestHandler::StartStream(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::StopStream(const Request&) RequestResult RequestHandler::StopStream(const Request &)
{ {
if (!obs_frontend_streaming_active()) if (!obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -135,7 +137,7 @@ RequestResult RequestHandler::StopStream(const Request&)
* @category stream * @category stream
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SendStreamCaption(const Request& request) RequestResult RequestHandler::SendStreamCaption(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;

View File

@ -35,7 +35,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetTransitionKindList(const Request&) RequestResult RequestHandler::GetTransitionKindList(const Request &)
{ {
json responseData; json responseData;
responseData["transitionKinds"] = Utils::Obs::ArrayHelper::GetTransitionKindList(); responseData["transitionKinds"] = Utils::Obs::ArrayHelper::GetTransitionKindList();
@ -56,7 +56,7 @@ RequestResult RequestHandler::GetTransitionKindList(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetSceneTransitionList(const Request&) RequestResult RequestHandler::GetSceneTransitionList(const Request &)
{ {
json responseData; json responseData;
@ -91,11 +91,12 @@ RequestResult RequestHandler::GetSceneTransitionList(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetCurrentSceneTransition(const Request&) RequestResult RequestHandler::GetCurrentSceneTransition(const Request &)
{ {
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!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; json responseData;
responseData["transitionName"] = obs_source_get_name(transition); responseData["transitionName"] = obs_source_get_name(transition);
@ -135,7 +136,7 @@ RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request) RequestResult RequestHandler::SetCurrentSceneTransition(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -165,7 +166,7 @@ RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& request) RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -192,7 +193,7 @@ RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& r
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& request) RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -201,10 +202,12 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!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)) 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; bool overlay = true;
if (request.Contains("overlay")) { if (request.Contains("overlay")) {
@ -216,7 +219,8 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["transitionSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["transitionSettings"]);
if (!newSettings) 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) if (overlay)
obs_source_update(transition, newSettings); obs_source_update(transition, newSettings);
@ -242,11 +246,12 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&) RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request &)
{ {
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!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; json responseData;
responseData["transitionCursor"] = obs_transition_get_time(transition); responseData["transitionCursor"] = obs_transition_get_time(transition);
@ -264,7 +269,7 @@ RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::TriggerStudioModeTransition(const Request&) RequestResult RequestHandler::TriggerStudioModeTransition(const Request &)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
@ -291,7 +296,7 @@ RequestResult RequestHandler::TriggerStudioModeTransition(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetTBarPosition(const Request& request) RequestResult RequestHandler::SetTBarPosition(const Request &request)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
@ -309,7 +314,8 @@ RequestResult RequestHandler::SetTBarPosition(const Request& request)
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!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"]; float position = request.RequestData["position"];

View File

@ -36,7 +36,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStudioModeEnabled(const Request&) RequestResult RequestHandler::GetStudioModeEnabled(const Request &)
{ {
json responseData; json responseData;
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active(); responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active();
@ -55,7 +55,7 @@ RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request) RequestResult RequestHandler::SetStudioModeEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -67,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 // (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"]; bool studioModeEnabled = request.RequestData["studioModeEnabled"];
// Queue the task inside of the UI thread to prevent race conditions // Queue the task inside of the UI thread to prevent race conditions
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
auto studioModeEnabled = (bool*)param; OBS_TASK_UI,
obs_frontend_set_preview_program_mode(*studioModeEnabled); [](void *param) {
}, &studioModeEnabled, true); auto studioModeEnabled = (bool *)param;
obs_frontend_set_preview_program_mode(*studioModeEnabled);
},
&studioModeEnabled, true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -88,7 +91,7 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request) RequestResult RequestHandler::OpenInputPropertiesDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -113,7 +116,7 @@ RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request) RequestResult RequestHandler::OpenInputFiltersDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -138,7 +141,7 @@ RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputInteractDialog(const Request& request) RequestResult RequestHandler::OpenInputInteractDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -147,7 +150,8 @@ RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION)) 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); obs_frontend_open_source_interaction(input);
@ -166,19 +170,19 @@ RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetMonitorList(const Request&) RequestResult RequestHandler::GetMonitorList(const Request &)
{ {
json responseData; json responseData;
std::vector<json> monitorsData; std::vector<json> monitorsData;
QList<QScreen *> screensList = QGuiApplication::screens(); QList<QScreen *> screensList = QGuiApplication::screens();
for (int screenIndex = 0; screenIndex < screensList.size(); screenIndex++) for (int screenIndex = 0; screenIndex < screensList.size(); screenIndex++) {
{
json screenData; json screenData;
QScreen const* screen = screensList[screenIndex]; QScreen const *screen = screensList[screenIndex];
std::stringstream nameAndIndex; std::stringstream nameAndIndex;
nameAndIndex << screen->name().toStdString(); nameAndIndex << screen->name().toStdString();
nameAndIndex << '(' << screenIndex << ')'; nameAndIndex << '(' << screenIndex << ')';
screenData["monitorName"] = nameAndIndex.str(); screenData["monitorName"] = nameAndIndex.str();
screenData["monitorIndex"] = screenIndex;
const QRect screenGeometry = screen->geometry(); const QRect screenGeometry = screen->geometry();
screenData["monitorWidth"] = screenGeometry.width(); screenData["monitorWidth"] = screenGeometry.width();
screenData["monitorHeight"] = screenGeometry.height(); screenData["monitorHeight"] = screenGeometry.height();
@ -189,3 +193,112 @@ RequestResult RequestHandler::GetMonitorList(const Request&)
responseData["monitors"] = monitorsData; responseData["monitors"] = monitorsData;
return RequestResult::Success(responseData); 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 = "StudioProgram";
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();
}

View File

@ -29,11 +29,12 @@ json GetDefaultJsonObject(const json &requestData)
return requestData; return requestData;
} }
Request::Request(const std::string &requestType, const json &requestData, const RequestBatchExecutionType::RequestBatchExecutionType executionType) : Request::Request(const std::string &requestType, const json &requestData,
RequestType(requestType), const RequestBatchExecutionType::RequestBatchExecutionType executionType)
HasRequestData(requestData.is_object()), : RequestType(requestType),
RequestData(GetDefaultJsonObject(requestData)), HasRequestData(requestData.is_object()),
ExecutionType(executionType) RequestData(GetDefaultJsonObject(requestData)),
ExecutionType(executionType)
{ {
} }
@ -59,7 +60,8 @@ bool Request::ValidateBasic(const std::string &keyName, RequestStatus::RequestSt
return true; 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()) { if (!RequestData[keyName].is_number()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
@ -70,19 +72,22 @@ bool Request::ValidateOptionalNumber(const std::string &keyName, RequestStatus::
double value = RequestData[keyName]; double value = RequestData[keyName];
if (value < minValue) { if (value < minValue) {
statusCode = RequestStatus::RequestFieldOutOfRange; 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; return false;
} }
if (value > maxValue) { if (value > maxValue) {
statusCode = RequestStatus::RequestFieldOutOfRange; 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 false;
} }
return true; 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)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -93,7 +98,8 @@ bool Request::ValidateNumber(const std::string &keyName, RequestStatus::RequestS
return true; 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()) { if (!RequestData[keyName].is_string()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
@ -110,7 +116,8 @@ bool Request::ValidateOptionalString(const std::string &keyName, RequestStatus::
return true; 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)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -121,7 +128,8 @@ bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestS
return true; 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()) { if (!RequestData[keyName].is_boolean()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
@ -143,7 +151,8 @@ bool Request::ValidateBoolean(const std::string &keyName, RequestStatus::Request
return true; 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()) { if (!RequestData[keyName].is_object()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
@ -160,7 +169,8 @@ bool Request::ValidateOptionalObject(const std::string &keyName, RequestStatus::
return true; 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)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -171,7 +181,8 @@ bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestS
return true; 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()) { if (!RequestData[keyName].is_array()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
@ -188,7 +199,8 @@ bool Request::ValidateOptionalArray(const std::string &keyName, RequestStatus::R
return true; 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)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -199,7 +211,8 @@ bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestSt
return true; 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)) if (!ValidateString(keyName, statusCode, comment))
return nullptr; return nullptr;
@ -216,7 +229,8 @@ obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus:
return ret; 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); obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret) if (!ret)
@ -245,7 +259,8 @@ obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::
return ret; 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); OBSSourceAutoRelease sceneSource = ValidateSource(keyName, statusCode, comment);
if (!sceneSource) 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); obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret) if (!ret)
@ -291,7 +307,8 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
return ret; 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); obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment);
if (!source) 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()); obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str());
if (!filter) { if (!filter) {
statusCode = RequestStatus::ResourceNotFound; 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, nullptr};
} }
return FilterPair{source, filter}; 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); OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter);
if (!scene) 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); OBSSceneItem sceneItem = obs_scene_find_sceneitem_by_id(scene, sceneItemId);
if (!sceneItem) { if (!sceneItem) {
statusCode = RequestStatus::ResourceNotFound; 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; return nullptr;
} }
obs_sceneitem_addref(sceneItem); obs_sceneitem_addref(sceneItem);
return 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;
}

View File

@ -35,32 +35,50 @@ struct FilterPair {
OBSSourceAutoRelease filter; OBSSourceAutoRelease filter;
}; };
struct Request struct Request {
{ Request(const std::string &requestType, const json &requestData = nullptr,
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None); const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None);
// Contains the key and is not null // Contains the key and is not null
bool Contains(const std::string &keyName) const; bool Contains(const std::string &keyName) const;
bool ValidateBasic(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) 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 ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
bool ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue = -INFINITY, const double maxValue = INFINITY) const; 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 ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
bool ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; const double minValue = -INFINITY, const double maxValue = INFINITY) const;
bool ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) 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 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 ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
bool ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; const bool allowEmpty = false) const;
bool ValidateOptionalArray(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,
bool ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; 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 // All return values have incremented refcounts
obs_source_t *ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_source_t *ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode,
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; std::string &comment) 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 *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment,
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; 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; std::string RequestType;
bool HasRequestData; bool HasRequestData;

View File

@ -18,9 +18,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestBatchRequest.h" #include "RequestBatchRequest.h"
RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData, RequestBatchExecutionType::RequestBatchExecutionType executionType, const json &inputVariables, const json &outputVariables) : RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData,
Request(requestType, requestData, executionType), RequestBatchExecutionType::RequestBatchExecutionType executionType,
InputVariables(inputVariables), const json &inputVariables, const json &outputVariables)
OutputVariables(outputVariables) : Request(requestType, requestData, executionType), InputVariables(inputVariables), OutputVariables(outputVariables)
{ {
} }

View File

@ -21,7 +21,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Request.h" #include "Request.h"
struct RequestBatchRequest : Request { 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 InputVariables;
json OutputVariables; json OutputVariables;

View File

@ -19,11 +19,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestResult.h" #include "RequestResult.h"
RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment) : RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment)
StatusCode(statusCode), : StatusCode(statusCode), ResponseData(responseData), Comment(comment), SleepFrames(0)
ResponseData(responseData),
Comment(comment),
SleepFrames(0)
{ {
} }

View File

@ -22,9 +22,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../types/RequestStatus.h" #include "../types/RequestStatus.h"
#include "../../utils/Json.h" #include "../../utils/Json.h"
struct RequestResult struct RequestResult {
{ RequestResult(RequestStatus::RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr,
RequestResult(RequestStatus::RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr, std::string comment = ""); std::string comment = "");
static RequestResult Success(json responseData = nullptr); static RequestResult Success(json responseData = nullptr);
static RequestResult Error(RequestStatus::RequestStatus statusCode, std::string comment = ""); static RequestResult Error(RequestStatus::RequestStatus statusCode, std::string comment = "");
RequestStatus::RequestStatus StatusCode; RequestStatus::RequestStatus StatusCode;

View File

@ -22,7 +22,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <stdint.h> #include <stdint.h>
namespace RequestBatchExecutionType { namespace RequestBatchExecutionType {
enum RequestBatchExecutionType: int8_t { enum RequestBatchExecutionType : int8_t {
/** /**
* Not a request batch. * Not a request batch.
* *
@ -77,8 +77,5 @@ namespace RequestBatchExecutionType {
Parallel = 2, Parallel = 2,
}; };
inline bool IsValid(int8_t executionType) inline bool IsValid(int8_t executionType) { return executionType >= None && executionType <= Parallel; }
{
return executionType >= None && executionType <= Parallel;
}
} }

View File

@ -262,7 +262,6 @@ namespace RequestStatus {
* @api enums * @api enums
*/ */
StudioModeNotActive = 506, StudioModeNotActive = 506,
/** /**
* The resource was not found. * The resource was not found.

View File

@ -1,35 +1,32 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include "Compat.h" #include "Compat.h"
Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(std::function<void()> func) : Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(std::function<void()> func) : cb(std::move(func)) {}
cb(std::move(func))
{ void Utils::Compat::StdFunctionRunnable::run()
} {
cb();
void Utils::Compat::StdFunctionRunnable::run() }
{
cb(); QRunnable *Utils::Compat::CreateFunctionRunnable(std::function<void()> func)
} {
return new Utils::Compat::StdFunctionRunnable(std::move(func));
QRunnable *Utils::Compat::CreateFunctionRunnable(std::function<void()> func) }
{
return new Utils::Compat::StdFunctionRunnable(std::move(func));
}

View File

@ -27,9 +27,10 @@ namespace Utils {
// Reimplement QRunnable for std::function. Retrocompatability for Qt < 5.15 // Reimplement QRunnable for std::function. Retrocompatability for Qt < 5.15
class StdFunctionRunnable : public QRunnable { class StdFunctionRunnable : public QRunnable {
std::function<void()> cb; std::function<void()> cb;
public:
StdFunctionRunnable(std::function<void()> func); public:
void run() override; StdFunctionRunnable(std::function<void()> func);
void run() override;
}; };
QRunnable *CreateFunctionRunnable(std::function<void()> func); QRunnable *CreateFunctionRunnable(std::function<void()> func);

View File

@ -63,10 +63,7 @@ bool Utils::Crypto::CheckAuthenticationString(std::string secret, std::string ch
secretAndChallenge += QString::fromStdString(challenge); secretAndChallenge += QString::fromStdString(challenge);
// Generate a SHA256 hash of secretAndChallenge // Generate a SHA256 hash of secretAndChallenge
auto hash = QCryptographicHash::hash( auto hash = QCryptographicHash::hash(secretAndChallenge.toUtf8(), QCryptographicHash::Algorithm::Sha256);
secretAndChallenge.toUtf8(),
QCryptographicHash::Algorithm::Sha256
);
// Encode the SHA256 hash to Base64 // Encode the SHA256 hash to Base64
std::string expectedAuthenticationString = hash.toBase64().toStdString(); std::string expectedAuthenticationString = hash.toBase64().toStdString();

View File

@ -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(); 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()) if (!value.is_object())
continue; 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) 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()) { if (value.is_object()) {
obs_data_set_json_object(d, key.c_str(), value); obs_data_set_json_object(d, key.c_str(), value);
} else if (value.is_array()) { } else if (value.is_array()) {
@ -153,23 +153,22 @@ json Utils::Json::ObsDataToJson(obs_data_t *d, bool includeDefault)
continue; continue;
switch (type) { switch (type) {
case OBS_DATA_STRING: case OBS_DATA_STRING:
set_json_string(&j, name, item); set_json_string(&j, name, item);
break; break;
case OBS_DATA_NUMBER: case OBS_DATA_NUMBER:
set_json_number(&j, name, item); set_json_number(&j, name, item);
break; break;
case OBS_DATA_BOOLEAN: case OBS_DATA_BOOLEAN:
set_json_bool(&j, name, item); set_json_bool(&j, name, item);
break; break;
case OBS_DATA_OBJECT: case OBS_DATA_OBJECT:
set_json_object(&j, name, item, includeDefault); set_json_object(&j, name, item, includeDefault);
break; break;
case OBS_DATA_ARRAY: case OBS_DATA_ARRAY:
set_json_array(&j, name, item, includeDefault); set_json_array(&j, name, item, includeDefault);
break; break;
default: default:;
;
} }
} }
@ -184,8 +183,8 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
try { try {
content = json::parse(textContent); content = json::parse(textContent);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what()); blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what());
return false; return false;
} }
return true; return true;

View File

@ -25,6 +25,51 @@ with this program. If not, see <https://www.gnu.org/licenses/>
using json = nlohmann::json; 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 Utils {
namespace Json { namespace Json {
bool JsonArrayIsValidObsArray(const json &j); bool JsonArrayIsValidObsArray(const json &j);

View File

@ -26,43 +26,44 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Json.h" #include "Json.h"
// Autorelease object definitions // Autorelease object definitions
inline void ___properties_dummy_addref(obs_properties_t*){} inline void ___properties_dummy_addref(obs_properties_t *) {}
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>; using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t *, ___properties_dummy_addref, obs_properties_destroy>;
#if !defined(OBS_AUTORELEASE) #if !defined(OBS_AUTORELEASE)
inline void ___source_dummy_addref(obs_source_t*){} inline void ___source_dummy_addref(obs_source_t *) {}
inline void ___scene_dummy_addref(obs_scene_t*){} inline void ___scene_dummy_addref(obs_scene_t *) {}
inline void ___sceneitem_dummy_addref(obs_sceneitem_t*){} inline void ___sceneitem_dummy_addref(obs_sceneitem_t *) {}
inline void ___data_dummy_addref(obs_data_t*){} inline void ___data_dummy_addref(obs_data_t *) {}
inline void ___data_array_dummy_addref(obs_data_array_t*){} inline void ___data_array_dummy_addref(obs_data_array_t *) {}
inline void ___output_dummy_addref(obs_output_t*){} inline void ___output_dummy_addref(obs_output_t *) {}
inline void ___encoder_dummy_addref(obs_encoder_t *){} inline void ___encoder_dummy_addref(obs_encoder_t *) {}
inline void ___service_dummy_addref(obs_service_t *){} inline void ___service_dummy_addref(obs_service_t *) {}
inline void ___weak_source_dummy_addref(obs_weak_source_t*){} inline void ___weak_source_dummy_addref(obs_weak_source_t *) {}
inline void ___weak_output_dummy_addref(obs_weak_output_t *){} inline void ___weak_output_dummy_addref(obs_weak_output_t *) {}
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *){} inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *) {}
inline void ___weak_service_dummy_addref(obs_weak_service_t *){} inline void ___weak_service_dummy_addref(obs_weak_service_t *) {}
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_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 OBSSceneAutoRelease = OBSRef<obs_scene_t *, ___scene_dummy_addref, obs_scene_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_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 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 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 OBSOutputAutoRelease = OBSRef<obs_output_t *, ___output_dummy_addref, obs_output_release>;
using OBSEncoderAutoRelease = OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_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 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 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 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>; using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref, obs_weak_service_release>;
#endif #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; void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr); calldata_get_ptr(data, name, &ptr);
return static_cast<T*>(ptr); return static_cast<T *>(ptr);
} }
enum ObsOutputState { enum ObsOutputState {
@ -76,6 +77,16 @@ enum ObsOutputState {
OBS_WEBSOCKET_OUTPUT_RESUMED, 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 { enum ObsMediaInputAction {
/** /**
* No action. * No action.
@ -149,6 +160,17 @@ enum ObsMediaInputAction {
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS, 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 Utils {
namespace Obs { namespace Obs {
namespace StringHelper { namespace StringHelper {
@ -157,21 +179,9 @@ namespace Utils {
std::string GetCurrentProfile(); std::string GetCurrentProfile();
std::string GetCurrentProfilePath(); std::string GetCurrentProfilePath();
std::string GetCurrentRecordOutputPath(); std::string GetCurrentRecordOutputPath();
std::string GetSourceType(obs_source_t *source); std::string GetLastRecordFileName();
std::string GetInputMonitorType(enum obs_monitoring_type monitorType); std::string GetLastReplayBufferFileName();
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 DurationToTimecode(uint64_t); 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 { namespace NumberHelper {
@ -195,6 +205,7 @@ namespace Utils {
std::vector<json> GetSceneTransitionList(); std::vector<json> GetSceneTransitionList();
std::vector<json> GetSourceFilterList(obs_source_t *source); std::vector<json> GetSourceFilterList(obs_source_t *source);
std::vector<std::string> GetFilterKindList(); std::vector<std::string> GetFilterKindList();
std::vector<json> GetOutputList();
} }
namespace ObjectHelper { namespace ObjectHelper {
@ -205,13 +216,21 @@ namespace Utils {
namespace SearchHelper { namespace SearchHelper {
obs_hotkey_t *GetHotkeyByName(std::string name); obs_hotkey_t *GetHotkeyByName(std::string name);
obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name, int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name,
int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease
} }
namespace ActionHelper { 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 *
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 CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true,
obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease 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); void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index);
} }
} }

View File

@ -20,16 +20,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
struct CreateSceneItemData { struct CreateSceneItemData {
obs_source_t *source; // In obs_source_t *source; // In
bool sceneItemEnabled; // In bool sceneItemEnabled; // In
obs_transform_info *sceneItemTransform = nullptr; // In obs_transform_info *sceneItemTransform = nullptr; // In
obs_sceneitem_crop *sceneItemCrop = nullptr; // In obs_sceneitem_crop *sceneItemCrop = nullptr; // In
OBSSceneItem sceneItem; // Out 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); data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform) 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_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 // Sanity check for valid scene
if (!(source && scene)) if (!(source && scene))
@ -64,7 +66,8 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source,
return data.sceneItem; 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 // Create the input
OBSSourceAutoRelease input = obs_source_create(inputKind.c_str(), inputName.c_str(), inputSettings, nullptr); 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; 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); 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); size_t currentIndex = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
obs_order_movement direction = index > currentIndex ? OBS_ORDER_MOVE_DOWN : OBS_ORDER_MOVE_UP; 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); obs_source_filter_set_order(source, filter, direction);
if (direction == OBS_ORDER_MOVE_DOWN) if (direction == OBS_ORDER_MOVE_DOWN)

View File

@ -29,7 +29,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
return ret; return ret;
size_t index = 0; size_t index = 0;
char* value = nullptr; char *value = nullptr;
do { do {
value = array[index]; value = array[index];
if (value) if (value)
@ -42,7 +42,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList() 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); auto ret = ConvertStringArray(sceneCollections);
bfree(sceneCollections); bfree(sceneCollections);
return ret; return ret;
@ -50,7 +50,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList() std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList()
{ {
char** profiles = obs_frontend_get_profiles(); char **profiles = obs_frontend_get_profiles();
auto ret = ConvertStringArray(profiles); auto ret = ConvertStringArray(profiles);
bfree(profiles); bfree(profiles);
return ret; return ret;
@ -60,13 +60,15 @@ std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
{ {
std::vector<obs_hotkey_t *> ret; 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); auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data);
ret->push_back(hotkey); ret->push_back(hotkey);
return true; return true;
}, &ret); };
obs_enum_hotkeys(cb, &ret);
return ret; return ret;
} }
@ -112,7 +114,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
std::vector<std::string> ret; std::vector<std::string> ret;
auto cb = [](void *priv_data, obs_source_t *scene) { 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)) if (!obs_source_is_group(scene))
return true; return true;
@ -132,17 +134,23 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
std::pair<std::vector<json>, bool> enumData; std::pair<std::vector<json>, bool> enumData;
enumData.second = basic; enumData.second = basic;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData = static_cast<std::pair<std::vector<json>, bool>*>(param); auto enumData = static_cast<std::pair<std::vector<json>, bool> *>(param);
// TODO: Make ObjectHelper util for scene items
json item; json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem); item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position() item["sceneItemIndex"] =
item["sceneItemIndex"] = enumData->first.size(); enumData->first.size(); // Should be slightly faster than calling obs_sceneitem_get_order_position()
if (!enumData->second) { 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); OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
item["sourceName"] = obs_source_get_name(itemSource); 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) if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
item["inputKind"] = obs_source_get_id(itemSource); item["inputKind"] = obs_source_get_id(itemSource);
else else
@ -156,7 +164,9 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
enumData->first.push_back(item); enumData->first.push_back(item);
return true; return true;
}, &enumData); };
obs_scene_enum_items(scene, cb, &enumData);
return enumData.first; return enumData.first;
} }
@ -171,12 +181,12 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
EnumInputInfo inputInfo; EnumInputInfo inputInfo;
inputInfo.inputKind = inputKind; 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 // Sanity check in case the API changes
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return true; return true;
auto inputInfo = static_cast<EnumInputInfo*>(param); auto inputInfo = static_cast<EnumInputInfo *>(param);
std::string inputKind = obs_source_get_id(input); 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); inputInfo->inputs.push_back(inputJson);
return true; return true;
}; };
// Actually enumerates only public inputs, despite the name // Actually enumerates only public inputs, despite the name
obs_enum_sources(inputEnumProc, &inputInfo); obs_enum_sources(cb, &inputInfo);
return inputInfo.inputs; return inputInfo.inputs;
} }
@ -287,7 +298,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetFilterKindList()
size_t idx = 0; size_t idx = 0;
const char *kind; const char *kind;
while(obs_enum_filter_types(idx++, &kind)) while (obs_enum_filter_types(idx++, &kind))
ret.push_back(kind); ret.push_back(kind);
return ret; return ret;
@ -297,8 +308,8 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *sou
{ {
std::vector<json> filters; std::vector<json> filters;
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) { auto cb = [](obs_source_t *, obs_source_t *filter, void *param) {
auto filters = reinterpret_cast<std::vector<json>*>(param); auto filters = reinterpret_cast<std::vector<json> *>(param);
json filterJson; json filterJson;
filterJson["filterEnabled"] = obs_source_enabled(filter); 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); filters->push_back(filterJson);
}; };
obs_source_enum_filters(source, enumFilters, &filters);
obs_source_enum_filters(source, cb, &filters);
return 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;
}

View File

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

View File

@ -28,7 +28,7 @@ uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
if (!output || !obs_output_active(output)) if (!output || !obs_output_active(output))
return 0; 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); uint64_t frameTimeNs = video_output_get_frame_time(video);
int totalFrames = obs_output_get_total_frames(output); int totalFrames = obs_output_get_total_frames(output);
@ -39,7 +39,7 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
{ {
size_t ret; size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) { 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)) if (obs_source_is_group(scene))
return true; 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 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) if (filter == filterSearch->filter)
filterSearch->found = true; filterSearch->found = true;

View File

@ -29,7 +29,7 @@ json Utils::Obs::ObjectHelper::GetStats()
std::string outputPath = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); 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["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo());
ret["memoryUsage"] = (double)os_get_proc_resident_size() / (1024.0 * 1024.0); 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); obs_sceneitem_get_crop(item, &crop);
OBSSource source = obs_sceneitem_get_source(item); OBSSource source = obs_sceneitem_get_source(item);
float sourceWidth = float(obs_source_get_width(source)); float sourceWidth = (float)obs_source_get_width(source);
float sourceHeight = float(obs_source_get_height(source)); float sourceHeight = (float)obs_source_get_height(source);
ret["sourceWidth"] = sourceWidth; ret["sourceWidth"] = sourceWidth;
ret["sourceHeight"] = sourceHeight; ret["sourceHeight"] = sourceHeight;
@ -73,15 +73,15 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
ret["alignment"] = osi.alignment; ret["alignment"] = osi.alignment;
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type); ret["boundsType"] = osi.bounds_type;
ret["boundsAlignment"] = osi.bounds_alignment; ret["boundsAlignment"] = osi.bounds_alignment;
ret["boundsWidth"] = osi.bounds.x; ret["boundsWidth"] = osi.bounds.x;
ret["boundsHeight"] = osi.bounds.y; ret["boundsHeight"] = osi.bounds.y;
ret["cropLeft"] = int(crop.left); ret["cropLeft"] = (int)crop.left;
ret["cropRight"] = int(crop.right); ret["cropRight"] = (int)crop.right;
ret["cropTop"] = int(crop.top); ret["cropTop"] = (int)crop.top;
ret["cropBottom"] = int(crop.bottom); ret["cropBottom"] = (int)crop.bottom;
return ret; return ret;
} }

View File

@ -70,8 +70,8 @@ obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene
enumData.name = name; enumData.name = name;
enumData.offset = offset; enumData.offset = offset;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData = static_cast<SceneItemSearchData*>(param); auto enumData = static_cast<SceneItemSearchData *>(param);
OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
std::string sourceName = obs_source_get_name(itemSource); std::string sourceName = obs_source_get_name(itemSource);
@ -89,7 +89,9 @@ obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene
} }
return true; return true;
}, &enumData); };
obs_scene_enum_items(scene, cb, &enumData);
return enumData.ret; return enumData.ret;
} }

View File

@ -23,7 +23,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Obs.h" #include "Obs.h"
#include "../plugin-macros.generated.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() std::string Utils::Obs::StringHelper::GetObsVersion()
{ {
@ -70,54 +72,27 @@ std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
return ret; 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) { OBSDataAutoRelease outputSettings = obs_output_get_settings(output);
default:
CASE(OBS_SOURCE_TYPE_INPUT) obs_data_item_t *item = obs_data_item_byname(outputSettings, "url");
CASE(OBS_SOURCE_TYPE_FILTER) if (!item) {
CASE(OBS_SOURCE_TYPE_TRANSITION) item = obs_data_item_byname(outputSettings, "path");
CASE(OBS_SOURCE_TYPE_SCENE) 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) std::string Utils::Obs::StringHelper::GetLastReplayBufferFileName()
{
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()
{ {
OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output(); OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output();
if (!output) if (!output)
@ -135,34 +110,6 @@ std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
return savedReplayPath; 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) std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
{ {
uint64_t secs = ms / 1000ULL; 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 secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL; 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(); 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)
}
}

View File

@ -26,12 +26,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Obs_VolumeMeter_Helpers.h" #include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) : Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input)
PeakMeterType(SAMPLE_PEAK_METER), : PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)), _input(obs_source_get_weak_source(input)),
_channels(0), _channels(0),
_lastUpdate(0), _lastUpdate(0),
_volume(obs_source_get_volume(input)) _volume(obs_source_get_volume(input))
{ {
signal_handler_t *sh = obs_source_get_signal_handler(input); signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this); 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); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!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; return;
} }
@ -68,7 +69,8 @@ json Utils::Obs::VolumeMeter::Meter::GetMeterData()
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!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; return ret;
} }
@ -129,7 +131,7 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float *)data->data[planeNumber];
if (!samples) if (!samples)
continue; continue;
@ -143,41 +145,41 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
float peak; float peak;
switch (PeakMeterType) { switch (PeakMeterType) {
default: default:
case SAMPLE_PEAK_METER: case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount); peak = GetSamplePeak(previousSamples, samples, sampleCount);
break; break;
case TRUE_PEAK_METER: case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount); peak = GetTruePeak(previousSamples, samples, sampleCount);
break; break;
} }
switch (sampleCount) { switch (sampleCount) {
case 0: case 0:
break; break;
case 1: case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
case 2: case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
case 3: case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
default: default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4]; _previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
} }
_peak[channelNumber] = peak; _peak[channelNumber] = peak;
@ -196,7 +198,7 @@ void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *d
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float *)data->data[planeNumber];
if (!samples) if (!samples)
continue; 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); 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) 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"); c->_volume = (float)calldata_float(cd, "volume");
} }
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod)
_updateCallback(cb), : _updateCallback(cb), _updatePeriod(updatePeriod), _running(false)
_updatePeriod(updatePeriod),
_running(false)
{ {
signal_handler_t *sh = obs_get_signal_handler(); signal_handler_t *sh = obs_get_signal_handler();
if (!sh) if (!sh)
return; return;
auto enumProc = [](void *priv_data, obs_source_t *input) { 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)) if (!obs_source_active(input))
return true; return true;
@ -293,7 +294,7 @@ void Utils::Obs::VolumeMeter::Handler::UpdateThread()
while (_running) { while (_running) {
{ {
std::unique_lock<std::mutex> l(_mutex); 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; break;
} }
@ -313,7 +314,7 @@ void Utils::Obs::VolumeMeter::Handler::UpdateThread()
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) 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"); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input) 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) 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"); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input) if (!input)

View File

@ -36,37 +36,38 @@ namespace Utils {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c // 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 // Keeps a running tally of the current audio levels, for a specific input
class Meter { class Meter {
public: public:
Meter(obs_source_t *input); Meter(obs_source_t *input);
~Meter(); ~Meter();
bool InputValid(); bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; } obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData(); json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType; std::atomic<enum obs_peak_meter_type> PeakMeterType;
private: private:
OBSWeakSourceAutoRelease _input; OBSWeakSourceAutoRelease _input;
// All values in mul // All values in mul
std::mutex _mutex; std::mutex _mutex;
bool _muted; bool _muted;
int _channels; int _channels;
float _magnitude[MAX_AUDIO_CHANNELS]; float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS]; float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4]; float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate; std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume; std::atomic<float> _volume;
void ResetAudioLevels(); void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data); void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data); void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(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 InputAudioCaptureCallback(void *priv_data, obs_source_t *source,
static void InputVolumeCallback(void *priv_data, calldata_t *cd); const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
}; };
// Maintains an array of active inputs // Maintains an array of active inputs
@ -74,25 +75,25 @@ namespace Utils {
typedef std::function<void(std::vector<json>)> UpdateCallback; typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr; typedef std::unique_ptr<Meter> MeterPtr;
public: public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50); Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler(); ~Handler();
private: private:
UpdateCallback _updateCallback; UpdateCallback _updateCallback;
std::mutex _meterMutex; std::mutex _meterMutex;
std::vector<MeterPtr> _meters; std::vector<MeterPtr> _meters;
uint64_t _updatePeriod; uint64_t _updatePeriod;
std::mutex _mutex; std::mutex _mutex;
std::condition_variable _cond; std::condition_variable _cond;
std::atomic<bool> _running; std::atomic<bool> _running;
std::thread _updateThread; std::thread _updateThread;
void UpdateThread(); void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd); static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd); static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
}; };
} }
} }

View File

@ -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. // It should probably be imported as a libobs util someday though.
#define SHIFT_RIGHT_2PS(msb, lsb) \ #define SHIFT_RIGHT_2PS(msb, lsb) \
{ \ { \
__m128 tmp = \ __m128 tmp = _mm_shuffle_ps(lsb, msb, _MM_SHUFFLE(0, 0, 3, 3)); \
_mm_shuffle_ps(lsb, msb, _MM_SHUFFLE(0, 0, 3, 3)); \ lsb = _mm_shuffle_ps(lsb, tmp, _MM_SHUFFLE(2, 1, 2, 1)); \
lsb = _mm_shuffle_ps(lsb, tmp, _MM_SHUFFLE(2, 1, 2, 1)); \ msb = _mm_shuffle_ps(msb, msb, _MM_SHUFFLE(3, 3, 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) #define abs_ps(v) _mm_andnot_ps(_mm_set1_ps(-0.f), v)

View File

@ -53,7 +53,8 @@ std::string Utils::Platform::GetLocalAddress()
std::vector<std::pair<QString, uint8_t>> preferredAddresses; std::vector<std::pair<QString, uint8_t>> preferredAddresses;
for (auto address : validAddresses) { for (auto address : validAddresses) {
// Attribute a priority (0 is best) to the address to choose the best picks // 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 if (address.startsWith("192.168.1.") ||
address.startsWith("192.168.0.")) { // Prefer common consumer router network prefixes
if (address.startsWith("192.168.56.")) if (address.startsWith("192.168.56."))
preferredAddresses.push_back(std::make_pair(address, 255)); // Ignore virtualbox default preferredAddresses.push_back(std::make_pair(address, 255)); // Ignore virtualbox default
else else
@ -68,29 +69,13 @@ std::string Utils::Platform::GetLocalAddress()
} }
// Sort by priority // Sort by priority
std::sort(preferredAddresses.begin(), preferredAddresses.end(), [=](std::pair<QString, uint8_t> a, std::pair<QString, uint8_t> b) { std::sort(preferredAddresses.begin(), preferredAddresses.end(),
return a.second < b.second; [=](std::pair<QString, uint8_t> a, std::pair<QString, uint8_t> b) { return a.second < b.second; });
});
// Return highest priority address // Return highest priority address
return preferredAddresses[0].first.toStdString(); return preferredAddresses[0].first.toStdString();
} }
std::string Utils::Platform::GetLoopbackAddress(bool allowIpv6)
{
std::vector<QString> validAddresses;
for (auto address : QNetworkInterface::allAddresses()) {
if (address == QHostAddress::LocalHost)
return address.toString().toStdString();
else if (address == QHostAddress::LocalHostIPv6 && allowIpv6)
return address.toString().toStdString();
else if (address.isLoopback())
return address.toString().toStdString();
}
return "";
}
QString Utils::Platform::GetCommandLineArgument(QString arg) QString Utils::Platform::GetCommandLineArgument(QString arg)
{ {
QCommandLineParser parser; QCommandLineParser parser;
@ -127,14 +112,17 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS
SystemTrayNotification *notification = new SystemTrayNotification{icon, title, body}; SystemTrayNotification *notification = new SystemTrayNotification{icon, title, body};
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
void *systemTrayPtr = obs_frontend_get_system_tray(); OBS_TASK_UI,
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr); [](void *param) {
void *systemTrayPtr = obs_frontend_get_system_tray();
auto systemTray = static_cast<QSystemTrayIcon *>(systemTrayPtr);
auto notification = static_cast<SystemTrayNotification*>(param); auto notification = static_cast<SystemTrayNotification *>(param);
systemTray->showMessage(notification->title, notification->body, notification->icon); systemTray->showMessage(notification->title, notification->body, notification->icon);
delete notification; delete notification;
}, (void*)notification, false); },
(void *)notification, false);
} }
bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &content) bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &content)

View File

@ -26,7 +26,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
namespace Utils { namespace Utils {
namespace Platform { namespace Platform {
std::string GetLocalAddress(); std::string GetLocalAddress();
std::string GetLoopbackAddress(bool allowIpv6 = true);
QString GetCommandLineArgument(QString arg); QString GetCommandLineArgument(QString arg);
bool GetCommandLineFlagSet(QString arg); bool GetCommandLineFlagSet(QString arg);
void SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title, QString body); void SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title, QString body);

View File

@ -31,9 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Platform.h" #include "../utils/Platform.h"
#include "../utils/Compat.h" #include "../utils/Compat.h"
WebSocketServer::WebSocketServer() : WebSocketServer::WebSocketServer() : QObject(nullptr), _sessions()
QObject(nullptr),
_sessions()
{ {
_server.get_alog().clear_channels(websocketpp::log::alevel::all); _server.get_alog().clear_channels(websocketpp::log::alevel::all);
_server.get_elog().clear_channels(websocketpp::log::elevel::all); _server.get_elog().clear_channels(websocketpp::log::elevel::all);
@ -44,38 +42,17 @@ WebSocketServer::WebSocketServer() :
#endif #endif
_server.set_validate_handler( _server.set_validate_handler(
websocketpp::lib::bind( websocketpp::lib::bind(&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1));
&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,
_server.set_open_handler( websocketpp::lib::placeholders::_2));
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(); auto eventHandler = GetEventHandler();
eventHandler->SetBroadcastCallback( eventHandler->SetBroadcastCallback(std::bind(&WebSocketServer::BroadcastEvent, this, std::placeholders::_1,
std::bind( std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
&WebSocketServer::BroadcastEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4
)
);
eventHandler->SetObsLoadedCallback( eventHandler->SetObsLoadedCallback(std::bind(&WebSocketServer::onObsLoaded, this));
std::bind(
&WebSocketServer::onObsLoaded, this
)
);
} }
WebSocketServer::~WebSocketServer() WebSocketServer::~WebSocketServer()
@ -89,9 +66,9 @@ void WebSocketServer::ServerRunner()
blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread started."); blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread started.");
try { try {
_server.run(); _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()); 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()); blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what());
} catch (...) { } catch (...) {
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error"); blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error");
@ -118,7 +95,8 @@ void WebSocketServer::Start()
// Set log levels if debug is enabled // Set log levels if debug is enabled
if (IsDebugEnabled()) { if (IsDebugEnabled()) {
_server.get_alog().set_channels(websocketpp::log::alevel::all); _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_elog().set_channels(websocketpp::log::elevel::all);
_server.get_alog().clear_channels(websocketpp::log::elevel::devel | websocketpp::log::elevel::library); _server.get_alog().clear_channels(websocketpp::log::elevel::devel | websocketpp::log::elevel::library);
} else { } else {
@ -129,25 +107,17 @@ void WebSocketServer::Start()
_server.reset(); _server.reset();
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
if (conf->BindLoopback) { if (conf->Ipv4Only) {
std::string addr = Utils::Platform::GetLoopbackAddress(!conf->Ipv4Only); blog(LOG_INFO, "[WebSocketServer::Start] Locked to IPv4 bindings");
if (addr.empty()) {
blog(LOG_ERROR, "[WebSocketServer::Start] Failed to find loopback interface. Server not started.");
return;
}
_server.listen(addr, std::to_string(conf->ServerPort), errorCode);
blog(LOG_INFO, "[WebSocketServer::Start] Locked to loopback interface.");
} else if (conf->Ipv4Only) {
_server.listen(websocketpp::lib::asio::ip::tcp::v4(), conf->ServerPort, errorCode); _server.listen(websocketpp::lib::asio::ip::tcp::v4(), conf->ServerPort, errorCode);
blog(LOG_INFO, "[WebSocketServer::Start] Locked to IPv4 bindings.");
} else { } else {
blog(LOG_INFO, "[WebSocketServer::Start] Not locked to IPv4 bindings");
_server.listen(conf->ServerPort, errorCode); _server.listen(conf->ServerPort, errorCode);
blog(LOG_INFO, "[WebSocketServer::Start] Bound to all interfaces.");
} }
if (errorCode) { if (errorCode) {
std::string errorCodeMessage = errorCode.message(); std::string errorCodeMessage = errorCode.message();
blog(LOG_ERROR, "[WebSocketServer::Start] Listen failed: %s", errorCodeMessage.c_str()); blog(LOG_INFO, "[WebSocketServer::Start] Listen failed: %s", errorCodeMessage.c_str());
return; return;
} }
@ -155,7 +125,8 @@ void WebSocketServer::Start()
_serverThread = std::thread(&WebSocketServer::ServerRunner, this); _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() void WebSocketServer::Stop()
@ -168,7 +139,7 @@ void WebSocketServer::Stop()
_server.stop_listening(); _server.stop_listening();
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto const& [hdl, session] : _sessions) { for (auto const &[hdl, session] : _sessions) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
_server.pause_reading(hdl, errorCode); _server.pause_reading(hdl, errorCode);
if (errorCode) { if (errorCode) {
@ -219,14 +190,15 @@ std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocke
std::vector<WebSocketServer::WebSocketSessionState> webSocketSessions; std::vector<WebSocketServer::WebSocketSessionState> webSocketSessions;
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto & [hdl, session] : _sessions) { for (auto &[hdl, session] : _sessions) {
uint64_t connectedAt = session->ConnectedAt(); uint64_t connectedAt = session->ConnectedAt();
uint64_t incomingMessages = session->IncomingMessages(); uint64_t incomingMessages = session->IncomingMessages();
uint64_t outgoingMessages = session->OutgoingMessages(); uint64_t outgoingMessages = session->OutgoingMessages();
std::string remoteAddress = session->RemoteAddress(); std::string remoteAddress = session->RemoteAddress();
bool isIdentified = session->IsIdentified(); 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(); lock.unlock();
@ -372,7 +344,8 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
emit ClientDisconnected(state, conn->get_local_close_code()); emit ClientDisconnected(state, conn->get_local_close_code());
// Log disconnection // 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 // Get config for tray notification
auto conf = GetConfig(); auto conf = GetConfig();
@ -384,12 +357,14 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
// If previously identified, not going away, and notifications enabled, send a tray notification // 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) { if (isIdentified && (conn->get_local_close_code() != websocketpp::close::status::going_away) && conf->AlertsEnabled) {
QString title = obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Title"); 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); 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(); auto opCode = message->get_opcode();
std::string payload = message->get_payload(); std::string payload = message->get_payload();
@ -398,7 +373,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
SessionPtr session; SessionPtr session;
try { try {
session = _sessions.at(hdl); session = _sessions.at(hdl);
} catch (const std::out_of_range& oor) { } catch (const std::out_of_range &oor) {
return; return;
} }
lock.unlock(); lock.unlock();
@ -412,26 +387,32 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
uint8_t sessionEncoding = session->Encoding(); uint8_t sessionEncoding = session->Encoding();
if (sessionEncoding == WebSocketEncoding::Json) { if (sessionEncoding == WebSocketEncoding::Json) {
if (opCode != websocketpp::frame::opcode::text) { 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; return;
} }
try { try {
incomingMessage = json::parse(payload); incomingMessage = json::parse(payload);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode Json: ") + e.what(), errorCode); _server.close(hdl, WebSocketCloseCode::MessageDecodeError,
std::string("Unable to decode Json: ") + e.what(), errorCode);
return; return;
} }
} else if (sessionEncoding == WebSocketEncoding::MsgPack) { } else if (sessionEncoding == WebSocketEncoding::MsgPack) {
if (opCode != websocketpp::frame::opcode::binary) { 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; return;
} }
try { try {
incomingMessage = json::from_msgpack(payload); incomingMessage = json::from_msgpack(payload);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode MsgPack: ") + e.what(), errorCode); _server.close(hdl, WebSocketCloseCode::MessageDecodeError,
std::string("Unable to decode MsgPack: ") + e.what(), errorCode);
return; return;
} }
} }
@ -449,9 +430,11 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
// Disconnect client if 4.x protocol is detected // Disconnect client if 4.x protocol is detected
if (!session->IsIdentified() && incomingMessage.contains("request-type")) { 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.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; goto skipProcessing;
} }
@ -462,9 +445,15 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
goto skipProcessing; goto skipProcessing;
} }
if (!incomingMessage["op"].is_number()) {
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
ret.closeReason = "Your `op` is not a number.";
goto skipProcessing;
}
ProcessMessage(session, ret, incomingMessage["op"], incomingMessage["d"]); ProcessMessage(session, ret, incomingMessage["op"], incomingMessage["d"]);
skipProcessing: skipProcessing:
if (ret.closeCode != WebSocketCloseCode::DontClose) { if (ret.closeCode != WebSocketCloseCode::DontClose) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
_server.close(hdl, ret.closeCode, ret.closeReason, errorCode); _server.close(hdl, ret.closeCode, ret.closeReason, errorCode);
@ -486,7 +475,8 @@ skipProcessing:
blog_debug("[WebSocketServer::onMessage] Outgoing message:\n%s", ret.result.dump(2).c_str()); blog_debug("[WebSocketServer::onMessage] Outgoing message:\n%s", ret.result.dump(2).c_str());
if (errorCode) 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());
} }
})); }));
} }

View File

@ -34,73 +34,66 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../requesthandler/rpc/Request.h" #include "../requesthandler/rpc/Request.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class WebSocketServer : QObject class WebSocketServer : QObject {
{
Q_OBJECT Q_OBJECT
public: public:
enum WebSocketEncoding { enum WebSocketEncoding { Json, MsgPack };
Json,
MsgPack
};
struct WebSocketSessionState { struct WebSocketSessionState {
websocketpp::connection_hdl hdl; websocketpp::connection_hdl hdl;
std::string remoteAddress; std::string remoteAddress;
uint64_t connectedAt; uint64_t connectedAt;
uint64_t incomingMessages; uint64_t incomingMessages;
uint64_t outgoingMessages; uint64_t outgoingMessages;
bool isIdentified; bool isIdentified;
}; };
WebSocketServer(); WebSocketServer();
~WebSocketServer(); ~WebSocketServer();
void Start(); void Start();
void Stop(); void Stop();
void InvalidateSession(websocketpp::connection_hdl hdl); void InvalidateSession(websocketpp::connection_hdl hdl);
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr, uint8_t rpcVersion = 0); void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr,
uint8_t rpcVersion = 0);
bool IsListening() { bool IsListening() { return _server.is_listening(); }
return _server.is_listening();
}
std::vector<WebSocketSessionState> GetWebSocketSessions(); std::vector<WebSocketSessionState> GetWebSocketSessions();
QThreadPool *GetThreadPool() { QThreadPool *GetThreadPool() { return &_threadPool; }
return &_threadPool;
}
signals: signals:
void ClientConnected(WebSocketSessionState state); void ClientConnected(WebSocketSessionState state);
void ClientDisconnected(WebSocketSessionState state, uint16_t closeCode); void ClientDisconnected(WebSocketSessionState state, uint16_t closeCode);
private: private:
struct ProcessResult { struct ProcessResult {
WebSocketCloseCode::WebSocketCloseCode closeCode = WebSocketCloseCode::DontClose; WebSocketCloseCode::WebSocketCloseCode closeCode = WebSocketCloseCode::DontClose;
std::string closeReason; std::string closeReason;
json result; json result;
}; };
void ServerRunner(); void ServerRunner();
void onObsLoaded(); void onObsLoaded();
bool onValidate(websocketpp::connection_hdl hdl); bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl); void onOpen(websocketpp::connection_hdl hdl);
void onClose(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 onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message);
static void SetSessionParameters(SessionPtr session, WebSocketServer::ProcessResult &ret, const json &payloadData); static void SetSessionParameters(SessionPtr session, WebSocketServer::ProcessResult &ret, const json &payloadData);
void ProcessMessage(SessionPtr session, ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData); void ProcessMessage(SessionPtr session, ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData);
QThreadPool _threadPool; QThreadPool _threadPool;
std::thread _serverThread; std::thread _serverThread;
websocketpp::server<websocketpp::config::asio> _server; websocketpp::server<websocketpp::config::asio> _server;
std::string _authenticationSecret; std::string _authenticationSecret;
std::string _authenticationSalt; std::string _authenticationSalt;
std::mutex _sessionMutex; std::mutex _sessionMutex;
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions; std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions;
}; };

View File

@ -44,10 +44,7 @@ static json ConstructRequestResult(RequestResult requestResult, const json &requ
if (requestJson.contains("requestId") && !requestJson["requestId"].is_null()) if (requestJson.contains("requestId") && !requestJson["requestId"].is_null())
ret["requestId"] = requestJson["requestId"]; ret["requestId"] = requestJson["requestId"];
ret["requestStatus"] = { ret["requestStatus"] = {{"result", requestResult.StatusCode == RequestStatus::Success}, {"code", requestResult.StatusCode}};
{"result", requestResult.StatusCode == RequestStatus::Success},
{"code", requestResult.StatusCode}
};
if (!requestResult.Comment.empty()) if (!requestResult.Comment.empty())
ret["requestStatus"]["comment"] = requestResult.Comment; 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_object()) {
if (payloadData.is_null()) { if (payloadData.is_null()) {
@ -91,218 +89,253 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
} }
switch (opCode) { switch (opCode) {
case WebSocketOpCode::Identify: { // Identify case WebSocketOpCode::Identify: { // Identify
std::unique_lock<std::mutex> sessionLock(session->OperationMutex); std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
if (session->IsIdentified()) { if (session->IsIdentified()) {
ret.closeCode = WebSocketCloseCode::AlreadyIdentified; ret.closeCode = WebSocketCloseCode::AlreadyIdentified;
ret.closeReason = "You are already Identified with the obs-websocket server."; 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);
return; 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. // 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()) if (!_server.is_listening())
return; return;
@ -322,7 +355,7 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
// Recurse connected sessions and send the event to suitable sessions. // Recurse connected sessions and send the event to suitable sessions.
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto & it : _sessions) { for (auto &it : _sessions) {
if (!it.second->IsIdentified()) { if (!it.second->IsIdentified()) {
continue; continue;
} }
@ -332,24 +365,27 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
if ((it.second->EventSubscriptions() & requiredIntent) != 0) { if ((it.second->EventSubscriptions() & requiredIntent) != 0) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
switch (it.second->Encoding()) { switch (it.second->Encoding()) {
case WebSocketEncoding::Json: case WebSocketEncoding::Json:
if (messageJson.empty()) { if (messageJson.empty()) {
messageJson = eventMessage.dump(); messageJson = eventMessage.dump();
} }
_server.send((websocketpp::connection_hdl)it.first, messageJson, websocketpp::frame::opcode::text, errorCode); _server.send((websocketpp::connection_hdl)it.first, messageJson,
it.second->IncrementOutgoingMessages(); websocketpp::frame::opcode::text, errorCode);
break; it.second->IncrementOutgoingMessages();
case WebSocketEncoding::MsgPack: break;
if (messageMsgPack.empty()) { case WebSocketEncoding::MsgPack:
auto msgPackData = json::to_msgpack(eventMessage); if (messageMsgPack.empty()) {
messageMsgPack = std::string(msgPackData.begin(), msgPackData.end()); 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(); _server.send((websocketpp::connection_hdl)it.first, messageMsgPack,
break; websocketpp::frame::opcode::binary, errorCode);
it.second->IncrementOutgoingMessages();
break;
} }
if (errorCode) 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(); lock.unlock();

View File

@ -20,16 +20,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "WebSocketSession.h" #include "WebSocketSession.h"
#include "../../eventhandler/types/EventSubscription.h" #include "../../eventhandler/types/EventSubscription.h"
WebSocketSession::WebSocketSession() : WebSocketSession::WebSocketSession()
_remoteAddress(""), : _remoteAddress(""),
_connectedAt(0), _connectedAt(0),
_incomingMessages(0), _incomingMessages(0),
_outgoingMessages(0), _outgoingMessages(0),
_encoding(0), _encoding(0),
_challenge(""), _challenge(""),
_rpcVersion(OBS_WEBSOCKET_RPC_VERSION), _rpcVersion(OBS_WEBSOCKET_RPC_VERSION),
_isIdentified(false), _isIdentified(false),
_eventSubscriptions(EventSubscription::All) _eventSubscriptions(EventSubscription::All)
{ {
} }

View File

@ -29,59 +29,58 @@ with this program. If not, see <https://www.gnu.org/licenses/>
class WebSocketSession; class WebSocketSession;
typedef std::shared_ptr<WebSocketSession> SessionPtr; typedef std::shared_ptr<WebSocketSession> SessionPtr;
class WebSocketSession class WebSocketSession {
{ public:
public: WebSocketSession();
WebSocketSession();
std::string RemoteAddress(); std::string RemoteAddress();
void SetRemoteAddress(std::string address); void SetRemoteAddress(std::string address);
uint64_t ConnectedAt(); uint64_t ConnectedAt();
void SetConnectedAt(uint64_t at); void SetConnectedAt(uint64_t at);
uint64_t IncomingMessages(); uint64_t IncomingMessages();
void IncrementIncomingMessages(); void IncrementIncomingMessages();
uint64_t OutgoingMessages(); uint64_t OutgoingMessages();
void IncrementOutgoingMessages(); void IncrementOutgoingMessages();
uint8_t Encoding(); uint8_t Encoding();
void SetEncoding(uint8_t encoding); void SetEncoding(uint8_t encoding);
bool AuthenticationRequired(); bool AuthenticationRequired();
void SetAuthenticationRequired(bool required); void SetAuthenticationRequired(bool required);
std::string Secret(); std::string Secret();
void SetSecret(std::string secret); void SetSecret(std::string secret);
std::string Challenge(); std::string Challenge();
void SetChallenge(std::string challenge); void SetChallenge(std::string challenge);
uint8_t RpcVersion(); uint8_t RpcVersion();
void SetRpcVersion(uint8_t version); void SetRpcVersion(uint8_t version);
bool IsIdentified(); bool IsIdentified();
void SetIsIdentified(bool identified); void SetIsIdentified(bool identified);
uint64_t EventSubscriptions(); uint64_t EventSubscriptions();
void SetEventSubscriptions(uint64_t subscriptions); void SetEventSubscriptions(uint64_t subscriptions);
std::mutex OperationMutex; std::mutex OperationMutex;
private: private:
std::mutex _remoteAddressMutex; std::mutex _remoteAddressMutex;
std::string _remoteAddress; std::string _remoteAddress;
std::atomic<uint64_t> _connectedAt; std::atomic<uint64_t> _connectedAt;
std::atomic<uint64_t> _incomingMessages; std::atomic<uint64_t> _incomingMessages;
std::atomic<uint64_t> _outgoingMessages; std::atomic<uint64_t> _outgoingMessages;
std::atomic<uint8_t> _encoding; std::atomic<uint8_t> _encoding;
std::atomic<bool> _authenticationRequired; std::atomic<bool> _authenticationRequired;
std::mutex _secretMutex; std::mutex _secretMutex;
std::string _secret; std::string _secret;
std::mutex _challengeMutex; std::mutex _challengeMutex;
std::string _challenge; std::string _challenge;
std::atomic<uint8_t> _rpcVersion; std::atomic<uint8_t> _rpcVersion;
std::atomic<bool> _isIdentified; std::atomic<bool> _isIdentified;
std::atomic<uint64_t> _eventSubscriptions; std::atomic<uint64_t> _eventSubscriptions;
}; };

View File

@ -20,7 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
namespace WebSocketOpCode { namespace WebSocketOpCode {
enum WebSocketOpCode: uint8_t { enum WebSocketOpCode : uint8_t {
/** /**
* The initial message sent by obs-websocket to newly connected clients. * The initial message sent by obs-websocket to newly connected clients.
* *
@ -122,8 +122,5 @@ namespace WebSocketOpCode {
RequestBatchResponse = 9, RequestBatchResponse = 9,
}; };
inline bool IsValid(uint8_t opCode) inline bool IsValid(uint8_t opCode) { return opCode >= Hello && opCode <= RequestBatchResponse; }
{
return opCode >= Hello && opCode <= RequestBatchResponse;
}
} }