Compare commits

..

10 Commits
4.8.0 ... 4.5.1

82 changed files with 2955 additions and 9595 deletions

5
.gitignore vendored
View File

@ -4,7 +4,6 @@
/build32/ /build32/
/build64/ /build64/
/release/ /release/
/package/
/installer/Output/ /installer/Output/
.idea
.vscode .vscode

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "deps/websocketpp"]
path = deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio.git

49
.travis.yml Normal file
View File

@ -0,0 +1,49 @@
language: cpp
env:
global:
# AWS key ID
- secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU=
# AWS key secret
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
cache:
directories:
- node_modules
matrix:
include:
- os: linux
env: _generate_docs
script: "./CI/generate-docs.sh"
- os: linux
env: _linux_build
dist: trusty
sudo: required
services:
- docker
before_install:
- docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package
-e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04
- docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh
script:
- docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh
after_success:
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
deploy:
- provider: s3
region: eu-central-1
bucket: obs-websocket-linux-builds
access_key_id: "$AWS_ID"
secret_access_key: "$AWS_SECRET"
local_dir: /home/travis/package
skip_cleanup: true
acl: public_read
on:
repo: Palakis/obs-websocket
condition:
- "$TRAVIS_OS_NAME = linux"
- "-d /home/travis/package"
all_branches: true

View File

@ -1,37 +1,31 @@
# Compiling obs-websocket # Compiling obs-websocket
## Prerequisites ## Prerequisites
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/), You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
[CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your [CMake](https://cmake.org/download/), and a working [development environment for
OBS Studio](https://obsproject.com/wiki/install-instructions) installed on your
computer. computer.
## Windows ## Windows
In cmake-gui, you'll have to set the following variables : In cmake-gui, you'll have to set the following variables :
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture - **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio - **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
- **LIBOBS_LIB** (filepath) : location of the obs.lib file - **LIBOBS_LIB** (filepath) : location of the obs.lib file
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file - **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
## Linux ## Linux
On Debian/Ubuntu :
On Debian/Ubuntu : ```
sudo apt-get install libqt5websockets5-dev
```shell
sudo apt-get install libboost-all-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket cd obs-websocket
mkdir build && cd build mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true .. cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4 make -j4
sudo make install sudo make install
``` ```
## OS X ## OS X
As a prerequisite, you will need Xcode for your current OSX version, the command line tools, and [Homebrew](https://brew.sh/).
As a prerequisite, you will need Xcode for your current OSX version, the Xcode command line tools, and [Homebrew](https://brew.sh/).
Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running. Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running.
Use of the Travis macOS CI scripts is recommended. Please note that these Use of the Travis macOS CI scripts is recommended. Please note that these
@ -44,7 +38,7 @@ skip that script.
Of course, you're encouraged to dig through the contents of these scripts to Of course, you're encouraged to dig through the contents of these scripts to
look for issues or specificities. look for issues or specificities.
```shell ```
git clone --recursive https://github.com/Palakis/obs-websocket.git git clone --recursive https://github.com/Palakis/obs-websocket.git
cd obs-websocket cd obs-websocket
./CI/install-dependencies-macos.sh ./CI/install-dependencies-macos.sh
@ -52,7 +46,6 @@ cd obs-websocket
./CI/build-macos.sh ./CI/build-macos.sh
./CI/package-macos.sh ./CI/package-macos.sh
``` ```
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder. This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
## Automated Builds ## Automated Builds

View File

@ -1,6 +0,0 @@
#!/bin/sh
set -ex
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
make -j4

8
CI/build-xenial.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
set -ex
cd /root/obs-websocket
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make -j4

View File

@ -1,6 +0,0 @@
if not exist %DepsBasePath% (
curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
7z x %DepsBasePath%.zip -o%DepsBasePath%
) else (
echo "OBS dependencies are already there. Download skipped."
)

View File

@ -4,9 +4,6 @@ echo "-- Generating documentation."
echo "-- Node version: $(node -v)" echo "-- Node version: $(node -v)"
echo "-- NPM version: $(npm -v)" echo "-- NPM version: $(npm -v)"
git fetch origin
git checkout ${CHECKOUT_REF/refs\/heads\//}
cd docs cd docs
npm install npm install
npm run build npm run build
@ -18,14 +15,19 @@ if git diff --quiet; then
exit 0 exit 0
fi fi
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
exit 0
fi
REMOTE_URL="$(git config remote.origin.url)" REMOTE_URL="$(git config remote.origin.url)"
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/} TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO} GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
git config user.name "Azure CI" git config user.name "Travis CI"
git config user.email "$COMMIT_AUTHOR_EMAIL" git config user.email "$COMMIT_AUTHOR_EMAIL"
git add ./generated git add ./generated
git pull git pull
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]" git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
git push -q $GITHUB_REPO git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH

View File

@ -36,7 +36,6 @@ echo "[obs-websocket] Building obs-studio.."
cmake .. \ cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
-DDISABLE_PLUGINS=true \ -DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DDepsPath=/tmp/obsdeps \ -DDepsPath=/tmp/obsdeps \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \ -DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
&& make -j4 && make -j4

129
CI/install-build-obs.cmd Normal file
View File

@ -0,0 +1,129 @@
@echo off
SETLOCAL EnableDelayedExpansion
REM Check if obs-studio build exists.
REM If the obs-studio directory does exist, check if the last OBS tag built
REM matches the latest OBS tag.
REM If the tags match, do not build obs-studio.
REM If the tags do not match, build obs-studio.
REM If the obs-studio directory doesn't exist, build obs-studio.
echo Checking for obs-studio build...
set OBSLatestTagPrePull=0
set OBSLatestTagPostPull=0
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
REM Set up the build flag as undefined.
set "BuildOBS="
REM Check the last tag successfully built by CI.
if exist C:\projects\obs-studio-last-tag-built.txt (
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else (
set OBSLastTagBuilt=0
)
REM If obs-studio directory exists, run git pull and get the latest tag number.
if exist C:\projects\obs-studio\ (
echo obs-studio directory exists
echo Updating tag info
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
git checkout master
git pull
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
)
REM Check the obs-studio tags for mismatches.
REM If a new tag was pulled, set the build flag.
if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% (
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true
)
REM If the latest git tag doesn't match the last built tag, set the build flag.
if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
echo Last built OBS tag: %OBSLastTagBuilt%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Tags do not match. Need to rebuild OBS.
set BuildOBS=true
)
REM If obs-studio directory does not exist, clone the git repo, get the latest
REM tag number, and set the build flag.
if not exist C:\projects\obs-studio (
echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio
cd C:\projects\obs-studio\
git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
set BuildOBS=true
)
REM If the needed obs-studio libs for this build_config do not exist,
REM set the build flag.
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
set BuildOBS=true
)
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
set BuildOBS=true
)
REM Some debug info
echo:
echo Latest tag pre-pull: %OBSLatestTagPrePull%
echo Latest tag post-pull: %OBSLatestTagPostPull%
echo Latest tag: %OBSLatestTag%
echo Last built OBS tag: %OBSLastTagBuilt%
if defined BuildOBS (
echo BuildOBS: true
) else (
echo BuildOBS: false
)
echo:
REM If the build flag is set, build obs-studio.
if defined BuildOBS (
echo Building obs-studio...
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
echo Removing previous build dirs...
if exist build rmdir /s /q C:\projects\obs-studio\build
if exist build32 rmdir /s /q C:\projects\obs-studio\build32
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
echo Making new build dirs...
mkdir build
mkdir build32
mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd ./build32
cmake -G "Visual Studio 14 2015" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ../build64
cmake -G "Visual Studio 14 2015 Win64" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
cd ..
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
) else (
echo Last OBS tag built is: %OBSLastTagBuilt%
echo No need to rebuild OBS.
)

View File

@ -1,19 +0,0 @@
#!/bin/sh
set -ex
sudo add-apt-repository -y ppa:obsproject/obs-studio
sudo apt-get -qq update
sudo apt-get install -y \
libc-dev-bin \
libc6-dev git \
build-essential \
checkinstall \
cmake \
obs-studio \
qtbase5-dev
# Dirty hack
sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h
sudo ldconfig

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -ex
add-apt-repository -y ppa:obsproject/obs-studio
apt-get -qq update
apt-get install -y \
libc-dev-bin \
libc6-dev git \
build-essential \
checkinstall \
cmake \
obs-studio \
libqt5websockets5-dev
# Dirty hack
wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h
ldconfig

View File

@ -1,8 +0,0 @@
if not exist %QtBaseDir% (
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z
7z x Qt_5.10.1.7z -o%QtBaseDir%
) else (
echo "Qt is already installed. Download skipped."
)
dir %QtBaseDir%

6
CI/install-setup-qt.cmd Normal file
View File

@ -0,0 +1,6 @@
@echo off
REM Set default values to use AppVeyor's built-in Qt.
set QTDIR32=C:\Qt\5.10.1\msvc2015
set QTDIR64=C:\Qt\5.10.1\msvc2015_64
set QTCompileVersion=5.10.1

View File

