mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
272 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc0b110d61 | |||
82a1e7d253 | |||
87b47689eb | |||
c3346f9192 | |||
3b0cf02574 | |||
4404889019 | |||
4e642d97fc | |||
44bd25646d | |||
09b04037a0 | |||
3c50e1e4d8 | |||
179ba9a9e6 | |||
9ce2b1983a | |||
37dde278cb | |||
1cc57e7d1d | |||
60a5cdb154 | |||
f290cbc148 | |||
2e2e9b1332 | |||
7915e3815b | |||
beb34deed8 | |||
a263d8a364 | |||
1eccf4c899 | |||
d8d19d839a | |||
17e573d67f | |||
59de83b45d | |||
712bd6e8f3 | |||
75b21d070a | |||
e91c9e7bf4 | |||
2562272775 | |||
819621e307 | |||
4b9229311c | |||
4c4c1de190 | |||
6b6b7feff2 | |||
9e3ce26ba0 | |||
eac19a7c83 | |||
ee1486274d | |||
8692e2afda | |||
f995268444 | |||
bd2e974718 | |||
d1c64c7509 | |||
30a19cfe8a | |||
cc3097b09a | |||
c00681b52d | |||
61931c179f | |||
c7190cb94a | |||
a1bd27dfde | |||
6ac3a3de57 | |||
e198ed7a9c | |||
4b89464349 | |||
dba599c127 | |||
586f9076f0 | |||
add39cfc5f | |||
e3ad148c15 | |||
acffacd67d | |||
54a16f4d2f | |||
35cb506d6e | |||
7675a1ee58 | |||
863f5e28b3 | |||
1a6f6096e4 | |||
b57982b6cb | |||
4132356141 | |||
e560e95310 | |||
65f4ff6a30 | |||
ab38f33530 | |||
1f04ee8252 | |||
739bd6f696 | |||
066145ab31 | |||
54d0f764d4 | |||
1398689ebf | |||
647625628d | |||
e17df69b80 | |||
f001d18eea | |||
f49980350a | |||
d0a90ecea4 | |||
4506b46ba0 | |||
a8c36d7366 | |||
781eaec683 | |||
bdee8f318a | |||
82dac4d208 | |||
386e1f3b46 | |||
a3cbbf3ea9 | |||
e647debcfb | |||
fba9bd2b76 | |||
56217e0176 | |||
77c5801c4c | |||
7db879cca9 | |||
8d0cb2e875 | |||
dc7b386295 | |||
03c8c6385c | |||
841de2f752 | |||
9de6e229d9 | |||
8dfe471ef2 | |||
da05f315be | |||
d014a7ab25 | |||
d0118c63c0 | |||
9ddfad99ea | |||
82d74fcb2f | |||
0f4f029a76 | |||
2c581e9998 | |||
f2028c506a | |||
200db77140 | |||
007604cc21 | |||
89486e9172 | |||
a60ca96fd1 | |||
0ade2c869d | |||
9986382850 | |||
89b9165c25 | |||
5d290165a2 | |||
d267171cc7 | |||
cebe325e81 | |||
4e6178881b | |||
6bca8194cb | |||
a4885f332d | |||
cf97fb2051 | |||
20a8853854 | |||
dd487a5055 | |||
72ca07f571 | |||
c7305889c3 | |||
bc24497760 | |||
200e65c730 | |||
b32d8ef1e7 | |||
8cfc613a3d | |||
5fbc2cbac7 | |||
9d7a32aa1f | |||
bcc9ef82d1 | |||
d6b28191e8 | |||
acd8a9ea69 | |||
d4b8a8ff9e | |||
a698f7bdf5 | |||
16f07ff0c3 | |||
ed4526751e | |||
e241518f8d | |||
a6677edbf5 | |||
5748e163f8 | |||
85fa6b60e2 | |||
f0bb941c47 | |||
b7df1e8596 | |||
ff8eda3682 | |||
8c4bd91c78 | |||
07c868edcd | |||
69061869a7 | |||
98b2ac9bdc | |||
ef2bbff4e5 | |||
7bb8e56072 | |||
df0926f6fd | |||
75572279a9 | |||
328c6a0f7c | |||
ecd5062975 | |||
c01ae5610d | |||
564e7f31c3 | |||
efa61952b3 | |||
ae27a26ebe | |||
d81076f720 | |||
528f16c5e1 | |||
13ac8bfa90 | |||
935c58b17b | |||
31e133bf06 | |||
eb7fb6694c | |||
a298577da1 | |||
8cd6d43ec4 | |||
b8fd143cc1 | |||
045ae058a3 | |||
52c9816db2 | |||
32440580f6 | |||
906d986f4b | |||
0198651ca0 | |||
d911c40897 | |||
ca8a117335 | |||
f75e2f0ada | |||
ea12b62235 | |||
0af4af7e50 | |||
f017491f44 | |||
ed433d3312 | |||
9ba2e83857 | |||
af0ec74704 | |||
251402e844 | |||
1a5318ebe0 | |||
15aac3f436 | |||
5f373632e6 | |||
0d518bf8df | |||
16b735c3a4 | |||
1239f094fd | |||
6986fa51eb | |||
f4bc88bf73 | |||
ad915536a3 | |||
f48b31664e | |||
d6c0eb1998 | |||
d3dcfba463 | |||
3619dcd777 | |||
86c002e318 | |||
42a807ce9c | |||
9bfff9539e | |||
edf4d919fe | |||
b0fb3d8c61 | |||
5143090142 | |||
bc1555ca5b | |||
146a392f1a | |||
e2fc205a35 | |||
d4bdb216d2 | |||
bffc1d6a1f | |||
df0d890c11 | |||
dceee4dede | |||
dedbbf12bb | |||
bdb53d07f5 | |||
547991df72 | |||
fd17557a96 | |||
a9e928a799 | |||
84b3e086e6 | |||
5e3c794aec | |||
98acbb2b2b | |||
318f0753b0 | |||
8908adbdee | |||
fad7a26550 | |||
38005fc0ef | |||
e3d3181ad6 | |||
83d069e4d9 | |||
f2db5a8229 | |||
6ebb777256 | |||
db50c531d9 | |||
218f7af2e3 | |||
a19d2956c1 | |||
7a6b5b965d | |||
8a871aaddd | |||
bec0a0df10 | |||
1068a2dfbe | |||
b62be5d584 | |||
9686019693 | |||
32a066b4d4 | |||
d5a415e01e | |||
33f83b8486 | |||
adedf8d34f | |||
9243071f03 | |||
36050850d4 | |||
c6d5cc555a | |||
b271aa2aaa | |||
d24961b3b9 | |||
e52543efe7 | |||
0b0e27ad3b | |||
9f460f6c99 | |||
c4529bb9a3 | |||
5a069d2ffc | |||
4fc78e455a | |||
75e2198315 | |||
0d4bb4ed2d | |||
577738ad0a | |||
a8dfdb03fb | |||
aebd470d49 | |||
ce3dfd9678 | |||
b82801b145 | |||
a6ab35f1fb | |||
a265cea4fb | |||
166760651e | |||
41a9191223 | |||
1465e7760e | |||
1a043f1dc0 | |||
70c5b00c90 | |||
bb5177dc79 | |||
f60a9a632b | |||
024c47132b | |||
73ce9cb4cc | |||
f45d439094 | |||
2d5749a78c | |||
f078a10028 | |||
701f88e532 | |||
1524a1997d | |||
b16f812f91 | |||
cde307d644 | |||
c47f6d093c | |||
f5336938c9 | |||
4985548edf | |||
8bc4841e84 | |||
634f9833a1 | |||
80052e62ee |
22
.github/CONTRIBUTING.md
vendored
Normal file
22
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
## Contributing to obs-websocket
|
||||
|
||||
### Translating obs-websocket to your language
|
||||
Localization happens on Crowdin: https://crowdin.com/project/obs-websocket
|
||||
|
||||
### Writing code for obs-websocket
|
||||
#### Coding Guidelines
|
||||
- Function and variable names: snake_case for C names, CamelCase for C++ names
|
||||
- Tabs are 8 columns wide
|
||||
- 80 columns max.
|
||||
|
||||
#### Commit Guidelines
|
||||
- Commits follow the 50/72 standard:
|
||||
- 50 characters max for the title
|
||||
- One empty line after the title
|
||||
- Description wrapped to 72 columns max per line.
|
||||
- Commit titles:
|
||||
- Use present tense
|
||||
- Prefix the title with a "scope" name
|
||||
- e.g: "CI: fix wrong behaviour when packaging for OS X"
|
||||
- Typical scopes: CI, General, Request, Event, Server
|
||||
- Look at existing commits for more examples
|
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
##### Issue type
|
||||
Bug report? Feature request? Other?
|
||||
|
||||
##### Description
|
||||
*Replace this with a description of the bug encountered or feature requested.*
|
||||
|
||||
##### Steps to reproduce and other useful info
|
||||
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.*
|
||||
|
||||
##### Technical information
|
||||
- **Operating System** :
|
||||
- **OBS Studio version** :
|
||||
|
||||
##### Development Environment
|
||||
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
|
||||
Remove this section if it doesn't apply to your case.*
|
BIN
.github/images/mediaunit_logo_black.png
vendored
Normal file
BIN
.github/images/mediaunit_logo_black.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
.github/images/supportclass_logo_blacktext.png
vendored
Normal file
BIN
.github/images/supportclass_logo_blacktext.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,9 @@
|
||||
*~
|
||||
.DS_Store
|
||||
/build/
|
||||
/build32/
|
||||
/build64/
|
||||
/release/
|
||||
/installer/Output/
|
||||
|
||||
.vscode
|
70
.travis.yml
70
.travis.yml
@ -1,11 +1,69 @@
|
||||
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
|
||||
dist: trusty
|
||||
sudo: required
|
||||
before_install: "./CI/install-dependencies-linux.sh"
|
||||
before_script: "./CI/before-script-linux.sh"
|
||||
- os: linux
|
||||
env: _generate_docs
|
||||
script: "./CI/generate-docs.sh"
|
||||
|
||||
script: cd ./build && make -j4 && cd -
|
||||
- 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
|
||||
|
||||
- os: osx
|
||||
env: _macos_build
|
||||
osx_image: xcode8.3
|
||||
before_install: "./CI/install-dependencies-osx.sh"
|
||||
script: "./CI/build-osx.sh"
|
||||
after_success:
|
||||
- ./CI/package-osx.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
|
||||
- provider: s3
|
||||
region: eu-central-1
|
||||
bucket: obs-websocket-osx-builds
|
||||
access_key_id: "$AWS_ID"
|
||||
secret_access_key: "$AWS_SECRET"
|
||||
local_dir: release
|
||||
skip_cleanup: true
|
||||
acl: public_read
|
||||
on:
|
||||
repo: Palakis/obs-websocket
|
||||
condition: "$TRAVIS_OS_NAME = osx"
|
||||
all_branches: true
|
||||
|
29
BUILDING.md
Normal file
29
BUILDING.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Compiling obs-websocket
|
||||
## Prerequisites
|
||||
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
|
||||
## Windows
|
||||
In cmake-gui, you'll have to set the following variables :
|
||||
- **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_LIB** (filepath) : location of the obs.lib file
|
||||
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
|
||||
|
||||
## Linux
|
||||
On Debian/Ubuntu :
|
||||
```
|
||||
sudo apt-get install libqt5websockets5-dev
|
||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||
cd obs-websocket
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## OS X
|
||||
*To do*
|
||||
|
||||
## Automated Builds
|
||||
- Windows : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||
- Linux & OS X : [](https://travis-ci.org/Palakis/obs-websocket)
|
12
CI/build-osx.sh
Executable file
12
CI/build-osx.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DQTDIR=/usr/local/opt/qt \
|
||||
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
&& make -j4
|
@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
cd /root/obs-websocket
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
32
CI/generate-docs.sh
Executable file
32
CI/generate-docs.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
echo "-- Generating documentation."
|
||||
echo "-- Node version: $(node -v)"
|
||||
echo "-- NPM version: $(npm -v)"
|
||||
|
||||
cd docs
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
echo "-- Documentation successfully generated."
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "-- No documentation changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; 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)"
|
||||
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||
|
||||
git add ./generated
|
||||
git pull
|
||||
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
28
CI/install-dependencies-osx.sh
Executable file
28
CI/install-dependencies-osx.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
brew update
|
||||
brew install ffmpeg
|
||||
brew install libav
|
||||
|
||||
# qtwebsockets deps
|
||||
brew install qt5
|
||||
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
git clone --recursive https://github.com/jp9000/obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.3
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||
&& make -j4
|
||||
|
||||
sudo make install
|
||||
|
||||
# Packages app
|
||||
cd ..
|
||||
curl -L -O https://www.slepin.fr/obs-websocket/ci/Packages.pkg -f --retry 5 -C -
|
||||
sudo installer -pkg ./Packages.pkg -target /
|
@ -2,18 +2,22 @@
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next -y
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y \
|
||||
apt-get -qq update
|
||||
apt-get install -y \
|
||||
libc-dev-bin libc6-dev \
|
||||
git \
|
||||
build-essential
|
||||
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
libasound2-dev \
|
||||
libavcodec-ffmpeg-dev \
|
||||
libavdevice-ffmpeg-dev \
|
||||
libavfilter-ffmpeg-dev \
|
||||
libavformat-ffmpeg-dev \
|
||||
libavutil-ffmpeg-dev \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfontconfig-dev \
|
||||
libfreetype6-dev \
|
||||
@ -23,8 +27,8 @@ sudo apt-get install -y \
|
||||
libpulse-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libspeexdsp-dev \
|
||||
libswresample-ffmpeg-dev \
|
||||
libswscale-ffmpeg-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev \
|
||||
libv4l-dev \
|
||||
libvlc-dev \
|
||||
@ -37,25 +41,17 @@ sudo apt-get install -y \
|
||||
pkg-config \
|
||||
qtbase5-dev
|
||||
|
||||
# qtwebsockets deps
|
||||
sudo apt-get install -y qt5-qmake
|
||||
|
||||
# obs-websocket deps
|
||||
cd ..
|
||||
git clone https://github.com/qt/qtwebsockets/ ./qtwebsockets
|
||||
cd qtwebsockets
|
||||
git checkout v5.3.0
|
||||
qmake
|
||||
make -j4
|
||||
sudo make install
|
||||
apt-get install -y libqt5websockets5-dev
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
cd /root
|
||||
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.3
|
||||
mkdir build && cd build
|
||||
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
sudo make install
|
||||
make install
|
||||
|
||||
sudo ldconfig
|
||||
ldconfig
|
843
CI/osx/obs-websocket.pkgproj
Normal file
843
CI/osx/obs-websocket.pkgproj
Normal file
@ -0,0 +1,843 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PROJECT</key>
|
||||
<dict>
|
||||
<key>PACKAGE_FILES</key>
|
||||
<dict>
|
||||
<key>DEFAULT_INSTALL_LOCATION</key>
|
||||
<string>/</string>
|
||||
<key>HIERARCHY</key>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../../build/obs-websocket.so</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</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>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../../data</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</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>obs-websocket</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</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>plugins</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</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-studio</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</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>Application Support</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Automator</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Documentation</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Extensions</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Filesystems</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Frameworks</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Input Methods</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Internet Plug-Ins</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchAgents</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchDaemons</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PreferencePanes</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Preferences</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Printers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PrivilegedHelperTools</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickLook</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickTime</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Screen Savers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Scripts</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Services</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Widgets</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Library</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Shared</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>1023</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Users</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>/</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>PAYLOAD_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>VERSION</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>PACKAGE_SCRIPTS</key>
|
||||
<dict>
|
||||
<key>RESOURCES</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>PACKAGE_SETTINGS</key>
|
||||
<dict>
|
||||
<key>AUTHENTICATION</key>
|
||||
<integer>1</integer>
|
||||
<key>CONCLUSION_ACTION</key>
|
||||
<integer>0</integer>
|
||||
<key>IDENTIFIER</key>
|
||||
<string>fr.palakis.obswebsocket</string>
|
||||
<key>OVERWRITE_PERMISSIONS</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>4.0.0</string>
|
||||
</dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
||||
<key>NOTES</key>
|
||||
<data>
|
||||
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
|
||||
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
|
||||
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
|
||||
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
|
||||
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
|
||||
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
|
||||
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
|
||||
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
|
||||
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg
|
||||
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
|
||||
Pgo8L2JvZHk+CjwvaHRtbD4K
|
||||
</data>
|
||||
</dict>
|
||||
<key>PROJECT_SETTINGS</key>
|
||||
<dict>
|
||||
<key>BUILD_PATH</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>../../release</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>EXCLUDED_FILES</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.DS_Store</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .DS_Store files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".DS_Store" files created by the Finder.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.pbdevelopment</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .pbdevelopment files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>CVS</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvsignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvspass</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.svn</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.git</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.gitignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove SCM metadata</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>classes.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>designable.db</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>info.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Optimize nib files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>Resources Disabled</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove Resources Disabled folders</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "Resources Disabled" folders.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SEPARATOR</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NAME</key>
|
||||
<string>obs-websocket</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>VERSION</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</plist>
|
66
CI/package-osx.sh
Executable file
66
CI/package-osx.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "-- Preparing package build"
|
||||
export QT_CELLAR_PREFIX="$(find /usr/local/Cellar/qt -d 1 | tail -n 1)"
|
||||
|
||||
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
|
||||
export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork"
|
||||
|
||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||
|
||||
export VERSION="$GIT_HASH-$TRAVIS_BRANCH"
|
||||
export LATEST_VERSION="$TRAVIS_BRANCH"
|
||||
if [ -n "${TRAVIS_TAG}" ]; then
|
||||
export VERSION="$TRAVIS_TAG"
|
||||
export LATEST_VERSION="$TRAVIS_TAG"
|
||||
fi
|
||||
|
||||
export FILENAME="obs-websocket-$VERSION.pkg"
|
||||
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
|
||||
|
||||
echo "-- Copying Qt dependencies"
|
||||
cp $WS_LIB ./build
|
||||
cp $NET_LIB ./build
|
||||
|
||||
chmod +rw ./build/QtWebSockets ./build/QtNetwork
|
||||
|
||||
echo "-- Modifying QtNetwork"
|
||||
install_name_tool \
|
||||
-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 "-- Modifying QtWebSockets"
|
||||
install_name_tool \
|
||||
-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 "-- Modifying obs-websocket.so"
|
||||
install_name_tool \
|
||||
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./build/obs-websocket.so
|
||||
|
||||
# Check if replacement worked
|
||||
echo "-- Dependencies for QtNetwork"
|
||||
otool -L ./build/QtNetwork
|
||||
echo "-- Dependencies for QtWebSockets"
|
||||
otool -L ./build/QtWebSockets
|
||||
echo "-- Dependencies for obs-websocket"
|
||||
otool -L ./build/obs-websocket.so
|
||||
|
||||
chmod -w ./build/QtWebSockets ./build/QtNetwork
|
||||
|
||||
echo "-- Actual package build"
|
||||
packagesbuild ./CI/osx/obs-websocket.pkgproj
|
||||
|
||||
echo "-- Renaming obs-websocket.pkg to $FILENAME"
|
||||
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
||||
cp ./release/$FILENAME ./release/$LATEST_FILENAME
|
24
CI/package-xenial.sh
Executable file
24
CI/package-xenial.sh
Executable 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/*
|
@ -7,6 +7,7 @@ set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
include(external/FindLibObs.cmake)
|
||||
|
||||
find_package(LibObs REQUIRED)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5WebSockets REQUIRED)
|
||||
@ -54,6 +55,7 @@ target_link_libraries(obs-websocket
|
||||
Qt5::WebSockets
|
||||
Qt5::Widgets
|
||||
mbedcrypto)
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
# --- Windows-specific build settings and tasks ---
|
||||
@ -63,6 +65,18 @@ if(WIN32)
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(ARCH_NAME "64bit")
|
||||
set(OBS_BUILDDIR_ARCH "build64")
|
||||
else()
|
||||
set(ARCH_NAME "32bit")
|
||||
set(OBS_BUILDDIR_ARCH "build32")
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI"
|
||||
)
|
||||
|
||||
target_link_libraries(obs-websocket
|
||||
"${OBS_FRONTEND_LIB}")
|
||||
|
||||
@ -82,12 +96,6 @@ if(WIN32)
|
||||
# The "release" folder has a structure similar OBS' one on Windows
|
||||
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(ARCH_NAME "64bit")
|
||||
else()
|
||||
set(ARCH_NAME "32bit")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
COMMAND if $<CONFIG:Release>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
@ -103,6 +111,23 @@ if(WIN32)
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
# Copy to obs-studio dev environment for immediate testing
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${QTDIR}/bin/Qt5WebSocketsd.dll"
|
||||
"${QTDIR}/bin/Qt5Networkd.dll"
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 (
|
||||
"${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/data"
|
||||
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||
)
|
||||
# --- End of sub-section ---
|
||||
|
||||
@ -118,11 +143,20 @@ if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(obs-websocket
|
||||
obs-frontend-api)
|
||||
|
||||
file(GLOB locale_files data/locale/*.ini)
|
||||
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
|
||||
install(FILES data/locale/en-US.ini data/locale/fr-FR.ini data/locale/de-DE.ini
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
|
||||
install(FILES ${locale_files}
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
||||
endif()
|
||||
# --- End of section ---
|
||||
|
||||
# TODO : OS X build settings and tasks
|
||||
# -- OS X specific build settings and tasks --
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
|
||||
|
||||
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
|
||||
endif()
|
||||
# -- End of section --
|
||||
|
230
Config.cpp
230
Config.cpp
@ -20,163 +20,165 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
#include "Config.h"
|
||||
#include <string>
|
||||
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_DEBUG "DebugEnabled"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
Config *Config::_instance = new Config();
|
||||
#include "Config.h"
|
||||
|
||||
Config::Config()
|
||||
{
|
||||
// Default settings
|
||||
ServerEnabled = true;
|
||||
ServerPort = 4444;
|
||||
Config* Config::_instance = new Config();
|
||||
|
||||
AuthRequired = false;
|
||||
Secret = "";
|
||||
Salt = "";
|
||||
SettingsLoaded = false;
|
||||
Config::Config() :
|
||||
ServerEnabled(true),
|
||||
ServerPort(4444),
|
||||
DebugEnabled(false),
|
||||
AuthRequired(false),
|
||||
Secret(""),
|
||||
Salt(""),
|
||||
SettingsLoaded(false) {
|
||||
// OBS Config defaults
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
if (obs_config) {
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obs_config,
|
||||
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
// OBS Config defaults
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
if (obs_config)
|
||||
{
|
||||
config_set_default_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_set_default_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_default_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
}
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SALT, Salt);
|
||||
}
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
|
||||
SessionChallenge = GenerateSalt();
|
||||
SessionChallenge = GenerateSalt();
|
||||
}
|
||||
|
||||
Config::~Config()
|
||||
{
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
Config::~Config() {
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
void Config::Load() {
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
|
||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
|
||||
|
||||
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||
DebugEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_DEBUG);
|
||||
|
||||
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
void Config::Save() {
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_save(obs_config);
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
|
||||
config_save(obs_config);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt()
|
||||
{
|
||||
// Generate 32 random chars
|
||||
unsigned char *random_chars = (unsigned char *)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
||||
const char* Config::GenerateSalt() {
|
||||
// Generate 32 random chars
|
||||
unsigned char* random_chars = (unsigned char*)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
unsigned char *salt = (unsigned char*)bzalloc(64);
|
||||
size_t salt_bytes;
|
||||
mbedtls_base64_encode(salt, 64, &salt_bytes, random_chars, 32);
|
||||
salt[salt_bytes] = 0; // Null-terminate the string
|
||||
// Convert the 32 random chars to a base64 string
|
||||
char* salt = (char*)bzalloc(64);
|
||||
size_t salt_bytes;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)salt, 64, &salt_bytes,
|
||||
random_chars, 32);
|
||||
|
||||
bfree(random_chars);
|
||||
return (char *)salt;
|
||||
bfree(random_chars);
|
||||
return salt;
|
||||
}
|
||||
|
||||
const char* Config::GenerateSecret(const char *password, const char *salt)
|
||||
{
|
||||
size_t passwordLength = strlen(password);
|
||||
size_t saltLength = strlen(salt);
|
||||
const char* Config::GenerateSecret(const char* password, const char* salt) {
|
||||
// Concatenate the password and the salt
|
||||
std::string passAndSalt = "";
|
||||
passAndSalt += password;
|
||||
passAndSalt += salt;
|
||||
|
||||
// Concatenate the password and the salt
|
||||
unsigned char *passAndSalt = (unsigned char*)bzalloc(passwordLength + saltLength);
|
||||
memcpy(passAndSalt, password, passwordLength);
|
||||
memcpy(passAndSalt + passwordLength, salt, saltLength);
|
||||
passAndSalt[passwordLength + saltLength] = 0; // Null-terminate the string
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)passAndSalt.c_str(), passAndSalt.length(),
|
||||
challengeHash, 0);
|
||||
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char *challengeHash = (unsigned char *)bzalloc(32);
|
||||
mbedtls_sha256(passAndSalt, passwordLength + saltLength, challengeHash, 0);
|
||||
// Encode SHA256 hash to Base64
|
||||
char* challenge = (char*)bzalloc(64);
|
||||
size_t challenge_bytes = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)challenge, 64, &challenge_bytes,
|
||||
challengeHash, 32);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
unsigned char *challenge = (unsigned char*)bzalloc(64);
|
||||
size_t challenge_bytes = 0;
|
||||
mbedtls_base64_encode(challenge, 64, &challenge_bytes, challengeHash, 32);
|
||||
challenge[64] = 0; // Null-terminate the string
|
||||
|
||||
bfree(passAndSalt);
|
||||
bfree(challengeHash);
|
||||
return (char*)challenge;
|
||||
bfree(challengeHash);
|
||||
return challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(const char *password)
|
||||
{
|
||||
const char *new_salt = GenerateSalt();
|
||||
const char *new_challenge = GenerateSecret(password, new_salt);
|
||||
void Config::SetPassword(const char* password) {
|
||||
const char* new_salt = GenerateSalt();
|
||||
const char* new_challenge = GenerateSecret(password, new_salt);
|
||||
|
||||
this->Salt = new_salt;
|
||||
this->Secret = new_challenge;
|
||||
this->Salt = new_salt;
|
||||
this->Secret = new_challenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(const char *response)
|
||||
{
|
||||
size_t secretLength = strlen(this->Secret);
|
||||
size_t sessChallengeLength = strlen(this->SessionChallenge);
|
||||
bool Config::CheckAuth(const char* response) {
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
std::string challengeAndResponse = "";
|
||||
challengeAndResponse += this->Secret;
|
||||
challengeAndResponse += this->SessionChallenge;
|
||||
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
char *challengeAndResponse = (char*)bzalloc(secretLength + sessChallengeLength);
|
||||
memcpy(challengeAndResponse, this->Secret, secretLength);
|
||||
memcpy(challengeAndResponse + secretLength, this->SessionChallenge, sessChallengeLength);
|
||||
challengeAndResponse[secretLength + sessChallengeLength] = 0; // Null-terminate the string
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char* hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)challengeAndResponse.c_str(),
|
||||
challengeAndResponse.length(),
|
||||
hash, 0);
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char *hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256((unsigned char*)challengeAndResponse, secretLength + sessChallengeLength, hash, 0);
|
||||
// Encode the SHA256 hash to Base64
|
||||
char* expected_response = (char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)expected_response, 64, &base64_size,
|
||||
hash, 32);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
unsigned char *expected_response = (unsigned char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(expected_response, 64, &base64_size, hash, 32);
|
||||
expected_response[64] = 0; // Null-terminate the string
|
||||
bool authSuccess = false;
|
||||
if (strcmp(expected_response, response) == 0) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
authSuccess = true;
|
||||
}
|
||||
|
||||
if (strcmp((char*)expected_response, response) == 0) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
bfree(hash);
|
||||
bfree(expected_response);
|
||||
return authSuccess;
|
||||
}
|
||||
|
||||
Config* Config::Current()
|
||||
{
|
||||
return _instance;
|
||||
Config* Config::Current() {
|
||||
return _instance;
|
||||
}
|
||||
|
48
Config.h
48
Config.h
@ -22,34 +22,36 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
void SetPassword(const char *password);
|
||||
bool CheckAuth(const char *userChallenge);
|
||||
const char* GenerateSalt();
|
||||
static const char* GenerateSecret(const char *password, const char *salt);
|
||||
void SetPassword(const char* password);
|
||||
bool CheckAuth(const char* userChallenge);
|
||||
const char* GenerateSalt();
|
||||
static const char* GenerateSecret(
|
||||
const char* password, const char* salt);
|
||||
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
|
||||
bool AuthRequired;
|
||||
const char *Secret;
|
||||
const char *Salt;
|
||||
const char *SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
bool DebugEnabled;
|
||||
|
||||
static Config* Current();
|
||||
bool AuthRequired;
|
||||
const char* Secret;
|
||||
const char* Salt;
|
||||
const char* SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
|
||||
private:
|
||||
static Config *_instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
static Config* Current();
|
||||
|
||||
private:
|
||||
static Config* _instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
};
|
||||
|
||||
#endif // CONFIG_H
|
576
PROTOCOL.md
576
PROTOCOL.md
@ -1,576 +0,0 @@
|
||||
obs-websocket protocol reference
|
||||
================================
|
||||
|
||||
## General Introduction
|
||||
Messages exchanged between the client and the server are JSON objects.
|
||||
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||
|
||||
### Table of contents
|
||||
* [Events](#events)
|
||||
- [Description](#description)
|
||||
- [Event Types](#event-types)
|
||||
- ["SwitchScenes"](#switchscenes)
|
||||
- ["ScenesChanged"](#sceneschanged)
|
||||
- ["SourceOrderChanged"](#sourceorderchanged)
|
||||
- ["SceneItemAdded"](#sceneitemadded)
|
||||
- ["SceneItemRemoved"](#sceneitemremoved)
|
||||
- ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged)
|
||||
- ["SceneCollectionChanged"](#scenecollectionchanged)
|
||||
- ["SceneCollectionListChanged"](#scenecollectionlistchanged)
|
||||
- ["SwitchTransition"](#switchtransition)
|
||||
- ["TransitionDurationChanged"](#transitiondurationchanged)
|
||||
- ["TransitionListChanged"](#transitionlistchanged)
|
||||
- ["TransitionBegin"](#transitionbegin)
|
||||
- ["ProfileChanged"](#profilechanged)
|
||||
- ["ProfileListChanged"](#profilelistchanged)
|
||||
- ["StreamStarting"](#streamstarting)
|
||||
- ["StreamStarted"](#streamstarted)
|
||||
- ["StreamStopping"](#streamstopping)
|
||||
- ["StreamStopped"](#streamstopped)
|
||||
- ["RecordingStarting"](#recordingstarting)
|
||||
- ["RecordingStarted"](#recordingstarted)
|
||||
- ["RecordingStopping"](#recordingstopping)
|
||||
- ["RecordingStopped"](#recordingstopped)
|
||||
- ["StreamStatus"](#streamstatus)
|
||||
- ["Exiting"](#exiting)
|
||||
* [Requests](#requests)
|
||||
- [Description](#description-1)
|
||||
- [Request Types](#request-types)
|
||||
- ["GetVersion"](#getversion)
|
||||
- ["GetAuthRequired"](#getauthrequired)
|
||||
- ["Authenticate"](#authenticate)
|
||||
- ["GetCurrentScene"](#getcurrentscene)
|
||||
- ["SetCurrentScene"](#setcurrentscene)
|
||||
- ["GetSceneList"](#getscenelist)
|
||||
- ["SetSourceRender"](#setsourcerender)
|
||||
- ["StartStopStreaming"](#startstopstreaming)
|
||||
- ["StartStopRecording"](#startstoprecording)
|
||||
- ["GetStreamingStatus"](#getstreamingstatus)
|
||||
- ["GetTransitionList"](#gettransitionlist)
|
||||
- ["GetCurrentTransition"](#getcurrenttransition)
|
||||
- ["SetCurrentTransition"](#setcurrenttransition)
|
||||
- ["SetTransitionDuration"](#settransitionduration)
|
||||
- ["SetVolume"](#setvolume)
|
||||
- ["GetVolume"](#getvolume)
|
||||
- ["SetMute"](#setmute)
|
||||
- ["ToggleMute"](#togglemute)
|
||||
- ["SetSceneItemPosition"](#setsceneitemposition)
|
||||
- ["SetSceneItemTransform"](#setsceneitemtransform)
|
||||
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
|
||||
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
|
||||
- ["ListSceneCollections"](#listscenecollections)
|
||||
- ["SetCurrentProfile"](#setcurrentprofile)
|
||||
- ["GetCurrentProfile"](#getcurrentprofile)
|
||||
- ["ListProfiles"](#listprofiles)
|
||||
* [Authentication](#authentication)
|
||||
|
||||
## Events
|
||||
### Description
|
||||
Events are sent exclusively by the server and broadcast to each connected client.
|
||||
An event message will contain at least one field :
|
||||
- **update-type** (string) : the type of event
|
||||
- **stream-timecode** (string, optional) : time elapsed between now and stream start (only present if OBS Studio is streaming)
|
||||
- **rec-timecode** (string, optional) : time elapsed between now and recording start (only present if OBS Studio is recording)
|
||||
|
||||
Timecodes are in the following format : HH:MM:SS.mmm
|
||||
|
||||
Additional fields will be present in the event message depending on the event type.
|
||||
|
||||
### Event Types
|
||||
#### "SwitchScenes"
|
||||
OBS is switching to another scene (called at the end of the transition).
|
||||
- **scene-name** (string) : The name of the scene being switched to.
|
||||
|
||||
---
|
||||
|
||||
#### "ScenesChanged"
|
||||
The scene list has been modified (Scenes have been added, removed, or renamed).
|
||||
|
||||
---
|
||||
|
||||
#### "SourceOrderChanged"
|
||||
Scene items have been reordered.
|
||||
- **"scene-name"** (string) : name of the scene where items have been reordered
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemAdded"
|
||||
An item has been added to the current scene.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item added to **scene-name**
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemRemoved"
|
||||
An item has been removed from the current scene.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item removed from **scene-name**
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemVisibilityChanged"
|
||||
An item's visibility has been toggled.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item in **scene-name**
|
||||
- **"item-visible"** (bool) : new visibility of item **item-name**
|
||||
|
||||
---
|
||||
|
||||
#### "SceneCollectionChanged"
|
||||
Triggered when switching to another scene collection or when renaming the current scene collection.
|
||||
|
||||
---
|
||||
|
||||
#### "SceneCollectionListChanged"
|
||||
Triggered when a scene collection is created, added, renamed or removed.
|
||||
|
||||
---
|
||||
|
||||
#### "SwitchTransition"
|
||||
The active transition has been changed.
|
||||
- **transition-name** (string) : The name of the active transition.
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionDurationChanged"
|
||||
Triggered when the transition duration has changed.
|
||||
- **"new-duration"** (integer) : new transition duration
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionListChanged"
|
||||
The list of available transitions has been modified (Transitions have been added, removed, or renamed).
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionBegin"
|
||||
A transition other than "Cut" has begun.
|
||||
|
||||
---
|
||||
|
||||
#### "ProfileChanged"
|
||||
Triggered when switching to another profile or when renaming the current profile.
|
||||
|
||||
---
|
||||
|
||||
#### "ProfileListChanged"
|
||||
Triggered when a profile is created, added, renamed or removed.
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStarting"
|
||||
A request to start streaming has been issued.
|
||||
- **preview-only** (bool) : Always false.
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStarted"
|
||||
Streaming started successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStopping"
|
||||
A request to stop streaming has been issued.
|
||||
- **preview-only** (bool) : Always false.
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStopped"
|
||||
Streaming stopped successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStarting"
|
||||
A request to start recording has been issued.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStarted"
|
||||
Recording started successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStopping"
|
||||
A request to stop streaming has been issued.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStopped"
|
||||
Recording stopped successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStatus"
|
||||
Sent every 2 seconds with the following information :
|
||||
- **streaming** (bool) : Current Streaming state.
|
||||
- **recording** (bool) : Current Recording state.
|
||||
- **preview-only** (bool) : Always false.
|
||||
- **bytes-per-sec** (integer) : Amount of data per second (in bytes) transmitted by the stream encoder.
|
||||
- **kbits-per-sec** (integer) : "bytes-per-sec" converted to kilobits per second
|
||||
- **strain** (double) : Percentage of dropped frames
|
||||
- **total-stream-time** (integer) : Total time (in seconds) since the stream started.
|
||||
- **num-total-frames** (integer) : Total number of frames transmitted since the stream started.
|
||||
- **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
|
||||
- **fps** (double) : Current framerate.
|
||||
|
||||
---
|
||||
|
||||
#### "Exiting"
|
||||
OBS is exiting.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
## Requests
|
||||
|
||||
### Description
|
||||
Requests are sent by the client and must have at least the following two fields :
|
||||
- **"request-type"** (string) : One of the request types listed in the sub-section "[Requests Types](#request-types)".
|
||||
- **"message-id"** (string) : An identifier defined by the client which will be embedded in the server response.
|
||||
|
||||
Depending on the request type additional fields may be required (see the "[Request Types](#request-types)" section below for more information).
|
||||
|
||||
Once a request is sent, the server will return a JSON response with the following fields :
|
||||
- **"message-id"** (string) : The identifier specified in the request.
|
||||
- **"status"** (string) : Response status, will be one of the following : "ok", "error"
|
||||
- **"error"** (string) : The error message associated with an "error" status.
|
||||
|
||||
Depending on the request type additional fields may be present (see the "[Request Types](#request-types)" section below for more information).
|
||||
|
||||
### Request Types
|
||||
#### "GetVersion"
|
||||
Returns the latest version of the plugin and the API.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility.
|
||||
- **"obs-websocket-version"** (string) : obs-websocket version string
|
||||
- **"obs-studio-version"** (string) : OBS Studio version string
|
||||
|
||||
---
|
||||
|
||||
#### "GetAuthRequired"
|
||||
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication").
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"authRequired"** (bool)
|
||||
- **"challenge"** (string)
|
||||
- **"salt"** (string)
|
||||
|
||||
---
|
||||
|
||||
#### "Authenticate"
|
||||
Try to authenticate the client on the server.
|
||||
|
||||
__Request fields__ :
|
||||
- **"auth"** (string) : response to the auth challenge (see "Authentication").
|
||||
|
||||
__Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentScene"
|
||||
Get the current scene's name and items.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"name"** (string) : name of the current scene
|
||||
- **"sources"** (array of objects) : ordered list of the current scene's items descriptions
|
||||
|
||||
Objects in the "sources" array have the following fields :
|
||||
- **"name"** (string) : name of the source associated with the scene item
|
||||
- **"type"** (string) : internal source type name
|
||||
- **"volume"** (double) : audio volume of the source, ranging from 0.0 to 1.0
|
||||
- **"x"** (double) : X coordinate of the top-left corner of the item in the scene
|
||||
- **"y"** (double) : Y coordinate of the top-left corner of the item in the scene
|
||||
- **"source_cx"** (integer) : width of the item (without scale applied)
|
||||
- **"source_cy"** (integer) : height of the item (without scale applied)
|
||||
- **"cx"** (double) : width of the item (with scale applied)
|
||||
- **"cy"** (double) : height of the item (with scale applied)
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentScene"
|
||||
Switch to the scene specified in "scene-name".
|
||||
|
||||
__Request fields__ :
|
||||
- **"scene-name"** (string) : name of the scene to switch to.
|
||||
|
||||
__Response__ : always OK if scene exists, error if it doesn't. No additional fields
|
||||
|
||||
---
|
||||
|
||||
#### "GetSceneList"
|
||||
List OBS' scenes.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"current-scene"** (string) : name of the currently active scene
|
||||
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
|
||||
|
||||
---
|
||||
|
||||
#### "SetSourceRender"
|
||||
Show or hide a specific source in the current scene.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : name of the source in the currently active scene.
|
||||
- **"render"** (bool) : desired visibility
|
||||
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if source exists in the current scene, error otherwise.
|
||||
|
||||
---
|
||||
|
||||
#### "StartStopStreaming"
|
||||
Toggle streaming on or off.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK. No additional fields.
|
||||
|
||||
---
|
||||
|
||||
#### "StartStopRecording"
|
||||
Toggle recording on or off.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK. No additional fields.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetStreamingStatus"
|
||||
Get current streaming and recording status.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"streaming"** (bool) : streaming status (active or not)
|
||||
- **"recording"** (bool) : recording status (active or not)
|
||||
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
|
||||
|
||||
---
|
||||
|
||||
#### "GetTransitionList"
|
||||
List all transitions available in the frontend's dropdown menu.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"current-transition"** (string) : name of the current transition
|
||||
- **"transitions"** (array of objects) : list of transition descriptions
|
||||
|
||||
Objects in the "transitions" array have only one field :
|
||||
- **"name"** (string) : name of the transition
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentTransition"
|
||||
Get the name of the currently selected transition in the frontend's dropdown menu.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"name"** (string) : name of the selected transition
|
||||
- **"duration"** (integer, only if transition supports this) : transition duration
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentTransition"
|
||||
__Request fields__ :
|
||||
- **"transition-name"** (string) : The name of the transition.
|
||||
|
||||
__Response__ : OK if specified transition exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetTransitionDuration"
|
||||
Set the duration of the currently selected transition.
|
||||
|
||||
__Request fields__ :
|
||||
- **"duration"** (integer) : desired transition duration in milliseconds
|
||||
|
||||
__Response__ : always OK.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetVolume"
|
||||
Set the volume of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
- **"volume"** (double) : the desired volume
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetVolume"
|
||||
Get the volume of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : name of the source
|
||||
|
||||
__Response__ : OK if source exists, with these additional fields :
|
||||
- **"name"** (string) : name of the requested source
|
||||
- **"volume"** (double) : volume of the requested source, on a linear scale (0.0 to 1.0)
|
||||
- **"muted"** (bool) : mute status of the requested source
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetMute"
|
||||
Mutes or unmutes a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
- **"mute"** (bool) : the desired mute status
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "ToggleMute"
|
||||
Inverts the mute status of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetSceneItemPosition"
|
||||
__Request fields__ :
|
||||
- **"item"** (string) : The name of the scene item.
|
||||
- **"x"** (float) : x coordinate
|
||||
- **"y"** (float) : y coordinate
|
||||
- **"scene-name"** (string) : scene the item belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if specified item exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetSceneItemTransform"
|
||||
__Request fields__ :
|
||||
- **"item"** (string) : The name of the scene item.
|
||||
- **"x-scale"** (float) : width scale factor
|
||||
- **"y-scale"** (float) : height scale factor
|
||||
- **"rotation"** (float) : item rotation (in degrees)
|
||||
- **"scene-name"** (string) : scene the item belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if specified item exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentSceneCollection"
|
||||
Change the current scene collection.
|
||||
|
||||
__Request fields__ :
|
||||
- **"sc-name"** (string) : name of the desired scene collection
|
||||
|
||||
__Response__ : OK if scene collection exists, error otherwise.
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentSceneCollection"
|
||||
Get the name of the current scene collection.
|
||||
|
||||
__Request fields__ : none
|
||||
|
||||
__Response__ : OK with these additional fields :
|
||||
- **"sc-name"** (string) : name of the current scene collection
|
||||
|
||||
---
|
||||
|
||||
#### "ListSceneCollections"
|
||||
Get a list of available scene collections.
|
||||
|
||||
__Request fields__ : none
|
||||
|
||||
__Response__ : OK with these additional fields :
|
||||
- **"scene-collections"** (array of objects) : names of available scene collections
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentProfile"
|
||||
Change the current profile.
|
||||
|
||||
__Request fields__ :
|
||||
- **"profile-name"** (string) : name of the desired profile
|
||||
|
||||
__Response__ : OK if profile exists, error otherwise.
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentProfile"
|
||||
Get the name of the current profile.
|
||||
|
||||
__Request fields__ : none
|
||||
|
||||
__Response__ : OK with these additional fields :
|
||||
- **"profile-name"** (string) : name of the current profile
|
||||
|
||||
---
|
||||
|
||||
#### "ListProfiles"
|
||||
Get a list of available profiles.
|
||||
|
||||
__Request fields__ : none
|
||||
|
||||
__Response__ : OK with the additional fields :
|
||||
- **"profiles"** (array of objects) : names of available profiles
|
||||
|
||||
---
|
||||
|
||||
### Authentication
|
||||
A call to `GetAuthRequired` gives the client two elements :
|
||||
- A challenge : a random string that will be used to generate the auth response
|
||||
- A salt : applied to the password when generating the auth response
|
||||
|
||||
The client knows a password and must it to authenticate itself to the server.
|
||||
However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket.
|
||||
|
||||
After a call to `GetAuthRequired`, the client knows a password (kept secret), a challenge and a salt (sent by the server).
|
||||
To generate the answer to the auth challenge, follow this procedure :
|
||||
- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64.
|
||||
- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64.
|
||||
- Voilà, this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request.
|
||||
|
||||
Here's how it looks in pseudocode :
|
||||
```
|
||||
password = "supersecretpassword"
|
||||
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
|
||||
salt = "PZVbYpvAnZut2SS6JNJytDm9"
|
||||
|
||||
secret_string = password + salt
|
||||
secret_hash = binary_sha256(secret_string)
|
||||
secret = base64_encode(secret_hash)
|
||||
|
||||
auth_response_string = secret + challenge
|
||||
auth_response_hash = binary_sha256(auth_response_string)
|
||||
auth_response = base64_encode(auth_response_hash)
|
||||
```
|
66
README.md
66
README.md
@ -2,6 +2,10 @@ obs-websocket
|
||||
==============
|
||||
Remote control of OBS Studio made easy.
|
||||
|
||||
Follow the project on Twitter for news & updates : [@obswebsocket](https://twitter.com/obswebsocket)
|
||||
|
||||
[](https://gitter.im/obs-websocket/obs-websocket) [](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
||||
|
||||
## Downloads
|
||||
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||
|
||||
@ -17,40 +21,50 @@ It is **highly recommended** to protect obs-websocket with a password against un
|
||||
|
||||
### 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 protocol understood by the server is documented in [PROTOCOL.md](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 :
|
||||
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
|
||||
- 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)
|
||||
- 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
|
||||
|
||||
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
|
||||
### Prerequisites
|
||||
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
See the [build instructions](BUILDING.md).
|
||||
|
||||
### Windows
|
||||
In cmake-gui, you'll have to set the following variables :
|
||||
- **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_LIB** (filepath) : location of the obs.lib file
|
||||
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
|
||||
## Translations
|
||||
**We need your help on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
|
||||
|
||||
### Linux
|
||||
On Debian/Ubuntu :
|
||||
```
|
||||
sudo apt-get install libqt5websockets5-dev
|
||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||
cd obs-websocket
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
sudo make install
|
||||
```
|
||||
## Special thanks
|
||||
In 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
|
||||
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
|
||||
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
|
||||
- [Larissa Gabilan](https://github.com/laris151) : Portuguese translation
|
||||
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
|
||||
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
|
||||
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions
|
||||
- [yinzara](https://github.com/yinzara) : Code contributions
|
||||
- [Chris Angelico](https://github.com/Rosuav) : Code contributions
|
||||
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions
|
||||
- [Marwin M](https://github.com/dragonbane0) : Code contributions
|
||||
|
||||
### OS X
|
||||
*To do*
|
||||
And also: special thanks to supporters of the project!
|
||||
|
||||
### Automated Builds
|
||||
- Windows : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||
- Linux & OS X : coming soon
|
||||
## Supporters
|
||||
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
|
||||
|
||||
---
|
||||
|
||||
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
||||
|
||||
[](http://supportclass.net)
|
||||
|
||||
---
|
||||
|
||||
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
||||
|
||||
[](http://www.mediaunit.no/)
|
||||
|
602
Utils.cpp
602
Utils.cpp
@ -16,238 +16,516 @@ 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 "Utils.h"
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QMainWindow>
|
||||
#include <QSpinBox>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include "obs-websocket.h"
|
||||
|
||||
obs_data_array_t* string_list_to_array(char** strings, char* key)
|
||||
{
|
||||
if (!strings)
|
||||
return obs_data_array_create();
|
||||
#include "Utils.h"
|
||||
|
||||
obs_data_array_t *list = obs_data_array_create();
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
|
||||
char* value = "";
|
||||
for (int i = 0; value != nullptr; i++)
|
||||
{
|
||||
value = strings[i];
|
||||
obs_data_array_t* string_list_to_array(char** strings, char* key) {
|
||||
if (!strings)
|
||||
return obs_data_array_create();
|
||||
|
||||
obs_data_t *item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
obs_data_array_t* list = obs_data_array_create();
|
||||
|
||||
if (value)
|
||||
obs_data_array_push_back(list, item);
|
||||
char* value = "";
|
||||
for (int i = 0; value != nullptr; i++) {
|
||||
value = strings[i];
|
||||
|
||||
obs_data_release(item);
|
||||
}
|
||||
obs_data_t* item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
|
||||
return list;
|
||||
if (value)
|
||||
obs_data_array_push_back(list, item);
|
||||
|
||||
obs_data_release(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
|
||||
obs_data_array_t *items = obs_data_array_create();
|
||||
obs_scene_t *scene = obs_scene_from_source(source);
|
||||
if (scene == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
||||
obs_data_array_t* items = obs_data_array_create();
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
|
||||
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
|
||||
obs_data_array_t *data = static_cast<obs_data_array_t *>(param);
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
obs_data_t *item_data = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, item_data);
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
obs_data_array_t* data = static_cast<obs_data_array_t*>(param);
|
||||
|
||||
obs_data_release(item_data);
|
||||
return true;
|
||||
}, items);
|
||||
obs_data_t* item_data = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, item_data);
|
||||
|
||||
return items;
|
||||
obs_data_release(item_data);
|
||||
return true;
|
||||
}, items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
obs_source_t* item_source = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(item_source));
|
||||
float item_height = float(obs_source_get_height(item_source));
|
||||
obs_source_t* item_source = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(item_source));
|
||||
float item_height = float(obs_source_get_height(item_source));
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_string(data, "type", obs_source_get_id(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
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_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "name",
|
||||
obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_string(data, "type",
|
||||
obs_source_get_id(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "volume",
|
||||
obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
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));
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_sceneitem_t* result;
|
||||
};
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_sceneitem_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = NULL;
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_scene_t *scene = obs_scene_from_source(source);
|
||||
if (scene == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
if (scene == nullptr)
|
||||
return nullptr;
|
||||
|
||||
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
|
||||
current_search *search = static_cast<current_search *>(param);
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = static_cast<current_search*>(param);
|
||||
|
||||
const char* currentItemName = obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
if (strcmp(currentItemName, search->query) == 0) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
const char* currentItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
if (strcmp(currentItemName, search->query) == 0) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return search.result;
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
|
||||
obs_source_t *found_transition = NULL;
|
||||
obs_source_t* Utils::GetTransitionFromName(const char* search_name) {
|
||||
obs_source_t* found_transition = NULL;
|
||||
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||
obs_source_t *transition = transition_list.sources.array[i];
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||
obs_source_t* transition = transition_list.sources.array[i];
|
||||
|
||||
const char *transition_name = obs_source_get_name(transition);
|
||||
if (strcmp(transition_name, search_name) == 0) {
|
||||
found_transition = transition;
|
||||
obs_source_addref(found_transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const char* transition_name = obs_source_get_name(transition);
|
||||
if (strcmp(transition_name, search_name) == 0)
|
||||
{
|
||||
found_transition = transition;
|
||||
obs_source_addref(found_transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
|
||||
return found_transition;
|
||||
return found_transition;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char *scene_name) {
|
||||
obs_source_t* scene;
|
||||
if (!scene_name || !strlen(scene_name)) {
|
||||
scene = obs_frontend_get_current_scene();
|
||||
}
|
||||
else {
|
||||
scene = obs_get_source_by_name(scene_name);
|
||||
}
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char* scene_name) {
|
||||
obs_source_t* scene = nullptr;
|
||||
|
||||
return scene;
|
||||
if (!scene_name || !strlen(scene_name))
|
||||
scene = obs_frontend_get_current_scene();
|
||||
else
|
||||
scene = obs_get_source_by_name(scene_name);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||
obs_source_t *scene = sceneList.sources.array[i];
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||
obs_source_t* scene = sceneList.sources.array[i];
|
||||
|
||||
obs_data_t *scene_data = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, scene_data);
|
||||
obs_data_t* scene_data = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, scene_data);
|
||||
|
||||
obs_data_release(scene_data);
|
||||
}
|
||||
obs_data_release(scene_data);
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
|
||||
return scenes;
|
||||
return scenes;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneData(obs_source *source) {
|
||||
obs_data_array_t *scene_items = GetSceneItems(source);
|
||||
obs_data_t* Utils::GetSceneData(obs_source* source) {
|
||||
obs_data_array_t* scene_items = GetSceneItems(source);
|
||||
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", scene_items);
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", scene_items);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
return sceneData;
|
||||
obs_data_array_release(scene_items);
|
||||
return sceneData;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneCollections()
|
||||
{
|
||||
char** scene_collections = obs_frontend_get_scene_collections();
|
||||
obs_data_array_t *list = string_list_to_array(scene_collections, "sc-name");
|
||||
obs_data_array_t* Utils::GetSceneCollections() {
|
||||
char** scene_collections = obs_frontend_get_scene_collections();
|
||||
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
|
||||
|
||||
bfree(scene_collections);
|
||||
return list;
|
||||
bfree(scene_collections);
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetProfiles()
|
||||
{
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
obs_data_array_t *list = string_list_to_array(profiles, "profile-name");
|
||||
obs_data_array_t* Utils::GetProfiles() {
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
|
||||
|
||||
bfree(profiles);
|
||||
return list;
|
||||
bfree(profiles);
|
||||
return list;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl()
|
||||
{
|
||||
QMainWindow *window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration()
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
{
|
||||
return control->value();
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int Utils::GetTransitionDuration() {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
return control->value();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms)
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
void Utils::SetTransitionDuration(int ms) {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control && ms >= 0)
|
||||
control->setValue(ms);
|
||||
}
|
||||
|
||||
if (control && ms >= 0)
|
||||
{
|
||||
control->setValue(ms);
|
||||
}
|
||||
bool Utils::SetTransitionByName(const char* transition_name) {
|
||||
obs_source_t* transition = GetTransitionFromName(transition_name);
|
||||
|
||||
if (transition) {
|
||||
obs_frontend_set_current_transition(transition);
|
||||
obs_source_release(transition);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QPushButton*>("modeSwitch");
|
||||
}
|
||||
|
||||
QListWidget* Utils::GetSceneListControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QListWidget*>("scenes");
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
QVariant item_data = item->data(static_cast<int>(Qt::UserRole));
|
||||
return item_data.value<OBSScene>();
|
||||
}
|
||||
|
||||
QLayout* Utils::GetPreviewLayout() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QLayout*>("previewLayout");
|
||||
}
|
||||
|
||||
bool Utils::IsPreviewModeActive() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// Clue 1 : "Studio Mode" button is toggled on
|
||||
bool buttonToggledOn = GetPreviewModeButtonControl()->isChecked();
|
||||
|
||||
// Clue 2 : Preview layout has more than one item
|
||||
int previewChildCount = GetPreviewLayout()->count();
|
||||
blog(LOG_INFO, "preview layout children count : %d", previewChildCount);
|
||||
|
||||
return buttonToggledOn || (previewChildCount >= 2);
|
||||
}
|
||||
|
||||
void Utils::EnablePreviewMode() {
|
||||
if (!IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::DisablePreviewMode() {
|
||||
if (IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::TogglePreviewMode() {
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::GetPreviewScene() {
|
||||
if (IsPreviewModeActive()) {
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
QList<QListWidgetItem*> selected = sceneList->selectedItems();
|
||||
|
||||
// Qt::UserRole == QtUserRole::OBSRef
|
||||
obs_scene_t* scene = Utils::SceneListItemToScene(selected.first());
|
||||
|
||||
obs_scene_addref(scene);
|
||||
return scene;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Utils::SetPreviewScene(const char* name) {
|
||||
if (IsPreviewModeActive()) {
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
QList<QListWidgetItem*> matchingItems =
|
||||
sceneList->findItems(name, Qt::MatchExactly);
|
||||
|
||||
if (matchingItems.count() > 0) {
|
||||
sceneList->setCurrentItem(matchingItems.first());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Utils::TransitionToProgram() {
|
||||
if (!IsPreviewModeActive())
|
||||
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();
|
||||
}
|
||||
|
||||
const char* Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
char *result = (char*)bmalloc(sizeof(char) * 12);
|
||||
sprintf(result, "%d.%d.%d", major, minor, patch);
|
||||
char* result = (char*)bmalloc(sizeof(char) * 12);
|
||||
sprintf(result, "%d.%d.%d", major, minor, patch);
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChildren<QSystemTrayIcon*>().first();
|
||||
}
|
||||
|
||||
void Utils::SysTrayNotify(QString &text,
|
||||
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||
if (!QSystemTrayIcon::supportsMessages())
|
||||
return;
|
||||
|
||||
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||
if (trayIcon)
|
||||
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() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0) {
|
||||
// Advanced mode
|
||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||
} else {
|
||||
// Simple mode
|
||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::SetRecordingFolder(const char* path) {
|
||||
if (!QDir(path).exists())
|
||||
return false;
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0) {
|
||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||
} else {
|
||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||
}
|
||||
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString* Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||
QString* query = nullptr;
|
||||
if (data) {
|
||||
obs_data_item_t* item = obs_data_first(data);
|
||||
if (item) {
|
||||
query = new QString();
|
||||
bool isFirst = true;
|
||||
do {
|
||||
if (!obs_data_item_has_user_value(item))
|
||||
continue;
|
||||
|
||||
if (!isFirst)
|
||||
query->append('&');
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
const char* attrName = obs_data_item_get_name(item);
|
||||
query->append(attrName).append("=");
|
||||
|
||||
switch (obs_data_item_gettype(item)) {
|
||||
case OBS_DATA_BOOLEAN:
|
||||
query->append(obs_data_item_get_bool(item)?"true":"false");
|
||||
break;
|
||||
case OBS_DATA_NUMBER:
|
||||
switch (obs_data_item_numtype(item))
|
||||
{
|
||||
case OBS_DATA_NUM_DOUBLE:
|
||||
query->append(
|
||||
QString::number(obs_data_item_get_double(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INT:
|
||||
query->append(
|
||||
QString::number(obs_data_item_get_int(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OBS_DATA_STRING:
|
||||
query->append(QUrl::toPercentEncoding(
|
||||
QString(obs_data_item_get_string(item))));
|
||||
break;
|
||||
default:
|
||||
//other types are not supported
|
||||
break;
|
||||
}
|
||||
} while (obs_data_item_next(&item));
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
obs_hotkey_t* Utils::FindHotkeyByName(const char* name) {
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_hotkey_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
|
||||
current_search* search = static_cast<current_search*>(data);
|
||||
|
||||
const char* hk_name = obs_hotkey_get_name(hotkey);
|
||||
if (strcmp(hk_name, search->query) == 0) {
|
||||
search->result = hotkey;
|
||||
blog(LOG_INFO, "Utils::FindHotkeyByName: found %s", hk_name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
bool Utils::ReplayBufferEnabled() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Simple") == 0) {
|
||||
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Utils::RPHotkeySet() {
|
||||
obs_output_t* rp_output = obs_frontend_get_replay_buffer_output();
|
||||
|
||||
obs_data_t *hotkeys = obs_hotkeys_save_output(rp_output);
|
||||
obs_data_array_t *bindings = obs_data_get_array(hotkeys,
|
||||
"ReplayBuffer.Save");
|
||||
|
||||
size_t count = obs_data_array_count(bindings);
|
||||
|
||||
obs_data_array_release(bindings);
|
||||
obs_data_release(hotkeys);
|
||||
obs_output_release(rp_output);
|
||||
|
||||
return (count > 0);
|
||||
}
|
||||
|
70
Utils.h
70
Utils.h
@ -20,29 +20,67 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#define UTILS_H
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QLayout>
|
||||
#include <QListWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
class Utils
|
||||
{
|
||||
public:
|
||||
static obs_data_array_t* GetSceneItems(obs_source_t *source);
|
||||
static obs_data_t* GetSceneItemData(obs_scene_item *item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char *name);
|
||||
static obs_source_t* GetTransitionFromName(const char *search_name);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(const char *scene_name);
|
||||
class Utils {
|
||||
public:
|
||||
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||
static obs_data_t* GetSceneItemData(obs_scene_item* item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(
|
||||
obs_source_t* source, const char* name);
|
||||
static obs_source_t* GetTransitionFromName(const char* search_name);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(const char* scene_name);
|
||||
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source *source);
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source* source);
|
||||
|
||||
static obs_data_array_t* GetSceneCollections();
|
||||
static obs_data_array_t* GetProfiles();
|
||||
static obs_data_array_t* GetSceneCollections();
|
||||
static obs_data_array_t* GetProfiles();
|
||||
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
|
||||
static const char* OBSVersionString();
|
||||
static bool SetTransitionByName(const char* transition_name);
|
||||
|
||||
static QPushButton* GetPreviewModeButtonControl();
|
||||
static QLayout* GetPreviewLayout();
|
||||
static QListWidget* GetSceneListControl();
|
||||
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
|
||||
|
||||
static bool IsPreviewModeActive();
|
||||
static void EnablePreviewMode();
|
||||
static void DisablePreviewMode();
|
||||
static void TogglePreviewMode();
|
||||
|
||||
static obs_scene_t* GetPreviewScene();
|
||||
static bool SetPreviewScene(const char* name);
|
||||
static void TransitionToProgram();
|
||||
|
||||
static const char* OBSVersionString();
|
||||
|
||||
static QSystemTrayIcon* GetTrayIcon();
|
||||
static void SysTrayNotify(
|
||||
QString &text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
static QString FormatIPAddress(QHostAddress &addr);
|
||||
static const char* GetRecordingFolder();
|
||||
static bool SetRecordingFolder(const char* path);
|
||||
|
||||
static QString* ParseDataToQueryString(obs_data_t * data);
|
||||
static obs_hotkey_t* FindHotkeyByName(const char* name);
|
||||
static bool ReplayBufferEnabled();
|
||||
static bool RPHotkeySet();
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
||||
|
1252
WSEvents.cpp
1252
WSEvents.cpp
File diff suppressed because it is too large
Load Diff
107
WSEvents.h
107
WSEvents.h
@ -21,68 +21,85 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#define WSEVENTS_H
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QListWidgetItem>
|
||||
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
class WSEvents : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSEvents(WSServer* srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(
|
||||
enum obs_frontend_event event, void* private_data);
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
static WSEvents* Instance;
|
||||
|
||||
public:
|
||||
explicit WSEvents(WSServer *srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data);
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
uint64_t GetStreamingTime();
|
||||
const char* GetStreamingTimecode();
|
||||
uint64_t GetRecordingTime();
|
||||
const char* GetRecordingTimecode();
|
||||
|
||||
private Q_SLOTS:
|
||||
void StreamStatus();
|
||||
void TransitionDurationChanged(int ms);
|
||||
private slots:
|
||||
void deferredInitOperations();
|
||||
void StreamStatus();
|
||||
void TransitionDurationChanged(int ms);
|
||||
void SelectedSceneChanged(
|
||||
QListWidgetItem* current, QListWidgetItem* prev);
|
||||
void ModeSwitchClicked(bool checked);
|
||||
|
||||
private:
|
||||
WSServer *_srv;
|
||||
signal_handler_t *transition_handler;
|
||||
signal_handler_t *scene_handler;
|
||||
private:
|
||||
WSServer* _srv;
|
||||
signal_handler_t* transition_handler;
|
||||
signal_handler_t* scene_handler;
|
||||
|
||||
bool _streaming_active;
|
||||
bool _recording_active;
|
||||
bool _streaming_active;
|
||||
bool _recording_active;
|
||||
|
||||
uint64_t _stream_starttime;
|
||||
uint64_t _rec_starttime;
|
||||
uint64_t _stream_starttime;
|
||||
uint64_t _rec_starttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
|
||||
void broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
|
||||
void OnExit();
|
||||
void OnReplayStarting();
|
||||
void OnReplayStarted();
|
||||
void OnReplayStopping();
|
||||
void OnReplayStopped();
|
||||
|
||||
static void OnTransitionBegin(void *param, calldata_t *data);
|
||||
void OnExit();
|
||||
|
||||
static void OnSceneReordered(void *param, calldata_t *data);
|
||||
static void OnSceneItemAdd(void *param, calldata_t *data);
|
||||
static void OnSceneItemDelete(void *param, calldata_t *data);
|
||||
static void OnSceneItemVisibilityChanged(void *param, calldata_t *data);
|
||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||
|
||||
static void OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
};
|
||||
|
||||
#endif // WSEVENTS_H
|
2706
WSRequestHandler.cpp
2706
WSRequestHandler.cpp
File diff suppressed because it is too large
Load Diff
@ -20,66 +20,105 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef WSREQUESTHANDLER_H
|
||||
#define WSREQUESTHANDLER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QWebSocket>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtWebSockets/QWebSocketServer>
|
||||
|
||||
class WSRequestHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
class WSRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket *client);
|
||||
~WSRequestHandler();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket* client);
|
||||
~WSRequestHandler();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
bool hasField(const char* name);
|
||||
|
||||
private:
|
||||
QWebSocket *_client;
|
||||
const char *_messageId;
|
||||
const char *_requestType;
|
||||
obs_data_t *_requestData;
|
||||
private:
|
||||
static obs_service_t* _service;
|
||||
QWebSocket* _client;
|
||||
const char* _messageId;
|
||||
const char* _requestType;
|
||||
obs_data_t* data;
|
||||
|
||||
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
QSet<QString> authNotRequired;
|
||||
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
QSet<QString> authNotRequired;
|
||||
|
||||
void SendOKResponse(obs_data_t *additionalFields = NULL);
|
||||
void SendErrorResponse(const char *errorMessage);
|
||||
static void ErrNotImplemented(WSRequestHandler *owner);
|
||||
void SendOKResponse(obs_data_t* additionalFields = NULL);
|
||||
void SendErrorResponse(const char* errorMessage);
|
||||
void SendResponse(obs_data_t* response);
|
||||
|
||||
static void HandleGetVersion(WSRequestHandler *owner);
|
||||
static void HandleGetAuthRequired(WSRequestHandler *owner);
|
||||
static void HandleAuthenticate(WSRequestHandler *owner);
|
||||
static void HandleGetVersion(WSRequestHandler* req);
|
||||
static void HandleGetAuthRequired(WSRequestHandler* req);
|
||||
static void HandleAuthenticate(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentScene(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentScene(WSRequestHandler *owner);
|
||||
static void HandleGetSceneList(WSRequestHandler *owner);
|
||||
static void HandleSetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetSceneList(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetSourceRender(WSRequestHandler *owner);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler *owner);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler *owner);
|
||||
static void HandleSetSceneItemRender(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||
static void HandleResetSceneItem(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStreamingStatus(WSRequestHandler *owner);
|
||||
static void HandleStartStopStreaming(WSRequestHandler *owner);
|
||||
static void HandleStartStopRecording(WSRequestHandler *owner);
|
||||
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
||||
static void HandleStartStopStreaming(WSRequestHandler* req);
|
||||
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);
|
||||
|
||||
static void HandleGetTransitionList(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler *owner);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler *owner);
|
||||
static void HandleStartStopReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleStartReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleStopReplayBuffer(WSRequestHandler* req);
|
||||
static void HandleSaveReplayBuffer(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetVolume(WSRequestHandler *owner);
|
||||
static void HandleGetVolume(WSRequestHandler *owner);
|
||||
static void HandleToggleMute(WSRequestHandler *owner);
|
||||
static void HandleSetMute(WSRequestHandler *owner);
|
||||
static void HandleSetRecordingFolder(WSRequestHandler* req);
|
||||
static void HandleGetRecordingFolder(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler *owner);
|
||||
static void HandleListSceneCollections(WSRequestHandler *owner);
|
||||
static void HandleGetTransitionList(WSRequestHandler* req);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler* req);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler *owner);
|
||||
static void HandleListProfiles(WSRequestHandler *owner);
|
||||
static void HandleSetVolume(WSRequestHandler* req);
|
||||
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 HandleSetTransitionDuration(WSRequestHandler *owner);
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleListSceneCollections(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleListProfiles(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleGetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleSaveStreamSettings(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTransitionDuration(WSRequestHandler* req);
|
||||
static void HandleGetTransitionDuration(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||
static void HandleGetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleSetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleTransitionToProgram(WSRequestHandler* req);
|
||||
static void HandleEnableStudioMode(WSRequestHandler* req);
|
||||
static void HandleDisableStudioMode(WSRequestHandler* req);
|
||||
static void HandleToggleStudioMode(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||
};
|
||||
|
||||
#endif // WSPROTOCOL_H
|
||||
|
186
WSServer.cpp
186
WSServer.cpp
@ -24,126 +24,124 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "WSServer.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
WSServer* WSServer::Instance = new WSServer();
|
||||
WSServer* WSServer::Instance = nullptr;
|
||||
|
||||
WSServer::WSServer(QObject *parent) :
|
||||
QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive)
|
||||
{
|
||||
_serverThread = new QThread();
|
||||
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode,
|
||||
this);
|
||||
|
||||
_wsServer->moveToThread(_serverThread);
|
||||
_serverThread->start();
|
||||
WSServer::WSServer(QObject* parent)
|
||||
: QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive) {
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode);
|
||||
}
|
||||
|
||||
WSServer::~WSServer()
|
||||
{
|
||||
Stop();
|
||||
|
||||
delete _serverThread;
|
||||
WSServer::~WSServer() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void WSServer::Start(quint16 port)
|
||||
{
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
void WSServer::Start(quint16 port) {
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted)
|
||||
{
|
||||
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
|
||||
}
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted) {
|
||||
connect(_wsServer, SIGNAL(newConnection()),
|
||||
this, SLOT(onNewConnection()));
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::Stop()
|
||||
{
|
||||
_clMutex.lock();
|
||||
Q_FOREACH(QWebSocket *pClient, _clients)
|
||||
{
|
||||
pClient->close();
|
||||
}
|
||||
_clMutex.unlock();
|
||||
void WSServer::Stop() {
|
||||
_clMutex.lock();
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
pClient->close();
|
||||
}
|
||||
_clMutex.unlock();
|
||||
|
||||
_wsServer->close();
|
||||
_wsServer->close();
|
||||
}
|
||||
|
||||
void WSServer::broadcast(QString message)
|
||||
{
|
||||
_clMutex.lock();
|
||||
|
||||
Q_FOREACH(QWebSocket *pClient, _clients)
|
||||
{
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
|
||||
{
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
|
||||
_clMutex.unlock();
|
||||
void WSServer::broadcast(QString message) {
|
||||
_clMutex.lock();
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false)) {
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
_clMutex.unlock();
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection()
|
||||
{
|
||||
QWebSocket *pSocket = _wsServer->nextPendingConnection();
|
||||
void WSServer::onNewConnection() {
|
||||
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
||||
if (pSocket) {
|
||||
connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
|
||||
this, SLOT(onTextMessageReceived(QString)));
|
||||
connect(pSocket, SIGNAL(disconnected()),
|
||||
this, SLOT(onSocketDisconnected()));
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::textMessageReceived);
|
||||
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients << pSocket;
|
||||
_clMutex.unlock();
|
||||
_clMutex.lock();
|
||||
_clients << pSocket;
|
||||
_clMutex.unlock();
|
||||
|
||||
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
|
||||
blog(LOG_INFO, "new client connection from %s:%d", client_ip.constData(), pSocket->peerPort());
|
||||
}
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "new client connection from %s:%d",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Connected")));
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::textMessageReceived(QString message)
|
||||
{
|
||||
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
void WSServer::onTextMessageReceived(QString message) {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::socketDisconnected()
|
||||
{
|
||||
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
|
||||
void WSServer::onSocketDisconnected() {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
_clMutex.lock();
|
||||
_clients.removeAll(pSocket);
|
||||
_clMutex.unlock();
|
||||
|
||||
_clMutex.lock();
|
||||
_clients.removeAll(pSocket);
|
||||
_clMutex.unlock();
|
||||
pSocket->deleteLater();
|
||||
|
||||
pSocket->deleteLater();
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
|
||||
blog(LOG_INFO, "client %s:%d disconnected", client_ip.constData(), pSocket->peerPort());
|
||||
}
|
||||
blog(LOG_INFO, "client %s:%d disconnected",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Disconnected")));
|
||||
}
|
||||
}
|
47
WSServer.h
47
WSServer.h
@ -19,37 +19,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef WSSERVER_H
|
||||
#define WSSERVER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onTextMessageReceived(QString message);
|
||||
void onSocketDisconnected();
|
||||
|
||||
public:
|
||||
explicit WSServer(QObject *parent = Q_NULLPTR);
|
||||
virtual ~WSServer();
|
||||
void Start(quint16 port);
|
||||
void Stop();
|
||||
void broadcast(QString message);
|
||||
static WSServer* Instance;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onNewConnection();
|
||||
void textMessageReceived(QString message);
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer *_wsServer;
|
||||
QList<QWebSocket *> _clients;
|
||||
QMutex _clMutex;
|
||||
QThread *_serverThread;
|
||||
private:
|
||||
QWebSocketServer* _wsServer;
|
||||
QList<QWebSocket*> _clients;
|
||||
QMutex _clMutex;
|
||||
};
|
||||
|
||||
#endif // WSSERVER_H
|
@ -15,7 +15,7 @@ install:
|
||||
- set build_config=Release
|
||||
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||
- cd C:\projects\obs-studio\
|
||||
- git checkout 18.0.0
|
||||
- git checkout 19.0.3
|
||||
- mkdir build
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
|
@ -4,3 +4,7 @@ OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Enable authentication"
|
||||
OBSWebsocket.Settings.Password="Password"
|
||||
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
|
||||
OBSWebsocket.ConnectNotify.Connected="New WebSocket connection"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="WebSocket client disconnected"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Client Address:"
|
@ -4,3 +4,6 @@ OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
|
||||
OBSWebsocket.Settings.ServerPort="Port du serveur"
|
||||
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
|
||||
OBSWebsocket.Settings.Password="Mot de passe"
|
||||
OBSWebsocket.ConnectNotify.Connected="Nouvelle connexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="Déconnexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Adresse du client :"
|
6
data/locale/nl-NL.ini
Normal file
6
data/locale/nl-NL.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Poort"
|
||||
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
|
||||
OBSWebsocket.Settings.Password="Wachtwoord"
|
6
data/locale/pl-PL.ini
Normal file
6
data/locale/pl-PL.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Ustawienia serwera zdalnego sterowania"
|
||||
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"
|
6
data/locale/pt-BR.ini
Normal file
6
data/locale/pt-BR.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
|
||||
OBSWebsocket.Settings.Password="Senha"
|
11
docs/.editorconfig
Normal file
11
docs/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md, *.mustache]
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
4
docs/.gitignore
vendored
Normal file
4
docs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
1
docs/.npmrc
Normal file
1
docs/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
package-lock=false
|
21
docs/README.md
Normal file
21
docs/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
## Installation
|
||||
|
||||
Install node and update npm if necessary.
|
||||
|
||||
```sh
|
||||
cd obs-websocket/docs
|
||||
npm install
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
# Just extract the comments.
|
||||
npm run comments
|
||||
|
||||
# Just render the markdown.
|
||||
npm run docs
|
||||
|
||||
# Do both comments and markdown.
|
||||
npm run build
|
||||
```
|
52
docs/comments.js
Normal file
52
docs/comments.js
Normal file
@ -0,0 +1,52 @@
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const parseComments = require('parse-comments');
|
||||
|
||||
/**
|
||||
* Read each file and call `parse-comments` on it.
|
||||
*
|
||||
* @param {String|Array} `files` List of file paths to read from.
|
||||
* @return {Object|Array} Array of `parse-comments` objects.
|
||||
*/
|
||||
const parseFiles = files => {
|
||||
let response = [];
|
||||
files.forEach(file => {
|
||||
const f = fs.readFileSync(file, 'utf8').toString();
|
||||
response = response.concat(parseComments(f));
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters/sorts the results from `parse-comments`.
|
||||
* @param {Object|Array} `comments` Array of `parse-comments` objects.
|
||||
* @return {Object} Filtered comments sorted by `@api` and `@category`.
|
||||
*/
|
||||
const processComments = comments => {
|
||||
let sorted = {};
|
||||
|
||||
comments.forEach(comment => {
|
||||
if (typeof comment.api === 'undefined') return;
|
||||
|
||||
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
|
||||
comment.category = comment.category || 'miscellaneous';
|
||||
|
||||
// Remove some unnecessary properties to avoid result differences in travis.
|
||||
comment.comment = undefined;
|
||||
comment.context = undefined;
|
||||
|
||||
// Create an entry in sorted for the api/category if one does not exist.
|
||||
sorted[comment.api] = sorted[comment.api] || {};
|
||||
sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || [];
|
||||
|
||||
// Store the comment in the appropriate api/category.
|
||||
sorted[comment.api][comment.category].push(comment);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const files = glob.sync("./../*.@(cpp|h)");
|
||||
const comments = processComments(parseFiles(files));
|
||||
fs.writeFileSync('./generated/comments.json', JSON.stringify(comments, null, 2));
|
31
docs/docs.js
Normal file
31
docs/docs.js
Normal file
@ -0,0 +1,31 @@
|
||||
const fs = require('fs');
|
||||
const toc = require('markdown-toc');
|
||||
const handlebars = require('handlebars');
|
||||
|
||||
const helpers = require('handlebars-helpers')({
|
||||
handlebars: handlebars
|
||||
});
|
||||
|
||||
// Allows pipe characters to be used within markdown tables.
|
||||
handlebars.registerHelper('depipe', (text) => {
|
||||
return text.replace('|', `\\|`);
|
||||
});
|
||||
|
||||
const insertHeader = (text) => {
|
||||
return '<!-- This file was generated based on handlebars templates. Do not edit directly! -->\n\n' + text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes `protocol.md` using `protocol.mustache`.
|
||||
*
|
||||
* @param {Object} `data` Data to assign to the mustache template.
|
||||
*/
|
||||
const generateProtocol = (templatePath, data) => {
|
||||
const template = fs.readFileSync(templatePath).toString();
|
||||
const generated = handlebars.compile(template)(data);
|
||||
return insertHeader(toc.insert(generated));
|
||||
};
|
||||
|
||||
const comments = fs.readFileSync('./generated/comments.json', 'utf8');
|
||||
const markdown = generateProtocol('./protocol.hbs', JSON.parse(comments));
|
||||
fs.writeFileSync('./generated/protocol.md', markdown);
|
4370
docs/generated/comments.json
Normal file
4370
docs/generated/comments.json
Normal file
File diff suppressed because it is too large
Load Diff
1840
docs/generated/protocol.md
Normal file
1840
docs/generated/protocol.md
Normal file
File diff suppressed because it is too large
Load Diff
21
docs/package.json
Normal file
21
docs/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "obs-websocket-docs",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "docs.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"docs": "node ./docs.js",
|
||||
"comments": "node ./comments.js",
|
||||
"build": "npm run comments && npm run docs"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.2",
|
||||
"handlebars": "^4.0.10",
|
||||
"handlebars-helpers": "^0.9.6",
|
||||
"markdown-toc": "^1.1.0",
|
||||
"parse-comments": "^0.4.3"
|
||||
}
|
||||
}
|
11
docs/partials/eventsHeader.md
Normal file
11
docs/partials/eventsHeader.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Events
|
||||
Events are broadcast by the server to each connected client when a recognized action occurs within OBS.
|
||||
|
||||
An event message will contain at least the following base fields:
|
||||
- `update-type` _String_: the type of event.
|
||||
- `stream-timecode` _String (optional)_: time elapsed between now and stream start (only present if OBS Studio is streaming).
|
||||
- `rec-timecode` _String (optional)_: time elapsed between now and recording start (only present if OBS Studio is recording).
|
||||
|
||||
Timecodes are sent using the format: `HH:MM:SS.mmm`
|
||||
|
||||
Additional fields may be present in the event message depending on the event type.
|
39
docs/partials/introduction.md
Normal file
39
docs/partials/introduction.md
Normal file
@ -0,0 +1,39 @@
|
||||
# obs-websocket 4.2.0 protocol reference
|
||||
|
||||
**This is the reference for obs-websocket 4.2.0. See the list below for older versions.**
|
||||
- [4.1.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.1.0/PROTOCOL.md)
|
||||
- [4.0.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)
|
||||
|
||||
# General Introduction
|
||||
Messages are exchanged between the client and the server as JSON objects.
|
||||
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||
|
||||
|
||||
# Authentication
|
||||
OBSWebSocket uses SHA256 to transmit credentials.
|
||||
|
||||
A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
|
||||
- A `challenge`: a random string that will be used to generate the auth response.
|
||||
- A `salt`: applied to the password when generating the auth response.
|
||||
|
||||
To generate the answer to the auth challenge, follow this procedure:
|
||||
- Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`).
|
||||
- Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`.
|
||||
- Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`).
|
||||
- Generate a binary SHA256 hash of the result and encode it to base64.
|
||||
- Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request.
|
||||
|
||||
Pseudo Code Example:
|
||||
```
|
||||
password = "supersecretpassword"
|
||||
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
|
||||
salt = "PZVbYpvAnZut2SS6JNJytDm9"
|
||||
|
||||
secret_string = password + salt
|
||||
secret_hash = binary_sha256(secret_string)
|
||||
secret = base64_encode(secret_hash)
|
||||
|
||||
auth_response_string = secret + challenge
|
||||
auth_response_hash = binary_sha256(auth_response_string)
|
||||
auth_response = base64_encode(auth_response_hash)
|
||||
```
|
11
docs/partials/requestsHeader.md
Normal file
11
docs/partials/requestsHeader.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Requests
|
||||
Requests are sent by the client and require at least the following two fields:
|
||||
- `request-type` _String_: String name of the request type.
|
||||
- `message-id` _String_: Client defined identifier for the message, will be echoed in the response.
|
||||
|
||||
Once a request is sent, the server will return a JSON response with at least the following fields:
|
||||
- `message-id` _String_: The client defined identifier specified in the request.
|
||||
- `status` _String_: Response status, will be one of the following: `ok`, `error`
|
||||
- `error` _String_: An error message accompanying an `error` status.
|
||||
|
||||
Additional information may be required/returned depending on the request type. See below for more information.
|
92
docs/protocol.hbs
Normal file
92
docs/protocol.hbs
Normal file
@ -0,0 +1,92 @@
|
||||
{{#read "partials/introduction.md"}}{{/read}}
|
||||
|
||||
|
||||
|
||||
# Table of Contents
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
{{#read "partials/eventsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each events}}
|
||||
## {{capitalizeAll @key}}
|
||||
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
- Added in v{{since}}
|
||||
{{/eq}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
**Response Items:**
|
||||
|
||||
{{#if returns.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No additional response items._
|
||||
{{/if}}
|
||||
|
||||
---
|
||||
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
|
||||
|
||||
{{#read "partials/requestsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each requests}}
|
||||
## {{capitalizeAll @key}}
|
||||
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
- Added in v{{since}}
|
||||
{{/eq}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
{{#if params.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each params}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No specified parameters._
|
||||
{{/if}}
|
||||
|
||||
**Response Items:**
|
||||
|
||||
{{#if returns.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No additional response items._
|
||||
{{/if}}
|
||||
|
||||
---
|
||||
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
@ -26,90 +26,85 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged, this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted);
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged,
|
||||
this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, &SettingsDialog::FormAccepted);
|
||||
|
||||
AuthCheckboxChanged();
|
||||
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent *event)
|
||||
void SettingsDialog::showEvent(QShowEvent* event)
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
Config* conf = Config::Current();
|
||||
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
ui->debugEnabled->setChecked(conf->DebugEnabled);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide()
|
||||
{
|
||||
if (!isVisible()) {
|
||||
setVisible(true);
|
||||
}
|
||||
else {
|
||||
setVisible(false);
|
||||
}
|
||||
if (!isVisible())
|
||||
setVisible(true);
|
||||
else
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged()
|
||||
{
|
||||
if (ui->authRequired->isChecked()) {
|
||||
ui->password->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
if (ui->authRequired->isChecked())
|
||||
ui->password->setEnabled(true);
|
||||
else
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted()
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
Config* conf = Config::Current();
|
||||
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
|
||||
if (ui->authRequired->isChecked())
|
||||
{
|
||||
if (ui->password->text() != CHANGE_ME)
|
||||
{
|
||||
QByteArray pwd = ui->password->text().toUtf8();
|
||||
const char *new_password = pwd;
|
||||
conf->DebugEnabled = ui->debugEnabled->isChecked();
|
||||
|
||||
conf->SetPassword(new_password);
|
||||
}
|
||||
if (ui->authRequired->isChecked())
|
||||
{
|
||||
if (ui->password->text() != CHANGE_ME)
|
||||
{
|
||||
QByteArray pwd = ui->password->text().toUtf8();
|
||||
const char *new_password = pwd;
|
||||
|
||||
if (strcmp(Config::Current()->Secret, "") != 0)
|
||||
{
|
||||
conf->AuthRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
conf->SetPassword(new_password);
|
||||
}
|
||||
|
||||
conf->Save();
|
||||
if (strcmp(Config::Current()->Secret, "") != 0)
|
||||
conf->AuthRequired = true;
|
||||
else
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
{
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
conf->Save();
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
else
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
|
@ -30,17 +30,17 @@ class SettingsDialog : public QDialog
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget *parent = 0);
|
||||
explicit SettingsDialog(QWidget* parent = 0);
|
||||
~SettingsDialog();
|
||||
void showEvent(QShowEvent *event);
|
||||
void ToggleShowHide();
|
||||
void showEvent(QShowEvent* event);
|
||||
void ToggleShowHide();
|
||||
|
||||
private Q_SLOTS:
|
||||
void AuthCheckboxChanged();
|
||||
void FormAccepted();
|
||||
void AuthCheckboxChanged();
|
||||
void FormAccepted();
|
||||
|
||||
private:
|
||||
Ui::SettingsDialog *ui;
|
||||
Ui::SettingsDialog* ui;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>407</width>
|
||||
<height>155</height>
|
||||
<height>175</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -79,6 +79,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="debugEnabled">
|
||||
<property name="text">
|
||||
<string>OBSWebsocket.Settings.DebugEnable</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@ -103,11 +113,11 @@
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
<y>294</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
@ -119,11 +129,11 @@
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
<y>280</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
<y>294</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
@ -2,7 +2,7 @@
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "4.0.0"
|
||||
#define MyAppVersion "4.2.0"
|
||||
#define MyAppPublisher "St<53>phane Lepin"
|
||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||
|
||||
|
@ -31,43 +31,44 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
WSEvents *eventHandler;
|
||||
SettingsDialog *settings_dialog;
|
||||
SettingsDialog* settings_dialog;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
bool obs_module_load(void) {
|
||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
|
||||
QT_VERSION_STR, qVersion());
|
||||
|
||||
// Core setup
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
// Core setup
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
WSServer::Instance = new WSServer();
|
||||
WSEvents::Instance = new WSEvents(WSServer::Instance);
|
||||
|
||||
eventHandler = new WSEvents(WSServer::Instance);
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
|
||||
// UI setup
|
||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||
// 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);
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload()
|
||||
{
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
void obs_module_unload() {
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#define OBSWEBSOCKET_H
|
||||
|
||||
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
||||
#define OBS_WEBSOCKET_VERSION "4.0.0"
|
||||
#define OBS_WEBSOCKET_VERSION "4.2.0"
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||
|
||||
|
Reference in New Issue
Block a user