@ -12,6 +12,123 @@
<dict> <dict>
<key>CHILDREN</key> <key>CHILDREN</key>
<array> <array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/QtNetwork</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../build/QtWebSockets</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>292</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>bin</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Resources</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Contents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>OBS.app</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Applications</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict> <dict>
<key>CHILDREN</key> <key>CHILDREN</key>
<array> <array>
@ -514,11 +631,11 @@
<key>CONCLUSION_ACTION</key> <key>CONCLUSION_ACTION</key>
<integer>0</integer> <integer>0</integer>
<key>IDENTIFIER</key> <key>IDENTIFIER</key>
<string>fr.palakis.obs-websocket</string> <string>fr.palakis.obswebsocket</string>
<key>OVERWRITE_PERMISSIONS</key> <key>OVERWRITE_PERMISSIONS</key>
<false/> <false/>
<key>VERSION</key> <key>VERSION</key>
<string>4.8.0</string> <string>4.5.1</string>
</dict> </dict>
<key>PROJECT_COMMENTS</key> <key>PROJECT_COMMENTS</key>
<dict> <dict>

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
set -e set -e
@ -12,79 +12,61 @@ fi
echo "[obs-websocket] Preparing package build" echo "[obs-websocket] Preparing package build"
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)" export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
GIT_HASH=$(git rev-parse --short HEAD) export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}') export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork"
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG" export GIT_HASH=$(git rev-parse --short HEAD)
export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg" export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
FILENAME="obs-websocket-$VERSION.pkg" export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
export FILENAME="obs-websocket-$VERSION.pkg"
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
echo "[obs-websocket] Copying Qt dependencies"
if [ ! -f ./build/$(basename $WS_LIB) ]; then cp $WS_LIB ./build; fi
if [ ! -f ./build/$(basename $NET_LIB) ]; then cp $NET_LIB ./build; fi
chmod +rw ./build/QtWebSockets ./build/QtNetwork
echo "[obs-websocket] Modifying QtNetwork"
install_name_tool \
-id @rpath/QtNetwork \
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtNetwork
echo "[obs-websocket] Modifying QtWebSockets"
install_name_tool \
-id @rpath/QtWebSockets \
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
-change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
./build/QtWebSockets
echo "[obs-websocket] Modifying obs-websocket.so" echo "[obs-websocket] Modifying obs-websocket.so"
install_name_tool \ install_name_tool \
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \ -change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \ -change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \ -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
./build/obs-websocket.so ./build/obs-websocket.so
# Check if replacement worked # Check if replacement worked
echo "[obs-websocket] Dependencies for QtNetwork"
otool -L ./build/QtNetwork
echo "[obs-websocket] Dependencies for QtWebSockets"
otool -L ./build/QtWebSockets
echo "[obs-websocket] Dependencies for obs-websocket" echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so otool -L ./build/obs-websocket.so
if [[ "$RELEASE_MODE" == "True" ]]; then chmod -w ./build/QtWebSockets ./build/QtNetwork
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
else
echo "[obs-websocket] Skipped plugin codesigning"
fi
echo "[obs-websocket] Actual package build" echo "[obs-websocket] Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME" echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED mv ./release/obs-websocket.pkg ./release/$FILENAME
cp ./release/$FILENAME ./release/$LATEST_FILENAME
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing installer: $FILENAME"
productsign \
--sign "$INSTALLER_SIGNING_IDENTITY" \
./release/$FILENAME_UNSIGNED \
./release/$FILENAME
rm ./release/$FILENAME_UNSIGNED
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
zip -r ./release/$FILENAME.zip ./release/$FILENAME
UPLOAD_RESULT=$(xcrun altool \
--notarize-app \
--primary-bundle-id "fr.palakis.obs-websocket" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME" \
--file "./release/$FILENAME.zip")
rm ./release/$FILENAME.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
echo "Request UUID: $REQUEST_UUID"
echo "[obs-websocket] Wait for notarization result"
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME")
echo $CHECK_RESULT
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
xcrun stapler staple ./release/$FILENAME
break
fi
done
else
echo "[obs-websocket] Skipped installer codesigning and notarization"
fi

View File

@ -1,23 +0,0 @@
#!/bin/bash
set -e
export GIT_HASH=$(git rev-parse --short HEAD)
export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git"
if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then
export PKG_VERSION="$BRANCH_SHORT_NAME"
fi
cd ./build
PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \
--pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \
--pakdir="../package"
sudo chmod ao+r ../package/*

View File

@ -1,12 +0,0 @@
mkdir package
cd package
git rev-parse --short HEAD > package-version.txt
set /p PackageVersion=<package-version.txt
del package-version.txt
REM Package ZIP archive
7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*"
REM Build installer
iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer"

24
CI/package-xenial.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
set -e
cd /root/obs-websocket
export GIT_HASH=$(git rev-parse --short HEAD)
export PKG_VERSION="1-$GIT_HASH-$TRAVIS_BRANCH-git"
if [ -n "${TRAVIS_TAG}" ]; then
export PKG_VERSION="$TRAVIS_TAG"
fi
cd /root/obs-websocket/build
PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
--backup=no --deldoc=yes --install=no \
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
--requires="libqt5websockets5" --pkggroup="video" \
--pkgsource="https://github.com/Palakis/obs-websocket" \
--pakdir="/package"
chmod ao+r /package/*

View File

@ -1,37 +0,0 @@
@echo off
SETLOCAL EnableDelayedExpansion
REM If obs-studio directory does not exist, clone the git repo
if not exist %OBSPath% (
echo obs-studio directory does not exist
git clone https://github.com/obsproject/obs-studio %OBSPath%
cd /D %OBSPath%\
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
)
REM Prepare OBS Studio builds
echo Running CMake...
cd /D %OBSPath%
echo git checkout %OBSLatestTag%
git checkout %OBSLatestTag%
echo:
if not exist build32 mkdir build32
if not exist build64 mkdir build64
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
echo:
echo:
dir "%OBSPath%\libobs"

View File

@ -1,7 +0,0 @@
mkdir build32
mkdir build64
cd build32
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
cd ..\build64
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..

View File

@ -1,27 +1,23 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.2)
project(obs-websocket VERSION 4.8.0) project(obs-websocket)
set(CMAKE_PREFIX_PATH "${QTDIR}") set(CMAKE_PREFIX_PATH "${QTDIR}")
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DASIO_STANDALONE)
if (WIN32 OR APPLE) if (WIN32 OR APPLE)
include(external/FindLibObs.cmake) include(external/FindLibObs.cmake)
endif() endif()
find_package(LibObs REQUIRED) find_package(LibObs REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets) find_package(Qt5Core REQUIRED)
find_package(Qt5WebSockets REQUIRED)
find_package(Qt5Widgets REQUIRED)
set(obs-websocket_SOURCES set(obs-websocket_SOURCES
src/obs-websocket.cpp src/obs-websocket.cpp
src/WSServer.cpp src/WSServer.cpp
src/ConnectionProperties.cpp
src/WSRequestHandler.cpp src/WSRequestHandler.cpp
src/WSRequestHandler_General.cpp src/WSRequestHandler_General.cpp
src/WSRequestHandler_Profiles.cpp src/WSRequestHandler_Profiles.cpp
@ -34,28 +30,18 @@ set(obs-websocket_SOURCES
src/WSRequestHandler_Streaming.cpp src/WSRequestHandler_Streaming.cpp
src/WSRequestHandler_StudioMode.cpp src/WSRequestHandler_StudioMode.cpp
src/WSRequestHandler_Transitions.cpp src/WSRequestHandler_Transitions.cpp
src/WSRequestHandler_Outputs.cpp
src/WSEvents.cpp src/WSEvents.cpp
src/Config.cpp src/Config.cpp
src/Utils.cpp src/Utils.cpp
src/rpc/RpcRequest.cpp
src/rpc/RpcResponse.cpp
src/rpc/RpcEvent.cpp
src/protocol/OBSRemoteProtocol.cpp
src/forms/settings-dialog.cpp) src/forms/settings-dialog.cpp)
set(obs-websocket_HEADERS set(obs-websocket_HEADERS
src/obs-websocket.h src/obs-websocket.h
src/WSServer.h src/WSServer.h
src/ConnectionProperties.h
src/WSRequestHandler.h src/WSRequestHandler.h
src/WSEvents.h src/WSEvents.h
src/Config.h src/Config.h
src/Utils.h src/Utils.h
src/rpc/RpcRequest.h
src/rpc/RpcResponse.h
src/rpc/RpcEvent.h
src/protocol/OBSRemoteProtocol.h
src/forms/settings-dialog.h) src/forms/settings-dialog.h)
# --- Platform-independent build settings --- # --- Platform-independent build settings ---
@ -66,13 +52,13 @@ add_library(obs-websocket MODULE
include_directories( include_directories(
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
${Qt5Core_INCLUDES} ${Qt5Core_INCLUDES}
${Qt5Widgets_INCLUDES} ${Qt5WebSockets_INCLUDES}
"${CMAKE_SOURCE_DIR}/deps/asio/asio/include" ${Qt5Widgets_INCLUDES})
"${CMAKE_SOURCE_DIR}/deps/websocketpp")
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
libobs libobs
Qt5::Core Qt5::Core
Qt5::WebSockets
Qt5::Widgets) Qt5::Widgets)
# --- End of section --- # --- End of section ---
@ -84,13 +70,6 @@ if(WIN32)
message(FATAL_ERROR "Could not find OBS Frontend API's library !") message(FATAL_ERROR "Could not find OBS Frontend API's library !")
endif() endif()
if(MSVC)
# Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL)
add_definitions(/MP /d2FH4-)
endif()
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
if(CMAKE_SIZEOF_VOID_P EQUAL 8) if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ARCH_NAME "64bit") set(ARCH_NAME "64bit")
set(OBS_BUILDDIR_ARCH "build64") set(OBS_BUILDDIR_ARCH "build64")
@ -123,18 +102,10 @@ if(WIN32)
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
# In Release mode, copy Qt image format plugins
COMMAND if $<CONFIG:Release>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E copy
"${QTDIR}/plugins/imageformats/qjpeg.dll"
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
# If config is RelWithDebInfo, package release files # If config is RelWithDebInfo, package release files
COMMAND if $<CONFIG:RelWithDebInfo>==1 ( COMMAND if $<CONFIG:RelWithDebInfo>==1 (
"${CMAKE_COMMAND}" -E make_directory "${CMAKE_COMMAND}" -E make_directory
@ -147,6 +118,8 @@ if(WIN32)
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll"
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
@ -157,6 +130,8 @@ if(WIN32)
COMMAND if $<CONFIG:Debug>==1 ( COMMAND if $<CONFIG:Debug>==1 (
"${CMAKE_COMMAND}" -E copy "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:obs-websocket>" "$<TARGET_FILE:obs-websocket>"
"${QTDIR}/bin/Qt5WebSocketsd.dll"
"${QTDIR}/bin/Qt5Networkd.dll"
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}") "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
COMMAND if $<CONFIG:Debug>==1 ( COMMAND if $<CONFIG:Debug>==1 (
@ -180,22 +155,23 @@ endif()
# --- Linux-specific build settings and tasks --- # --- Linux-specific build settings and tasks ---
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
include(GNUInstallDirs) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set_target_properties(obs-websocket PROPERTIES PREFIX "") set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket obs-frontend-api) target_link_libraries(obs-websocket
obs-frontend-api)
file(GLOB locale_files data/locale/*.ini) file(GLOB locale_files data/locale/*.ini)
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE)
if(${USE_UBUNTU_FIX})
install(TARGETS obs-websocket
LIBRARY DESTINATION "/usr/lib/obs-plugins")
endif()
install(TARGETS obs-websocket install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins") LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
# Dirty fix for Ubuntu
install(TARGETS obs-websocket
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
install(FILES ${locale_files} install(FILES ${locale_files}
DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale") DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
endif() endif()
# --- End of section --- # --- End of section ---

113
README.md
View File

@ -1,124 +1,65 @@
obs-websocket obs-websocket
============== ==============
Remote control of OBS Studio made easy.
WebSockets API for OBS Studio. Follow the project on Twitter for news & updates : [@obswebsocket](https://twitter.com/obswebsocket)
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis) [![Gitter chat](https://badges.gitter.im/obs-websocket/obs-websocket.png)](https://gitter.im/obs-websocket/obs-websocket) [![Build Status - Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [![Build Status - Linux & OS X](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket)
[![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
## Downloads ## Downloads
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
## Using obs-websocket ## Using obs-websocket
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/ A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it. It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
### Possible use cases ### Possible use cases
- Remote control OBS from a phone or tablet on the same local network - Remote control OBS from a phone or tablet on the same local network
- Change your stream overlay/graphics based on the current scene - Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...) - Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
### For developers ### For developers
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog). The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
Here's a list of available language APIs for obs-websocket : Here's a list of available language APIs for obs-websocket :
- Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan - Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet) - C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi - Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik - Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468
- Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET
- Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf
- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `stephane /dot/ lepin /at/ gmail /dot/ com` ! I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `contact at slepin dot fr` !
## Compiling obs-websocket ## Compiling obs-websocket
See the [build instructions](BUILDING.md). See the [build instructions](BUILDING.md).
## Contributing
### Branches
Development happens on `4.x-current`
### Pull Requests
Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`). Start your work in a new branch
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
**If your Pull Request is not ready to merge yet, create it as a Draft Pull Request** (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request").
### Code style & formatting
Source code is indented with tabs, with spaces allowed for alignment.
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
These are required to automatically generate the [protocol specification document](docs/generated/protocol.md).
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
```cpp
if (success) {
return req->SendOKResponse();
} else {
return req->SendErrorResponse("something went wrong");
}
```
is better like this:
```cpp
if (!success) {
return req->SendErrorResponse("something went wrong");
}
return req->SendOKResponse();
```
## Translations ## Translations
**We need your help on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
**Your help is welcome on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
## Special thanks ## Special thanks
In order of appearance:
In (almost) order of appearance: - [Brendan H.](https://github.com/haganbmj) : Code contributions and gooder English in the Protocol specification
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
- [Brendan H.](https://github.com/haganbmj): Code contributions and gooder English in the Protocol specification - [Tobias Frahmer](https://github.com/Frahmer) : German localization
- [Mikhail Swift](https://github.com/mikhailswift): Code contributions - [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese localizations
- [Tobias Frahmer](https://github.com/Frahmer): Initial German localization - [Larissa Gabilan](https://github.com/laris151) : Portuguese localization
- [Genture](https://github.com/Genteure): Initial Simplified Chinese and Traditional Chinese localizations - [Andy Asquelt](https://github.com/asquelt) : Polish localization
- [Larissa Gabilan](https://github.com/laris151): Initial Portuguese localization - [Marcel Haazen](https://github.com/inpothet) : Dutch localization
- [Andy Asquelt](https://github.com/asquelt): Initial Polish localization - [Peter Antonvich](https://github.com/pantonvich) : Code contributions
- [Marcel Haazen](https://github.com/nekocentral): Initial Dutch localization - [yinzara](https://github.com/yinzara) : Code contributions
- [Peter Antonvich](https://github.com/pantonvich): Code contributions - [Chris Angelico](https://github.com/Rosuav) : Code contributions
- [yinzara](https://github.com/yinzara): Code contributions - [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions
- [Chris Angelico](https://github.com/Rosuav): Code contributions - [Marwin M](https://github.com/dragonbane0) : Code contributions
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi): Code contributions - [Logan S.](https://github.com/lsdaniel) : Code contributions
- [Marwin M](https://github.com/dragonbane0): Code contributions - [RainbowEK](https://github.com/RainbowEK) : Code contributions
- [Logan S.](https://github.com/lsdaniel): Code contributions - [RytoEX](https://github.com/RytoEX) : CI script and code contributions
- [RainbowEK](https://github.com/RainbowEK): Code contributions - [Theodore Stoddard](https://github.com/TStod) : Code contributions
- [RytoEX](https://github.com/RytoEX): CI script and code contributions - [Philip Loche](https://github.com/PicoCentauri) : Code contributions
- [Theodore Stoddard](https://github.com/TStod): Code contributions
- [Philip Loche](https://github.com/PicoCentauri): Code contributions
- [Patrick Heyer](https://github.com/PatTheMav): Code contributions and CI fixes
- [Alex Van Camp](https://github.com/Lange): Code contributions
- [Freddie Meyer](https://github.com/DungFu): Code contributions
- [Casey Muller](https://github.com/caseymrm): CI fixes
- [Chris Angelico](https://github.com/Rosuav): Documentation fixes
And also: special thanks to supporters of the project! And also: special thanks to supporters of the project!
## Supporters ## Supporters
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them! They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
--- ---

40
appveyor.yml Normal file
View File

@ -0,0 +1,40 @@
environment:
CURL_VERSION: 7.39.0
install:
- git submodule update --init --recursive
- cd C:\projects\
- if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
- 7z x dependencies2015.zip -odependencies2015
- set DepsPath32=%CD%\dependencies2015\win32
- set DepsPath64=%CD%\dependencies2015\win64
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
- set build_config=RelWithDebInfo
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
- cd C:\projects\obs-websocket\
- mkdir build32
- mkdir build64
- cd ./build32
- cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
- cd ../build64
- cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
build_script:
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
before_deploy:
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
- iscc "C:\projects\obs-websocket\installer\installer.iss"
deploy_script:
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
test: off
cache:
- C:\projects\dependencies2015.zip
- C:\projects\obs-studio-last-tag-built.txt
- C:\projects\obs-studio\

View File

@ -1,183 +1,20 @@
variables: pool:
isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }} vmImage: 'macOS-10.13'
trigger: steps:
branches: - script: ./CI/install-dependencies-macos.sh
include: displayName: 'Install Dependencies'
- master
tags:
include:
- '*'
jobs: - script: ./CI/install-build-obs-macos.sh
- job: 'GenerateDocs' displayName: 'Build OBS'
condition: |
or(
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
eq(variables['Build.SourceBranch'], 'refs/heads/master')
)
pool:
vmImage: 'ubuntu-18.04'
steps:
- checkout: self
submodules: false
- script: ./CI/generate-docs.sh - script: ./CI/build-macos.sh
displayName: 'Generate docs' displayName: 'Build obs-websocket'
env:
CHECKOUT_REF: $(Build.SourceBranch)
GH_TOKEN: $(GithubToken)
- job: 'Build_Windows' - script: ./CI/package-macos.sh
pool: displayName: 'Package'
vmImage: 'windows-2019'
variables:
build_config: RelWithDebInfo
DepsBasePath: 'D:\obsdependencies'
DepsPath32: '$(DepsBasePath)\win32'
DepsPath64: '$(DepsBasePath)\win64'
QtBaseDir: 'D:\QtDep'
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
OBSPath: 'D:\obs-studio'
steps:
- checkout: self
submodules: true
- script: ./CI/install-qt-win.cmd - task: PublishBuildArtifacts@1
displayName: 'Install Qt' inputs:
env: pathtoPublish: './release'
QtBaseDir: $(QtBaseDir) artifactName: 'build'
- task: Cache@2
displayName: Restore cached OBS Studio dependencies
inputs:
key: 'obsdeps | "$(Agent.OS)"'
restoreKeys: |
obsdeps | "$(Agent.OS)"
path: $(DepsBasePath)
- script: ./CI/download-obs-deps.cmd
displayName: 'Download OBS Studio dependencies'
- task: Cache@2
displayName: Restore cached OBS Studio builds
inputs:
key: 'obs | "$(Agent.OS)"'
restoreKeys: |
obs | "$(Agent.OS)"
path: $(OBSPath)
- script: ./CI/prepare-obs-windows.cmd
displayName: 'Checkout & CMake OBS Studio'
env:
build_config: $(build_config)
DepsPath32: $(DepsPath32)
DepsPath64: $(DepsPath64)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build OBS Studio 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build32\obs-studio.sln'
- task: MSBuild@1
displayName: 'Build OBS Studio 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '$(OBSPath)\build64\obs-studio.sln'
- script: ./CI/prepare-windows.cmd
displayName: 'CMake obs-websocket'
env:
build_config: $(build_config)
QTDIR32: $(QTDIR32)
QTDIR64: $(QTDIR64)
OBSPath: $(OBSPath)
- task: MSBuild@1
displayName: 'Build obs-websocket 32-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build32\obs-websocket.sln'
- task: MSBuild@1
displayName: 'Build obs-websocket 64-bit'
inputs:
msbuildArguments: '/m /p:Configuration=$(build_config)'
solution: '.\build64\obs-websocket.sln'
- script: ./CI/package-windows.cmd
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
displayName: 'Upload package artifacts'
inputs:
pathtoPublish: './package'
artifactName: 'windows_build'
- job: 'Build_Linux'
pool:
vmImage: 'ubuntu-18.04'
variables:
BUILD_REASON: $(Build.Reason)
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
BRANCH_FULL_NAME: $(Build.SourceBranch)
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-ubuntu.sh
displayName: 'Install dependencies'
- script: ./CI/build-ubuntu.sh
displayName: 'Build obs-websocket'
- script: ./CI/package-ubuntu.sh
displayName: 'Package obs-websocket'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './package'
artifactName: 'deb_build'
- job: 'Build_macOS'
pool:
vmImage: 'macos-10.14'
steps:
- checkout: self
submodules: true
- script: ./CI/install-dependencies-macos.sh
displayName: 'Install dependencies'
- script: ./CI/install-build-obs-macos.sh
displayName: 'Build OBS'
- script: ./CI/build-macos.sh
displayName: 'Build obs-websocket'
- task: InstallAppleCertificate@1
displayName: 'Install release signing certificates'
condition: eq(variables['isReleaseMode'], true)
inputs:
certSecureFile: 'Certificates.p12'
certPwd: $(secrets.macOS.certificatesImportPassword)
- script: ./CI/package-macos.sh
displayName: 'Package obs-websocket'
env:
RELEASE_MODE: $(isReleaseMode)
CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity)
INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity)
AC_USERNAME: $(secrets.macOS.notarization.username)
AC_PASSWORD: $(secrets.macOS.notarization.password)
AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: './release'
artifactName: 'macos_build'

View File

View File

@ -1,16 +1,14 @@
OBSWebsocket.Settings.DialogTitle="WebSockets-Servereinstellungen" OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen"
OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren" OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen"
OBSWebsocket.Settings.ServerPort="Server-Port" OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren" OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
OBSWebsocket.Settings.Password="Passwort" OBSWebsocket.Settings.Password="Passwort"
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren" OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren" OBSWebsocket.Settings.AlertsEnable="Infobereich-Benachrichtigungen aktivieren"
OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung" OBSWebsocket.NotifyConnect.Title="Neue WebSocket Verbindung"
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden" OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt" OBSWebsocket.NotifyDisconnect.Title="WebSocket-Client getrennt"
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt" OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler" OBSWebsocket.Server.StartFailed.Title="WebSocket-Server Fehler"
OBSWebsocket.Server.StartFailed.Message="Der WebSockets-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es mit anderen Einstellungen, einem OBS-Neustart oder einem Systemneustart erneut." OBSWebsocket.Server.StartFailed.Message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - TCP Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den WebSocket-Server Einstellungen zu setzten oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es erneut mit anderen Einstellungen, einem OBS neustart oder einem System neustart."
OBSWebsocket.ProfileChanged.Started="WebSockets-Server in diesem Profil aktiviert. Server gestartet."
OBSWebsocket.ProfileChanged.Stopped="WebSockets-Server in diesem Profil deaktiviert. Server gestoppt."
OBSWebsocket.ProfileChanged.Restarted="WebSockets-Server in diesem Profil geändert. Server startet neu."

View File

@ -1,5 +1,6 @@
OBSWebsocket.Settings.DialogTitle="WebSockets Server Settings" OBSWebsocket.Menu.SettingsItem="Websocket server settings"
OBSWebsocket.Settings.ServerEnable="Enable WebSockets server" OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
OBSWebsocket.Settings.ServerPort="Server Port" OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Enable authentication" OBSWebsocket.Settings.AuthRequired="Enable authentication"
OBSWebsocket.Settings.Password="Password" OBSWebsocket.Settings.Password="Password"
@ -9,8 +10,5 @@ OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
OBSWebsocket.NotifyConnect.Message="Client %1 connected" OBSWebsocket.NotifyConnect.Message="Client %1 connected"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected" OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected" OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure" OBSWebsocket.Server.StartFailed.Title="WebSocket Server failure"
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2" OBSWebsocket.Server.StartFailed.Message="The obs-websocket server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system."
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."

View File

@ -1,12 +1,14 @@
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets" OBSWebsocket.Menu.SettingsItem="Configuración del servidor obs-websocket"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor Websocket"
OBSWebsocket.Settings.ServerPort="Puerto del Servidor" OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación" OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
OBSWebsocket.Settings.Password="Contraseña" OBSWebsocket.Settings.Password="Contraseña"
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración" OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas en la bandeja de sistema" OBSWebsocket.Settings.AlertsEnable="Habilitar alertas de la bandeja de sistema"
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket" OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado" OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado" OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Falla en el servidor WebSockets" OBSWebsocket.Server.StartFailed.Title="Fallo del servidor WebSocket"
OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente siendo usado este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha afectado su sistema. Inténtelo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema." OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente en uso en este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha ocurrido en su sistema. Inténtalo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema."

View File

@ -1,4 +1,4 @@
OBSWebsocket.Settings.DialogTitle="Paramètres du serveur WebSockets" OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket" OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
OBSWebsocket.Settings.ServerPort="Port du serveur" OBSWebsocket.Settings.ServerPort="Port du serveur"
OBSWebsocket.Settings.AuthRequired="Activer l'authentification" OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
@ -9,8 +9,5 @@ OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté" OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket" OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté" OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets" OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSocket"
OBSWebsocket.Server.StartFailed.Message="Le serveur WebSockets n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur." OBSWebsocket.Server.StartFailed.Message="Le serveur WebSocket n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur."
OBSWebsocket.ProfileChanged.Started="Serveur WebSockets actif dans ce profil."
OBSWebsocket.ProfileChanged.Stopped="Serveur WebSockets désactivé dans ce profil."
OBSWebsocket.ProfileChanged.Restarted="Le port actuel diffère du port configuré dans ce profil. Serveur WebSockets redémarré."

View File

View File

@ -1,4 +1,6 @@
OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets" OBSWebsocket.Menu.SettingsItem="Impostazioni del server di WebSocket"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Abilitare il server Websocket"
OBSWebsocket.Settings.ServerPort="Porta del server" OBSWebsocket.Settings.ServerPort="Porta del server"
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione" OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
OBSWebsocket.Settings.Password="Password" OBSWebsocket.Settings.Password="Password"
@ -9,4 +11,4 @@ OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso" OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server" OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di WebSockets: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSockets, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema." OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di obs-websocket: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSocket, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema."

View File

@ -1,11 +1,8 @@
OBSWebsocket.Settings.ServerEnable="WebSockets サーバーを有効にする" OBSWebsocket.Menu.SettingsItem="Websocket サーバー設定"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Websocket サーバーを有効にする"
OBSWebsocket.Settings.ServerPort="サーバーポート" OBSWebsocket.Settings.ServerPort="サーバーポート"
OBSWebsocket.Settings.AuthRequired="認証を有効にする" OBSWebsocket.Settings.AuthRequired="認証を有効にする"
OBSWebsocket.Settings.Password="パスワード" OBSWebsocket.Settings.Password="パスワード"
OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする" OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする"
OBSWebsocket.Settings.AlertsEnable="システムトレイ通知を有効にする" OBSWebsocket.NotifyConnect.Title="新しいWebSocket接続"
OBSWebsocket.NotifyConnect.Title="新しい WebSocket 接続"
OBSWebsocket.NotifyConnect.Message="接続されているクライアント %1"
OBSWebsocket.NotifyDisconnect.Title="WebSocket クライアントが切断されました"
OBSWebsocket.NotifyDisconnect.Message="切断されたクライアント %1"
OBSWebsocket.Server.StartFailed.Title="WebSockets サーバー障害"

View File

@ -1,9 +1,14 @@
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
OBSWebsocket.Settings.ServerPort="Serverpoort" OBSWebsocket.Settings.ServerPort="Serverpoort"
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
OBSWebsocket.Settings.Password="Wachtwoord"
OBSWebsocket.Settings.DebugEnable="Activeer debug logs" OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen" OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding" OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden" OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken" OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld" OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
OBSWebsocket.Server.StartFailed.Title="Fout in WebSocket server" OBSWebsocket.Server.StartFailed.Title="WebSocket Server mislukt"
OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel elders wordt gebruikt op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort in te stellen in de WebSocket Server-instellingen of stop elke toepassing die deze poort zou kunnen gebruiken.\n Een onbekende Netwerkfout op uw systeem. Probeer het opnieuw door de instellingen te wijzigen, OBS te herstarten of uw systeem te herstarten." OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel wordt gebruikt elders op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort instellen in de WebSocket Server-instellingen of stoppen van elke toepassing die deze poort zouden kunnen gebruiken.\n Een onbekende netwerkfout gebeurde op uw systeem. Probeer het opnieuw door de instellingen wijzigen, OBS herstarten of opnieuw opstarten van uw systeem."

View File

@ -1,10 +1,14 @@
OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets" OBSWebsocket.Menu.SettingsItem="Ustawienia serwera zdalnego sterowania"
OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania" OBSWebsocket.Settings.DialogTitle="Serwer zdalnego sterowania"
OBSWebsocket.Settings.ServerEnable="Włącz serwer zdalnego sterowania (Websocket)"
OBSWebsocket.Settings.ServerPort="Port serwera"
OBSWebsocket.Settings.AuthRequired="Wymagaj hasła"
OBSWebsocket.Settings.Password="Hasło"
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania" OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym" OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia o zasobniku systemowym"
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket" OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony" OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony" OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony" OBSWebsocket.NotifyDisconnect.Message="Klient %1 połączony"
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets" OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSocket"
OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera WebSockets, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSockets, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system." OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera obs-websocket, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSocket, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system."

5
data/locale/pt-BR.ini Normal file
View File

@ -0,0 +1,5 @@
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
OBSWebsocket.Settings.Password="Senha"

View File

@ -1,3 +1,5 @@
OBSWebsocket.Menu.SettingsItem="Configurações do servidor Websocket"
OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets" OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
OBSWebsocket.Settings.ServerPort="Porta do Servidor" OBSWebsocket.Settings.ServerPort="Porta do Servidor"
OBSWebsocket.Settings.AuthRequired="Activar autenticação" OBSWebsocket.Settings.AuthRequired="Activar autenticação"
@ -9,4 +11,4 @@ OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket" OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema." OBSWebsocket.Server.StartFailed.Message="O servidor obs-websocket falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSocket, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema."

View File

@ -1,7 +1,8 @@
OBSWebsocket.Settings.DialogTitle="Настройки сервера WebSockets" OBSWebsocket.Menu.SettingsItem="Параметры сервера Websocket"
OBSWebsocket.Settings.ServerEnable="Включить сервер WebSockets" OBSWebsocket.Settings.DialogTitle="obs-websocket"
OBSWebsocket.Settings.ServerEnable="Включить сервер Websocket"
OBSWebsocket.Settings.ServerPort="Порт сервера" OBSWebsocket.Settings.ServerPort="Порт сервера"
OBSWebsocket.Settings.AuthRequired="Включить авторизацию" OBSWebsocket.Settings.AuthRequired="Включить аутентификацию"
OBSWebsocket.Settings.Password="Пароль" OBSWebsocket.Settings.Password="Пароль"
OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки" OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки"
OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее" OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее"
@ -9,5 +10,5 @@ OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket"
OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен" OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен"
OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён" OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён"
OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен" OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен"
OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSockets" OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSocket"
OBSWebsocket.Server.StartFailed.Message="Ошибка запуска сервера WebSockets. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSockets или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или системы." OBSWebsocket.Server.StartFailed.Message="Сбой запуска сервера obs-websocket. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSocket или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или перезапуска системы."

View File

@ -1,12 +1,6 @@
OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器" OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置"
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
OBSWebsocket.Settings.ServerPort="服务器端口" OBSWebsocket.Settings.ServerPort="服务器端口"
OBSWebsocket.Settings.AuthRequired="启用身份验证" OBSWebsocket.Settings.AuthRequired="启用密码认证"
OBSWebsocket.Settings.Password="密码" OBSWebsocket.Settings.Password="密码"
OBSWebsocket.Settings.DebugEnable="启用调试日志"
OBSWebsocket.Settings.AlertsEnable="启用系统托盘通知"
OBSWebsocket.NotifyConnect.Title="新 WebSocket 连接"
OBSWebsocket.NotifyConnect.Message="客户端 %1 已连接"
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客户端已断开"
OBSWebsocket.NotifyDisconnect.Message="客户端 %1 已断开连接"
OBSWebsocket.Server.StartFailed.Title="WebSockets 服务器错误"
OBSWebsocket.Server.StartFailed.Message="WebSockets 服务器启动失败,可能是因为:\n - TCP 端口 %1 可能被本机的另一个应用程序占用。尝试在 WebSockets 服务器设置中设置不同的 TCP 端口,或关闭任何可能使用此端口的应用程序。\n - 在您的系统上发生了未知的网络错误。请尝试更改设置、重新启动 OBS 或重新启动系统。"

View File

@ -1,9 +1,6 @@
OBSWebsocket.Settings.ServerPort="伺服器連接埠" OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定"
OBSWebsocket.Settings.DebugEnable="啟用除錯日誌" OBSWebsocket.Settings.DialogTitle="obs-websocket 設定"
OBSWebsocket.Settings.AlertsEnable="啟用系統列通知" OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器"
OBSWebsocket.NotifyConnect.Title="新的 WebSocket 連線" OBSWebsocket.Settings.ServerPort="伺服器端口"
OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線" OBSWebsocket.Settings.AuthRequired="啟用密碼認證"
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客戶端已離線" OBSWebsocket.Settings.Password="密碼"
OBSWebsocket.NotifyDisconnect.Message="客戶端 %1 已離線"
OBSWebsocket.Server.StartFailed.Title="WebSocket 伺服器錯誤"
OBSWebsocket.Server.StartFailed.Message="WebSockets 伺服器啟動失敗,可能的原因有:\n - TCP 連接埠 %1 被系統的其他程式所使用,試著為 WebSockets 伺服器指定不同的 TCP 連接埠,或是關閉任何可能使用此連接埠的程式。\n - 發生的未知的網路錯誤,試著更改設定、重新啟動 OBS 或是重新啟動您的系統。"

1
deps/asio vendored

Submodule deps/asio deleted from b73dc1d2c0

1
deps/websocketpp vendored

Submodule deps/websocketpp deleted from c6d7e295bf

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# obs-websocket 4.8.0 protocol reference # obs-websocket 4.5.0 protocol reference
# General Introduction # General Introduction
Messages are exchanged between the client and the server as JSON objects. Messages are exchanged between the client and the server as JSON objects.

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket" #define MyAppName "obs-websocket"
#define MyAppVersion "4.8.0" #define MyAppVersion "4.5.1"
#define MyAppPublisher "Stephane Lepin" #define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket" #define MyAppURL "http://github.com/Palakis/obs-websocket"
@ -23,13 +23,14 @@ DefaultGroupName={#MyAppName}
OutputBaseFilename=obs-websocket-Windows-Installer OutputBaseFilename=obs-websocket-Windows-Installer
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
LicenseFile=..\LICENSE
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
[Files] [Files]
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
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]
@ -37,21 +38,6 @@ 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();
var
GPLText: AnsiString;
Page: TOutputMsgMemoWizardPage;
begin
ExtractTemporaryFile('LICENSE');
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
Page := CreateOutputMsgMemoPage(wpWelcome,
'License Information', 'Please review the license terms before installing obs-websocket',
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
String(GPLText)
);
end;
// 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;

View File

@ -17,10 +17,10 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/config-file.h>
#include <QtCore/QCryptographicHash> #include <QCryptographicHash>
#include <QtCore/QTime> #include <QTime>
#include <QtWidgets/QSystemTrayIcon>
#define SECTION_NAME "WebsocketAPI" #define SECTION_NAME "WebsocketAPI"
#define PARAM_ENABLE "ServerEnabled" #define PARAM_ENABLE "ServerEnabled"
@ -31,13 +31,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define PARAM_SECRET "AuthSecret" #define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt" #define PARAM_SALT "AuthSalt"
#include "Utils.h"
#include "WSServer.h"
#include "Config.h" #include "Config.h"
#include "Utils.h"
#define QT_TO_UTF8(str) str.toUtf8().constData() #define QT_TO_UTF8(str) str.toUtf8().constData()
Config* Config::_instance = new Config();
Config::Config() : Config::Config() :
ServerEnabled(true), ServerEnabled(true),
ServerPort(4444), ServerPort(4444),
@ -50,55 +50,8 @@ Config::Config() :
{ {
qsrand(QTime::currentTime().msec()); qsrand(QTime::currentTime().msec());
SetDefaults();
SessionChallenge = GenerateSalt();
obs_frontend_add_event_callback(OnFrontendEvent, this);
}
Config::~Config()
{
obs_frontend_remove_event_callback(OnFrontendEvent, this);
}
void Config::Load()
{
config_t* obsConfig = GetConfigStore();
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
}
void Config::Save()
{
config_t* obsConfig = GetConfigStore();
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort);
config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
QT_TO_UTF8(Secret));
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
QT_TO_UTF8(Salt));
config_save(obsConfig);
}
void Config::SetDefaults()
{
// OBS Config defaults // OBS Config defaults
config_t* obsConfig = GetConfigStore(); config_t* obsConfig = obs_frontend_get_global_config();
if (obsConfig) { if (obsConfig) {
config_set_default_bool(obsConfig, config_set_default_bool(obsConfig,
SECTION_NAME, PARAM_ENABLE, ServerEnabled); SECTION_NAME, PARAM_ENABLE, ServerEnabled);
@ -117,11 +70,46 @@ void Config::SetDefaults()
config_set_default_string(obsConfig, config_set_default_string(obsConfig,
SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt)); SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt));
} }
SessionChallenge = GenerateSalt();
} }
config_t* Config::GetConfigStore() Config::~Config()
{ {
return obs_frontend_get_profile_config(); }
void Config::Load()
{
config_t* obsConfig = obs_frontend_get_global_config();
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
}
void Config::Save()
{
config_t* obsConfig = obs_frontend_get_global_config();
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort);
config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
QT_TO_UTF8(Secret));
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
QT_TO_UTF8(Salt));
config_save(obsConfig);
} }
QString Config::GenerateSalt() QString Config::GenerateSalt()
@ -192,95 +180,7 @@ bool Config::CheckAuth(QString response)
return authSuccess; return authSuccess;
} }
void Config::OnFrontendEvent(enum obs_frontend_event event, void* param) Config* Config::Current()
{ {
auto config = reinterpret_cast<Config*>(param); return _instance;
if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
obs_frontend_push_ui_translation(obs_module_get_string);
QString startMessage = QObject::tr("OBSWebsocket.ProfileChanged.Started");
QString stopMessage = QObject::tr("OBSWebsocket.ProfileChanged.Stopped");
QString restartMessage = QObject::tr("OBSWebsocket.ProfileChanged.Restarted");
obs_frontend_pop_ui_translation();
bool previousEnabled = config->ServerEnabled;
uint64_t previousPort = config->ServerPort;
config->SetDefaults();
config->Load();
if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) {
auto server = GetServer();
server->stop();
if (config->ServerEnabled) {
server->start(config->ServerPort);
if (previousEnabled != config->ServerEnabled) {
Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information);
} else {
Utils::SysTrayNotify(restartMessage, QSystemTrayIcon::MessageIcon::Information);
}
} else {
Utils::SysTrayNotify(stopMessage, QSystemTrayIcon::MessageIcon::Information);
}
}
}
}
void Config::MigrateFromGlobalSettings()
{
config_t* source = obs_frontend_get_global_config();
config_t* destination = obs_frontend_get_profile_config();
if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);
config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);
config_remove_value(source, SECTION_NAME, PARAM_PORT);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);
config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);
config_remove_value(source, SECTION_NAME, PARAM_ALERT);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);
config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);
config_remove_value(source, SECTION_NAME, PARAM_SECRET);
}
if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);
config_remove_value(source, SECTION_NAME, PARAM_SALT);
}
config_save(destination);
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@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
@ -16,12 +16,10 @@ 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/>
*/ */
#pragma once #ifndef CONFIG_H
#define CONFIG_H
#include <obs-frontend-api.h> #include <QString>
#include <util/config-file.h>
#include <QtCore/QString>
#include <QtCore/QSharedPointer>
class Config { class Config {
public: public:
@ -29,10 +27,6 @@ class Config {
~Config(); ~Config();
void Load(); void Load();
void Save(); void Save();
void SetDefaults();
config_t* GetConfigStore();
void MigrateFromGlobalSettings();
void SetPassword(QString password); void SetPassword(QString password);
bool CheckAuth(QString userChallenge); bool CheckAuth(QString userChallenge);
@ -52,6 +46,10 @@ class Config {
QString SessionChallenge; QString SessionChallenge;
bool SettingsLoaded; bool SettingsLoaded;
static Config* Current();
private: private:
static void OnFrontendEvent(enum obs_frontend_event event, void* param); static Config* _instance;
}; };
#endif // CONFIG_H

View File

@ -1,34 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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 "ConnectionProperties.h"
ConnectionProperties::ConnectionProperties()
: _authenticated(false)
{
}
bool ConnectionProperties::isAuthenticated()
{
return _authenticated.load();
}
void ConnectionProperties::setAuthenticated(bool authenticated)
{
_authenticated.store(authenticated);
}

View File

@ -1,31 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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/>
*/
#pragma once
#include <atomic>
class ConnectionProperties
{
public:
explicit ConnectionProperties();
bool isAuthenticated();
void setAuthenticated(bool authenticated);
private:
std::atomic<bool> _authenticated;
};

View File

@ -16,15 +16,11 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <inttypes.h> #include <QMainWindow>
#include <QtWidgets/QMainWindow> #include <QDir>
#include <QtCore/QDir> #include <QUrl>
#include <QtCore/QUrl>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <obs.hpp> #include <obs.hpp>
#include <util/platform.h>
#include "obs-websocket.h" #include "obs-websocket.h"
#include "Utils.h" #include "Utils.h"
@ -32,47 +28,22 @@ with this program. If not, see <https://www.gnu.org/licenses/>
Q_DECLARE_METATYPE(OBSScene); Q_DECLARE_METATYPE(OBSScene);
const QHash<obs_bounds_type, QString> boundTypeNames = { obs_data_array_t* Utils::StringListToArray(char** strings, char* key) {
{ OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" }, if (!strings)
{ OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" }, return obs_data_array_create();
{ 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" },
{ OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" },
};
QString getBoundsNameFromType(obs_bounds_type type) {
QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE);
return boundTypeNames.value(type, fallback);
}
obs_bounds_type getBoundsTypeFromName(QString name) {
return boundTypeNames.key(name);
}
obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) {
obs_data_array_t* list = obs_data_array_create(); obs_data_array_t* list = obs_data_array_create();
if (!strings || !key) { char* value = "";
return list; // empty list for (int i = 0; value != nullptr; i++) {
} value = strings[i];
size_t index = 0;
char* value = nullptr;
do {
value = strings[index];
OBSDataAutoRelease item = obs_data_create(); OBSDataAutoRelease item = obs_data_create();
obs_data_set_string(item, key, value); obs_data_set_string(item, key, value);
if (value) { if (value)
obs_data_array_push_back(list, item); obs_data_array_push_back(list, item);
} }
index++;
} while (value != nullptr);
return list; return list;
} }
@ -81,16 +52,15 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
obs_data_array_t* items = obs_data_array_create(); obs_data_array_t* items = obs_data_array_create();
OBSScene scene = obs_scene_from_source(source); OBSScene scene = obs_scene_from_source(source);
if (!scene) { if (!scene)
return nullptr; return nullptr;
}
obs_scene_enum_items(scene, []( obs_scene_enum_items(scene, [](
obs_scene_t* scene, obs_scene_t* scene,
obs_sceneitem_t* currentItem, obs_sceneitem_t* currentItem,
void* param) void* param)
{ {
obs_data_array_t* data = reinterpret_cast<obs_data_array_t*>(param); obs_data_array_t* data = static_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemData(currentItem); OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
obs_data_array_insert(data, 0, itemData); obs_data_array_insert(data, 0, itemData);
@ -100,29 +70,9 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
return items; return items;
} }
/**
* @typedef {Object} `SceneItem` An OBS Scene Item.
* @property {Number} `cy`
* @property {Number} `cx`
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @property {String} `name` The name of this Scene Item.
* @property {int} `id` Scene item ID
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
* @property {Number} `source_cx`
* @property {Number} `source_cy`
* @property {String} `type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown"
* @property {Number} `volume`
* @property {Number} `x`
* @property {Number} `y`
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItem> (optional)} `groupChildren` List of children (if this item is a group)
*/
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) { obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
if (!item) { if (!item)
return nullptr; return nullptr;
}
vec2 pos; vec2 pos;
obs_sceneitem_get_pos(item, &pos); obs_sceneitem_get_pos(item, &pos);
@ -137,77 +87,58 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
obs_data_t* data = obs_data_create(); obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name", obs_data_set_string(data, "name",
obs_source_get_name(itemSource)); obs_source_get_name(obs_sceneitem_get_source(item)));
obs_data_set_int(data, "id",
obs_sceneitem_get_id(item));
obs_data_set_string(data, "type", obs_data_set_string(data, "type",
obs_source_get_id(itemSource)); obs_source_get_id(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "volume", obs_data_set_double(data, "volume",
obs_source_get_volume(itemSource)); obs_source_get_volume(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "x", pos.x); obs_data_set_double(data, "x", pos.x);
obs_data_set_double(data, "y", pos.y); obs_data_set_double(data, "y", pos.y);
obs_data_set_int(data, "source_cx", (int)item_width); obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_int(data, "source_cy", (int)item_height); obs_data_set_int(data, "source_cy", (int)item_height);
obs_data_set_bool(data, "muted", obs_source_muted(itemSource)); obs_data_set_double(data, "cx", item_width* scale.x);
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item)); obs_data_set_double(data, "cy", item_height* scale.y);
obs_data_set_double(data, "cx", item_width * scale.x);
obs_data_set_double(data, "cy", item_height * scale.y);
obs_data_set_bool(data, "render", obs_sceneitem_visible(item)); obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
obs_data_set_bool(data, "locked", obs_sceneitem_locked(item));
obs_scene_t* parent = obs_sceneitem_get_scene(item);
if (parent) {
OBSSource parentSource = obs_scene_get_source(parent);
QString parentKind = obs_source_get_id(parentSource);
if (parentKind == "group") {
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
}
}
if (obs_sceneitem_is_group(item)) {
OBSDataArrayAutoRelease children = obs_data_array_create();
obs_sceneitem_group_enum_items(item, [](obs_scene_t*, obs_sceneitem_t* currentItem, void* param) {
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
obs_data_array_push_back(items, itemData);
return true;
}, children);
obs_data_set_array(data, "groupChildren", children);
}
return data; return data;
} }
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) { obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* item) {
if (!scene) { OBSSceneItem sceneItem;
return nullptr; if (obs_data_has_user_value(item, "id")) {
} sceneItem = GetSceneItemFromId(source, obs_data_get_int(item, "id"));
if (obs_data_has_user_value(item, "name") &&
(QString)obs_source_get_name(obs_sceneitem_get_source(sceneItem)) !=
(QString)obs_data_get_string(item, "name")) {
return nullptr;
}
}
else if (obs_data_has_user_value(item, "name")) {
sceneItem = GetSceneItemFromName(source, obs_data_get_string(item, "name"));
}
return sceneItem;
}
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name) {
struct current_search { struct current_search {
QString query; QString query;
obs_sceneitem_t* result; obs_sceneitem_t* result;
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
}; };
current_search search; current_search search;
search.query = name; search.query = name;
search.result = nullptr; search.result = nullptr;
search.enumCallback = []( OBSScene scene = obs_scene_from_source(source);
if (!scene)
return nullptr;
obs_scene_enum_items(scene, [](
obs_scene_t* scene, obs_scene_t* scene,
obs_sceneitem_t* currentItem, obs_sceneitem_t* currentItem,
void* param) void* param)
{ {
current_search* search = reinterpret_cast<current_search*>(param); current_search* search = static_cast<current_search*>(param);
if (obs_sceneitem_is_group(currentItem)) {
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
if (search->result) {
return false;
}
}
QString currentItemName = QString currentItemName =
obs_source_get_name(obs_sceneitem_get_source(currentItem)); obs_source_get_name(obs_sceneitem_get_source(currentItem));
@ -219,41 +150,31 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
} }
return true; return true;
}; }, &search);
obs_scene_enum_items(scene, search.enumCallback, &search);
return search.result; return search.result;
} }
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) { obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
if (!scene) {
return nullptr;
}
struct current_search { struct current_search {
int query; size_t query;
obs_sceneitem_t* result; obs_sceneitem_t* result;
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
}; };
current_search search; current_search search;
search.query = id; search.query = id;
search.result = nullptr; search.result = nullptr;
search.enumCallback = []( OBSScene scene = obs_scene_from_source(source);
obs_scene_t* scene, if (!scene)
obs_sceneitem_t* currentItem, return nullptr;
void* param)
{
current_search* search = reinterpret_cast<current_search*>(param);
if (obs_sceneitem_is_group(currentItem)) { obs_scene_enum_items(scene, [](
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search); obs_scene_t* scene,
if (search->result) { obs_sceneitem_t* currentItem,
return false; void* param)
} {
} current_search* search = static_cast<current_search*>(param);
if (obs_sceneitem_get_id(currentItem) == search->query) { if (obs_sceneitem_get_id(currentItem) == search->query) {
search->result = currentItem; search->result = currentItem;
@ -262,56 +183,11 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
} }
return true; return true;
}; }, &search);
obs_scene_enum_items(scene, search.enumCallback, &search);
return search.result; return search.result;
} }
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) {
if (!scene) {
return nullptr;
}
OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id");
int id = obs_data_item_get_int(idInfoItem);
OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name");
const char* name = obs_data_item_get_string(nameInfoItem);
if (idInfoItem) {
obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id);
obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem);
QString sceneItemName = obs_source_get_name(sceneItemSource);
if (nameInfoItem && (QString(name) != sceneItemName)) {
return nullptr;
}
return sceneItem;
} else if (nameInfoItem) {
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem)
{
enum obs_data_type dataType = obs_data_item_gettype(dataItem);
if (dataType == OBS_DATA_OBJECT) {
OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem);
return GetSceneItemFromItem(scene, itemData);
} else if (dataType == OBS_DATA_STRING) {
QString name = obs_data_item_get_string(dataItem);
return GetSceneItemFromName(scene, name);
}
return nullptr;
}
bool Utils::IsValidAlignment(const uint32_t alignment) { bool Utils::IsValidAlignment(const uint32_t alignment) {
switch (alignment) { switch (alignment) {
case OBS_ALIGN_CENTER: case OBS_ALIGN_CENTER:
@ -350,19 +226,17 @@ obs_source_t* Utils::GetTransitionFromName(QString searchName) {
return foundTransition; return foundTransition;
} }
obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) { obs_source_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
// Both obs_frontend_get_current_scene() and obs_get_source_by_name() // Both obs_frontend_get_current_scene() and obs_get_source_by_name()
// increase the returned source's refcount // do addref on the return source, so no need to use an OBSSource helper
OBSSourceAutoRelease sceneSource = nullptr; obs_source_t* scene = nullptr;
if (sceneName.isEmpty() || sceneName.isNull()) { if (sceneName.isEmpty() || sceneName.isNull())
sceneSource = obs_frontend_get_current_scene(); scene = obs_frontend_get_current_scene();
} else
else { scene = obs_get_source_by_name(sceneName.toUtf8());
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
}
return obs_scene_from_source(sceneSource); return scene;
} }
obs_data_array_t* Utils::GetScenes() { obs_data_array_t* Utils::GetScenes() {
@ -395,37 +269,18 @@ QSpinBox* Utils::GetTransitionDurationControl() {
return window->findChild<QSpinBox*>("transitionDuration"); return window->findChild<QSpinBox*>("transitionDuration");
} }
int Utils::GetTransitionDuration(obs_source_t* transition) { int Utils::GetTransitionDuration() {
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) { QSpinBox* control = GetTransitionDurationControl();
if (control)
return control->value();
else
return -1; return -1;
} }
QString transitionKind = obs_source_get_id(transition); void Utils::SetTransitionDuration(int ms) {
if (transitionKind == "cut_transition") { QSpinBox* control = GetTransitionDurationControl();
// If this is a Cut transition, return 0 if (control && ms >= 0)
return 0; control->setValue(ms);
}
if (obs_transition_fixed(transition)) {
// If this transition has a fixed duration (such as a Stinger),
// we don't currently have a way of retrieving that number.
// For now, return -1 to indicate that we don't know the actual duration.
return -1;
}
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
// Detect if transition is the global transition or a transition override.
// Fetching the duration is different depending on the case.
obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration");
int duration = (
transitionDurationItem
? obs_data_item_get_int(transitionDurationItem)
: obs_frontend_get_transition_duration()
);
return duration;
} }
bool Utils::SetTransitionByName(QString transitionName) { bool Utils::SetTransitionByName(QString transitionName) {
@ -439,35 +294,51 @@ bool Utils::SetTransitionByName(QString transitionName) {
} }
} }
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) { QPushButton* Utils::GetPreviewModeButtonControl() {
int duration = Utils::GetTransitionDuration(transition); QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
if (duration < 0) { return main->findChild<QPushButton*>("modeSwitch");
blog(LOG_WARNING, "GetTransitionData: duration is negative !"); }
}
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A); QListWidget* Utils::GetSceneListControl() {
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QListWidget*>("scenes");
}
obs_data_t* transitionData = obs_data_create(); obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item) {
obs_data_set_string(transitionData, "name", obs_source_get_name(transition)); if (!item)
obs_data_set_string(transitionData, "type", obs_source_get_id(transition)); return nullptr;
obs_data_set_int(transitionData, "duration", duration);
// When a transition starts and while it is running, SOURCE_A is the source scene QVariant itemData = item->data(static_cast<int>(Qt::UserRole));
// and SOURCE_B is the destination scene. return itemData.value<OBSScene>();
// Before the transition_end event is triggered on a transition, the destination scene }
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
// we don't know what was the source scene
// TODO fix this in libobs
bool isTransitionEndEvent = (sourceScene == destinationScene); QLayout* Utils::GetPreviewLayout() {
if (!isTransitionEndEvent) { QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene)); return main->findChild<QLayout*>("previewLayout");
} }
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
return transitionData; void Utils::TransitionToProgram() {
if (!obs_frontend_preview_program_mode_active())
return;
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
// then this won't work as expected
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
// The program options widget is the second item in the left-to-right layout
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
// The "Transition" button lies in the mainButtonLayout
// which is the first itemin the program options' layout
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
// Try to cast that widget into a button
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
// Perform a click on that button
transitionBtn->click();
} }
QString Utils::OBSVersionString() { QString Utils::OBSVersionString() {
@ -488,13 +359,12 @@ QSystemTrayIcon* Utils::GetTrayIcon() {
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window(); QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
if (!main) return nullptr; if (!main) return nullptr;
QList<QSystemTrayIcon*> trays = main->findChildren<QSystemTrayIcon*>(); return main->findChildren<QSystemTrayIcon*>().first();
return trays.isEmpty() ? nullptr : trays.first();
} }
void Utils::SysTrayNotify(QString text, void Utils::SysTrayNotify(QString &text,
QSystemTrayIcon::MessageIcon icon, QString title) { QSystemTrayIcon::MessageIcon icon, QString title) {
if (!GetConfig()->AlertsEnabled || if (!Config::Current()->AlertsEnabled ||
!QSystemTrayIcon::isSystemTrayAvailable() || !QSystemTrayIcon::isSystemTrayAvailable() ||
!QSystemTrayIcon::supportsMessages()) !QSystemTrayIcon::supportsMessages())
{ {
@ -506,6 +376,15 @@ void Utils::SysTrayNotify(QString text,
trayIcon->showMessage(title, text, icon); trayIcon->showMessage(title, text, icon);
} }
QString Utils::FormatIPAddress(QHostAddress &addr) {
QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive);
QString addrString = addr.toString();
if (addrString.contains(v4regex)) {
addrString = QHostAddress(addr.toIPv4Address()).toString();
}
return addrString;
}
const char* Utils::GetRecordingFolder() { const char* Utils::GetRecordingFolder() {
config_t* profile = obs_frontend_get_profile_config(); config_t* profile = obs_frontend_get_profile_config();
QString outputMode = config_get_string(profile, "Output", "Mode"); QString outputMode = config_get_string(profile, "Output", "Mode");
@ -521,13 +400,17 @@ const char* Utils::GetRecordingFolder() {
bool Utils::SetRecordingFolder(const char* path) { bool Utils::SetRecordingFolder(const char* path) {
QDir dir(path); QDir dir(path);
if (!dir.exists()) { if (!dir.exists())
dir.mkpath("."); dir.mkpath(".");
}
config_t* profile = obs_frontend_get_profile_config(); config_t* profile = obs_frontend_get_profile_config();
config_set_string(profile, "AdvOut", "RecFilePath", path); QString outputMode = config_get_string(profile, "Output", "Mode");
config_set_string(profile, "SimpleOutput", "FilePath", path);
if (outputMode == "Advanced") {
config_set_string(profile, "AdvOut", "RecFilePath", path);
} else {
config_set_string(profile, "SimpleOutput", "FilePath", path);
}
config_save(profile); config_save(profile);
return true; return true;
@ -601,7 +484,7 @@ obs_hotkey_t* Utils::FindHotkeyByName(QString name) {
search.result = nullptr; search.result = nullptr;
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) { obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
current_search* search = reinterpret_cast<current_search*>(data); current_search* search = static_cast<current_search*>(data);
const char* hk_name = obs_hotkey_get_name(hotkey); const char* hk_name = obs_hotkey_get_name(hotkey);
if (hk_name == search->query) { if (hk_name == search->query) {
@ -637,7 +520,7 @@ void Utils::StartReplayBuffer() {
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output(); obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput); OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
OBSDataAutoRelease dummyBinding = obs_data_create(); OBSData dummyBinding = obs_data_create();
obs_data_set_bool(dummyBinding, "control", true); obs_data_set_bool(dummyBinding, "control", true);
obs_data_set_bool(dummyBinding, "alt", true); obs_data_set_bool(dummyBinding, "alt", true);
obs_data_set_bool(dummyBinding, "shift", true); obs_data_set_bool(dummyBinding, "shift", true);
@ -679,183 +562,3 @@ bool Utils::SetFilenameFormatting(const char* filenameFormatting) {
config_save(profile); config_save(profile);
return true; return true;
} }
// Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet
/**
* @typedef {Object} `SceneItemTransform`
* @property {int} `position.x` The x position of the scene item from the left.
* @property {int} `position.y` The y position of the scene item from the top.
* @property {int} `position.alignment` The point on the scene item that the item is manipulated from.
* @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.
* @property {double} `scale.x` The x-scale factor of the scene item.
* @property {double} `scale.y` The y-scale factor of the scene item.
* @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.
* @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.
* @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.
* @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling.
* @property {bool} `visible` If the scene item is visible.
* @property {bool} `locked` If the scene item is locked in position.
* @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @property {int} `bounds.alignment` Alignment of the bounding box.
* @property {double} `bounds.x` Width of the bounding box.
* @property {double} `bounds.y` Height of the bounding box.
* @property {int} `sourceWidth` Base width (without scaling) of the source
* @property {int} `sourceHeight` Base source (without scaling) of the source
* @property {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @property {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*/
obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
if (!sceneItem) {
return nullptr;
}
OBSSource source = obs_sceneitem_get_source(sceneItem);
uint32_t baseSourceWidth = obs_source_get_width(source);
uint32_t baseSourceHeight = obs_source_get_height(source);
vec2 pos, scale, bounds;
obs_sceneitem_crop crop;
obs_sceneitem_get_pos(sceneItem, &pos);
obs_sceneitem_get_scale(sceneItem, &scale);
obs_sceneitem_get_crop(sceneItem, &crop);
obs_sceneitem_get_bounds(sceneItem, &bounds);
uint32_t alignment = obs_sceneitem_get_alignment(sceneItem);
float rotation = obs_sceneitem_get_rot(sceneItem);
bool isVisible = obs_sceneitem_visible(sceneItem);
bool isLocked = obs_sceneitem_locked(sceneItem);
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem);
QString boundsTypeName = getBoundsNameFromType(boundsType);
OBSDataAutoRelease posData = obs_data_create();
obs_data_set_double(posData, "x", pos.x);
obs_data_set_double(posData, "y", pos.y);
obs_data_set_int(posData, "alignment", alignment);
OBSDataAutoRelease scaleData = obs_data_create();
obs_data_set_double(scaleData, "x", scale.x);
obs_data_set_double(scaleData, "y", scale.y);
OBSDataAutoRelease cropData = obs_data_create();
obs_data_set_int(cropData, "left", crop.left);
obs_data_set_int(cropData, "top", crop.top);
obs_data_set_int(cropData, "right", crop.right);
obs_data_set_int(cropData, "bottom", crop.bottom);
OBSDataAutoRelease boundsData = obs_data_create();
obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8());
obs_data_set_int(boundsData, "alignment", boundsAlignment);
obs_data_set_double(boundsData, "x", bounds.x);
obs_data_set_double(boundsData, "y", bounds.y);
obs_data_t* data = obs_data_create();
obs_data_set_obj(data, "position", posData);
obs_data_set_double(data, "rotation", rotation);
obs_data_set_obj(data, "scale", scaleData);
obs_data_set_obj(data, "crop", cropData);
obs_data_set_bool(data, "visible", isVisible);
obs_data_set_bool(data, "locked", isLocked);
obs_data_set_obj(data, "bounds", boundsData);
obs_data_set_int(data, "sourceWidth", baseSourceWidth);
obs_data_set_int(data, "sourceHeight", baseSourceHeight);
obs_data_set_double(data, "width", baseSourceWidth * scale.x);
obs_data_set_double(data, "height", baseSourceHeight * scale.y);
obs_scene_t* parent = obs_sceneitem_get_scene(sceneItem);
if (parent) {
OBSSource parentSource = obs_scene_get_source(parent);
QString parentKind = obs_source_get_id(parentSource);
if (parentKind == "group") {
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
}
}
if (obs_sceneitem_is_group(sceneItem)) {
OBSDataArrayAutoRelease children = obs_data_array_create();
obs_sceneitem_group_enum_items(sceneItem, [](obs_scene_t*, obs_sceneitem_t* subItem, void* param) {
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease itemData = GetSceneItemPropertiesData(subItem);
obs_data_array_push_back(items, itemData);
return true;
}, children);
obs_data_set_array(data, "groupChildren", children);
}
return data;
}
obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings)
{
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "enabled", obs_source_enabled(filter));
obs_data_set_string(data, "type", obs_source_get_id(filter));
obs_data_set_string(data, "name", obs_source_get_name(filter));
if (includeSettings) {
OBSDataAutoRelease settings = obs_source_get_settings(filter);
obs_data_set_obj(data, "settings", settings);
}
return data;
}
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
{
struct enum_params {
obs_data_array_t* filters;
bool includeSettings;
};
if (!source) {
return nullptr;
}
struct enum_params enumParams;
enumParams.filters = obs_data_array_create();
enumParams.includeSettings = includeSettings;
obs_source_enum_filters(source, [](obs_source_t* parent, obs_source_t* child, void* param)
{
auto enumParams = reinterpret_cast<struct enum_params*>(param);
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
obs_data_array_push_back(enumParams->filters, filterData);
}, &enumParams);
return enumParams.filters;
}
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
{
void* frontendApi = os_dlopen("obs-frontend-api");
if (recPausedFuncPtr) {
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
}
if (pauseRecFuncPtr) {
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
}
}
QString Utils::nsToTimestamp(uint64_t ns)
{
uint64_t ms = ns / 1000000ULL;
uint64_t secs = ms / 1000ULL;
uint64_t minutes = secs / 60ULL;
uint64_t hoursPart = minutes / 60ULL;
uint64_t minutesPart = minutes % 60ULL;
uint64_t secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL;
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
}

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@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
@ -16,73 +16,72 @@ 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/>
*/ */
#pragma once #ifndef UTILS_H
#define UTILS_H
#include <stdio.h> #include <stdio.h>
#include <QtCore/QString> #include <QSpinBox>
#include <QtWidgets/QSpinBox> #include <QPushButton>
#include <QtWidgets/QPushButton> #include <QLayout>
#include <QtWidgets/QLayout> #include <QListWidget>
#include <QtWidgets/QListWidget> #include <QSystemTrayIcon>
#include <QtWidgets/QSystemTrayIcon> #include <QHostAddress>
#include <obs.hpp> #include <obs.hpp>
#include <obs-module.h> #include <obs-module.h>
#include <util/config-file.h> #include <util/config-file.h>
typedef void(*PauseRecordingFunction)(bool); class Utils {
typedef bool(*RecordingPausedFunction)(); public:
static obs_data_array_t* StringListToArray(char** strings, char* key);
static obs_data_array_t* GetSceneItems(obs_source_t* source);
static obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
static obs_sceneitem_t* GetSceneItemFromName(
obs_source_t* source, QString name);
static obs_sceneitem_t* GetSceneItemFromId(obs_source_t* source, size_t id);
static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item);
static obs_source_t* GetTransitionFromName(QString transitionName);
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
namespace Utils { static bool IsValidAlignment(const uint32_t alignment);
obs_data_array_t* StringListToArray(char** strings, const char* key);
obs_data_array_t* GetSceneItems(obs_source_t* source);
obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
// These functions support nested lookup into groups static obs_data_array_t* GetScenes();
obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name); static obs_data_t* GetSceneData(obs_source_t* source);
obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id);
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem);
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName); static QSpinBox* GetTransitionDurationControl();
obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item); static int GetTransitionDuration();
static void SetTransitionDuration(int ms);
obs_data_t* GetSourceFilterInfo(obs_source_t* filter, bool includeSettings); static bool SetTransitionByName(QString transitionName);
obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
bool IsValidAlignment(const uint32_t alignment); static QPushButton* GetPreviewModeButtonControl();
static QLayout* GetPreviewLayout();
static QListWidget* GetSceneListControl();
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
obs_data_array_t* GetScenes(); static void TransitionToProgram();
obs_data_t* GetSceneData(obs_source_t* source);
// TODO contribute a proper frontend API method for this to OBS and remove this hack static QString OBSVersionString();
QSpinBox* GetTransitionDurationControl();
int GetTransitionDuration(obs_source_t* transition);
obs_source_t* GetTransitionFromName(QString transitionName);
bool SetTransitionByName(QString transitionName);
obs_data_t* GetTransitionData(obs_source_t* transition);
QString OBSVersionString(); static QSystemTrayIcon* GetTrayIcon();
static void SysTrayNotify(
QSystemTrayIcon* GetTrayIcon(); QString &text,
void SysTrayNotify(
QString text,
QSystemTrayIcon::MessageIcon n, QSystemTrayIcon::MessageIcon n,
QString title = QString("obs-websocket")); QString title = QString("obs-websocket"));
const char* GetRecordingFolder(); static QString FormatIPAddress(QHostAddress &addr);
bool SetRecordingFolder(const char* path);
QString ParseDataToQueryString(obs_data_t* data); static const char* GetRecordingFolder();
obs_hotkey_t* FindHotkeyByName(QString name); static bool SetRecordingFolder(const char* path);
bool ReplayBufferEnabled(); static QString ParseDataToQueryString(obs_data_t* data);
void StartReplayBuffer(); static obs_hotkey_t* FindHotkeyByName(QString name);
bool IsRPHotkeySet(); static bool ReplayBufferEnabled();
static void StartReplayBuffer();
const char* GetFilenameFormatting(); static bool IsRPHotkeySet();
bool SetFilenameFormatting(const char* filenameFormatting); static const char* GetFilenameFormatting();
static bool SetFilenameFormatting(const char* filenameFormatting);
QString nsToTimestamp(uint64_t ns);
}; };
#endif // UTILS_H

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj> Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -17,61 +17,50 @@ 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/>
*/ */
#pragma once #ifndef WSEVENTS_H
#define WSEVENTS_H
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h> #include <QListWidgetItem>
#include <QtWidgets/QListWidgetItem>
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>
#include "WSServer.h" #include "WSServer.h"
class WSEvents : public QObject class WSEvents : public QObject {
{ Q_OBJECT
Q_OBJECT public:
explicit WSEvents(WSServer* srv);
public:
explicit WSEvents(WSServerPtr srv);
~WSEvents(); ~WSEvents();
static void FrontendEventHandler(
enum obs_frontend_event event, void* privateData);
static WSEvents* Instance;
void connectSceneSignals(obs_source_t* scene);
void connectSourceSignals(obs_source_t* source); void hookTransitionBeginEvent();
void disconnectSourceSignals(obs_source_t* source);
void connectFilterSignals(obs_source_t* filter); uint64_t GetStreamingTime();
void disconnectFilterSignals(obs_source_t* filter); const char* GetStreamingTimecode();
uint64_t GetRecordingTime();
void hookTransitionPlaybackEvents(); const char* GetRecordingTimecode();
void unhookTransitionPlaybackEvents();
uint64_t getStreamingTime();
uint64_t getRecordingTime();
QString getStreamingTimecode();
QString getRecordingTimecode();
obs_data_t* GetStats();
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
bool HeartbeatIsActive; bool HeartbeatIsActive;
private slots: private slots:
void deferredInitOperations();
void StreamStatus(); void StreamStatus();
void Heartbeat(); void Heartbeat();
void TransitionDurationChanged(int ms); void TransitionDurationChanged(int ms);
private: private:
WSServerPtr _srv; WSServer* _srv;
QTimer streamStatusTimer; OBSSource currentScene;
QTimer heartbeatTimer;
os_cpu_usage_info_t* cpuUsageInfo;
bool pulse; bool pulse;
bool _streamingActive;
bool _recordingActive;
uint64_t _streamStarttime; uint64_t _streamStarttime;
uint64_t _recStarttime;
uint64_t _lastBytesSent; uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime; uint64_t _lastBytesSentTime;
@ -99,8 +88,6 @@ private:
void OnRecordingStarted(); void OnRecordingStarted();
void OnRecordingStopping(); void OnRecordingStopping();
void OnRecordingStopped(); void OnRecordingStopped();
void OnRecordingPaused();
void OnRecordingResumed();
void OnReplayStarting(); void OnReplayStarting();
void OnReplayStarted(); void OnReplayStarted();
@ -112,34 +99,12 @@ private:
void OnExit(); void OnExit();
static void FrontendEventHandler(
enum obs_frontend_event event, void* privateData);
static void OnTransitionBegin(void* param, calldata_t* data); static void OnTransitionBegin(void* param, calldata_t* data);
static void OnTransitionEnd(void* param, calldata_t* data);
static void OnTransitionVideoEnd(void* param, calldata_t* data);
static void OnSourceCreate(void* param, calldata_t* data);
static void OnSourceDestroy(void* param, calldata_t* data);
static void OnSourceVolumeChange(void* param, calldata_t* data);
static void OnSourceMuteStateChange(void* param, calldata_t* data);
static void OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data);
static void OnSourceAudioMixersChanged(void* param, calldata_t* data);
static void OnSourceRename(void* param, calldata_t* data);
static void OnSourceFilterAdded(void* param, calldata_t* data);
static void OnSourceFilterRemoved(void* param, calldata_t* data);
static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data);
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
static void OnSceneReordered(void* param, calldata_t* data); static void OnSceneReordered(void* param, calldata_t* data);
static void OnSceneItemAdd(void* param, calldata_t* data); static void OnSceneItemAdd(void* param, calldata_t* data);
static void OnSceneItemDelete(void* param, calldata_t* data); static void OnSceneItemDelete(void* param, calldata_t* data);
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data); static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
static void OnSceneItemLockChanged(void* param, calldata_t* data);
static void OnSceneItemTransform(void* param, calldata_t* data);
static void OnSceneItemSelected(void* param, calldata_t* data);
static void OnSceneItemDeselected(void* param, calldata_t* data);
}; };
#endif // WSEVENTS_H

View File

@ -17,8 +17,6 @@
* with this program. If not, see <https://www.gnu.org/licenses/> * with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <functional>
#include <obs-data.h> #include <obs-data.h>
#include "Config.h" #include "Config.h"
@ -26,156 +24,207 @@
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
using namespace std::placeholders; QHash<QString, void(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
{ "GetVersion", WSRequestHandler::HandleGetVersion },
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap { { "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
{ "GetVersion", &WSRequestHandler::GetVersion },
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
{ "Authenticate", &WSRequestHandler::Authenticate },
{ "GetStats", &WSRequestHandler::GetStats }, { "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat }, { "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
{ "OpenProjector", &WSRequestHandler::OpenProjector },
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting }, { "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting }, { "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage }, { "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene }, { "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene }, { "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
{ "GetSceneList", &WSRequestHandler::GetSceneList }, { "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
{ "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride }, { "StartStreaming", WSRequestHandler::HandleStartStreaming },
{ "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride }, { "StopStreaming", WSRequestHandler::HandleStopStreaming },
{ "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride }, { "StartRecording", WSRequestHandler::HandleStartRecording },
{ "StopRecording", WSRequestHandler::HandleStopRecording },
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat { "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender }, { "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition }, { "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
{ "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform }, { "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
{ "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop },
{ "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties },
{ "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties },
{ "ResetSceneItem", &WSRequestHandler::ResetSceneItem },
{ "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem },
{ "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem },
{ "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems },
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus }, { "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming }, { "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
{ "StartStreaming", &WSRequestHandler::StartStreaming }, { "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
{ "StopStreaming", &WSRequestHandler::StopStreaming }, { "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
{ "StartRecording", &WSRequestHandler::StartRecording }, { "SetVolume", WSRequestHandler::HandleSetVolume },
{ "StopRecording", &WSRequestHandler::StopRecording }, { "GetVolume", WSRequestHandler::HandleGetVolume },
{ "PauseRecording", &WSRequestHandler::PauseRecording }, { "ToggleMute", WSRequestHandler::HandleToggleMute },
{ "ResumeRecording", &WSRequestHandler::ResumeRecording }, { "SetMute", WSRequestHandler::HandleSetMute },
{ "GetMute", WSRequestHandler::HandleGetMute },
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer }, { "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer }, { "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer }, { "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer }, { "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder }, { "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder }, { "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
{ "GetTransitionList", &WSRequestHandler::GetTransitionList }, { "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition }, { "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition }, { "ListProfiles", WSRequestHandler::HandleListProfiles },
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
{ "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition },
{ "SetVolume", &WSRequestHandler::SetVolume }, { "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
{ "GetVolume", &WSRequestHandler::GetVolume }, { "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
{ "ToggleMute", &WSRequestHandler::ToggleMute }, { "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
{ "SetMute", &WSRequestHandler::SetMute },
{ "GetMute", &WSRequestHandler::GetMute },
{ "SetSourceName", &WSRequestHandler::SetSourceName },
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
{ "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType },
{ "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType },
{ "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot },
{ "GetSourceFilters", &WSRequestHandler::GetSourceFilters }, { "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
{ "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo }, { "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
{ "AddFilterToSource", &WSRequestHandler::AddFilterToSource }, { "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
{ "RemoveFilterFromSource", &WSRequestHandler::RemoveFilterFromSource }, { "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
{ "ReorderSourceFilter", &WSRequestHandler::ReorderSourceFilter }, { "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
{ "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter }, { "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
{ "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings }, { "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
{ "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility },
{ "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection }, { "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
{ "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection }, { "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
{ "ListSceneCollections", &WSRequestHandler::ListSceneCollections },
{ "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile }, { "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
{ "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile }, { "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
{ "ListProfiles", &WSRequestHandler::ListProfiles },
{ "SetStreamSettings", &WSRequestHandler::SetStreamSettings }, { "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
{ "GetStreamSettings", &WSRequestHandler::GetStreamSettings }, { "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
{ "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings },
#if BUILD_CAPTIONS
{ "SendCaptions", &WSRequestHandler::SendCaptions },
#endif
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
{ "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties },
{ "ListOutputs", &WSRequestHandler::ListOutputs },
{ "GetOutputInfo", &WSRequestHandler::GetOutputInfo },
{ "StartOutput", &WSRequestHandler::StartOutput },
{ "StopOutput", &WSRequestHandler::StopOutput }
}; };
const QSet<QString> WSRequestHandler::authNotRequired { QSet<QString> WSRequestHandler::authNotRequired {
"GetVersion", "GetVersion",
"GetAuthRequired", "GetAuthRequired",
"Authenticate" "Authenticate"
}; };
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) : WSRequestHandler::WSRequestHandler(QWebSocket* client) :
_connProperties(connProperties) _messageId(0),
_requestType(""),
data(nullptr),
_client(client)
{ {
} }
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){ void WSRequestHandler::processIncomingMessage(QString textMessage) {
if (GetConfig()->AuthRequired QByteArray msgData = textMessage.toUtf8();
&& (!authNotRequired.contains(request.methodName())) const char* msg = msgData.constData();
&& (!_connProperties.isAuthenticated()))
data = obs_data_create_from_json(msg);
if (!data) {
if (!msg)
msg = "<null pointer>";
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
SendErrorResponse("invalid JSON payload");
return;
}
if (Config::Current()->DebugEnabled) {
blog(LOG_DEBUG, "Request >> '%s'", msg);
}
if (!hasField("request-type")
|| !hasField("message-id"))
{ {
return RpcResponse::fail(request, "Not Authenticated"); SendErrorResponse("missing request parameters");
return;
} }
RpcMethodHandler handlerFunc = messageMap[request.methodName()]; _requestType = obs_data_get_string(data, "request-type");
if (!handlerFunc) { _messageId = obs_data_get_string(data, "message-id");
return RpcResponse::fail(request, "invalid request type");
if (Config::Current()->AuthRequired
&& (_client->property(PROP_AUTHENTICATED).toBool() == false)
&& (authNotRequired.find(_requestType) == authNotRequired.end()))
{
SendErrorResponse("Not Authenticated");
return;
} }
return std::bind(handlerFunc, this, _1)(request); void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
if (handlerFunc != nullptr)
handlerFunc(this);
else
SendErrorResponse("invalid request type");
}
WSRequestHandler::~WSRequestHandler() {
}
void WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "status", "ok");
obs_data_set_string(response, "message-id", _messageId);
if (additionalFields)
obs_data_apply(response, additionalFields);
SendResponse(response);
}
void WSRequestHandler::SendErrorResponse(const char* errorMessage) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "status", "error");
obs_data_set_string(response, "error", errorMessage);
obs_data_set_string(response, "message-id", _messageId);
SendResponse(response);
}
void WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "status", "error");
obs_data_set_string(response, "message-id", _messageId);
if (additionalFields)
obs_data_set_obj(response, "error", additionalFields);
SendResponse(response);
}
void WSRequestHandler::SendResponse(obs_data_t* response) {
QString json = obs_data_get_json(response);
_client->sendTextMessage(json);
if (Config::Current()->DebugEnabled)
blog(LOG_DEBUG, "Response << '%s'", json.toUtf8().constData());
}
bool WSRequestHandler::hasField(QString name) {
if (!data || name.isEmpty() || name.isNull())
return false;
return obs_data_has_user_value(data, name.toUtf8());
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift> Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -17,155 +17,137 @@ 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/>
*/ */
#pragma once #ifndef WSREQUESTHANDLER_H
#define WSREQUESTHANDLER_H
#include <QtCore/QString> #include <QHash>
#include <QtCore/QHash> #include <QSet>
#include <QtCore/QSet> #include <QWebSocket>
#include <QWebSocketServer>
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include "ConnectionProperties.h"
#include "rpc/RpcRequest.h"
#include "rpc/RpcResponse.h"
#include "obs-websocket.h" #include "obs-websocket.h"
class WSRequestHandler; class WSRequestHandler : public QObject {
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&); Q_OBJECT
class WSRequestHandler {
public: public:
explicit WSRequestHandler(ConnectionProperties& connProperties); explicit WSRequestHandler(QWebSocket* client);
RpcResponse processRequest(const RpcRequest& textMessage); ~WSRequestHandler();
void processIncomingMessage(QString textMessage);
bool hasField(QString name);
private: private:
ConnectionProperties& _connProperties; QWebSocket* _client;
const char* _messageId;
const char* _requestType;
OBSDataAutoRelease data;
static const QHash<QString, RpcMethodHandler> messageMap; void SendOKResponse(obs_data_t* additionalFields = NULL);
static const QSet<QString> authNotRequired; void SendErrorResponse(const char* errorMessage);
void SendErrorResponse(obs_data_t* additionalFields = NULL);
void SendResponse(obs_data_t* response);
RpcResponse GetVersion(const RpcRequest&); static QHash<QString, void(*)(WSRequestHandler*)> messageMap;
RpcResponse GetAuthRequired(const RpcRequest&); static QSet<QString> authNotRequired;
RpcResponse Authenticate(const RpcRequest&);
RpcResponse GetStats(const RpcRequest&); static void HandleGetVersion(WSRequestHandler* req);
RpcResponse SetHeartbeat(const RpcRequest&); static void HandleGetAuthRequired(WSRequestHandler* req);
RpcResponse GetVideoInfo(const RpcRequest&); static void HandleAuthenticate(WSRequestHandler* req);
RpcResponse OpenProjector(const RpcRequest&);
RpcResponse SetFilenameFormatting(const RpcRequest&); static void HandleSetHeartbeat(WSRequestHandler* req);
RpcResponse GetFilenameFormatting(const RpcRequest&);
RpcResponse BroadcastCustomMessage(const RpcRequest&); static void HandleSetFilenameFormatting(WSRequestHandler* req);
static void HandleGetFilenameFormatting(WSRequestHandler* req);
RpcResponse SetCurrentScene(const RpcRequest&); static void HandleSetCurrentScene(WSRequestHandler* req);
RpcResponse GetCurrentScene(const RpcRequest&); static void HandleGetCurrentScene(WSRequestHandler* req);
RpcResponse GetSceneList(const RpcRequest&); static void HandleGetSceneList(WSRequestHandler* req);
RpcResponse SetSceneTransitionOverride(const RpcRequest&);
RpcResponse RemoveSceneTransitionOverride(const RpcRequest&);
RpcResponse GetSceneTransitionOverride(const RpcRequest&);
RpcResponse SetSceneItemRender(const RpcRequest&); static void HandleSetSceneItemRender(WSRequestHandler* req);
RpcResponse SetSceneItemPosition(const RpcRequest&); static void HandleSetSceneItemPosition(WSRequestHandler* req);
RpcResponse SetSceneItemTransform(const RpcRequest&); static void HandleSetSceneItemTransform(WSRequestHandler* req);
RpcResponse SetSceneItemCrop(const RpcRequest&); static void HandleSetSceneItemCrop(WSRequestHandler* req);
RpcResponse GetSceneItemProperties(const RpcRequest&); static void HandleGetSceneItemProperties(WSRequestHandler* req);
RpcResponse SetSceneItemProperties(const RpcRequest&); static void HandleSetSceneItemProperties(WSRequestHandler* req);
RpcResponse ResetSceneItem(const RpcRequest&); static void HandleResetSceneItem(WSRequestHandler* req);
RpcResponse DuplicateSceneItem(const RpcRequest&); static void HandleDuplicateSceneItem(WSRequestHandler* req);
RpcResponse DeleteSceneItem(const RpcRequest&); static void HandleDeleteSceneItem(WSRequestHandler* req);
RpcResponse ReorderSceneItems(const RpcRequest&); static void HandleReorderSceneItems(WSRequestHandler* req);
RpcResponse GetStreamingStatus(const RpcRequest&); static void HandleGetStreamingStatus(WSRequestHandler* req);
RpcResponse StartStopStreaming(const RpcRequest&); static void HandleStartStopStreaming(WSRequestHandler* req);
RpcResponse StartStopRecording(const RpcRequest&); static void HandleStartStopRecording(WSRequestHandler* req);
static void HandleStartStreaming(WSRequestHandler* req);
static void HandleStopStreaming(WSRequestHandler* req);
static void HandleStartRecording(WSRequestHandler* req);
static void HandleStopRecording(WSRequestHandler* req);
RpcResponse StartStreaming(const RpcRequest&); static void HandleStartStopReplayBuffer(WSRequestHandler* req);
RpcResponse StopStreaming(const RpcRequest&); static void HandleStartReplayBuffer(WSRequestHandler* req);
static void HandleStopReplayBuffer(WSRequestHandler* req);
static void HandleSaveReplayBuffer(WSRequestHandler* req);
RpcResponse StartRecording(const RpcRequest&); static void HandleSetRecordingFolder(WSRequestHandler* req);
RpcResponse StopRecording(const RpcRequest&); static void HandleGetRecordingFolder(WSRequestHandler* req);
RpcResponse PauseRecording(const RpcRequest&);
RpcResponse ResumeRecording(const RpcRequest&);
RpcResponse StartStopReplayBuffer(const RpcRequest&); static void HandleGetTransitionList(WSRequestHandler* req);
RpcResponse StartReplayBuffer(const RpcRequest&); static void HandleGetCurrentTransition(WSRequestHandler* req);
RpcResponse StopReplayBuffer(const RpcRequest&); static void HandleSetCurrentTransition(WSRequestHandler* req);
RpcResponse SaveReplayBuffer(const RpcRequest&);
RpcResponse SetRecordingFolder(const RpcRequest&); static void HandleSetVolume(WSRequestHandler* req);
RpcResponse GetRecordingFolder(const RpcRequest&); static void HandleGetVolume(WSRequestHandler* req);
static void HandleToggleMute(WSRequestHandler* req);
static void HandleSetMute(WSRequestHandler* req);
static void HandleGetMute(WSRequestHandler* req);
static void HandleSetSyncOffset(WSRequestHandler* req);
static void HandleGetSyncOffset(WSRequestHandler* req);
static void HandleGetSpecialSources(WSRequestHandler* req);
static void HandleGetSourcesList(WSRequestHandler* req);
static void HandleGetSourceTypesList(WSRequestHandler* req);
static void HandleGetSourceSettings(WSRequestHandler* req);
static void HandleSetSourceSettings(WSRequestHandler* req);
RpcResponse GetTransitionList(const RpcRequest&); static void HandleGetSourceFilters(WSRequestHandler* req);
RpcResponse GetCurrentTransition(const RpcRequest&); static void HandleAddFilterToSource(WSRequestHandler* req);
RpcResponse SetCurrentTransition(const RpcRequest&); static void HandleRemoveFilterFromSource(WSRequestHandler* req);
RpcResponse SetTransitionDuration(const RpcRequest&); static void HandleReorderSourceFilter(WSRequestHandler* req);
RpcResponse GetTransitionDuration(const RpcRequest&); static void HandleMoveSourceFilter(WSRequestHandler* req);
RpcResponse GetTransitionPosition(const RpcRequest&); static void HandleSetSourceFilterSettings(WSRequestHandler* req);
RpcResponse SetVolume(const RpcRequest&); static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
RpcResponse GetVolume(const RpcRequest&); static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
RpcResponse ToggleMute(const RpcRequest&); static void HandleListSceneCollections(WSRequestHandler* req);
RpcResponse SetMute(const RpcRequest&);
RpcResponse GetMute(const RpcRequest&);
RpcResponse SetSourceName(const RpcRequest&);
RpcResponse SetSyncOffset(const RpcRequest&);
RpcResponse GetSyncOffset(const RpcRequest&);
RpcResponse GetSpecialSources(const RpcRequest&);
RpcResponse GetSourcesList(const RpcRequest&);
RpcResponse GetSourceTypesList(const RpcRequest&);
RpcResponse GetSourceSettings(const RpcRequest&);
RpcResponse SetSourceSettings(const RpcRequest&);
RpcResponse GetAudioMonitorType(const RpcRequest&);
RpcResponse SetAudioMonitorType(const RpcRequest&);
RpcResponse TakeSourceScreenshot(const RpcRequest&);
RpcResponse GetSourceFilters(const RpcRequest&); static void HandleSetCurrentProfile(WSRequestHandler* req);
RpcResponse GetSourceFilterInfo(const RpcRequest&); static void HandleGetCurrentProfile(WSRequestHandler* req);
RpcResponse AddFilterToSource(const RpcRequest&); static void HandleListProfiles(WSRequestHandler* req);
RpcResponse RemoveFilterFromSource(const RpcRequest&);
RpcResponse ReorderSourceFilter(const RpcRequest&);
RpcResponse MoveSourceFilter(const RpcRequest&);
RpcResponse SetSourceFilterSettings(const RpcRequest&);
RpcResponse SetSourceFilterVisibility(const RpcRequest&);
RpcResponse SetCurrentSceneCollection(const RpcRequest&); static void HandleSetStreamSettings(WSRequestHandler* req);
RpcResponse GetCurrentSceneCollection(const RpcRequest&); static void HandleGetStreamSettings(WSRequestHandler* req);
RpcResponse ListSceneCollections(const RpcRequest&); static void HandleSaveStreamSettings(WSRequestHandler* req);
RpcResponse SetCurrentProfile(const RpcRequest&); static void HandleSetTransitionDuration(WSRequestHandler* req);
RpcResponse GetCurrentProfile(const RpcRequest&); static void HandleGetTransitionDuration(WSRequestHandler* req);
RpcResponse ListProfiles(const RpcRequest&);
RpcResponse SetStreamSettings(const RpcRequest&); static void HandleGetStudioModeStatus(WSRequestHandler* req);
RpcResponse GetStreamSettings(const RpcRequest&); static void HandleGetPreviewScene(WSRequestHandler* req);
RpcResponse SaveStreamSettings(const RpcRequest&); static void HandleSetPreviewScene(WSRequestHandler* req);
#if BUILD_CAPTIONS static void HandleTransitionToProgram(WSRequestHandler* req);
RpcResponse SendCaptions(const RpcRequest&); static void HandleEnableStudioMode(WSRequestHandler* req);
#endif static void HandleDisableStudioMode(WSRequestHandler* req);
static void HandleToggleStudioMode(WSRequestHandler* req);
RpcResponse GetStudioModeStatus(const RpcRequest&); static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
RpcResponse GetPreviewScene(const RpcRequest&); static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
RpcResponse SetPreviewScene(const RpcRequest&);
RpcResponse TransitionToProgram(const RpcRequest&);
RpcResponse EnableStudioMode(const RpcRequest&);
RpcResponse DisableStudioMode(const RpcRequest&);
RpcResponse ToggleStudioMode(const RpcRequest&);
RpcResponse SetTextGDIPlusProperties(const RpcRequest&); static void HandleSetTextFreetype2Properties(WSRequestHandler* req);
RpcResponse GetTextGDIPlusProperties(const RpcRequest&); static void HandleGetTextFreetype2Properties(WSRequestHandler* req);
RpcResponse SetTextFreetype2Properties(const RpcRequest&); static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
RpcResponse GetTextFreetype2Properties(const RpcRequest&); static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
RpcResponse ListOutputs(const RpcRequest&);
RpcResponse GetOutputInfo(const RpcRequest&);
RpcResponse StartOutput(const RpcRequest&);
RpcResponse StopOutput(const RpcRequest&);
}; };
#endif // WSPROTOCOL_H

View File

@ -1,60 +1,10 @@
#include "WSRequestHandler.h" #include <QString>
#include <QtCore/QByteArray>
#include <QtGui/QImageWriter>
#include "obs-websocket.h"
#include "Config.h" #include "Config.h"
#include "Utils.h" #include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
#define CASE(x) case x: return #x; #include "WSRequestHandler.h"
const char *describe_output_format(int format) {
switch (format) {
default:
CASE(VIDEO_FORMAT_NONE)
CASE(VIDEO_FORMAT_I420)
CASE(VIDEO_FORMAT_NV12)
CASE(VIDEO_FORMAT_YVYU)
CASE(VIDEO_FORMAT_YUY2)
CASE(VIDEO_FORMAT_UYVY)
CASE(VIDEO_FORMAT_RGBA)
CASE(VIDEO_FORMAT_BGRA)
CASE(VIDEO_FORMAT_BGRX)
CASE(VIDEO_FORMAT_Y800)
CASE(VIDEO_FORMAT_I444)
}
}
const char *describe_color_space(int cs) {
switch (cs) {
default:
CASE(VIDEO_CS_DEFAULT)
CASE(VIDEO_CS_601)
CASE(VIDEO_CS_709)
}
}
const char *describe_color_range(int range) {
switch (range) {
default:
CASE(VIDEO_RANGE_DEFAULT)
CASE(VIDEO_RANGE_PARTIAL)
CASE(VIDEO_RANGE_FULL)
}
}
const char *describe_scale_type(int scale) {
switch (scale) {
default:
CASE(VIDEO_SCALE_DEFAULT)
CASE(VIDEO_SCALE_POINT)
CASE(VIDEO_SCALE_FAST_BILINEAR)
CASE(VIDEO_SCALE_BILINEAR)
CASE(VIDEO_SCALE_BICUBIC)
}
}
#undef CASE
/** /**
* Returns the latest version of the plugin and the API. * Returns the latest version of the plugin and the API.
@ -63,41 +13,31 @@ const char *describe_scale_type(int scale) {
* @return {String} `obs-websocket-version` obs-websocket plugin version. * @return {String} `obs-websocket-version` obs-websocket plugin version.
* @return {String} `obs-studio-version` OBS Studio program version. * @return {String} `obs-studio-version` OBS Studio program version.
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). * @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
* *
* @api requests * @api requests
* @name GetVersion * @name GetVersion
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) { void WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
QString obsVersion = Utils::OBSVersionString(); QString obsVersion = Utils::OBSVersionString();
QList<QString> names = messageMap.keys(); QList<QString> names = req->messageMap.keys();
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats(); names.sort(Qt::CaseInsensitive);
// (Palakis) OBS' data arrays only support object arrays, so I improvised. // (Palakis) OBS' data arrays only support object arrays, so I improvised.
QString requests; QString requests;
names.sort(Qt::CaseInsensitive);
requests += names.takeFirst(); requests += names.takeFirst();
for (const QString& reqName : names) { for (QString reqName : names) {
requests += ("," + reqName); requests += ("," + reqName);
} }
QString supportedImageExportFormats;
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
for (const QByteArray& format : imageWriterFormats) {
supportedImageExportFormats += ("," + QString::fromUtf8(format));
}
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_double(data, "version", 1.1);
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION); obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8()); obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
obs_data_set_string(data, "available-requests", requests.toUtf8()); obs_data_set_string(data, "available-requests", requests.toUtf8());
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
return request.success(data); req->SendOKResponse(data);
} }
/** /**
@ -113,21 +53,20 @@ RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) { void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
bool authRequired = GetConfig()->AuthRequired; bool authRequired = Config::Current()->AuthRequired;
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "authRequired", authRequired); obs_data_set_bool(data, "authRequired", authRequired);
if (authRequired) { if (authRequired) {
auto config = GetConfig();
obs_data_set_string(data, "challenge", obs_data_set_string(data, "challenge",
config->SessionChallenge.toUtf8()); Config::Current()->SessionChallenge.toUtf8());
obs_data_set_string(data, "salt", obs_data_set_string(data, "salt",
config->Salt.toUtf8()); Config::Current()->Salt.toUtf8());
} }
return request.success(data); req->SendOKResponse(data);
} }
/** /**
@ -140,26 +79,26 @@ RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
* @category general * @category general
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) { void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
if (!request.hasField("auth")) { if (!req->hasField("auth")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
if (_connProperties.isAuthenticated()) { QString auth = obs_data_get_string(req->data, "auth");
return request.failed("already authenticated");
}
QString auth = obs_data_get_string(request.parameters(), "auth");
if (auth.isEmpty()) { if (auth.isEmpty()) {
return request.failed("auth not specified!"); req->SendErrorResponse("auth not specified!");
return;
} }
if (GetConfig()->CheckAuth(auth) == false) { if ((req->_client->property(PROP_AUTHENTICATED).toBool() == false)
return request.failed("Authentication Failed."); && Config::Current()->CheckAuth(auth))
{
req->_client->setProperty(PROP_AUTHENTICATED, true);
req->SendOKResponse();
} else {
req->SendErrorResponse("Authentication Failed.");
} }
_connProperties.setAuthenticated(true);
return request.success();
} }
/** /**
@ -172,18 +111,19 @@ RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) { void WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
if (!request.hasField("enable")) { if (!req->hasField("enable")) {
return request.failed("Heartbeat <enable> parameter missing"); req->SendErrorResponse("Heartbeat <enable> parameter missing");
return;
} }
auto events = GetEventsSystem(); WSEvents::Instance->HeartbeatIsActive =
events->HeartbeatIsActive = obs_data_get_bool(request.parameters(), "enable"); obs_data_get_bool(req->data, "enable");
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "enable", events->HeartbeatIsActive); obs_data_set_bool(response, "enable",
WSEvents::Instance->HeartbeatIsActive);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -196,19 +136,19 @@ RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) { void WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
if (!request.hasField("filename-formatting")) { if (!req->hasField("filename-formatting")) {
return request.failed("<filename-formatting> parameter missing"); req->SendErrorResponse("<filename-formatting> parameter missing");
return;
} }
QString filenameFormatting = obs_data_get_string(request.parameters(), "filename-formatting"); QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
if (filenameFormatting.isEmpty()) { if (!filenameFormatting.isEmpty()) {
return request.failed("invalid request parameters"); Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
req->SendOKResponse();
} else {
req->SendErrorResponse("invalid request parameters");
} }
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
return request.success();
} }
/** /**
@ -221,126 +161,8 @@ RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
* @category general * @category general
* @since 4.3.0 * @since 4.3.0
*/ */
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) { void WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting()); obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
req->SendOKResponse(response);
return request.success(response);
}
/**
* Get OBS stats (almost the same info as provided in OBS' stats window)
*
* @return {OBSStats} `stats` OBS stats
*
* @api requests
* @name GetStats
* @category general
* @since 4.6.0
*/
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_obj(response, "stats", stats);
return request.success(response);
}
/**
* Broadcast custom message to all connected WebSocket clients
*
* @param {String} `realm` Identifier to be choosen by the client
* @param {Object} `data` User-defined data
*
* @api requests
* @name BroadcastCustomMessage
* @category general
* @since 4.7.0
*/
RpcResponse WSRequestHandler::BroadcastCustomMessage(const RpcRequest& request) {
if (!request.hasField("realm") || !request.hasField("data")) {
return request.failed("missing request parameters");
}
QString realm = obs_data_get_string(request.parameters(), "realm");
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "data");
if (realm.isEmpty()) {
return request.failed("realm not specified!");
}
if (!data) {
return request.failed("data not specified!");
}
auto events = GetEventsSystem();
events->OnBroadcastCustomMessage(realm, data);
return request.success();
}
/**
* Get basic OBS video information
*
* @return {int} `baseWidth` Base (canvas) width
* @return {int} `baseHeight` Base (canvas) height
* @return {int} `outputWidth` Output width
* @return {int} `outputHeight` Output height
* @return {String} `scaleType` Scaling method used if output size differs from base size
* @return {double} `fps` Frames rendered per second
* @return {String} `videoFormat` Video color format
* @return {String} `colorSpace` Color space for YUV
* @return {String} `colorRange` Color range (full or partial)
*
* @api requests
* @name GetVideoInfo
* @category general
* @since 4.6.0
*/
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
obs_video_info ovi;
obs_get_video_info(&ovi);
OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "baseWidth", ovi.base_width);
obs_data_set_int(response, "baseHeight", ovi.base_height);
obs_data_set_int(response, "outputWidth", ovi.output_width);
obs_data_set_int(response, "outputHeight", ovi.output_height);
obs_data_set_double(response, "fps", (double)ovi.fps_num / ovi.fps_den);
obs_data_set_string(response, "videoFormat", describe_output_format(ovi.output_format));
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
return request.success(response);
}
/**
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
*
* @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
*
* @api requests
* @name OpenProjector
* @category general
* @since 4.8.0
*/
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
const char* type = obs_data_get_string(request.parameters(), "type");
int monitor = -1;
if (request.hasField("monitor")) {
monitor = obs_data_get_int(request.parameters(), "monitor");
}
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
const char* name = obs_data_get_string(request.parameters(), "name");
obs_frontend_open_projector(type, monitor, geometry, name);
return request.success();
} }

View File

@ -1,184 +0,0 @@
#include <functional>
#include "WSRequestHandler.h"
/**
* @typedef {Object} `Output`
* @property {String} `name` Output name
* @property {String} `type` Output type/kind
* @property {int} `width` Video output width
* @property {int} `height` Video output height
* @property {Object} `flags` Output flags
* @property {int} `flags.rawValue` Raw flags value
* @property {boolean} `flags.audio` Output uses audio
* @property {boolean} `flags.video` Output uses video
* @property {boolean} `flags.encoded` Output is encoded
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
* @property {boolean} `flags.service` Output uses a service
* @property {Object} `settings` Output name
* @property {boolean} `active` Output status (active or not)
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
* @property {double} `congestion` Output congestion
* @property {int} `totalFrames` Number of frames sent
* @property {int} `droppedFrames` Number of frames dropped
* @property {int} `totalBytes` Total bytes sent
*/
obs_data_t* getOutputInfo(obs_output_t* output)
{
if (!output) {
return nullptr;
}
OBSDataAutoRelease settings = obs_output_get_settings(output);
uint32_t rawFlags = obs_output_get_flags(output);
OBSDataAutoRelease flags = obs_data_create();
obs_data_set_int(flags, "rawValue", rawFlags);
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "name", obs_output_get_name(output));
obs_data_set_string(data, "type", obs_output_get_id(output));
obs_data_set_int(data, "width", obs_output_get_width(output));
obs_data_set_int(data, "height", obs_output_get_height(output));
obs_data_set_obj(data, "flags", flags);
obs_data_set_obj(data, "settings", settings);
obs_data_set_bool(data, "active", obs_output_active(output));
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));
return data;
}
RpcResponse findOutputOrFail(const RpcRequest& request, std::function<RpcResponse (obs_output_t*)> callback)
{
if (!request.hasField("outputName")) {
return request.failed("missing request parameters");
}
const char* outputName = obs_data_get_string(request.parameters(), "outputName");
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
if (!output) {
return request.failed("specified output doesn't exist");
}
return callback(output);
}
/**
* List existing outputs
*
* @return {Array<Output>} `outputs` Outputs list
*
* @api requests
* @name ListOutputs
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ListOutputs(const RpcRequest& request)
{
OBSDataArrayAutoRelease outputs = obs_data_array_create();
obs_enum_outputs([](void* param, obs_output_t* output) {
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);
OBSDataAutoRelease outputInfo = getOutputInfo(output);
obs_data_array_push_back(outputs, outputInfo);
return true;
}, outputs);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_array(fields, "outputs", outputs);
return request.success(fields);
}
/**
* Get information about a single output
*
* @param {String} `outputName` Output name
*
* @return {Output} `outputInfo` Output info
*
* @api requests
* @name GetOutputInfo
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::GetOutputInfo(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
OBSDataAutoRelease outputInfo = getOutputInfo(output);
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_obj(fields, "outputInfo", outputInfo);
return request.success(fields);
});
}
/**
* Start an output
*
* @param {String} `outputName` Output name
*
* @api requests
* @name StartOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StartOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (obs_output_active(output)) {
return request.failed("output already active");
}
bool success = obs_output_start(output);
if (!success) {
QString lastError = obs_output_get_last_error(output);
QString errorMessage = QString("output start failed: %1").arg(lastError);
return request.failed(errorMessage);
}
return request.success();
});
}
/**
* Stop an output
*
* @param {String} `outputName` Output name
* @param {boolean (optional)} `force` Force stop (default: false)
*
* @api requests
* @name StopOutput
* @category outputs
* @since 4.7.0
*/
RpcResponse WSRequestHandler::StopOutput(const RpcRequest& request)
{
return findOutputOrFail(request, [request](obs_output_t* output) {
if (!obs_output_active(output)) {
return request.failed("output not active");
}
bool forceStop = obs_data_get_bool(request.parameters(), "force");
if (forceStop) {
obs_output_force_stop(output);
} else {
obs_output_stop(output);
}
return request.success();
});
}

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -12,19 +13,20 @@
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) { void WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
if (!request.hasField("profile-name")) { if (!req->hasField("profile-name")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString profileName = obs_data_get_string(request.parameters(), "profile-name"); QString profileName = obs_data_get_string(req->data, "profile-name");
if (profileName.isEmpty()) { if (!profileName.isEmpty()) {
return request.failed("invalid request parameters"); // TODO : check if profile exists
obs_frontend_set_current_profile(profileName.toUtf8());
req->SendOKResponse();
} else {
req->SendErrorResponse("invalid request parameters");
} }
// TODO : check if profile exists
obs_frontend_set_current_profile(profileName.toUtf8());
return request.success();
} }
/** /**
@ -37,12 +39,12 @@ RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) { void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
char* currentProfile = obs_frontend_get_current_profile(); obs_data_set_string(response, "profile-name",
obs_data_set_string(response, "profile-name", currentProfile); obs_frontend_get_current_profile());
bfree(currentProfile);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -55,13 +57,14 @@ RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
* @category profiles * @category profiles
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) { void WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
char** profiles = obs_frontend_get_profiles(); char** profiles = obs_frontend_get_profiles();
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name"); OBSDataArrayAutoRelease list =
Utils::StringListToArray(profiles, "profile-name");
bfree(profiles); bfree(profiles);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "profiles", list); obs_data_set_array(response, "profiles", list);
return request.success(response); req->SendOKResponse(response);
} }

View File

@ -1,17 +1,7 @@
#include "WSRequestHandler.h" #include <QString>
#include <functional>
#include <util/platform.h>
#include "Utils.h" #include "Utils.h"
RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> callback) #include "WSRequestHandler.h"
{
if (!obs_frontend_recording_active()) {
return request.failed("recording is not active");
}
return callback();
}
/** /**
* Toggle recording on or off. * Toggle recording on or off.
@ -21,9 +11,13 @@ RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> c
* @category recording * @category recording
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) { void WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start()); if (obs_frontend_recording_active())
return request.success(); obs_frontend_recording_stop();
else
obs_frontend_recording_start();
req->SendOKResponse();
} }
/** /**
@ -35,13 +29,13 @@ RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) { void WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active()) { if (obs_frontend_recording_active() == false) {
return request.failed("recording already active"); obs_frontend_recording_start();
req->SendOKResponse();
} else {
req->SendErrorResponse("recording already active");
} }
obs_frontend_recording_start();
return request.success();
} }
/** /**
@ -53,63 +47,18 @@ RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) { void WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
if (!obs_frontend_recording_active()) { if (obs_frontend_recording_active() == true) {
return request.failed("recording not active"); obs_frontend_recording_stop();
req->SendOKResponse();
} else {
req->SendErrorResponse("recording not active");
} }
obs_frontend_recording_stop();
return request.success();
} }
/** /**
* Pause the current recording. * Change the current recording folder.
* Returns an error if recording is not active or already paused. *
*
* @api requests
* @name PauseRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (obs_frontend_recording_paused()) {
return request.failed("recording already paused");
}
obs_frontend_recording_pause(true);
return request.success();
});
}
/**
* Resume/unpause the current recording (if paused).
* Returns an error if recording is not active or not paused.
*
* @api requests
* @name ResumeRecording
* @category recording
* @since 4.7.0
*/
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
return ifCanPause(request, [request]() {
if (!obs_frontend_recording_paused()) {
return request.failed("recording is not paused");
}
obs_frontend_recording_pause(false);
return request.success();
});
}
/**
* In the current profile, sets the recording folder of the Simple and Advanced
* output modes to the specified value.
*
* Please note: if `SetRecordingFolder` is called while a recording is
* in progress, the change won't be applied immediately and will be
* effective on the next recording.
*
* @param {String} `rec-folder` Path of the recording folder. * @param {String} `rec-folder` Path of the recording folder.
* *
* @api requests * @api requests
@ -117,18 +66,18 @@ RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) { void WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
if (!request.hasField("rec-folder")) { if (!req->hasField("rec-folder")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
const char* newRecFolder = obs_data_get_string(request.parameters(), "rec-folder"); const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
bool success = Utils::SetRecordingFolder(newRecFolder); bool success = Utils::SetRecordingFolder(newRecFolder);
if (!success) { if (success)
return request.failed("invalid request parameters"); req->SendOKResponse();
} else
req->SendErrorResponse("invalid request parameters");
return request.success();
} }
/** /**
@ -141,11 +90,11 @@ RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
* @category recording * @category recording
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) { void WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
const char* recFolder = Utils::GetRecordingFolder(); const char* recFolder = Utils::GetRecordingFolder();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "rec-folder", recFolder); obs_data_set_string(response, "rec-folder", recFolder);
return request.success(response); req->SendOKResponse(response);
} }

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -10,13 +11,13 @@
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) { void WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
if (obs_frontend_replay_buffer_active()) { if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop(); obs_frontend_replay_buffer_stop();
} else { } else {
Utils::StartReplayBuffer(); Utils::StartReplayBuffer();
} }
return request.success(); req->SendOKResponse();
} }
/** /**
@ -31,17 +32,19 @@ RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) { void WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
if (!Utils::ReplayBufferEnabled()) { if (!Utils::ReplayBufferEnabled()) {
return request.failed("replay buffer disabled in settings"); req->SendErrorResponse("replay buffer disabled in settings");
return;
} }
if (obs_frontend_replay_buffer_active() == true) { if (obs_frontend_replay_buffer_active() == true) {
return request.failed("replay buffer already active"); req->SendErrorResponse("replay buffer already active");
return;
} }
Utils::StartReplayBuffer(); Utils::StartReplayBuffer();
return request.success(); req->SendOKResponse();
} }
/** /**
@ -53,12 +56,12 @@ RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) { void WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
if (obs_frontend_replay_buffer_active() == true) { if (obs_frontend_replay_buffer_active() == true) {
obs_frontend_replay_buffer_stop(); obs_frontend_replay_buffer_stop();
return request.success(); req->SendOKResponse();
} else { } else {
return request.failed("replay buffer not active"); req->SendErrorResponse("replay buffer not active");
} }
} }
@ -72,9 +75,10 @@ RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
* @category replay buffer * @category replay buffer
* @since 4.2.0 * @since 4.2.0
*/ */
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) { void WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
if (!obs_frontend_replay_buffer_active()) { if (!obs_frontend_replay_buffer_active()) {
return request.failed("replay buffer not active"); req->SendErrorResponse("replay buffer not active");
return;
} }
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output(); OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
@ -84,5 +88,5 @@ RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
proc_handler_call(ph, "save", &cd); proc_handler_call(ph, "save", &cd);
calldata_free(&cd); calldata_free(&cd);
return request.success(); req->SendOKResponse();
} }

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -12,19 +13,20 @@
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) { void WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
if (!request.hasField("sc-name")) { if (!req->hasField("sc-name")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name"); QString sceneCollection = obs_data_get_string(req->data, "sc-name");
if (sceneCollection.isEmpty()) { if (!sceneCollection.isEmpty()) {
return request.failed("invalid request parameters"); // TODO : Check if specified profile exists and if changing is allowed
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
req->SendOKResponse();
} else {
req->SendErrorResponse("invalid request parameters");
} }
// TODO : Check if specified profile exists and if changing is allowed
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
return request.success();
} }
/** /**
@ -37,14 +39,12 @@ RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& reques
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) { void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "sc-name",
obs_frontend_get_current_scene_collection());
char* sceneCollection = obs_frontend_get_current_scene_collection(); req->SendOKResponse(response);
obs_data_set_string(response, "sc-name", sceneCollection);
bfree(sceneCollection);
return request.success(response);
} }
/** /**
@ -57,7 +57,7 @@ RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& reques
* @category scene collections * @category scene collections
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) { void WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
char** sceneCollections = obs_frontend_get_scene_collections(); char** sceneCollections = obs_frontend_get_scene_collections();
OBSDataArrayAutoRelease list = OBSDataArrayAutoRelease list =
Utils::StringListToArray(sceneCollections, "sc-name"); Utils::StringListToArray(sceneCollections, "sc-name");
@ -66,5 +66,5 @@ RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_array(response, "scene-collections", list); obs_data_set_array(response, "scene-collections", list);
return request.success(response); req->SendOKResponse(response);
} }

View File

@ -1,18 +1,15 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
/** /**
* Gets the scene specific properties of the specified source item. * Gets the scene specific properties of the specified source item.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). * @param {String} `item` The name of the source.
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
* *
* @return {String} `name` Scene Item name. * @return {String} `name` The name of the source.
* @return {int} `itemId` Scene Item ID.
* @return {int} `position.x` The x position of the source from the left. * @return {int} `position.x` The x position of the source from the left.
* @return {int} `position.y` The y position of the source from the top. * @return {int} `position.y` The y position of the source from the top.
* @return {int} `position.alignment` The point on the source that the item is manipulated from. * @return {int} `position.alignment` The point on the source that the item is manipulated from.
@ -24,164 +21,220 @@
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. * @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
* @return {bool} `visible` If the source is visible. * @return {bool} `visible` If the source is visible.
* @return {bool} `muted` If the source is muted. * @return {String} `bounds.type` Type of bounding box.
* @return {bool} `locked` If the source's transform is locked.
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
* @return {int} `bounds.alignment` Alignment of the bounding box. * @return {int} `bounds.alignment` Alignment of the bounding box.
* @return {double} `bounds.x` Width of the bounding box. * @return {double} `bounds.x` Width of the bounding box.
* @return {double} `bounds.y` Height of the bounding box. * @return {double} `bounds.y` Height of the bounding box.
* @return {int} `sourceWidth` Base width (without scaling) of the source *
* @return {int} `sourceHeight` Base source (without scaling) of the source
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
*
* @api requests * @api requests
* @name GetSceneItemProperties * @name GetSceneItemProperties
* @category scene items * @category scene items
* @since 4.3.0 * @since 4.3.0
*/ */
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) { void WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
if (!request.hasField("item")) { if (!req->hasField("item")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
OBSData params = request.parameters(); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters");
return;
}
QString sceneName = obs_data_get_string(params, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); OBSSceneItemAutoRelease sceneItem =
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (!sceneItem) {
return request.failed("specified scene item doesn't exist"); req->SendErrorResponse("specified scene item doesn't exist");
return;
} }
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", itemName.toUtf8());
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem); OBSDataAutoRelease posData = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource)); vec2 pos;
obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem)); obs_sceneitem_get_pos(sceneItem, &pos);
obs_data_set_double(posData, "x", pos.x);
obs_data_set_double(posData, "y", pos.y);
obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem));
obs_data_set_obj(data, "position", posData);
return request.success(data); obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem));
OBSDataAutoRelease scaleData = obs_data_create();
vec2 scale;
obs_sceneitem_get_scale(sceneItem, &scale);
obs_data_set_double(scaleData, "x", scale.x);
obs_data_set_double(scaleData, "y", scale.y);
obs_data_set_obj(data, "scale", scaleData);
OBSDataAutoRelease cropData = obs_data_create();
obs_sceneitem_crop crop;
obs_sceneitem_get_crop(sceneItem, &crop);
obs_data_set_int(cropData, "left", crop.left);
obs_data_set_int(cropData, "top", crop.top);
obs_data_set_int(cropData, "right", crop.right);
obs_data_set_int(cropData, "bottom", crop.bottom);
obs_data_set_obj(data, "crop", cropData);
obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem));
OBSDataAutoRelease boundsData = obs_data_create();
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
if (boundsType == OBS_BOUNDS_NONE) {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE");
}
else {
switch (boundsType) {
case OBS_BOUNDS_STRETCH: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH");
break;
}
case OBS_BOUNDS_SCALE_INNER: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER");
break;
}
case OBS_BOUNDS_SCALE_OUTER: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER");
break;
}
case OBS_BOUNDS_SCALE_TO_WIDTH: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH");
break;
}
case OBS_BOUNDS_SCALE_TO_HEIGHT: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT");
break;
}
case OBS_BOUNDS_MAX_ONLY: {
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY");
break;
}
}
obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem));
vec2 bounds;
obs_sceneitem_get_bounds(sceneItem, &bounds);
obs_data_set_double(boundsData, "x", bounds.x);
obs_data_set_double(boundsData, "y", bounds.y);
}
obs_data_set_obj(data, "bounds", boundsData);
req->SendOKResponse(data);
} }
/** /**
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged. * Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
* Coordinates are relative to the item's parent (the scene or group it belongs to).
* *
* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). * @param {String} `item` The name of the source.
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object) * @param {int} `position.x` The new x position of the source.
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object) * @param {int} `position.y` The new y position of the source.
* @param {int (optional)} `position.x` The new x position of the source. * @param {int} `position.alignment` The new alignment of the source.
* @param {int (optional)} `position.y` The new y position of the source. * @param {double} `rotation` The new clockwise rotation of the item in degrees.
* @param {int (optional)} `position.alignment` The new alignment of the source. * @param {double} `scale.x` The new x scale of the item.
* @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees. * @param {double} `scale.y` The new y scale of the item.
* @param {double (optional)} `scale.x` The new x scale of the item. * @param {int} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
* @param {double (optional)} `scale.y` The new y scale of the item. * @param {int} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.
* @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling. * @param {int} `crop.left` The new amount of pixels cropped off the left of the source before scaling.
* @param {int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling. * @param {int} `crop.right` The new amount of pixels cropped off the right of the source before scaling.
* @param {int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling. * @param {bool} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
* @param {int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling. * @param {String} `bounds.type` The new bounds type of the source.
* @param {bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source. * @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
* @param {bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement. * @param {double} `bounds.x` The new width of the bounding box.
* @param {String (optional)} `bounds.type` The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @param {double} `bounds.y` The new height of the bounding box.
* @param {int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
* @param {double (optional)} `bounds.x` The new width of the bounding box.
* @param {double (optional)} `bounds.y` The new height of the bounding box.
* *
* @api requests * @api requests
* @name SetSceneItemProperties * @name SetSceneItemProperties
* @category scene items * @category scene items
* @since 4.3.0 * @since 4.3.0
*/ */
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) { void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
if (!request.hasField("item")) { if (!req->hasField("item")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
OBSData params = request.parameters(); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) {
req->SendErrorResponse("invalid request parameters");
return;
}
QString sceneName = obs_data_get_string(params, "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); OBSSceneItemAutoRelease sceneItem =
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (!sceneItem) {
return request.failed("specified scene item doesn't exist"); req->SendErrorResponse("specified scene item doesn't exist");
return;
} }
bool badRequest = false; bool badRequest = false;
OBSDataAutoRelease errorData = obs_data_create(); OBSDataAutoRelease errorMessage = obs_data_create();
obs_sceneitem_defer_update_begin(sceneItem); if (req->hasField("position")) {
if (request.hasField("position")) {
vec2 oldPosition; vec2 oldPosition;
OBSDataAutoRelease positionError = obs_data_create(); OBSDataAutoRelease positionError = obs_data_create();
obs_sceneitem_get_pos(sceneItem, &oldPosition); obs_sceneitem_get_pos(sceneItem, &oldPosition);
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position");
vec2 newPosition = oldPosition; vec2 newPosition = oldPosition;
if (obs_data_has_user_value(reqPosition, "x")) { if (obs_data_has_user_value(reqPosition, "x")) {
newPosition.x = obs_data_get_int(reqPosition, "x"); newPosition.x = obs_data_get_int(reqPosition, "x");
} }
if (obs_data_has_user_value(reqPosition, "y")) { if (obs_data_has_user_value(reqPosition, "y")) {
newPosition.y = obs_data_get_int(reqPosition, "y"); newPosition.y = obs_data_get_int(reqPosition, "y");
} }
if (obs_data_has_user_value(reqPosition, "alignment")) { if (obs_data_has_user_value(reqPosition, "alignment")) {
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment"); const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
if (Utils::IsValidAlignment(alignment)) { if (Utils::IsValidAlignment(alignment)) {
obs_sceneitem_set_alignment(sceneItem, alignment); obs_sceneitem_set_alignment(sceneItem, alignment);
} else { }
else {
badRequest = true; badRequest = true;
obs_data_set_string(positionError, "alignment", "invalid"); obs_data_set_string(positionError, "alignment", "invalid");
obs_data_set_obj(errorData, "position", positionError); obs_data_set_obj(errorMessage, "position", positionError);
} }
} }
obs_sceneitem_set_pos(sceneItem, &newPosition); obs_sceneitem_set_pos(sceneItem, &newPosition);
} }
if (request.hasField("rotation")) { if (req->hasField("rotation")) {
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation")); obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
} }
if (request.hasField("scale")) { if (req->hasField("scale")) {
vec2 oldScale; vec2 oldScale;
obs_sceneitem_get_scale(sceneItem, &oldScale); obs_sceneitem_get_scale(sceneItem, &oldScale);
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
vec2 newScale = oldScale; vec2 newScale = oldScale;
OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale");
if (obs_data_has_user_value(reqScale, "x")) { if (obs_data_has_user_value(reqScale, "x")) {
newScale.x = obs_data_get_double(reqScale, "x"); newScale.x = obs_data_get_double(reqScale, "x");
} }
if (obs_data_has_user_value(reqScale, "y")) { if (obs_data_has_user_value(reqScale, "y")) {
newScale.y = obs_data_get_double(reqScale, "y"); newScale.y = obs_data_get_double(reqScale, "y");
} }
obs_sceneitem_set_scale(sceneItem, &newScale); obs_sceneitem_set_scale(sceneItem, &newScale);
} }
if (request.hasField("crop")) { if (req->hasField("crop")) {
obs_sceneitem_crop oldCrop; obs_sceneitem_crop oldCrop;
obs_sceneitem_get_crop(sceneItem, &oldCrop); obs_sceneitem_get_crop(sceneItem, &oldCrop);
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop");
obs_sceneitem_crop newCrop = oldCrop; obs_sceneitem_crop newCrop = oldCrop;
if (obs_data_has_user_value(reqCrop, "top")) { if (obs_data_has_user_value(reqCrop, "top")) {
newCrop.top = obs_data_get_int(reqCrop, "top"); newCrop.top = obs_data_get_int(reqCrop, "top");
} }
@ -194,25 +247,19 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request)
if (obs_data_has_user_value(reqCrop, "left")) { if (obs_data_has_user_value(reqCrop, "left")) {
newCrop.left = obs_data_get_int(reqCrop, "left"); newCrop.left = obs_data_get_int(reqCrop, "left");
} }
obs_sceneitem_set_crop(sceneItem, &newCrop); obs_sceneitem_set_crop(sceneItem, &newCrop);
} }
if (request.hasField("visible")) { if (req->hasField("visible")) {
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible")); obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
} }
if (request.hasField("locked")) { if (req->hasField("bounds")) {
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked"));
}
if (request.hasField("bounds")) {
bool badBounds = false; bool badBounds = false;
OBSDataAutoRelease boundsError = obs_data_create(); OBSDataAutoRelease boundsError = obs_data_create();
OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds"); OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
if (obs_data_has_user_value(reqBounds, "type")) { if (obs_data_has_user_value(reqBounds, "type")) {
QString newBoundsType = obs_data_get_string(reqBounds, "type"); const char* newBoundsType = obs_data_get_string(reqBounds, "type");
if (newBoundsType == "OBS_BOUNDS_NONE") { if (newBoundsType == "OBS_BOUNDS_NONE") {
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE); obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
} }
@ -239,20 +286,16 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request)
obs_data_set_string(boundsError, "type", "invalid"); obs_data_set_string(boundsError, "type", "invalid");
} }
} }
vec2 oldBounds; vec2 oldBounds;
obs_sceneitem_get_bounds(sceneItem, &oldBounds); obs_sceneitem_get_bounds(sceneItem, &oldBounds);
vec2 newBounds = oldBounds; vec2 newBounds = oldBounds;
if (obs_data_has_user_value(reqBounds, "x")) { if (obs_data_has_user_value(reqBounds, "x")) {
newBounds.x = obs_data_get_double(reqBounds, "x"); newBounds.x = obs_data_get_double(reqBounds, "x");
} }
if (obs_data_has_user_value(reqBounds, "y")) { if (obs_data_has_user_value(reqBounds, "y")) {
newBounds.y = obs_data_get_double(reqBounds, "y"); newBounds.y = obs_data_get_double(reqBounds, "y");
} }
obs_sceneitem_set_bounds(sceneItem, &newBounds); obs_sceneitem_set_bounds(sceneItem, &newBounds);
if (obs_data_has_user_value(reqBounds, "alignment")) { if (obs_data_has_user_value(reqBounds, "alignment")) {
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment"); const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
if (Utils::IsValidAlignment(bounds_alignment)) { if (Utils::IsValidAlignment(bounds_alignment)) {
@ -263,67 +306,71 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request)
obs_data_set_string(boundsError, "alignment", "invalid"); obs_data_set_string(boundsError, "alignment", "invalid");
} }
} }
if (badBounds) { if (badBounds) {
obs_data_set_obj(errorData, "bounds", boundsError); obs_data_set_obj(errorMessage, "bounds", boundsError);
} }
} }
obs_sceneitem_defer_update_end(sceneItem);
if (badRequest) { if (badRequest) {
return request.failed("error", errorData); req->SendErrorResponse(errorMessage);
}
else {
req->SendOKResponse();
} }
return request.success();
} }
/** /**
* Reset a scene item. * Reset a scene item.
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). * @param {String} `item` Name of the source item.
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
* *
* @api requests * @api requests
* @name ResetSceneItem * @name ResetSceneItem
* @category scene items * @category scene items
* @since 4.2.0 * @since 4.2.0
*/ */
RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) { void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
if (!request.hasField("item")) { // TODO: remove this request, or refactor it to ResetSource
return request.failed("missing request parameters");
if (!req->hasField("item")) {
req->SendErrorResponse("missing request parameters");
return;
} }
OBSData params = request.parameters(); const char* itemName = obs_data_get_string(req->data, "item");
if (!itemName) {
req->SendErrorResponse("invalid request parameters");
return;
}
const char* sceneName = obs_data_get_string(params, "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); if (sceneItem) {
if (!sceneItem) { OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
return request.failed("specified scene item doesn't exist");
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
obs_source_update(sceneItemSource, settings);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
obs_source_update(sceneItemSource, settings);
return request.success();
} }
/** /**
* Show or hide a specified source item in a specified scene. * Show or hide a specified source item in a specified scene.
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene. * @param {String} `source` Scene item name in the specified scene.
* @param {String} `source` Scene Item name.
* @param {boolean} `render` true = shown ; false = hidden * @param {boolean} `render` true = shown ; false = hidden
* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene.
* *
* @api requests * @api requests
* @name SetSceneItemRender * @name SetSceneItemRender
@ -331,41 +378,45 @@ RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) {
* @since 0.3 * @since 0.3
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) { void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
if (!request.hasField("source") || if (!req->hasField("source") ||
!request.hasField("render")) !req->hasField("render"))
{ {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
const char* itemName = obs_data_get_string(request.parameters(), "source"); const char* itemName = obs_data_get_string(req->data, "source");
bool isVisible = obs_data_get_bool(request.parameters(), "render"); bool isVisible = obs_data_get_bool(req->data, "render");
if (!itemName) { if (!itemName) {
return request.failed("invalid request parameters"); req->SendErrorResponse("invalid request parameters");
return;
} }
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = OBSSceneItemAutoRelease sceneItem =
Utils::GetSceneItemFromName(scene, itemName); Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (sceneItem) {
return request.failed("specified scene item doesn't exist"); obs_sceneitem_set_visible(sceneItem, isVisible);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
obs_sceneitem_set_visible(sceneItem, isVisible);
return request.success();
} }
/** /**
* Sets the coordinates of a specified source item. * Sets the coordinates of a specified source item.
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name. * @param {String} `item` The name of the source item.
* @param {double} `x` X coordinate. * @param {double} `x` X coordinate.
* @param {double} `y` Y coordinate. * @param {double} `y` Y coordinate.
@ -376,41 +427,45 @@ RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
* @since 4.0.0 * @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) { void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
if (!request.hasField("item") || if (!req->hasField("item") ||
!request.hasField("x") || !request.hasField("y")) { !req->hasField("x") || !req->hasField("y")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(request.parameters(), "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
return request.failed("invalid request parameters"); req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(request.parameters(), "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene could not be found"); req->SendErrorResponse("requested scene could not be found");
return;
} }
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (sceneItem) {
return request.failed("specified scene item doesn't exist"); vec2 item_position = { 0 };
item_position.x = obs_data_get_double(req->data, "x");
item_position.y = obs_data_get_double(req->data, "y");
obs_sceneitem_set_pos(sceneItem, &item_position);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
vec2 item_position = { 0 };
item_position.x = obs_data_get_double(request.parameters(), "x");
item_position.y = obs_data_get_double(request.parameters(), "y");
obs_sceneitem_set_pos(sceneItem, &item_position);
return request.success();
} }
/** /**
* Set the transform of the specified source item. * Set the transform of the specified source item.
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name. * @param {String} `item` The name of the source item.
* @param {double} `x-scale` Width scale factor. * @param {double} `x-scale` Width scale factor.
* @param {double} `y-scale` Height scale factor. * @param {double} `y-scale` Height scale factor.
* @param {double} `rotation` Source item rotation (in degrees). * @param {double} `rotation` Source item rotation (in degrees).
@ -421,51 +476,50 @@ RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
* @since 4.0.0 * @since 4.0.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) { void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
if (!request.hasField("item") || if (!req->hasField("item") ||
!request.hasField("x-scale") || !req->hasField("x-scale") ||
!request.hasField("y-scale") || !req->hasField("y-scale") ||
!request.hasField("rotation")) !req->hasField("rotation"))
{ {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(request.parameters(), "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
return request.failed("invalid request parameters"); req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(request.parameters(), "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
vec2 scale; vec2 scale;
scale.x = obs_data_get_double(request.parameters(), "x-scale"); scale.x = obs_data_get_double(req->data, "x-scale");
scale.y = obs_data_get_double(request.parameters(), "y-scale"); scale.y = obs_data_get_double(req->data, "y-scale");
float rotation = obs_data_get_double(request.parameters(), "rotation"); float rotation = obs_data_get_double(req->data, "rotation");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (sceneItem) {
return request.failed("specified scene item doesn't exist"); obs_sceneitem_set_scale(sceneItem, &scale);
obs_sceneitem_set_rot(sceneItem, rotation);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
obs_sceneitem_defer_update_begin(sceneItem);
obs_sceneitem_set_scale(sceneItem, &scale);
obs_sceneitem_set_rot(sceneItem, rotation);
obs_sceneitem_defer_update_end(sceneItem);
return request.success();
} }
/** /**
* Sets the crop coordinates of the specified source item. * Sets the crop coordinates of the specified source item.
* *
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
* @param {String} `item` Scene Item name. * @param {String} `item` The name of the source.
* @param {int} `top` Pixel position of the top of the source item. * @param {int} `top` Pixel position of the top of the source item.
* @param {int} `bottom` Pixel position of the bottom of the source item. * @param {int} `bottom` Pixel position of the bottom of the source item.
* @param {int} `left` Pixel position of the left of the source item. * @param {int} `left` Pixel position of the left of the source item.
@ -477,71 +531,90 @@ RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
* @since 4.1.0 * @since 4.1.0
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. * @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
*/ */
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) { void WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
if (!request.hasField("item")) { if (!req->hasField("item")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString itemName = obs_data_get_string(request.parameters(), "item"); QString itemName = obs_data_get_string(req->data, "item");
if (itemName.isEmpty()) { if (itemName.isEmpty()) {
return request.failed("invalid request parameters"); req->SendErrorResponse("invalid request parameters");
return;
} }
QString sceneName = obs_data_get_string(request.parameters(), "scene-name"); QString sceneName = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (!sceneItem) { if (sceneItem) {
return request.failed("specified scene item doesn't exist"); struct obs_sceneitem_crop crop = { 0 };
crop.top = obs_data_get_int(req->data, "top");
crop.bottom = obs_data_get_int(req->data, "bottom");
crop.left = obs_data_get_int(req->data, "left");
crop.right = obs_data_get_int(req->data, "right");
obs_sceneitem_set_crop(sceneItem, &crop);
req->SendOKResponse();
}
else {
req->SendErrorResponse("specified scene item doesn't exist");
} }
struct obs_sceneitem_crop crop = { 0 };
crop.top = obs_data_get_int(request.parameters(), "top");
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
crop.left = obs_data_get_int(request.parameters(), "left");
crop.right = obs_data_get_int(request.parameters(), "right");
obs_sceneitem_set_crop(sceneItem, &crop);
return request.success();
} }
/** /**
* Deletes a scene item. * Deletes a scene item.
* *
* @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene. * @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.
* @param {Object} `item` Scene item to delete (required) * @param {Object} `item` item to delete (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable). * @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID. * @param {int} `item.id` id of the scene item.
* *
* @api requests * @api requests
* @name DeleteSceneItem * @name DeleteSceneItem
* @category scene items * @category scene items
* @since 4.5.0 * @since 4.5.0
*/ */
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) { void WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
if (!request.hasField("item")) { if (!req->hasField("item")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
const char* sceneName = obs_data_get_string(request.parameters(), "scene"); const char* sceneName = obs_data_get_string(req->data, "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item"); OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
if (!sceneItem) { if (!sceneItem) {
return request.failed("item with id/name combination not found in specified scene"); req->SendErrorResponse("item with id/name combination not found in specified scene");
return;
} }
obs_sceneitem_remove(sceneItem); obs_sceneitem_remove(sceneItem);
return request.success(); req->SendOKResponse();
}
struct DuplicateSceneItemData {
obs_sceneitem_t *referenceItem;
obs_source_t *fromSource;
obs_sceneitem_t *newItem;
};
static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
DuplicateSceneItemData *data = (DuplicateSceneItemData *)_data;
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
} }
/** /**
@ -549,47 +622,45 @@ RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
* *
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene. * @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene. * @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
* @param {Object} `item` Scene Item to duplicate from the source scene (required) * @param {Object} `item` item to duplicate (required)
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable). * @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
* @param {int} `item.id` Scene Item ID. * @param {int} `item.id` id of the scene item.
* *
* @return {String} `scene` Name of the scene where the new item was created * @return {String} `scene` Name of the scene where the new item was created
* @return {Object} `item` New item info * @return {Object} `item` New item info
* @return {int} `item.id` New item ID * @return {int} `̀item.id` New item ID
* @return {String} `item.name` New item name * @return {String} `item.name` New item name
* *
* @api requests * @api requests
* @name DuplicateSceneItem * @name DuplicateSceneItem
* @category scene items * @category scene items
* @since 4.5.0 * @since 4.5.0
*/ */
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) { void WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
struct DuplicateSceneItemData { if (!req->hasField("item")) {
obs_sceneitem_t *referenceItem; req->SendErrorResponse("missing request parameters");
obs_source_t *fromSource; return;
obs_sceneitem_t *newItem;
};
if (!request.hasField("item")) {
return request.failed("missing request parameters");
} }
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene"); const char* fromSceneName = obs_data_get_string(req->data, "fromScene");
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName); OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
if (!fromScene) { if (!fromScene) {
return request.failed("requested fromScene doesn't exist"); req->SendErrorResponse("requested fromScene doesn't exist");
return;
} }
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene"); const char* toSceneName = obs_data_get_string(req->data, "toScene");
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName); OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
if (!toScene) { if (!toScene) {
return request.failed("requested toScene doesn't exist"); req->SendErrorResponse("requested toScene doesn't exist");
return;
} }
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item"); OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField); OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
if (!referenceItem) { if (!referenceItem) {
return request.failed("item with id/name combination not found in specified scene"); req->SendErrorResponse("item with id/name combination not found in specified scene");
return;
} }
DuplicateSceneItemData data; DuplicateSceneItemData data;
@ -597,16 +668,13 @@ RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
data.referenceItem = referenceItem; data.referenceItem = referenceItem;
obs_enter_graphics(); obs_enter_graphics();
obs_scene_atomic_update(toScene, [](void *_data, obs_scene_t *scene) { obs_scene_atomic_update(obs_scene_from_source(toScene), DuplicateSceneItem, &data);
auto data = reinterpret_cast<DuplicateSceneItemData*>(_data);
data->newItem = obs_scene_add(scene, data->fromSource);
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
}, &data);
obs_leave_graphics(); obs_leave_graphics();
obs_sceneitem_t *newItem = data.newItem; obs_sceneitem_t *newItem = data.newItem;
if (!newItem) { if (!newItem) {
return request.failed("Error duplicating scene item"); req->SendErrorResponse("Error duplicating scene item");
return;
} }
OBSDataAutoRelease itemData = obs_data_create(); OBSDataAutoRelease itemData = obs_data_create();
@ -615,7 +683,7 @@ RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
OBSDataAutoRelease responseData = obs_data_create(); OBSDataAutoRelease responseData = obs_data_create();
obs_data_set_obj(responseData, "item", itemData); obs_data_set_obj(responseData, "item", itemData);
obs_data_set_string(responseData, "scene", obs_source_get_name(obs_scene_get_source(toScene))); obs_data_set_string(responseData, "scene", obs_source_get_name(toScene));
return request.success(responseData); req->SendOKResponse(responseData);
} }

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -5,7 +6,7 @@
/** /**
* @typedef {Object} `Scene` * @typedef {Object} `Scene`
* @property {String} `name` Name of the currently active scene. * @property {String} `name` Name of the currently active scene.
* @property {Array<SceneItem>} `sources` Ordered list of the current scene's source items. * @property {Array<Source>} `sources` Ordered list of the current scene's source items.
*/ */
/** /**
@ -18,19 +19,20 @@
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) { void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
if (!request.hasField("scene-name")) { if (!req->hasField("scene-name")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name"); const char* sceneName = obs_data_get_string(req->data, "scene-name");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName); OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
if (source) { if (source) {
obs_frontend_set_current_scene(source); obs_frontend_set_current_scene(source);
return request.success(); req->SendOKResponse();
} else { } else {
return request.failed("requested scene does not exist"); req->SendErrorResponse("requested scene does not exist");
} }
} }
@ -38,14 +40,14 @@ RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) {
* Get the current scene's name and source items. * Get the current scene's name and source items.
* *
* @return {String} `name` Name of the currently active scene. * @return {String} `name` Name of the currently active scene.
* @return {Array<SceneItem>} `sources` Ordered list of the current scene's source items. * @return {Array<Source>} `sources` Ordered list of the current scene's source items.
* *
* @api requests * @api requests
* @name GetCurrentScene * @name GetCurrentScene
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) { void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene); OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
@ -53,21 +55,21 @@ RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) {
obs_data_set_string(data, "name", obs_source_get_name(currentScene)); obs_data_set_string(data, "name", obs_source_get_name(currentScene));
obs_data_set_array(data, "sources", sceneItems); obs_data_set_array(data, "sources", sceneItems);
return request.success(data); req->SendOKResponse(data);
} }
/** /**
* Get a list of scenes in the currently active profile. * Get a list of scenes in the currently active profile.
* *
* @return {String} `current-scene` Name of the currently active scene. * @return {String} `current-scene` Name of the currently active scene.
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information). * @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
* *
* @api requests * @api requests
* @name GetSceneList * @name GetSceneList
* @category scenes * @category scenes
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) { void WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
OBSDataArrayAutoRelease scenes = Utils::GetScenes(); OBSDataArrayAutoRelease scenes = Utils::GetScenes();
@ -76,7 +78,7 @@ RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
obs_source_get_name(currentScene)); obs_source_get_name(currentScene));
obs_data_set_array(data, "scenes", scenes); obs_data_set_array(data, "scenes", scenes);
return request.success(data); req->SendOKResponse(data);
} }
/** /**
@ -92,184 +94,55 @@ RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
* @category scenes * @category scenes
* @since 4.5.0 * @since 4.5.0
*/ */
RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) { void WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
QString sceneName = obs_data_get_string(request.parameters(), "scene"); QString sceneName = obs_data_get_string(req->data, "scene");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
if (!scene) { if (!scene) {
return request.failed("requested scene doesn't exist"); req->SendErrorResponse("requested scene doesn't exist");
return;
} }
OBSDataArrayAutoRelease items = obs_data_get_array(request.parameters(), "items"); OBSDataArrayAutoRelease items = obs_data_get_array(req->data, "items");
if (!items) { if (!items) {
return request.failed("sceneItem order not specified"); req->SendErrorResponse("sceneItem order not specified");
return;
} }
struct reorder_context { size_t count = obs_data_array_count(items);
obs_data_array_t* items;
bool success;
QString errorMessage;
};
struct reorder_context ctx; std::vector<obs_sceneitem_t*> newOrder;
ctx.success = false; newOrder.reserve(count);
ctx.items = items;
obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) { for (size_t i = 0; i < count; ++i) {
auto ctx = reinterpret_cast<struct reorder_context*>(param); OBSDataAutoRelease item = obs_data_array_item(items, i);
QVector<struct obs_sceneitem_order_info> orderList; OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
struct obs_sceneitem_order_info info; obs_sceneitem_release(sceneItem); // ref dec
size_t itemCount = obs_data_array_count(ctx->items); if (!sceneItem) {
for (uint i = 0; i < itemCount; i++) { req->SendErrorResponse("Invalid sceneItem id or name specified");
OBSDataAutoRelease item = obs_data_array_item(ctx->items, i); return;
}
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
if (!sceneItem) { for (size_t j = 0; j <= i; ++j) {
ctx->success = false; if (sceneItem == newOrder[j]) {
ctx->errorMessage = "Invalid sceneItem id or name specified"; req->SendErrorResponse("Duplicate sceneItem in specified order");
return; return;
} }
info.group = nullptr;
info.item = sceneItem;
orderList.insert(0, info);
} }
ctx->success = obs_scene_reorder_items2(scene, orderList.data(), orderList.size()); newOrder.push_back(sceneItem);
if (!ctx->success) {
ctx->errorMessage = "Invalid sceneItem order";
}
}, &ctx);
if (!ctx.success) {
return request.failed(ctx.errorMessage);
} }
return request.success(); bool success = obs_scene_reorder_items(obs_scene_from_source(scene), newOrder.data(), count);
} if (!success) {
req->SendErrorResponse("Invalid sceneItem order");
/** return;
* Set a scene to use a specific transition override. }
*
* @param {String} `sceneName` Name of the scene to switch to. for (auto const& item: newOrder) {
* @param {String} `transitionName` Name of the transition to use. obs_sceneitem_release(item);
* @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given. }
*
* @api requests req->SendOKResponse();
* @name SetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName") || !request.hasField("transitionName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
QString transitionName = obs_data_get_string(request.parameters(), "transitionName");
if (!Utils::GetTransitionFromName(transitionName)) {
return request.failed("requested transition does not exist");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData());
if (request.hasField("transitionDuration")) {
int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration");
obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration);
} else if(!obs_data_has_user_value(sourceData, "transition_duration")) {
obs_data_set_int(sourceData, "transition_duration",
obs_frontend_get_transition_duration()
);
}
return request.success();
}
/**
* Remove any transition override on a scene.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @api requests
* @name RemoveSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
obs_data_erase(sourceData, "transition");
obs_data_erase(sourceData, "transition_duration");
return request.success();
}
/**
* Get the current scene transition override.
*
* @param {String} `sceneName` Name of the scene to switch to.
*
* @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set.
* @return {int} `transitionDuration` Transition duration. `-1` if no override is set.
*
* @api requests
* @name GetSceneTransitionOverride
* @category scenes
* @since 4.9.0
*/
RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) {
if (!request.hasField("sceneName")) {
return request.failed("missing request parameters");
}
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
if (!source) {
return request.failed("requested scene does not exist");
}
enum obs_source_type sourceType = obs_source_get_type(source);
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
return request.failed("requested scene is invalid");
}
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
const char* transitionOverrideName = obs_data_get_string(sourceData, "transition");
bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration");
int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration");
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "transitionName", transitionOverrideName);
obs_data_set_int(fields, "transitionDuration",
(hasDurationOverride ? transitionOverrideDuration : -1)
);
return request.success(fields);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#include "obs-websocket.h" #include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
@ -8,7 +8,7 @@
/** /**
* Get current streaming and recording status. * Get current streaming and recording status.
* *
* @return {boolean} `streaming` Current streaming status. * @return {boolean} `streaming` Current streaming status.
* @return {boolean} `recording` Current recording status. * @return {boolean} `recording` Current recording status.
* @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming). * @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).
@ -20,26 +20,26 @@
* @category streaming * @category streaming
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) { void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
auto events = GetEventsSystem();
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
obs_data_set_bool(data, "preview-only", false); obs_data_set_bool(data, "preview-only", false);
const char* tc = nullptr;
if (obs_frontend_streaming_active()) { if (obs_frontend_streaming_active()) {
QString streamingTimecode = events->getStreamingTimecode(); tc = WSEvents::Instance->GetStreamingTimecode();
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData()); obs_data_set_string(data, "stream-timecode", tc);
bfree((void*)tc);
} }
if (obs_frontend_recording_active()) { if (obs_frontend_recording_active()) {
QString recordingTimecode = events->getRecordingTimecode(); tc = WSEvents::Instance->GetRecordingTimecode();
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData()); obs_data_set_string(data, "rec-timecode", tc);
bfree((void*)tc);
} }
return request.success(data); req->SendOKResponse(data);
} }
/** /**
@ -50,11 +50,11 @@ RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
* @category streaming * @category streaming
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) { void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return StopStreaming(request); HandleStopStreaming(req);
else else
return StartStreaming(request); HandleStartStreaming(req);
} }
/** /**
@ -63,28 +63,28 @@ RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
* *
* @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration. * @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration.
* @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream. * @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream.
* @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field. * @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field.
* @param {Object (optional)} `stream.settings` Settings for the stream. * @param {Object (optional)} `stream.settings` Settings for the stream.
* @param {String (optional)} `stream.settings.server` The publish URL. * @param {String (optional)} `stream.settings.server` The publish URL.
* @param {String (optional)} `stream.settings.key` The publish key of the stream. * @param {String (optional)} `stream.settings.key` The publish key of the stream.
* @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. * @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. * @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`.
* *
* @api requests * @api requests
* @name StartStreaming * @name StartStreaming
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) { void WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active() == false) { if (obs_frontend_streaming_active() == false) {
OBSService configuredService = obs_frontend_get_streaming_service(); OBSService configuredService = obs_frontend_get_streaming_service();
OBSService newService = nullptr; OBSService newService = nullptr;
// TODO: fix service memory leak // TODO: fix service memory leak
if (request.hasField("stream")) { if (req->hasField("stream")) {
OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream"); OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings"); OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata"); OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
@ -103,10 +103,10 @@ RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
&& obs_data_has_user_value(newSettings, "key")) && obs_data_has_user_value(newSettings, "key"))
{ {
const char* key = obs_data_get_string(newSettings, "key"); const char* key = obs_data_get_string(newSettings, "key");
size_t keylen = strlen(key); int keylen = strlen(key);
bool hasQuestionMark = false; bool hasQuestionMark = false;
for (size_t i = 0; i < keylen; i++) { for (int i = 0; i < keylen; i++) {
if (key[i] == '?') { if (key[i] == '?') {
hasQuestionMark = true; hasQuestionMark = true;
break; break;
@ -157,9 +157,9 @@ RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
obs_frontend_set_streaming_service(configuredService); obs_frontend_set_streaming_service(configuredService);
} }
return request.success(); req->SendOKResponse();
} else { } else {
return request.failed("streaming already active"); req->SendErrorResponse("streaming already active");
} }
} }
@ -172,23 +172,23 @@ RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) { void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
if (obs_frontend_streaming_active() == true) { if (obs_frontend_streaming_active() == true) {
obs_frontend_streaming_stop(); obs_frontend_streaming_stop();
return request.success(); req->SendOKResponse();
} else { } else {
return request.failed("streaming not active"); req->SendErrorResponse("streaming not active");
} }
} }
/** /**
* Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings). * Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings).
* *
* @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`. * @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.
* @param {Object} `settings` The actual settings of the stream. * @param {Object} `settings` The actual settings of the stream.
* @param {String (optional)} `settings.server` The publish URL. * @param {String (optional)} `settings.server` The publish URL.
* @param {String (optional)} `settings.key` The publish key. * @param {String (optional)} `settings.key` The publish key.
* @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @param {String (optional)} `settings.username` The username for the streaming service. * @param {String (optional)} `settings.username` The username for the streaming service.
* @param {String (optional)} `settings.password` The password for the streaming service. * @param {String (optional)} `settings.password` The password for the streaming service.
* @param {boolean} `save` Persist the settings to disk. * @param {boolean} `save` Persist the settings to disk.
@ -198,22 +198,22 @@ RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) { void WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
OBSService service = obs_frontend_get_streaming_service(); OBSService service = obs_frontend_get_streaming_service();
OBSDataAutoRelease requestSettings = obs_data_get_obj(request.parameters(), "settings"); OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
if (!requestSettings) { if (!requestSettings) {
return request.failed("'settings' are required'"); req->SendErrorResponse("'settings' are required'");
return;
} }
QString serviceType = obs_service_get_type(service); QString serviceType = obs_service_get_type(service);
QString requestedType = obs_data_get_string(request.parameters(), "type"); QString requestedType = obs_data_get_string(req->data, "type");
if (requestedType != nullptr && requestedType != serviceType) { if (requestedType != nullptr && requestedType != serviceType) {
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service); OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
service = obs_service_create( service = obs_service_create(
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys); requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
obs_frontend_set_streaming_service(service);
} else { } else {
// If type isn't changing, we should overlay the settings we got // If type isn't changing, we should overlay the settings we got
// to the existing settings. By doing so, you can send a request that // to the existing settings. By doing so, you can send a request that
@ -232,19 +232,17 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
} }
//if save is specified we should immediately save the streaming service //if save is specified we should immediately save the streaming service
if (obs_data_get_bool(request.parameters(), "save")) { if (obs_data_get_bool(req->data, "save")) {
obs_frontend_save_streaming_service(); obs_frontend_save_streaming_service();
} }
OBSService responseService = obs_frontend_get_streaming_service(); OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService);
const char* responseType = obs_service_get_type(responseService);
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_string(response, "type", responseType); obs_data_set_string(response, "type", requestedType.toUtf8());
obs_data_set_obj(response, "settings", serviceSettings); obs_data_set_obj(response, "settings", serviceSettings);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -254,16 +252,16 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
* @return {Object} `settings` Stream settings object. * @return {Object} `settings` Stream settings object.
* @return {String} `settings.server` The publish URL. * @return {String} `settings.server` The publish URL.
* @return {String} `settings.key` The publish key of the stream. * @return {String} `settings.key` The publish key of the stream.
* @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`. * @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`. * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`.
* *
* @api requests * @api requests
* @name GetStreamSettings * @name GetStreamSettings
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) { void WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
OBSService service = obs_frontend_get_streaming_service(); OBSService service = obs_frontend_get_streaming_service();
const char* serviceType = obs_service_get_type(service); const char* serviceType = obs_service_get_type(service);
@ -273,7 +271,7 @@ RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
obs_data_set_string(response, "type", serviceType); obs_data_set_string(response, "type", serviceType);
obs_data_set_obj(response, "settings", settings); obs_data_set_obj(response, "settings", settings);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -284,37 +282,7 @@ RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
* @category streaming * @category streaming
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) { void WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
obs_frontend_save_streaming_service(); obs_frontend_save_streaming_service();
return request.success(); req->SendOKResponse();
} }
/**
* Send the provided text as embedded CEA-608 caption data.
* As of OBS Studio 23.1, captions are not yet available on Linux.
*
* @param {String} `text` Captions text
*
* @api requests
* @name SendCaptions
* @category streaming
* @since 4.6.0
*/
#if BUILD_CAPTIONS
RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) {
if (!request.hasField("text")) {
return request.failed("missing request parameters");
}
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
if (output) {
const char* caption = obs_data_get_string(request.parameters(), "text");
// Send caption text with immediately (0 second delay)
obs_output_output_caption_text2(output, caption, 0.0);
}
return request.success();
}
#endif

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -12,13 +13,13 @@
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) { void WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
bool previewActive = obs_frontend_preview_program_mode_active(); bool previewActive = obs_frontend_preview_program_mode_active();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_bool(response, "studio-mode", previewActive); obs_data_set_bool(response, "studio-mode", previewActive);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -26,16 +27,17 @@ RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) {
* Will return an `error` if Studio Mode is not enabled. * Will return an `error` if Studio Mode is not enabled.
* *
* @return {String} `name` The name of the active preview scene. * @return {String} `name` The name of the active preview scene.
* @return {Array<SceneItem>} `sources` * @return {Array<Source>} `sources`
* *
* @api requests * @api requests
* @name GetPreviewScene * @name GetPreviewScene
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) { void WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled"); req->SendErrorResponse("studio mode not enabled");
return;
} }
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
@ -45,7 +47,7 @@ RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
obs_data_set_string(data, "name", obs_source_get_name(scene)); obs_data_set_string(data, "name", obs_source_get_name(scene));
obs_data_set_array(data, "sources", sceneItems); obs_data_set_array(data, "sources", sceneItems);
return request.success(data); req->SendOKResponse(data);
} }
/** /**
@ -59,23 +61,26 @@ RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) { void WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled"); req->SendErrorResponse("studio mode not enabled");
return;
} }
if (!request.hasField("scene-name")) { if (!req->hasField("scene-name")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
const char* scene_name = obs_data_get_string(request.parameters(), "scene-name"); const char* scene_name = obs_data_get_string(req->data, "scene-name");
OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name); OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
if (!scene) {
return request.failed("specified scene doesn't exist");
}
obs_frontend_set_current_preview_scene(obs_scene_get_source(scene)); if (scene) {
return request.success(); obs_frontend_set_current_preview_scene(scene);
req->SendOKResponse();
} else {
req->SendErrorResponse("specified scene doesn't exist");
}
} }
/** /**
@ -91,37 +96,40 @@ RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) { void WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { if (!obs_frontend_preview_program_mode_active()) {
return request.failed("studio mode not enabled"); req->SendErrorResponse("studio mode not enabled");
return;
} }
if (request.hasField("with-transition")) { if (req->hasField("with-transition")) {
OBSDataAutoRelease transitionInfo = OBSDataAutoRelease transitionInfo =
obs_data_get_obj(request.parameters(), "with-transition"); obs_data_get_obj(req->data, "with-transition");
if (obs_data_has_user_value(transitionInfo, "name")) { if (obs_data_has_user_value(transitionInfo, "name")) {
QString transitionName = QString transitionName =
obs_data_get_string(transitionInfo, "name"); obs_data_get_string(transitionInfo, "name");
if (transitionName.isEmpty()) { if (transitionName.isEmpty()) {
return request.failed("invalid request parameters"); req->SendErrorResponse("invalid request parameters");
return;
} }
bool success = Utils::SetTransitionByName(transitionName); bool success = Utils::SetTransitionByName(transitionName);
if (!success) { if (!success) {
return request.failed("specified transition doesn't exist"); req->SendErrorResponse("specified transition doesn't exist");
return;
} }
} }
if (obs_data_has_user_value(transitionInfo, "duration")) { if (obs_data_has_user_value(transitionInfo, "duration")) {
int transitionDuration = int transitionDuration =
obs_data_get_int(transitionInfo, "duration"); obs_data_get_int(transitionInfo, "duration");
obs_frontend_set_transition_duration(transitionDuration); Utils::SetTransitionDuration(transitionDuration);
} }
} }
obs_frontend_preview_program_trigger_transition(); Utils::TransitionToProgram();
return request.success(); req->SendOKResponse();
} }
/** /**
@ -132,16 +140,9 @@ RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { void WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
if (obs_frontend_preview_program_mode_active()) { obs_frontend_set_preview_program_mode(true);
return request.failed("studio mode already active"); req->SendOKResponse();
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(true);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
} }
/** /**
@ -152,17 +153,9 @@ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { void WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
if (!obs_frontend_preview_program_mode_active()) { obs_frontend_set_preview_program_mode(false);
return request.failed("studio mode not active"); req->SendOKResponse();
}
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_preview_program_mode(false);
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
} }
/** /**
@ -173,13 +166,8 @@ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
* @category studio mode * @category studio mode
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) { void WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
obs_queue_task(OBS_TASK_UI, [](void* param) { bool previewProgramMode = obs_frontend_preview_program_mode_active();
bool previewProgramMode = obs_frontend_preview_program_mode_active(); obs_frontend_set_preview_program_mode(!previewProgramMode);
obs_frontend_set_preview_program_mode(!previewProgramMode); req->SendOKResponse();
UNUSED_PARAMETER(param);
}, nullptr, true);
return request.success();
} }

View File

@ -1,3 +1,4 @@
#include <QString>
#include "Utils.h" #include "Utils.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
@ -14,7 +15,7 @@
* @category transitions * @category transitions
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) { void WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {}; obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList); obs_frontend_get_transitions(&transitionList);
@ -34,7 +35,7 @@ RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
obs_source_get_name(currentTransition)); obs_source_get_name(currentTransition));
obs_data_set_array(response, "transitions", transitions); obs_data_set_array(response, "transitions", transitions);
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -48,7 +49,7 @@ RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
* @category transitions * @category transitions
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) { void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
@ -56,9 +57,9 @@ RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
obs_source_get_name(currentTransition)); obs_source_get_name(currentTransition));
if (!obs_transition_fixed(currentTransition)) if (!obs_transition_fixed(currentTransition))
obs_data_set_int(response, "duration", obs_frontend_get_transition_duration()); obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
return request.success(response); req->SendOKResponse(response);
} }
/** /**
@ -71,18 +72,18 @@ RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
* @category transitions * @category transitions
* @since 0.3 * @since 0.3
*/ */
RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) { void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
if (!request.hasField("transition-name")) { if (!req->hasField("transition-name")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
QString name = obs_data_get_string(request.parameters(), "transition-name"); QString name = obs_data_get_string(req->data, "transition-name");
bool success = Utils::SetTransitionByName(name); bool success = Utils::SetTransitionByName(name);
if (!success) { if (success)
return request.failed("requested transition does not exist"); req->SendOKResponse();
} else
req->SendErrorResponse("requested transition does not exist");
return request.success();
} }
/** /**
@ -95,14 +96,15 @@ RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) {
* @category transitions * @category transitions
* @since 4.0.0 * @since 4.0.0
*/ */
RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) { void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
if (!request.hasField("duration")) { if (!req->hasField("duration")) {
return request.failed("missing request parameters"); req->SendErrorResponse("missing request parameters");
return;
} }
int ms = obs_data_get_int(request.parameters(), "duration"); int ms = obs_data_get_int(req->data, "duration");
obs_frontend_set_transition_duration(ms); Utils::SetTransitionDuration(ms);
return request.success(); req->SendOKResponse();
} }
/** /**
@ -115,27 +117,10 @@ RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) {
* @category transitions * @category transitions
* @since 4.1.0 * @since 4.1.0
*/ */
RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) { void WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
OBSDataAutoRelease response = obs_data_create(); OBSDataAutoRelease response = obs_data_create();
obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration()); obs_data_set_int(response, "transition-duration",
return request.success(response); Utils::GetTransitionDuration());
}
req->SendOKResponse(response);
/**
* Get the position of the current transition.
*
* @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.
*
* @api requests
* @name GetTransitionPosition
* @category transitions
* @since 4.8.0
*/
RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) {
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
OBSDataAutoRelease response = obs_data_create();
obs_data_set_double(response, "position", obs_transition_get_time(currentTransition));
return request.success(response);
} }

View File

@ -16,232 +16,153 @@ 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 <chrono> #include <QtWebSockets/QWebSocket>
#include <thread>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QtWidgets/QMainWindow> #include <QMainWindow>
#include <QtWidgets/QMessageBox> #include <QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h>
#include "WSServer.h" #include "WSServer.h"
#include "obs-websocket.h" #include "obs-websocket.h"
#include "Config.h" #include "Config.h"
#include "Utils.h" #include "Utils.h"
#include "protocol/OBSRemoteProtocol.h"
QT_USE_NAMESPACE QT_USE_NAMESPACE
using websocketpp::lib::placeholders::_1; WSServer* WSServer::Instance = nullptr;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
WSServer::WSServer() WSServer::WSServer(QObject* parent)
: QObject(nullptr), : QObject(parent),
_connections(), _wsServer(Q_NULLPTR),
_clients(),
_clMutex(QMutex::Recursive) _clMutex(QMutex::Recursive)
{ {
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control); _wsServer = new QWebSocketServer(
_server.init_asio(); QStringLiteral("obs-websocket"),
#ifndef _WIN32 QWebSocketServer::NonSecureMode);
_server.set_reuse_addr(true);
#endif
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
_server.set_message_handler(bind(&WSServer::onMessage, this, ::_1, ::_2));
} }
WSServer::~WSServer() WSServer::~WSServer() {
{ Stop();
stop();
} }
void WSServer::start(quint16 port) void WSServer::Start(quint16 port) {
{ if (port == _wsServer->serverPort())
if (_server.is_listening() && port == _serverPort) {
blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed");
return; return;
if(_wsServer->isListening())
Stop();
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
if (serverStarted) {
blog(LOG_INFO, "server started successfully on TCP port %d", port);
connect(_wsServer, SIGNAL(newConnection()),
this, SLOT(onNewConnection()));
} }
else {
QString errorString = _wsServer->errorString();
blog(LOG_ERROR,
"error: failed to start server on TCP port %d: %s",
port, errorString.toUtf8().constData());
if (_server.is_listening()) { QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
stop();
}
_server.reset();
_serverPort = port;
websocketpp::lib::error_code errorCode;
_server.listen(_serverPort, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str());
obs_frontend_push_ui_translation(obs_module_get_string); obs_frontend_push_ui_translation(obs_module_get_string);
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title"); QString title = tr("OBSWebsocket.Server.StartFailed.Title");
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str()); QString msg = tr("OBSWebsocket.Server.StartFailed.Message").arg(port);
obs_frontend_pop_ui_translation(); obs_frontend_pop_ui_translation();
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window()); QMessageBox::warning(mainWindow, title, msg);
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
return;
} }
_server.start_accept();
QtConcurrent::run([=]() {
blog(LOG_INFO, "io thread started");
_server.run();
blog(LOG_INFO, "io thread exited");
});
blog(LOG_INFO, "server started successfully on port %d", _serverPort);
} }
void WSServer::stop() void WSServer::Stop() {
{ QMutexLocker locker(&_clMutex);
if (!_server.is_listening()) { for(QWebSocket* pClient : _clients) {
return; pClient->close();
} }
locker.unlock();
_server.stop_listening(); _wsServer->close();
for (connection_hdl hdl : _connections) {
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping");
}
_connections.clear();
_connectionProperties.clear();
_threadPool.waitForDone();
while (!_server.stopped()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
blog(LOG_INFO, "server stopped successfully"); blog(LOG_INFO, "server stopped successfully");
} }
void WSServer::broadcast(const RpcEvent& event) void WSServer::broadcast(QString message) {
{
OBSRemoteProtocol protocol;
std::string message = protocol.encodeEvent(event);
if (GetConfig()->DebugEnabled) {
blog(LOG_INFO, "Update << '%s'", message.c_str());
}
QMutexLocker locker(&_clMutex); QMutexLocker locker(&_clMutex);
for (connection_hdl hdl : _connections) { for(QWebSocket* pClient : _clients) {
if (GetConfig()->AuthRequired) { if (Config::Current()->AuthRequired
bool authenticated = _connectionProperties[hdl].isAuthenticated(); && (pClient->property(PROP_AUTHENTICATED).toBool() == false)) {
if (!authenticated) { // Skip this client if unauthenticated
continue; continue;
}
}
websocketpp::lib::error_code errorCode;
_server.send(hdl, message, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(broadcast): send failed: %s",
errorCodeMessage.c_str());
} }
pClient->sendTextMessage(message);
} }
} }
void WSServer::onOpen(connection_hdl hdl) void WSServer::onNewConnection() {
{ QWebSocket* pSocket = _wsServer->nextPendingConnection();
QMutexLocker locker(&_clMutex); if (pSocket) {
_connections.insert(hdl); connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
locker.unlock(); this, SLOT(onTextMessageReceived(QString)));
connect(pSocket, SIGNAL(disconnected()),
this, SLOT(onSocketDisconnected()));
QString clientIp = getRemoteEndpoint(hdl); pSocket->setProperty(PROP_AUTHENTICATED, false);
notifyConnection(clientIp);
blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData());
}
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
{
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
QtConcurrent::run(&_threadPool, [=]() {
std::string payload = message->get_payload();
QMutexLocker locker(&_clMutex); QMutexLocker locker(&_clMutex);
ConnectionProperties& connProperties = _connectionProperties[hdl]; _clients << pSocket;
locker.unlock(); locker.unlock();
if (GetConfig()->DebugEnabled) { QHostAddress clientAddr = pSocket->peerAddress();
blog(LOG_INFO, "Request >> '%s'", payload.c_str()); QString clientIp = Utils::FormatIPAddress(clientAddr);
}
WSRequestHandler requestHandler(connProperties); blog(LOG_INFO, "new client connection from %s:%d",
OBSRemoteProtocol protocol; clientIp.toUtf8().constData(), pSocket->peerPort());
std::string response = protocol.processMessage(requestHandler, payload);
if (GetConfig()->DebugEnabled) { obs_frontend_push_ui_translation(obs_module_get_string);
blog(LOG_INFO, "Response << '%s'", response.c_str()); QString title = tr("OBSWebsocket.NotifyConnect.Title");
} QString msg = tr("OBSWebsocket.NotifyConnect.Message")
.arg(Utils::FormatIPAddress(clientAddr));
obs_frontend_pop_ui_translation();
websocketpp::lib::error_code errorCode; Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
_server.send(hdl, response, websocketpp::frame::opcode::text, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server(response): send failed: %s",
errorCodeMessage.c_str());
}
});
}
void WSServer::onClose(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.erase(hdl);
_connectionProperties.erase(hdl);
locker.unlock();
auto conn = _server.get_con_from_hdl(hdl);
auto localCloseCode = conn->get_local_close_code();
if (localCloseCode != websocketpp::close::status::going_away) {
QString clientIp = getRemoteEndpoint(hdl);
notifyDisconnection(clientIp);
blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData());
} }
} }
QString WSServer::getRemoteEndpoint(connection_hdl hdl) void WSServer::onTextMessageReceived(QString message) {
{ QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
auto conn = _server.get_con_from_hdl(hdl); if (pSocket) {
return QString::fromStdString(conn->get_remote_endpoint()); WSRequestHandler handler(pSocket);
handler.processIncomingMessage(message);
}
} }
void WSServer::notifyConnection(QString clientIp) void WSServer::onSocketDisconnected() {
{ QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
obs_frontend_push_ui_translation(obs_module_get_string); if (pSocket) {
QString title = tr("OBSWebsocket.NotifyConnect.Title"); pSocket->setProperty(PROP_AUTHENTICATED, false);
QString msg = tr("OBSWebsocket.NotifyConnect.Message").arg(clientIp);
obs_frontend_pop_ui_translation();
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); QMutexLocker locker(&_clMutex);
} _clients.removeAll(pSocket);
locker.unlock();
void WSServer::notifyDisconnection(QString clientIp)
{ pSocket->deleteLater();
obs_frontend_push_ui_translation(obs_module_get_string);
QString title = tr("OBSWebsocket.NotifyDisconnect.Title"); QHostAddress clientAddr = pSocket->peerAddress();
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message").arg(clientIp); QString clientIp = Utils::FormatIPAddress(clientAddr);
obs_frontend_pop_ui_translation();
blog(LOG_INFO, "client %s:%d disconnected",
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title); clientIp.toUtf8().constData(), pSocket->peerPort());
obs_frontend_push_ui_translation(obs_module_get_string);
QString title = tr("OBSWebsocket.NotifyDisconnect.Title");
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message")
.arg(Utils::FormatIPAddress(clientAddr));
obs_frontend_pop_ui_translation();
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
}
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@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
@ -16,54 +16,37 @@ 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/>
*/ */
#pragma once #ifndef WSSERVER_H
#define WSSERVER_H
#include <map> #include <QObject>
#include <set> #include <QList>
#include <QtCore/QObject> #include <QMutex>
#include <QtCore/QMutex>
#include <QtCore/QSharedPointer>
#include <QtCore/QVariantHash>
#include <QtCore/QThreadPool>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include "ConnectionProperties.h"
#include "WSRequestHandler.h" #include "WSRequestHandler.h"
#include "rpc/RpcEvent.h"
using websocketpp::connection_hdl; QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
typedef websocketpp::server<websocketpp::config::asio> server; class WSServer : public QObject {
Q_OBJECT
public:
explicit WSServer(QObject* parent = Q_NULLPTR);
virtual ~WSServer();
void Start(quint16 port);
void Stop();
void broadcast(QString message);
static WSServer* Instance;
class WSServer : public QObject private slots:
{ void onNewConnection();
Q_OBJECT void onTextMessageReceived(QString message);
void onSocketDisconnected();
public: private:
explicit WSServer(); QWebSocketServer* _wsServer;
virtual ~WSServer(); QList<QWebSocket*> _clients;
void start(quint16 port); QMutex _clMutex;
void stop();
void broadcast(const RpcEvent& event);
QThreadPool* threadPool() {
return &_threadPool;
}
private:
void onOpen(connection_hdl hdl);
void onMessage(connection_hdl hdl, server::message_ptr message);
void onClose(connection_hdl hdl);
QString getRemoteEndpoint(connection_hdl hdl);
void notifyConnection(QString clientIp);
void notifyDisconnection(QString clientIp);
server _server;
quint16 _serverPort;
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
std::map<connection_hdl, ConnectionProperties, std::owner_less<connection_hdl>> _connectionProperties;
QMutex _clMutex;
QThreadPool _threadPool;
}; };
#endif // WSSERVER_H

View File

@ -41,7 +41,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
} }
void SettingsDialog::showEvent(QShowEvent* event) { void SettingsDialog::showEvent(QShowEvent* event) {
auto conf = GetConfig(); Config* conf = Config::Current();
ui->serverEnabled->setChecked(conf->ServerEnabled); ui->serverEnabled->setChecked(conf->ServerEnabled);
ui->serverPort->setValue(conf->ServerPort); ui->serverPort->setValue(conf->ServerPort);
@ -68,7 +68,7 @@ void SettingsDialog::AuthCheckboxChanged() {
} }
void SettingsDialog::FormAccepted() { void SettingsDialog::FormAccepted() {
auto conf = GetConfig(); Config* conf = Config::Current();
conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerEnabled = ui->serverEnabled->isChecked();
conf->ServerPort = ui->serverPort->value(); conf->ServerPort = ui->serverPort->value();
@ -81,7 +81,7 @@ void SettingsDialog::FormAccepted() {
conf->SetPassword(ui->password->text()); conf->SetPassword(ui->password->text());
} }
if (!GetConfig()->Secret.isEmpty()) if (!Config::Current()->Secret.isEmpty())
conf->AuthRequired = true; conf->AuthRequired = true;
else else
conf->AuthRequired = false; conf->AuthRequired = false;
@ -93,12 +93,10 @@ void SettingsDialog::FormAccepted() {
conf->Save(); conf->Save();
auto server = GetServer(); if (conf->ServerEnabled)
if (conf->ServerEnabled) { WSServer::Instance->Start(conf->ServerPort);
server->start(conf->ServerPort); else
} else { WSServer::Instance->Stop();
server->stop();
}
} }
SettingsDialog::~SettingsDialog() { SettingsDialog::~SettingsDialog() {

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@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
@ -16,9 +16,10 @@ 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/>
*/ */
#pragma once #ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include <QtWidgets/QDialog> #include <QDialog>
#include "ui_settings-dialog.h" #include "ui_settings-dialog.h"
@ -39,3 +40,5 @@ private Q_SLOTS:
private: private:
Ui::SettingsDialog* ui; Ui::SettingsDialog* ui;
}; };
#endif // SETTINGSDIALOG_H

View File

@ -18,11 +18,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h> #include <obs-module.h>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <obs-data.h> #include <QAction>
#include <QMainWindow>
#include <QtCore/QTimer> #include <QTimer>
#include <QtWidgets/QAction>
#include <QtWidgets/QMainWindow>
#include "obs-websocket.h" #include "obs-websocket.h"
#include "WSServer.h" #include "WSServer.h"
@ -36,17 +34,10 @@ void ___data_dummy_addref(obs_data_t*) {}
void ___data_array_dummy_addref(obs_data_array_t*) {} void ___data_array_dummy_addref(obs_data_array_t*) {}
void ___output_dummy_addref(obs_output_t*) {} void ___output_dummy_addref(obs_output_t*) {}
void ___data_item_dummy_addref(obs_data_item_t*) {}
void ___data_item_release(obs_data_item_t* dataItem) {
obs_data_item_release(&dataItem);
}
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
ConfigPtr _config; SettingsDialog* settings_dialog;
WSServerPtr _server;
WSEventsPtr _eventsSystem;
bool obs_module_load(void) { bool obs_module_load(void) {
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION); blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
@ -54,39 +45,28 @@ bool obs_module_load(void) {
QT_VERSION_STR, qVersion()); QT_VERSION_STR, qVersion());
// Core setup // Core setup
_config = ConfigPtr(new Config()); Config* config = Config::Current();
_config->MigrateFromGlobalSettings(); // TODO remove this on the next minor jump config->Load();
_config->Load();
_server = WSServerPtr(new WSServer()); WSServer::Instance = new WSServer();
_eventsSystem = WSEventsPtr(new WSEvents(_server)); WSEvents::Instance = new WSEvents(WSServer::Instance);
if (config->ServerEnabled)
WSServer::Instance->Start(config->ServerPort);
// UI setup // UI setup
QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
obs_frontend_push_ui_translation(obs_module_get_string); obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window(); QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
SettingsDialog* settingsDialog = new SettingsDialog(mainWindow); settings_dialog = new SettingsDialog(main_window);
obs_frontend_pop_ui_translation(); obs_frontend_pop_ui_translation();
const char* menuActionText = auto menu_cb = [] {
obs_module_text("OBSWebsocket.Settings.DialogTitle"); settings_dialog->ToggleShowHide();
QAction* menuAction =
(QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [settingsDialog] {
// The settings dialog belongs to the main window. Should be ok
// to pass the pointer to this QAction belonging to the main window
settingsDialog->ToggleShowHide();
});
// Setup event handler to start the server once OBS is ready
auto eventCallback = [](enum obs_frontend_event event, void *param) {
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
if (_config->ServerEnabled) {
_server->start(_config->ServerPort);
}
obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr);
}
}; };
obs_frontend_add_event_callback(eventCallback, (void*)(obs_frontend_event_cb)eventCallback); menu_action->connect(menu_action, &QAction::triggered, menu_cb);
// Loading finished // Loading finished
blog(LOG_INFO, "module loaded!"); blog(LOG_INFO, "module loaded!");
@ -95,23 +75,6 @@ bool obs_module_load(void) {
} }
void obs_module_unload() { void obs_module_unload() {
_server->stop();
_eventsSystem.reset();
_server.reset();
_config.reset();
blog(LOG_INFO, "goodbye!"); blog(LOG_INFO, "goodbye!");
} }
ConfigPtr GetConfig() {
return _config;
}
WSServerPtr GetServer() {
return _server;
}
WSEventsPtr GetEventsSystem() {
return _eventsSystem;
}

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@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
@ -16,10 +16,10 @@ 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/>
*/ */
#pragma once #ifndef OBSWEBSOCKET_H
#define OBSWEBSOCKET_H
#include <obs.hpp> #include <obs.hpp>
#include <memory>
void ___source_dummy_addref(obs_source_t*); void ___source_dummy_addref(obs_source_t*);
void ___sceneitem_dummy_addref(obs_sceneitem_t*); void ___sceneitem_dummy_addref(obs_sceneitem_t*);
@ -38,24 +38,9 @@ using OBSDataArrayAutoRelease =
using OBSOutputAutoRelease = using OBSOutputAutoRelease =
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>; OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
void ___data_item_dummy_addref(obs_data_item_t*); #define PROP_AUTHENTICATED "wsclient_authenticated"
void ___data_item_release(obs_data_item_t*); #define OBS_WEBSOCKET_VERSION "4.5.1"
using OBSDataItemAutoRelease =
OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
class Config;
typedef std::shared_ptr<Config> ConfigPtr;
class WSServer;
typedef std::shared_ptr<WSServer> WSServerPtr;
class WSEvents;
typedef std::shared_ptr<WSEvents> WSEventsPtr;
ConfigPtr GetConfig();
WSServerPtr GetServer();
WSEventsPtr GetEventsSystem();
#define OBS_WEBSOCKET_VERSION "4.8.0"
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#endif // OBSWEBSOCKET_H

View File

@ -1,119 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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 <inttypes.h>
#include "OBSRemoteProtocol.h"
#include "../WSRequestHandler.h"
#include "../rpc/RpcEvent.h"
#include "../Utils.h"
std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, std::string message)
{
std::string msgContainer(message);
const char* msg = msgContainer.c_str();
OBSDataAutoRelease data = obs_data_create_from_json(msg);
if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return errorResponse(QString::Null(), "invalid JSON payload");
}
if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) {
return errorResponse(QString::Null(), "missing request parameters");
}
QString methodName = obs_data_get_string(data, "request-type");
QString messageId = obs_data_get_string(data, "message-id");
OBSDataAutoRelease params = obs_data_create();
obs_data_apply(params, data);
obs_data_unset_user_value(params, "request-type");
obs_data_unset_user_value(params, "message-id");
RpcRequest request(messageId, methodName, params);
RpcResponse response = requestHandler.processRequest(request);
OBSData additionalFields = response.additionalFields();
switch (response.status()) {
case RpcResponse::Status::Ok:
return successResponse(messageId, additionalFields);
case RpcResponse::Status::Error:
return errorResponse(messageId, response.errorMessage(), additionalFields);
}
return std::string();
}
std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
{
OBSDataAutoRelease eventData = obs_data_create();
QString updateType = event.updateType();
obs_data_set_string(eventData, "update-type", updateType.toUtf8().constData());
std::optional<uint64_t> streamTime = event.streamTime();
if (streamTime.has_value()) {
QString streamingTimecode = Utils::nsToTimestamp(streamTime.value());
obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData());
}
std::optional<uint64_t> recordingTime = event.recordingTime();
if (recordingTime.has_value()) {
QString recordingTimecode = Utils::nsToTimestamp(recordingTime.value());
obs_data_set_string(eventData, "rec-timecode", recordingTimecode.toUtf8().constData());
}
OBSData additionalFields = event.additionalFields();
if (additionalFields) {
obs_data_apply(eventData, additionalFields);
}
return std::string(obs_data_get_json(eventData));
}
std::string OBSRemoteProtocol::buildResponse(QString messageId, QString status, obs_data_t* fields)
{
OBSDataAutoRelease response = obs_data_create();
if (!messageId.isNull()) {
obs_data_set_string(response, "message-id", messageId.toUtf8().constData());
}
obs_data_set_string(response, "status", status.toUtf8().constData());
if (fields) {
obs_data_apply(response, fields);
}
std::string responseString = obs_data_get_json(response);
return responseString;
}
std::string OBSRemoteProtocol::successResponse(QString messageId, obs_data_t* fields)
{
return buildResponse(messageId, "ok", fields);
}
std::string OBSRemoteProtocol::errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields)
{
OBSDataAutoRelease fields = obs_data_create();
if (additionalFields) {
obs_data_apply(fields, additionalFields);
}
obs_data_set_string(fields, "error", errorMessage.toUtf8().constData());
return buildResponse(messageId, "error", fields);
}

View File

@ -1,38 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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/>
*/
#pragma once
#include <string>
#include <obs-data.h>
#include <QtCore/QString>
class WSRequestHandler;
class RpcEvent;
class OBSRemoteProtocol
{
public:
std::string processMessage(WSRequestHandler& requestHandler, std::string message);
std::string encodeEvent(const RpcEvent& event);
private:
std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr);
std::string successResponse(QString messageId, obs_data_t* fields = nullptr);
std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr);
};

View File

@ -1,35 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2020 Stéphane Lepin <stephane.lepin@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 "RpcEvent.h"
RpcEvent::RpcEvent(
const QString& updateType,
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
obs_data_t* additionalFields
) :
_updateType(updateType),
_streamTime(streamTime),
_recordingTime(recordingTime),
_additionalFields(nullptr)
{
if (additionalFields) {
_additionalFields = obs_data_create();
obs_data_apply(_additionalFields, additionalFields);
}
}

View File

@ -1,61 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2020 Stéphane Lepin <stephane.lepin@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/>
*/
#pragma once
#include <optional>
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
class RpcEvent
{
public:
explicit RpcEvent(
const QString& updateType,
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
obs_data_t* additionalFields = nullptr
);
const QString& updateType() const
{
return _updateType;
}
const std::optional<uint64_t> streamTime() const
{
return _streamTime;
}
const std::optional<uint64_t> recordingTime() const
{
return _recordingTime;
}
const OBSData additionalFields() const
{
return OBSData(_additionalFields);
}
private:
QString _updateType;
std::optional<uint64_t> _streamTime;
std::optional<uint64_t> _recordingTime;
OBSDataAutoRelease _additionalFields;
};

View File

@ -1,104 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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 "RpcRequest.h"
#include "RpcResponse.h"
RpcRequest::RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params) :
_messageId(messageId),
_methodName(methodName),
_parameters(nullptr)
{
if (params) {
_parameters = obs_data_create();
obs_data_apply(_parameters, params);
}
}
const RpcResponse RpcRequest::success(obs_data_t* additionalFields) const
{
return RpcResponse::ok(*this, additionalFields);
}
const RpcResponse RpcRequest::failed(const QString& errorMessage, obs_data_t* additionalFields) const
{
return RpcResponse::fail(*this, errorMessage, additionalFields);
}
const bool RpcRequest::hasField(QString name, obs_data_type expectedFieldType, obs_data_number_type expectedNumberType) const
{
if (!_parameters || name.isEmpty() || name.isNull()) {
return false;
}
OBSDataItemAutoRelease dataItem = obs_data_item_byname(_parameters, name.toUtf8());
if (!dataItem) {
return false;
}
if (expectedFieldType != OBS_DATA_NULL) {
obs_data_type fieldType = obs_data_item_gettype(dataItem);
if (fieldType != expectedFieldType) {
return false;
}
if (fieldType == OBS_DATA_NUMBER && expectedNumberType != OBS_DATA_NUM_INVALID) {
obs_data_number_type numberType = obs_data_item_numtype(dataItem);
if (numberType != expectedNumberType) {
return false;
}
}
}
return true;
}
const bool RpcRequest::hasBool(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_BOOLEAN);
}
const bool RpcRequest::hasString(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_STRING);
}
const bool RpcRequest::hasNumber(QString fieldName, obs_data_number_type expectedNumberType) const
{
return this->hasField(fieldName, OBS_DATA_NUMBER, expectedNumberType);
}
const bool RpcRequest::hasInteger(QString fieldName) const
{
return this->hasNumber(fieldName, OBS_DATA_NUM_INT);
}
const bool RpcRequest::hasDouble(QString fieldName) const
{
return this->hasNumber(fieldName, OBS_DATA_NUM_DOUBLE);
}
const bool RpcRequest::hasArray(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_ARRAY);
}
const bool RpcRequest::hasObject(QString fieldName) const
{
return this->hasField(fieldName, OBS_DATA_OBJECT);
}

View File

@ -1,65 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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/>
*/
#pragma once
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
// forward declarations
class RpcResponse;
class RpcRequest
{
public:
explicit RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params);
const QString& messageId() const
{
return _messageId;
}
const QString& methodName() const
{
return _methodName;
}
const OBSData parameters() const
{
return OBSData(_parameters);
}
const RpcResponse success(obs_data_t* additionalFields = nullptr) const;
const RpcResponse failed(const QString& errorMessage, obs_data_t* additionalFields = nullptr) const;
const bool hasField(QString fieldName, obs_data_type expectedFieldType = OBS_DATA_NULL,
obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
const bool hasBool(QString fieldName) const;
const bool hasString(QString fieldName) const;
const bool hasNumber(QString fieldName, obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
const bool hasInteger(QString fieldName) const;
const bool hasDouble(QString fieldName) const;
const bool hasArray(QString fieldName) const;
const bool hasObject(QString fieldName) const;
private:
const QString _messageId;
const QString _methodName;
OBSDataAutoRelease _parameters;
};

View File

@ -1,48 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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 "RpcResponse.h"
#include "RpcRequest.h"
RpcResponse::RpcResponse(
Status status, const QString& messageId,
const QString& methodName, obs_data_t* additionalFields
) :
_status(status),
_messageId(messageId),
_methodName(methodName),
_additionalFields(nullptr)
{
if (additionalFields) {
_additionalFields = obs_data_create();
obs_data_apply(_additionalFields, additionalFields);
}
}
const RpcResponse RpcResponse::ok(const RpcRequest& request, obs_data_t* additionalFields)
{
RpcResponse response(Status::Ok, request.messageId(), request.methodName(), additionalFields);
return response;
}
const RpcResponse RpcResponse::fail(const RpcRequest& request, const QString& errorMessage, obs_data_t* additionalFields)
{
RpcResponse response(Status::Error, request.messageId(), request.methodName(), additionalFields);
response._errorMessage = errorMessage;
return response;
}

View File

@ -1,70 +0,0 @@
/*
obs-websocket
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@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/>
*/
#pragma once
#include <obs-data.h>
#include <QtCore/QString>
#include "../obs-websocket.h"
class RpcRequest;
class RpcResponse
{
public:
enum Status { Unknown, Ok, Error };
static RpcResponse ofRequest(const RpcRequest& request);
static const RpcResponse ok(const RpcRequest& request, obs_data_t* additionalFields = nullptr);
static const RpcResponse fail(
const RpcRequest& request, const QString& errorMessage,
obs_data_t* additionalFields = nullptr
);
Status status() {
return _status;
}
const QString& messageId() const {
return _messageId;
}
const QString& methodName() const {
return _methodName;
}
const QString& errorMessage() const {
return _errorMessage;
}
const OBSData additionalFields() const {
return OBSData(_additionalFields);
}
private:
explicit RpcResponse(
Status status,
const QString& messageId, const QString& methodName,
obs_data_t* additionalFields = nullptr
);
const Status _status;
const QString _messageId;
const QString _methodName;
QString _errorMessage;
OBSDataAutoRelease _additionalFields;
};