├── .clang-format ├── .github ├── containers │ ├── fedora-common │ │ └── build.sh │ └── fedora-template │ │ ├── Dockerfile │ │ └── build.sh └── workflows │ ├── clang-format.yml │ ├── docker-build.yml │ └── main.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ci ├── ci_includes.cmd.in ├── ci_includes.sh.in ├── forum-update-info.json ├── macos │ ├── change-rpath.sh │ └── install-packagesbuild.sh ├── plugin.spec └── windows │ └── package-windows.cmd ├── cmake ├── ObsPluginHelpers.cmake └── bundle │ └── macos │ ├── Plugin-Info.plist.in │ └── entitlements.plist ├── data └── locale │ ├── en-US.ini │ ├── ja-JP.ini │ └── pt-BR.ini ├── doc └── environments.md ├── installer ├── installer-Windows.iss.in ├── installer-macOS.pkgproj.in └── obs-command-source.pkgproj ├── obs-command-source.c ├── plugin-macros.h.in └── tools └── request-websocket.py /.clang-format: -------------------------------------------------------------------------------- 1 | # please use clang-format version 8 or later 2 | 3 | Standard: Cpp11 4 | AccessModifierOffset: -8 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | #AllowAllArgumentsOnNextLine: false # requires clang-format 9 12 | #AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: false 18 | #AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: false 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterClass: false 28 | AfterControlStatement: false 29 | AfterEnum: false 30 | AfterFunction: true 31 | AfterNamespace: false 32 | AfterObjCDeclaration: false 33 | AfterStruct: true 34 | AfterUnion: false 35 | AfterExternBlock: false 36 | BeforeCatch: false 37 | BeforeElse: true 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyRecord: true 41 | SplitEmptyNamespace: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakStringLiterals: false # apparently unpredictable 47 | ColumnLimit: 120 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 8 51 | ContinuationIndentWidth: 8 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | FixNamespaceComments: false 56 | ForEachMacros: 57 | - 'json_object_foreach' 58 | - 'json_object_foreach_safe' 59 | - 'json_array_foreach' 60 | IncludeBlocks: Preserve 61 | IndentCaseLabels: false 62 | IndentPPDirectives: None 63 | IndentWidth: 8 64 | IndentWrappedFunctionNames: false 65 | KeepEmptyLinesAtTheStartOfBlocks: true 66 | MaxEmptyLinesToKeep: 1 67 | NamespaceIndentation: None 68 | #ObjCBinPackProtocolList: Auto # requires clang-format 7 69 | ObjCBlockIndentWidth: 8 70 | ObjCSpaceAfterProperty: true 71 | ObjCSpaceBeforeProtocolList: true 72 | 73 | PenaltyBreakAssignment: 10 74 | PenaltyBreakBeforeFirstCallParameter: 30 75 | PenaltyBreakComment: 10 76 | PenaltyBreakFirstLessLess: 0 77 | PenaltyBreakString: 10 78 | PenaltyExcessCharacter: 100 79 | PenaltyReturnTypeOnItsOwnLine: 60 80 | 81 | PointerAlignment: Right 82 | ReflowComments: false 83 | SortIncludes: false 84 | SortUsingDeclarations: false 85 | SpaceAfterCStyleCast: false 86 | #SpaceAfterLogicalNot: false # requires clang-format 9 87 | SpaceAfterTemplateKeyword: false 88 | SpaceBeforeAssignmentOperators: true 89 | #SpaceBeforeCtorInitializerColon: true # requires clang-format 7 90 | #SpaceBeforeInheritanceColon: true # requires clang-format 7 91 | SpaceBeforeParens: ControlStatements 92 | #SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 93 | SpaceInEmptyParentheses: false 94 | SpacesBeforeTrailingComments: 1 95 | SpacesInAngles: false 96 | SpacesInCStyleCastParentheses: false 97 | SpacesInContainerLiterals: false 98 | SpacesInParentheses: false 99 | SpacesInSquareBrackets: false 100 | #StatementMacros: # requires clang-format 8 101 | # - 'Q_OBJECT' 102 | TabWidth: 8 103 | #TypenameMacros: # requires clang-format 9 104 | # - 'DARRAY' 105 | UseTab: ForContinuationAndIndentation 106 | --- 107 | Language: ObjC 108 | -------------------------------------------------------------------------------- /.github/containers/fedora-common/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -ex 4 | 5 | docker_image="$1" 6 | rpmbuild="$2" 7 | 8 | PLUGIN_NAME=$(awk '/^project\(/{print gensub(/project\(([^ ()]*).*/, "\\1", 1, $0)}' CMakeLists.txt) 9 | PLUGIN_NAME_FEDORA="$(sed -e 's/^obs-/obs-studio-plugin-/' <<< "$PLUGIN_NAME")" 10 | eval $(git describe --tag --always --long | awk ' 11 | BEGIN { 12 | VERSION="unknown"; 13 | RELEASE=0; 14 | } 15 | { 16 | if (match($0, /^(.*)-([0-9]*)-g[0-9a-f]*$/, aa)) { 17 | VERSION = aa[1] 18 | RELEASE = aa[2] 19 | } 20 | } 21 | END { 22 | VERSION = gensub(/-(alpha|beta|rc)/, "~\\1", 1, VERSION); 23 | gsub(/["'\''-]/, ".", VERSION); 24 | printf("VERSION='\''%s'\'' RELEASE=%d\n", VERSION, RELEASE + 1); 25 | }') 26 | 27 | rm -rf $rpmbuild 28 | mkdir -p $rpmbuild/{BUILD,BUILDROOT,SRPMS,SOURCES,SPECS,RPMS} 29 | rpmbuild="$(cd $rpmbuild && pwd -P)" 30 | chmod a+w $rpmbuild/{BUILD,BUILDROOT,SRPMS,RPMS} 31 | test -x /usr/sbin/selinuxenabled && /usr/sbin/selinuxenabled && chcon -Rt container_file_t $rpmbuild 32 | 33 | # Prepare files 34 | sed \ 35 | -e "s/@PLUGIN_NAME@/$PLUGIN_NAME/g" \ 36 | -e "s/@PLUGIN_NAME_FEDORA@/$PLUGIN_NAME_FEDORA/g" \ 37 | -e "s/@VERSION@/$VERSION/g" \ 38 | -e "s/@RELEASE@/$RELEASE/g" \ 39 | < ci/plugin.spec \ 40 | > $rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec 41 | 42 | git archive --format=tar --prefix=$PLUGIN_NAME_FEDORA-$VERSION/ HEAD | bzip2 > $rpmbuild/SOURCES/$PLUGIN_NAME_FEDORA-$VERSION.tar.bz2 43 | 44 | docker run -v $rpmbuild:/home/rpm/rpmbuild $docker_image bash -c " 45 | sudo dnf builddep -y ~/rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec && 46 | sudo chown 0:0 ~/rpmbuild/SOURCES/* && 47 | sudo chown 0:0 ~/rpmbuild/SPECS/* && 48 | rpmbuild -ba ~/rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec 49 | " 50 | -------------------------------------------------------------------------------- /.github/containers/fedora-template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:%releasever% 2 | 3 | RUN dnf install -y rpm-build python3-dnf-plugins-core && dnf clean all 4 | RUN dnf install -y obs-studio obs-studio-devel && dnf clean all 5 | 6 | RUN useradd -s /bin/bash -m rpm 7 | RUN echo >> /etc/sudoers 8 | RUN echo "rpm ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 9 | 10 | USER rpm 11 | WORKDIR /home/rpm 12 | -------------------------------------------------------------------------------- /.github/containers/fedora-template/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -ex 3 | .github/containers/fedora-common/build.sh obs-plugin-build/fedora%releasever% fedora%releasever%-rpmbuild 4 | echo 'FILE_NAME=fedora%releasever%-rpmbuild/*RPMS/**/*.rpm' >> $GITHUB_ENV 5 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: Clang Format Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | clang: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Clang 17 | run: | 18 | sudo apt-get install -y clang-format-12 19 | clang-format -i -fallback-style=none $(git ls-files | grep '\.\(c\|h\)$') 20 | 21 | - name: Check 22 | # Build your program with the given configuration 23 | run: | 24 | dirty=$(git ls-files --modified) 25 | set +x 26 | if [[ $dirty ]]; then 27 | git diff 28 | echo "Error: File(s) are not properly formatted." 29 | echo "$dirty" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build on Docker 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | branches: 8 | - main 9 | tags: 10 | - '*' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | branches: 15 | - main 16 | 17 | env: 18 | artifactName: ${{ contains(github.ref_name, '/') && 'docker-artifact' || github.ref_name }}-rpm 19 | 20 | jobs: 21 | docker_build: 22 | runs-on: ubuntu-latest 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | target: 27 | - fedora41 28 | - fedora42 29 | defaults: 30 | run: 31 | shell: bash 32 | env: 33 | target: ${{ matrix.target }} 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 0 40 | submodules: recursive 41 | 42 | - name: Generate container directory 43 | run: | 44 | cp -a .github/containers/fedora-template .github/containers/$target 45 | releasever="$(cut -b 7- <<< "$target")" 46 | sed -i "s/%releasever%/$releasever/g" .github/containers/$target/* 47 | 48 | - name: Restore docker from cache 49 | id: docker-cache 50 | uses: actions/cache/restore@v4 51 | with: 52 | path: ${{ github.workspace }}/docker-cache 53 | key: docker-cache-${{ matrix.target }}-${{ hashFiles(format('.github/containers/{0}/Dockerfile', matrix.target)) }} 54 | 55 | - name: Build environment 56 | if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }} 57 | run: | 58 | docker build -t obs-plugin-build/$target .github/containers/$target 59 | mkdir -p docker-cache 60 | docker save obs-plugin-build/$target | gzip > docker-cache/obs-plugin-build-$target.tar.gz 61 | 62 | - name: Save docker to cache 63 | uses: actions/cache/save@v4 64 | if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }} 65 | with: 66 | path: ${{ github.workspace }}/docker-cache 67 | key: docker-cache-${{ matrix.target }}-${{ hashFiles(format('.github/containers/{0}/Dockerfile', matrix.target)) }} 68 | 69 | - name: Extract cached environment 70 | if: ${{ steps.docker-cache.outputs.cache-hit == 'true' }} 71 | run: | 72 | zcat docker-cache/obs-plugin-build-$target.tar.gz | docker load 73 | 74 | - name: Build package 75 | run: .github/containers/$target/build.sh 76 | 77 | - name: Upload artifact 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: ${{ env.artifactName }}-${{ matrix.target }} 81 | path: '${{ env.FILE_NAME }}' 82 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | branches: 8 | - main 9 | tags: 10 | - '*' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | branches: 15 | - main 16 | 17 | env: 18 | artifactName: ${{ contains(github.ref_name, '/') && 'artifact' || github.ref_name }} 19 | qt: false 20 | 21 | jobs: 22 | linux_build: 23 | runs-on: ${{ matrix.ubuntu }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | obs: [27, 28] 28 | ubuntu: ['ubuntu-20.04'] 29 | defaults: 30 | run: 31 | shell: bash 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | with: 36 | submodules: recursive 37 | 38 | - name: Download obs-studio development environment 39 | id: obsdeps 40 | uses: norihiro/obs-studio-devel-action@v1-beta 41 | with: 42 | obs: ${{ matrix.obs }} 43 | verbose: true 44 | qt: ${{ env.qt }} 45 | 46 | - name: Build plugin 47 | run: | 48 | OBS_QT_VERSION_MAJOR=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }} 49 | mkdir build 50 | cd build 51 | case ${{ matrix.obs }} in 52 | 27) 53 | cmake_opt=( 54 | -D CMAKE_INSTALL_LIBDIR=/usr/lib/ 55 | -D CPACK_DEBIAN_PACKAGE_DEPENDS='obs-studio (>= 27), obs-studio (<< 28)' 56 | ) 57 | ;; 58 | 28) 59 | cmake_opt=( 60 | -D CPACK_DEBIAN_PACKAGE_DEPENDS='obs-studio (>= 28)' 61 | ) 62 | ;; 63 | esac 64 | cmake .. \ 65 | -D QT_VERSION=$OBS_QT_VERSION_MAJOR \ 66 | -D CMAKE_INSTALL_PREFIX=/usr \ 67 | -D CMAKE_BUILD_TYPE=RelWithDebInfo \ 68 | -D LINUX_PORTABLE=OFF \ 69 | -D CPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON \ 70 | -D PKG_SUFFIX=-obs${{ matrix.obs }}-${{ matrix.ubuntu }}-x86_64 \ 71 | "${cmake_opt[@]}" 72 | make -j4 73 | make package 74 | echo "FILE_NAME=$(find $PWD -name '*.deb' | head -n 1)" >> $GITHUB_ENV 75 | - name: Upload build artifact 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: ${{ env.artifactName }}-linux-obs${{ matrix.obs }}-${{ matrix.ubuntu }} 79 | path: '${{ env.FILE_NAME }}' 80 | - name: Check package 81 | run: | 82 | . build/ci/ci_includes.generated.sh 83 | set -ex 84 | sudo apt install '${{ env.FILE_NAME }}' 85 | case ${{ matrix.obs }} in 86 | 27) plugins_dir=/usr/lib/obs-plugins ;; 87 | 28) plugins_dir=/usr/lib/x86_64-linux-gnu/obs-plugins ;; 88 | esac 89 | ldd $plugins_dir/${PLUGIN_NAME}.so > ldd.out 90 | if grep not.found ldd.out ; then 91 | echo "Error: unresolved shared object." >&2 92 | exit 1 93 | fi 94 | ls /usr/share/obs/obs-plugins/${PLUGIN_NAME}/ 95 | 96 | macos_build: 97 | runs-on: macos-12 98 | strategy: 99 | fail-fast: false 100 | matrix: 101 | include: 102 | - obs: 27 103 | arch: x86_64 104 | - obs: 28 105 | arch: universal 106 | defaults: 107 | run: 108 | shell: bash 109 | steps: 110 | - name: Checkout 111 | uses: actions/checkout@v4 112 | with: 113 | submodules: recursive 114 | 115 | - name: Setup Environment 116 | id: setup 117 | run: | 118 | set -e 119 | echo '::group::Set up code signing' 120 | if [[ '${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' != '' && \ 121 | '${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}' != '' && \ 122 | '${{ secrets.MACOS_SIGNING_CERT }}' != '' ]]; then 123 | echo "haveCodesignIdent=true" >> $GITHUB_OUTPUT 124 | else 125 | echo "haveCodesignIdent=false" >> $GITHUB_OUTPUT 126 | fi 127 | if [[ '${{ secrets.MACOS_NOTARIZATION_USERNAME }}' != '' && \ 128 | '${{ secrets.MACOS_NOTARIZATION_PASSWORD }}' != '' ]]; then 129 | echo "haveNotarizationUser=true" >> $GITHUB_OUTPUT 130 | else 131 | echo "haveNotarizationUser=false" >> $GITHUB_OUTPUT 132 | fi 133 | echo '::endgroup::' 134 | 135 | - name: Install Apple Developer Certificate 136 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 137 | uses: apple-actions/import-codesign-certs@v2 138 | with: 139 | keychain-password: ${{ github.run_id }} 140 | p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }} 141 | p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }} 142 | 143 | - name: Set Signing Identity 144 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' && steps.setup.outputs.haveNotarizationUser == 'true' }} 145 | run: | 146 | set -e 147 | TEAM_ID=$(echo "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" | sed 's/.*(\([A-Za-z0-9]*\))$/\1/') 148 | xcrun notarytool store-credentials AC_PASSWORD \ 149 | --apple-id "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \ 150 | --team-id "$TEAM_ID" \ 151 | --password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" 152 | 153 | - name: Download obs-studio development environment 154 | id: obsdeps 155 | uses: norihiro/obs-studio-devel-action@v1-beta 156 | with: 157 | path: /tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 158 | arch: ${{ matrix.arch }} 159 | obs: ${{ matrix.obs }} 160 | verbose: true 161 | qt: ${{ env.qt }} 162 | 163 | - name: Build plugin 164 | run: | 165 | arch=${{ matrix.arch }} 166 | deps=/tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 167 | MACOSX_DEPLOYMENT_TARGET=${{ steps.obsdeps.outputs.MACOSX_DEPLOYMENT_TARGET }} 168 | OBS_QT_VERSION_MAJOR=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }} 169 | GIT_TAG=$(git describe --tags --always) 170 | PKG_SUFFIX=-${GIT_TAG}-obs${{ matrix.obs }}-macos-${{ matrix.arch }} 171 | set -e 172 | case "${{ matrix.obs }}" in 173 | 27) 174 | cmake_opt=() 175 | ;; 176 | 28) 177 | cmake_opt=( 178 | -D MACOSX_PLUGIN_BUNDLE_TYPE=BNDL 179 | -D OBS_BUNDLE_CODESIGN_IDENTITY='${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' 180 | ) 181 | ;; 182 | esac 183 | cmake -S . -B build -G Ninja \ 184 | -D QT_VERSION=$OBS_QT_VERSION_MAJOR \ 185 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 186 | -DCMAKE_PREFIX_PATH="$PWD/release/" \ 187 | -DCMAKE_OSX_ARCHITECTURES=${arch/#universal/x86_64;arm64} \ 188 | -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ 189 | -DCMAKE_FRAMEWORK_PATH="$deps/Frameworks;$deps/lib/cmake;$deps" \ 190 | -D PKG_SUFFIX=$PKG_SUFFIX \ 191 | "${cmake_opt[@]}" 192 | cmake --build build --config RelWithDebInfo 193 | 194 | - name: Prepare package 195 | run: | 196 | set -ex 197 | . build/ci/ci_includes.generated.sh 198 | cmake --install build --config RelWithDebInfo --prefix=release 199 | case ${{ matrix.obs }} in 200 | 27) 201 | (cd release/${PLUGIN_NAME} && ../../ci/macos/change-rpath.sh -obs ${{ matrix.obs }} -lib lib/ bin/${PLUGIN_NAME}.so) 202 | cp LICENSE release/${PLUGIN_NAME}/data/LICENSE-$PLUGIN_NAME 203 | ;; 204 | 28) 205 | (cd release/${PLUGIN_NAME}.plugin/Contents && ../../../ci/macos/change-rpath.sh -obs 28 -lib lib/ MacOS/${PLUGIN_NAME}) 206 | cp LICENSE release/${PLUGIN_NAME}.plugin/Contents/Resources/LICENSE-$PLUGIN_NAME 207 | ;; 208 | esac 209 | 210 | - name: Codesign 211 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 212 | run: | 213 | . build/ci/ci_includes.generated.sh 214 | set -ex 215 | case ${{ matrix.obs }} in 216 | 27) 217 | files=( 218 | $(find release/${PLUGIN_NAME}/ -name '*.dylib') 219 | release/${PLUGIN_NAME}/bin/${PLUGIN_NAME}.so 220 | ) 221 | ;; 222 | 28) 223 | files=( 224 | $(find release/${PLUGIN_NAME}.plugin/ -name '*.dylib') 225 | release/${PLUGIN_NAME}.plugin/Contents/MacOS/${PLUGIN_NAME} 226 | ) 227 | ;; 228 | esac 229 | for dylib in "${files[@]}"; do 230 | codesign --force --sign "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" "$dylib" 231 | done 232 | for dylib in "${files[@]}"; do 233 | codesign -vvv --deep --strict "$dylib" 234 | done 235 | 236 | - name: Package 237 | run: | 238 | . build/ci/ci_includes.generated.sh 239 | set -ex 240 | zipfile=$PWD/package/${PLUGIN_NAME}${PKG_SUFFIX}.zip 241 | mkdir package 242 | case ${{ matrix.obs }} in 243 | 27) (cd release/ && zip -r $zipfile ${PLUGIN_NAME}) ;; 244 | 28) (cd release/ && zip -r $zipfile ${PLUGIN_NAME}.plugin) ;; 245 | esac 246 | ci/macos/install-packagesbuild.sh 247 | packagesbuild \ 248 | --build-folder $PWD/package/ \ 249 | build/installer-macOS.generated.pkgproj 250 | 251 | - name: Productsign 252 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 253 | run: | 254 | . build/ci/ci_includes.generated.sh 255 | pkgfile=package/${PLUGIN_NAME}${PKG_SUFFIX}.pkg 256 | set -e 257 | . build/ci/ci_includes.generated.sh 258 | productsign --sign "${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}" $pkgfile package/${PLUGIN_NAME}-signed.pkg 259 | mv package/${PLUGIN_NAME}-signed.pkg $pkgfile 260 | 261 | - name: Notarize 262 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 263 | uses: norihiro/macos-notarize-action@v1 264 | with: 265 | path: package/* 266 | keychainProfile: AC_PASSWORD 267 | verbose: true 268 | 269 | - name: Upload build artifact 270 | uses: actions/upload-artifact@v4 271 | with: 272 | name: ${{ env.artifactName }}-macos-obs${{ matrix.obs }}-${{ matrix.arch }} 273 | path: package/* 274 | 275 | windows_build: 276 | runs-on: windows-2022 277 | strategy: 278 | fail-fast: false 279 | matrix: 280 | obs: [27] 281 | arch: [x64] 282 | env: 283 | visualStudio: 'Visual Studio 17 2022' 284 | Configuration: 'RelWithDebInfo' 285 | defaults: 286 | run: 287 | shell: pwsh 288 | steps: 289 | - name: Checkout 290 | uses: actions/checkout@v4 291 | with: 292 | submodules: recursive 293 | - name: Download obs-studio 294 | id: obsdeps 295 | uses: norihiro/obs-studio-devel-action@v1-beta 296 | with: 297 | obs: ${{ matrix.obs }} 298 | qt: ${{ env.qt }} 299 | 300 | - name: Build plugin 301 | run: | 302 | $CmakeArgs = @( 303 | '-G', "${{ env.visualStudio }}" 304 | '-DQT_VERSION=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }}' 305 | '-DCMAKE_SYSTEM_VERSION=10.0.18363.657' 306 | "-DCMAKE_INSTALL_PREFIX=$(Resolve-Path -Path "./obs-build-dependencies/plugin-deps-${{ matrix.arch }}")" 307 | "-DCMAKE_PREFIX_PATH=$(Resolve-Path -Path "./obs-build-dependencies/plugin-deps-${{ matrix.arch }}")" 308 | ) 309 | cmake -S . -B build @CmakeArgs 310 | cmake --build build --config RelWithDebInfo -j 4 311 | cmake --install build --config RelWithDebInfo --prefix "$(Resolve-Path -Path .)/release" 312 | - name: Package plugin 313 | run: ci/windows/package-windows.cmd ${{ matrix.obs }} 314 | - name: Upload build artifact 315 | uses: actions/upload-artifact@v4 316 | with: 317 | name: ${{ env.artifactName }}-windows-obs${{ matrix.obs }}-${{ matrix.arch }} 318 | path: package/* 319 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(obs-command-source VERSION 0.5.0) 4 | 5 | set(PLUGIN_AUTHOR "Norihiro Kamae") 6 | 7 | set(MACOS_BUNDLEID "net.nagater.obs-command-source") 8 | set(MACOS_PACKAGE_UUID "DE99133F-7DE7-4A40-BAD9-814A519F32C3") 9 | set(MACOS_INSTALLER_UUID "D42A28A8-FCE7-45F4-901B-5F333C2ABC94") 10 | set(PLUGIN_URL "https://obsproject.com/forum/resources/dummy-source-to-execute-command.952/") 11 | 12 | set(LINUX_MAINTAINER_EMAIL "norihiro@nagater.net") 13 | # NOTE: Also edit ci/plugin.spec for RPM when creating a new plugin. 14 | 15 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) 16 | find_package(libobs REQUIRED) 17 | find_package(obs-frontend-api REQUIRED) 18 | include(cmake/ObsPluginHelpers.cmake) 19 | add_library(OBS::frontend-api ALIAS OBS::obs-frontend-api) 20 | endif() 21 | 22 | configure_file( 23 | plugin-macros.h.in 24 | plugin-macros.generated.h 25 | ) 26 | 27 | set(PLUGIN_SOURCES 28 | obs-command-source.c 29 | ) 30 | 31 | add_library(${PROJECT_NAME} MODULE ${PLUGIN_SOURCES}) 32 | 33 | target_link_libraries(${PROJECT_NAME} 34 | OBS::libobs 35 | OBS::frontend-api 36 | ) 37 | 38 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 39 | 40 | if(OS_WINDOWS) 41 | # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL when building with VS2019) 42 | if (MSVC) 43 | add_definitions(/MP /d2FH4-) 44 | endif() 45 | 46 | target_link_libraries(${PROJECT_NAME} OBS::w32-pthreads) 47 | endif() 48 | 49 | if(OS_LINUX) 50 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) 51 | endif() 52 | 53 | if(APPLE) 54 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") 55 | 56 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") 57 | set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") 58 | set(MACOSX_PLUGIN_BUNDLE_VERSION "${PROJECT_VERSION}") 59 | set(MACOSX_PLUGIN_SHORT_VERSION_STRING "1") 60 | endif() 61 | 62 | setup_plugin_target(${PROJECT_NAME}) 63 | 64 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) 65 | configure_file( 66 | installer/installer-Windows.iss.in 67 | installer-Windows.generated.iss 68 | ) 69 | 70 | configure_file( 71 | ci/ci_includes.sh.in 72 | ci/ci_includes.generated.sh 73 | ) 74 | configure_file( 75 | ci/ci_includes.cmd.in 76 | ci/ci_includes.generated.cmd 77 | ) 78 | 79 | configure_file( 80 | installer/installer-macOS.pkgproj.in 81 | installer-macOS.generated.pkgproj 82 | ) 83 | endif() 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dummy Source to Execute Command 2 | =============================== 3 | 4 | This plugin provides a dummy source to execute arbitrary commands when a scene is switched. 5 | 6 | ## Current Features 7 | 8 | * Start a command at the following events. 9 | * Show (the source is shown in either preview or program) 10 | * Hide (the source is hidden so that no longer shown in neither preview nor program) 11 | * Activate (the source goes to the program) 12 | * Deactivate (the source goes from the program) 13 | * Show in preview (the source goes to the preview) 14 | * Hide from preview (the source goes from the preview) 15 | * Optionally, kill the created process at these conditions. This feature is not available for Windows as of now. 16 | * When hiding, kill the process created at shown. 17 | * When deactivating, kill the process created at activated. 18 | * When hiding from the preview, kill the process created at preview. 19 | 20 | ## Possible Usage 21 | 22 | * Implementing custom tally lights. 23 | * Control PTZ cameras by switching the scene. 24 | You may combine with CURL to send some commands. 25 | * Start and stop a daemon program required for the scene. 26 | * Trigger other operations through websocket at the event. 27 | A helper script is available at `tools/request-websocket.py`. 28 | - Start or stop your streaming and recording. 29 | * Not limited to the above. 30 | 31 | ## Caution 32 | 33 | Since this plugin executes arbitrary commands, users need to consider security concerns. 34 | For example, when combining with `obs-websocket` plugin, 35 | a remote user could change property through the websocket interface so that the arbitrary commands can be executed. 36 | 37 | ## Build 38 | 39 | ### Linux 40 | ``` 41 | git clone https://github.com/norihiro/obs-command-source.git 42 | cd obs-command-source 43 | mkdir build && cd build 44 | cmake \ 45 | -DLIBOBS_INCLUDE_DIR="" \ 46 | -DCMAKE_INSTALL_PREFIX=/usr .. 47 | make -j4 48 | sudo make install 49 | ``` 50 | 51 | ### macOS 52 | ``` 53 | git clone https://github.com/norihiro/obs-command-source.git 54 | cd obs-command-source 55 | mkdir build && cd build 56 | cmake \ 57 | -DLIBOBS_INCLUDE_DIR= \ 58 | -DLIBOBS_LIB= \ 59 | -DOBS_FRONTEND_LIB= \ 60 | .. 61 | make -j4 62 | # Copy obs-command-source.so to the obs-plugins folder 63 | ``` 64 | -------------------------------------------------------------------------------- /ci/ci_includes.cmd.in: -------------------------------------------------------------------------------- 1 | set PluginName=@PROJECT_NAME@ 2 | set PluginVersion=@PROJECT_VERSION@ 3 | -------------------------------------------------------------------------------- /ci/ci_includes.sh.in: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME="@PROJECT_NAME@" 2 | PLUGIN_VERSION="@PROJECT_VERSION@" 3 | MACOS_BUNDLEID="@MACOS_BUNDLEID@" 4 | LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" 5 | PKG_SUFFIX='@PKG_SUFFIX@' 6 | -------------------------------------------------------------------------------- /ci/forum-update-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin_url": "https://obsproject.com/forum/resources/dummy-source-to-execute-command.952/" 3 | } 4 | -------------------------------------------------------------------------------- /ci/macos/change-rpath.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | libdir='' 4 | obsver='' 5 | 6 | while (($# > 0)); do 7 | case "$1" in 8 | -lib) 9 | libdir="$2" 10 | shift 2;; 11 | -obs) 12 | obsver="$2" 13 | shift 2;; 14 | *) 15 | break ;; 16 | esac 17 | done 18 | 19 | set -e 20 | 21 | function copy_local_dylib 22 | { 23 | local dylib 24 | t=$(mktemp) 25 | otool -L $1 > $t 26 | awk '/^ \/usr\/local\/(opt|Cellar)\/.*\.dylib/{print $1}' $t | 27 | while read -r dylib; do 28 | echo "Changing dependency $1 -> $dylib" 29 | local b=$(basename $dylib) 30 | if test ! -e $libdir/$b; then 31 | mkdir -p $libdir 32 | cp $dylib $libdir 33 | chmod +rwx $libdir/$b 34 | install_name_tool -id "@loader_path/$b" $libdir/$b 35 | copy_local_dylib $libdir/$b 36 | fi 37 | install_name_tool -change "$dylib" "@loader_path/../$libdir/$b" $1 38 | done 39 | rm -f "$t" 40 | } 41 | 42 | function change_obs27_libs 43 | { 44 | # obs-frontend-api: 45 | # OBS 27.2 provides only `libobs-frontend-api.dylib`. 46 | # OBS 28.0 will provide `libobs-frontend-api.1.dylib` and `libobs-frontend-api.dylib`. 47 | # libobs: 48 | # Both OBS 27.2 and 28.0 provides `libobs.dylib`, `libobs.0.dylib`, `libobs.framework/Versions/A/libobs`. 49 | 50 | install_name_tool \ 51 | -change @rpath/QtWidgets.framework/Versions/5/QtWidgets \ 52 | @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ 53 | -change @rpath/QtGui.framework/Versions/5/QtGui \ 54 | @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ 55 | -change @rpath/QtCore.framework/Versions/5/QtCore \ 56 | @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ 57 | -change @rpath/libobs.framework/Versions/A/libobs \ 58 | @rpath/libobs.0.dylib \ 59 | -change @rpath/libobs-frontend-api.0.dylib \ 60 | @rpath/libobs-frontend-api.dylib \ 61 | "$1" 62 | } 63 | 64 | for i in "$@"; do 65 | case "$obsver" in 66 | 27 | 27.*) 67 | change_obs27_libs "$i" 68 | ;; 69 | 28 | 28.*) 70 | : # Not necessary to change dylib paths for OBS 28 71 | ;; 72 | esac 73 | copy_local_dylib "$i" 74 | done 75 | -------------------------------------------------------------------------------- /ci/macos/install-packagesbuild.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | if which packagesbuild; then 6 | exit 0 7 | fi 8 | 9 | packages_url='http://www.nagater.net/obs-studio/Packages.dmg' 10 | packages_hash='6afdd25386295974dad8f078b8f1e41cabebd08e72d970bf92f707c7e48b16c9' 11 | 12 | for ((retry=5; retry>0; retry--)); do 13 | curl -o Packages.dmg $packages_url 14 | sha256sum -c <<<"$packages_hash Packages.dmg" && break 15 | done 16 | 17 | hdiutil attach -noverify Packages.dmg 18 | packages_volume="$(hdiutil info -plist | grep '/Volumes/Packages' | sed 's/.*\(\/Volumes\/[^<]*\)<\/string>/\1/')" 19 | 20 | sudo installer -pkg "${packages_volume}/packages/Packages.pkg" -target / 21 | hdiutil detach "${packages_volume}" 22 | -------------------------------------------------------------------------------- /ci/plugin.spec: -------------------------------------------------------------------------------- 1 | Name: @PLUGIN_NAME_FEDORA@ 2 | Version: @VERSION@ 3 | Release: @RELEASE@%{?dist} 4 | Summary: Dummy command source plugin for OBS Studio 5 | License: GPLv2+ 6 | 7 | Source0: %{name}-%{version}.tar.bz2 8 | BuildRequires: cmake, gcc, gcc-c++ 9 | BuildRequires: obs-studio-devel 10 | Obsoletes: @PLUGIN_NAME@ < %{version} 11 | 12 | %description 13 | Command source plugin for OBS Studio provides a dummy source that execute 14 | arbitrary commands when a scene is switched. 15 | 16 | %prep 17 | %autosetup -p1 18 | 19 | %build 20 | %{cmake} -DLINUX_PORTABLE=OFF -DLINUX_RPATH=OFF 21 | %{cmake_build} 22 | 23 | %install 24 | %{cmake_install} 25 | 26 | %files 27 | %{_libdir}/obs-plugins/@PLUGIN_NAME@.so 28 | %{_datadir}/obs/obs-plugins/@PLUGIN_NAME@/ 29 | -------------------------------------------------------------------------------- /ci/windows/package-windows.cmd: -------------------------------------------------------------------------------- 1 | call "build\ci\ci_includes.generated.cmd" 2 | 3 | mkdir package 4 | cd package 5 | 6 | git describe --tags > package-version.txt 7 | set /p PackageVersion= 266 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 267 | COMPONENT obs_rundir 268 | EXCLUDE_FROM_ALL) 269 | 270 | if(OS_WINDOWS) 271 | install( 272 | FILES $ 273 | CONFIGURATIONS "RelWithDebInfo" "Debug" 274 | DESTINATION ${OBS_PLUGIN_DESTINATION} 275 | COMPONENT ${target}_Runtime 276 | OPTIONAL) 277 | 278 | install( 279 | FILES $ 280 | CONFIGURATIONS "RelWithDebInfo" "Debug" 281 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 282 | COMPONENT obs_rundir 283 | OPTIONAL EXCLUDE_FROM_ALL) 284 | endif() 285 | 286 | if(MSVC) 287 | target_link_options( 288 | ${target} 289 | PRIVATE 290 | "LINKER:/OPT:REF" 291 | "$<$>:LINKER\:/SAFESEH\:NO>" 292 | "$<$:LINKER\:/INCREMENTAL:NO>" 293 | "$<$:LINKER\:/INCREMENTAL:NO>") 294 | endif() 295 | 296 | setup_target_resources(${target} obs-plugins/${target}) 297 | 298 | if(OS_WINDOWS) 299 | add_custom_command( 300 | TARGET ${target} 301 | POST_BUILD 302 | COMMAND 303 | "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR} 304 | -DCMAKE_INSTALL_COMPONENT=obs_rundir 305 | -DCMAKE_INSTALL_CONFIG_NAME=$ -P 306 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake 307 | COMMENT "Installing to plugin rundir" 308 | VERBATIM) 309 | endif() 310 | endfunction() 311 | 312 | function(setup_target_resources target destination) 313 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) 314 | install( 315 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ 316 | DESTINATION ${OBS_DATA_DESTINATION}/${destination} 317 | USE_SOURCE_PERMISSIONS 318 | COMPONENT obs_plugins) 319 | 320 | install( 321 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data 322 | DESTINATION $/${OBS_DATA_DESTINATION}/${destination} 323 | USE_SOURCE_PERMISSIONS 324 | COMPONENT obs_rundir 325 | EXCLUDE_FROM_ALL) 326 | endif() 327 | endfunction() 328 | endif() 329 | -------------------------------------------------------------------------------- /cmake/bundle/macos/Plugin-Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | ${MACOSX_PLUGIN_BUNDLE_NAME} 7 | CFBundleIdentifier 8 | ${MACOSX_PLUGIN_GUI_IDENTIFIER} 9 | CFBundleVersion 10 | ${MACOSX_PLUGIN_BUNDLE_VERSION} 11 | CFBundleShortVersionString 12 | ${MACOSX_PLUGIN_SHORT_VERSION_STRING} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleExecutable 16 | ${MACOSX_PLUGIN_EXECUTABLE_NAME} 17 | CFBundlePackageType 18 | ${MACOSX_PLUGIN_BUNDLE_TYPE} 19 | CFBundleSupportedPlatforms 20 | 21 | MacOSX 22 | 23 | LSMinimumSystemVersion 24 | 10.13 25 | 26 | 27 | -------------------------------------------------------------------------------- /cmake/bundle/macos/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | com.apple.security.cs.allow-dyld-environment-variables 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/locale/en-US.ini: -------------------------------------------------------------------------------- 1 | execute-command="Execute Command" 2 | shown="Shown" 3 | hidden="Hidden" 4 | activated="Activated" 5 | deactivated="Deactivated" 6 | shown-in-preview="Shown in preview" 7 | hidden-from-preview="Hidden from preview" 8 | prop-sig-show="Send signal to the command at shown when hiding" 9 | prop-sig-active="Send signal to the command at activated when deactivating" 10 | prop-sig-preview="Send signal to the command at preview when hiding from preview" 11 | prop-sig="Signal" 12 | -------------------------------------------------------------------------------- /data/locale/ja-JP.ini: -------------------------------------------------------------------------------- 1 | execute-command="コマンド実行" 2 | shown="表示時" 3 | hidden="非表示時" 4 | activated="アクティブ時" 5 | deactivated="非アクティブ時" 6 | shown-in-preview="プレビュー時" 7 | hidden-from-preview="プレビューからの非表示時" 8 | prop-sig-show="非表示時に、表示時コマンドへシグナルを送る" 9 | prop-sig-active="非アクティブ時に、アクティブ時に実行したコマンドへシグナルを送る" 10 | prop-sig-preview="プレビューからの非表示時に、プレビュー時に実行したコマンドへシグナルを送る" 11 | prop-sig="シグナル" 12 | -------------------------------------------------------------------------------- /data/locale/pt-BR.ini: -------------------------------------------------------------------------------- 1 | execute-command="Executar Comando" 2 | shown="Exibindo" 3 | hidden="Escondido" 4 | activated="Ativado" 5 | deactivated="Desativado" 6 | shown-in-preview="Exibindo na pré-visualização" 7 | hidden-from-preview="Escondido da pré-visualização" 8 | prop-sig-show="Enviar sinal ao comando em Exibindo quando saindo dela" 9 | prop-sig-active="Enviar sinal ao comando em Ativado quando desativando" 10 | prop-sig-preview="Enviar sinal ao comando em pré-visualização quando saindo dela" 11 | prop-sig="Sinal" 12 | -------------------------------------------------------------------------------- /doc/environments.md: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | 3 | This is an experimental feature for Linux and macOS. The behavior might change in future releases. 4 | Environment variables are set when calling a process. 5 | 6 | ### `OBS_CURRENT_SCENE` 7 | Name of the current scene. 8 | In the command for `Activated` and `Deactivated`, 9 | the new scene name will be set to this variable. 10 | 11 | ### `OBS_PREVIEW_SCENE` 12 | Name of the current scene in preview. 13 | 14 | ### `OBS_SOURCE_NAME` 15 | Name of the command source. 16 | It is useful to set the visibility of a scene item by a combination of `OBS_CURRENT_SCENE` or `OBS_PREVIEW_SCENE`. 17 | 18 | ### `OBS_TRANSITION_DURATION` 19 | Duration in milliseconds [ms] for the current transition. 20 | -------------------------------------------------------------------------------- /installer/installer-Windows.iss.in: -------------------------------------------------------------------------------- 1 | #define MyAppName "@PROJECT_NAME@" 2 | #define MyAppVersion "@PROJECT_VERSION@" 3 | #define MyAppPublisher "@PLUGIN_AUTHOR@" 4 | #define MyAppURL "@PLUGIN_URL@" 5 | 6 | [Setup] 7 | ; NOTE: The value of AppId uniquely identifies this application. 8 | ; Do not use the same AppId value in installers for other applications. 9 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 10 | AppId={{CD703FE5-1F2C-4837-BD3D-DD840D83C3E3} 11 | AppName={#MyAppName} 12 | AppVersion={#MyAppVersion} 13 | AppPublisher={#MyAppPublisher} 14 | AppPublisherURL={#MyAppURL} 15 | AppSupportURL={#MyAppURL} 16 | AppUpdatesURL={#MyAppURL} 17 | DefaultDirName={code:GetDirName} 18 | DefaultGroupName={#MyAppName} 19 | OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer 20 | Compression=lzma 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Files] 27 | Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 28 | Source: "..\LICENSE"; Flags: dontcopy 29 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 30 | 31 | [Icons] 32 | Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" 33 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 34 | 35 | [Code] 36 | procedure InitializeWizard(); 37 | var 38 | GPLText: AnsiString; 39 | Page: TOutputMsgMemoWizardPage; 40 | begin 41 | ExtractTemporaryFile('LICENSE'); 42 | LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText); 43 | Page := CreateOutputMsgMemoPage(wpWelcome, 44 | 'License Information', 'Please review the license terms before installing {#MyAppName}', 45 | 'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.', 46 | String(GPLText) 47 | ); 48 | end; 49 | 50 | // credit where it's due : 51 | // following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45 52 | function GetDirName(Value: string): string; 53 | var 54 | InstallPath: string; 55 | begin 56 | // initialize default path, which will be returned when the following registry 57 | // key queries fail due to missing keys or for some different reason 58 | Result := '{pf}\obs-studio'; 59 | // query the first registry value; if this succeeds, return the obtained value 60 | if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then 61 | Result := InstallPath 62 | end; 63 | 64 | -------------------------------------------------------------------------------- /installer/installer-macOS.pkgproj.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PACKAGES 6 | 7 | 8 | MUST-CLOSE-APPLICATION-ITEMS 9 | 10 | MUST-CLOSE-APPLICATIONS 11 | 12 | PACKAGE_FILES 13 | 14 | DEFAULT_INSTALL_LOCATION 15 | / 16 | HIERARCHY 17 | 18 | CHILDREN 19 | 20 | 21 | CHILDREN 22 | 23 | GID 24 | 80 25 | PATH 26 | Applications 27 | PATH_TYPE 28 | 0 29 | PERMISSIONS 30 | 509 31 | TYPE 32 | 1 33 | UID 34 | 0 35 | 36 | 37 | CHILDREN 38 | 39 | 40 | CHILDREN 41 | 42 | 43 | CHILDREN 44 | 45 | 46 | CHILDREN 47 | 48 | 49 | BUNDLE_CAN_DOWNGRADE 50 | 51 | BUNDLE_POSTINSTALL_PATH 52 | 53 | PATH_TYPE 54 | 0 55 | 56 | BUNDLE_PREINSTALL_PATH 57 | 58 | PATH_TYPE 59 | 0 60 | 61 | CHILDREN 62 | 63 | GID 64 | 80 65 | PATH 66 | ../@RELATIVE_INSTALL_PATH@/@PROJECT_NAME@@FIRST_DIR_SUFFIX@ 67 | PATH_TYPE 68 | 1 69 | PERMISSIONS 70 | 493 71 | TYPE 72 | 3 73 | UID 74 | 0 75 | 76 | 77 | GID 78 | 80 79 | PATH 80 | plugins 81 | PATH_TYPE 82 | 2 83 | PERMISSIONS 84 | 509 85 | TYPE 86 | 2 87 | UID 88 | 0 89 | 90 | 91 | GID 92 | 80 93 | PATH 94 | obs-studio 95 | PATH_TYPE 96 | 2 97 | PERMISSIONS 98 | 509 99 | TYPE 100 | 2 101 | UID 102 | 0 103 | 104 | 105 | GID 106 | 80 107 | PATH 108 | Application Support 109 | PATH_TYPE 110 | 0 111 | PERMISSIONS 112 | 493 113 | TYPE 114 | 1 115 | UID 116 | 0 117 | 118 | 119 | CHILDREN 120 | 121 | GID 122 | 0 123 | PATH 124 | Automator 125 | PATH_TYPE 126 | 0 127 | PERMISSIONS 128 | 493 129 | TYPE 130 | 1 131 | UID 132 | 0 133 | 134 | 135 | CHILDREN 136 | 137 | GID 138 | 0 139 | PATH 140 | Documentation 141 | PATH_TYPE 142 | 0 143 | PERMISSIONS 144 | 493 145 | TYPE 146 | 1 147 | UID 148 | 0 149 | 150 | 151 | CHILDREN 152 | 153 | GID 154 | 0 155 | PATH 156 | Extensions 157 | PATH_TYPE 158 | 0 159 | PERMISSIONS 160 | 493 161 | TYPE 162 | 1 163 | UID 164 | 0 165 | 166 | 167 | CHILDREN 168 | 169 | GID 170 | 0 171 | PATH 172 | Filesystems 173 | PATH_TYPE 174 | 0 175 | PERMISSIONS 176 | 493 177 | TYPE 178 | 1 179 | UID 180 | 0 181 | 182 | 183 | CHILDREN 184 | 185 | GID 186 | 0 187 | PATH 188 | Frameworks 189 | PATH_TYPE 190 | 0 191 | PERMISSIONS 192 | 493 193 | TYPE 194 | 1 195 | UID 196 | 0 197 | 198 | 199 | CHILDREN 200 | 201 | GID 202 | 0 203 | PATH 204 | Input Methods 205 | PATH_TYPE 206 | 0 207 | PERMISSIONS 208 | 493 209 | TYPE 210 | 1 211 | UID 212 | 0 213 | 214 | 215 | CHILDREN 216 | 217 | GID 218 | 0 219 | PATH 220 | Internet Plug-Ins 221 | PATH_TYPE 222 | 0 223 | PERMISSIONS 224 | 493 225 | TYPE 226 | 1 227 | UID 228 | 0 229 | 230 | 231 | CHILDREN 232 | 233 | GID 234 | 0 235 | PATH 236 | LaunchAgents 237 | PATH_TYPE 238 | 0 239 | PERMISSIONS 240 | 493 241 | TYPE 242 | 1 243 | UID 244 | 0 245 | 246 | 247 | CHILDREN 248 | 249 | GID 250 | 0 251 | PATH 252 | LaunchDaemons 253 | PATH_TYPE 254 | 0 255 | PERMISSIONS 256 | 493 257 | TYPE 258 | 1 259 | UID 260 | 0 261 | 262 | 263 | CHILDREN 264 | 265 | GID 266 | 0 267 | PATH 268 | PreferencePanes 269 | PATH_TYPE 270 | 0 271 | PERMISSIONS 272 | 493 273 | TYPE 274 | 1 275 | UID 276 | 0 277 | 278 | 279 | CHILDREN 280 | 281 | GID 282 | 0 283 | PATH 284 | Preferences 285 | PATH_TYPE 286 | 0 287 | PERMISSIONS 288 | 493 289 | TYPE 290 | 1 291 | UID 292 | 0 293 | 294 | 295 | CHILDREN 296 | 297 | GID 298 | 80 299 | PATH 300 | Printers 301 | PATH_TYPE 302 | 0 303 | PERMISSIONS 304 | 493 305 | TYPE 306 | 1 307 | UID 308 | 0 309 | 310 | 311 | CHILDREN 312 | 313 | GID 314 | 0 315 | PATH 316 | PrivilegedHelperTools 317 | PATH_TYPE 318 | 0 319 | PERMISSIONS 320 | 1005 321 | TYPE 322 | 1 323 | UID 324 | 0 325 | 326 | 327 | CHILDREN 328 | 329 | GID 330 | 0 331 | PATH 332 | QuickLook 333 | PATH_TYPE 334 | 0 335 | PERMISSIONS 336 | 493 337 | TYPE 338 | 1 339 | UID 340 | 0 341 | 342 | 343 | CHILDREN 344 | 345 | GID 346 | 0 347 | PATH 348 | QuickTime 349 | PATH_TYPE 350 | 0 351 | PERMISSIONS 352 | 493 353 | TYPE 354 | 1 355 | UID 356 | 0 357 | 358 | 359 | CHILDREN 360 | 361 | GID 362 | 0 363 | PATH 364 | Screen Savers 365 | PATH_TYPE 366 | 0 367 | PERMISSIONS 368 | 493 369 | TYPE 370 | 1 371 | UID 372 | 0 373 | 374 | 375 | CHILDREN 376 | 377 | GID 378 | 0 379 | PATH 380 | Scripts 381 | PATH_TYPE 382 | 0 383 | PERMISSIONS 384 | 493 385 | TYPE 386 | 1 387 | UID 388 | 0 389 | 390 | 391 | CHILDREN 392 | 393 | GID 394 | 0 395 | PATH 396 | Services 397 | PATH_TYPE 398 | 0 399 | PERMISSIONS 400 | 493 401 | TYPE 402 | 1 403 | UID 404 | 0 405 | 406 | 407 | CHILDREN 408 | 409 | GID 410 | 0 411 | PATH 412 | Widgets 413 | PATH_TYPE 414 | 0 415 | PERMISSIONS 416 | 493 417 | TYPE 418 | 1 419 | UID 420 | 0 421 | 422 | 423 | GID 424 | 0 425 | PATH 426 | Library 427 | PATH_TYPE 428 | 0 429 | PERMISSIONS 430 | 493 431 | TYPE 432 | 1 433 | UID 434 | 0 435 | 436 | 437 | CHILDREN 438 | 439 | 440 | CHILDREN 441 | 442 | GID 443 | 0 444 | PATH 445 | Shared 446 | PATH_TYPE 447 | 0 448 | PERMISSIONS 449 | 1023 450 | TYPE 451 | 1 452 | UID 453 | 0 454 | 455 | 456 | GID 457 | 80 458 | PATH 459 | Users 460 | PATH_TYPE 461 | 0 462 | PERMISSIONS 463 | 493 464 | TYPE 465 | 1 466 | UID 467 | 0 468 | 469 | 470 | GID 471 | 0 472 | PATH 473 | / 474 | PATH_TYPE 475 | 0 476 | PERMISSIONS 477 | 493 478 | TYPE 479 | 1 480 | UID 481 | 0 482 | 483 | PAYLOAD_TYPE 484 | 0 485 | PRESERVE_EXTENDED_ATTRIBUTES 486 | 487 | SHOW_INVISIBLE 488 | 489 | SPLIT_FORKS 490 | 491 | TREAT_MISSING_FILES_AS_WARNING 492 | 493 | VERSION 494 | 5 495 | 496 | PACKAGE_SCRIPTS 497 | 498 | POSTINSTALL_PATH 499 | 500 | PATH_TYPE 501 | 0 502 | 503 | PREINSTALL_PATH 504 | 505 | PATH_TYPE 506 | 0 507 | 508 | RESOURCES 509 | 510 | 511 | PACKAGE_SETTINGS 512 | 513 | AUTHENTICATION 514 | 0 515 | CONCLUSION_ACTION 516 | 0 517 | FOLLOW_SYMBOLIC_LINKS 518 | 519 | IDENTIFIER 520 | @MACOS_BUNDLEID@ 521 | LOCATION 522 | 0 523 | NAME 524 | @PROJECT_NAME@ 525 | OVERWRITE_PERMISSIONS 526 | 527 | PAYLOAD_SIZE 528 | -1 529 | REFERENCE_PATH 530 | 531 | RELOCATABLE 532 | 533 | USE_HFS+_COMPRESSION 534 | 535 | VERSION 536 | @PROJECT_VERSION@ 537 | 538 | TYPE 539 | 0 540 | UUID 541 | @MACOS_PACKAGE_UUID@ 542 | 543 | 544 | PROJECT 545 | 546 | PROJECT_COMMENTS 547 | 548 | NOTES 549 | 550 | 551 | 552 | PROJECT_PRESENTATION 553 | 554 | BACKGROUND 555 | 556 | APPAREANCES 557 | 558 | DARK_AQUA 559 | 560 | LIGHT_AQUA 561 | 562 | 563 | SHARED_SETTINGS_FOR_ALL_APPAREANCES 564 | 565 | 566 | INSTALLATION TYPE 567 | 568 | HIERARCHIES 569 | 570 | INSTALLER 571 | 572 | LIST 573 | 574 | 575 | CHILDREN 576 | 577 | DESCRIPTION 578 | 579 | OPTIONS 580 | 581 | HIDDEN 582 | 583 | STATE 584 | 1 585 | 586 | PACKAGE_UUID 587 | @MACOS_PACKAGE_UUID@ 588 | TITLE 589 | 590 | TYPE 591 | 0 592 | UUID 593 | @MACOS_INSTALLER_UUID@ 594 | 595 | 596 | REMOVED 597 | 598 | 599 | 600 | MODE 601 | 0 602 | 603 | INSTALLATION_STEPS 604 | 605 | 606 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 607 | ICPresentationViewIntroductionController 608 | INSTALLER_PLUGIN 609 | Introduction 610 | LIST_TITLE_KEY 611 | InstallerSectionTitle 612 | 613 | 614 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 615 | ICPresentationViewReadMeController 616 | INSTALLER_PLUGIN 617 | ReadMe 618 | LIST_TITLE_KEY 619 | InstallerSectionTitle 620 | 621 | 622 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 623 | ICPresentationViewLicenseController 624 | INSTALLER_PLUGIN 625 | License 626 | LIST_TITLE_KEY 627 | InstallerSectionTitle 628 | 629 | 630 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 631 | ICPresentationViewDestinationSelectController 632 | INSTALLER_PLUGIN 633 | TargetSelect 634 | LIST_TITLE_KEY 635 | InstallerSectionTitle 636 | 637 | 638 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 639 | ICPresentationViewInstallationTypeController 640 | INSTALLER_PLUGIN 641 | PackageSelection 642 | LIST_TITLE_KEY 643 | InstallerSectionTitle 644 | 645 | 646 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 647 | ICPresentationViewInstallationController 648 | INSTALLER_PLUGIN 649 | Install 650 | LIST_TITLE_KEY 651 | InstallerSectionTitle 652 | 653 | 654 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 655 | ICPresentationViewSummaryController 656 | INSTALLER_PLUGIN 657 | Summary 658 | LIST_TITLE_KEY 659 | InstallerSectionTitle 660 | 661 | 662 | INTRODUCTION 663 | 664 | LOCALIZATIONS 665 | 666 | 667 | LICENSE 668 | 669 | LOCALIZATIONS 670 | 671 | MODE 672 | 0 673 | 674 | README 675 | 676 | LOCALIZATIONS 677 | 678 | 679 | SUMMARY 680 | 681 | LOCALIZATIONS 682 | 683 | 684 | TITLE 685 | 686 | LOCALIZATIONS 687 | 688 | 689 | 690 | PROJECT_REQUIREMENTS 691 | 692 | LIST 693 | 694 | 695 | BEHAVIOR 696 | 3 697 | DICTIONARY 698 | 699 | IC_REQUIREMENT_OS_DISK_TYPE 700 | 1 701 | IC_REQUIREMENT_OS_DISTRIBUTION_TYPE 702 | 0 703 | IC_REQUIREMENT_OS_MINIMUM_VERSION 704 | 101300 705 | 706 | IC_REQUIREMENT_CHECK_TYPE 707 | 0 708 | IDENTIFIER 709 | fr.whitebox.Packages.requirement.os 710 | MESSAGE 711 | 712 | NAME 713 | Operating System 714 | STATE 715 | 716 | 717 | 718 | RESOURCES 719 | 720 | ROOT_VOLUME_ONLY 721 | 722 | 723 | PROJECT_SETTINGS 724 | 725 | ADVANCED_OPTIONS 726 | 727 | installer-script.domains:enable_currentUserHome 728 | 1 729 | 730 | BUILD_FORMAT 731 | 0 732 | BUILD_PATH 733 | 734 | PATH 735 | ../@RELATIVE_BUILD_PATH@ 736 | PATH_TYPE 737 | 1 738 | 739 | EXCLUDED_FILES 740 | 741 | 742 | PATTERNS_ARRAY 743 | 744 | 745 | REGULAR_EXPRESSION 746 | 747 | STRING 748 | .DS_Store 749 | TYPE 750 | 0 751 | 752 | 753 | PROTECTED 754 | 755 | PROXY_NAME 756 | Remove .DS_Store files 757 | PROXY_TOOLTIP 758 | Remove ".DS_Store" files created by the Finder. 759 | STATE 760 | 761 | 762 | 763 | PATTERNS_ARRAY 764 | 765 | 766 | REGULAR_EXPRESSION 767 | 768 | STRING 769 | .pbdevelopment 770 | TYPE 771 | 0 772 | 773 | 774 | PROTECTED 775 | 776 | PROXY_NAME 777 | Remove .pbdevelopment files 778 | PROXY_TOOLTIP 779 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 780 | STATE 781 | 782 | 783 | 784 | PATTERNS_ARRAY 785 | 786 | 787 | REGULAR_EXPRESSION 788 | 789 | STRING 790 | CVS 791 | TYPE 792 | 1 793 | 794 | 795 | REGULAR_EXPRESSION 796 | 797 | STRING 798 | .cvsignore 799 | TYPE 800 | 0 801 | 802 | 803 | REGULAR_EXPRESSION 804 | 805 | STRING 806 | .cvspass 807 | TYPE 808 | 0 809 | 810 | 811 | REGULAR_EXPRESSION 812 | 813 | STRING 814 | .svn 815 | TYPE 816 | 1 817 | 818 | 819 | REGULAR_EXPRESSION 820 | 821 | STRING 822 | .git 823 | TYPE 824 | 1 825 | 826 | 827 | REGULAR_EXPRESSION 828 | 829 | STRING 830 | .gitignore 831 | TYPE 832 | 0 833 | 834 | 835 | PROTECTED 836 | 837 | PROXY_NAME 838 | Remove SCM metadata 839 | PROXY_TOOLTIP 840 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 841 | STATE 842 | 843 | 844 | 845 | PATTERNS_ARRAY 846 | 847 | 848 | REGULAR_EXPRESSION 849 | 850 | STRING 851 | classes.nib 852 | TYPE 853 | 0 854 | 855 | 856 | REGULAR_EXPRESSION 857 | 858 | STRING 859 | designable.db 860 | TYPE 861 | 0 862 | 863 | 864 | REGULAR_EXPRESSION 865 | 866 | STRING 867 | info.nib 868 | TYPE 869 | 0 870 | 871 | 872 | PROTECTED 873 | 874 | PROXY_NAME 875 | Optimize nib files 876 | PROXY_TOOLTIP 877 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 878 | STATE 879 | 880 | 881 | 882 | PATTERNS_ARRAY 883 | 884 | 885 | REGULAR_EXPRESSION 886 | 887 | STRING 888 | Resources Disabled 889 | TYPE 890 | 1 891 | 892 | 893 | PROTECTED 894 | 895 | PROXY_NAME 896 | Remove Resources Disabled folders 897 | PROXY_TOOLTIP 898 | Remove "Resources Disabled" folders. 899 | STATE 900 | 901 | 902 | 903 | SEPARATOR 904 | 905 | 906 | 907 | NAME 908 | @PROJECT_NAME@@PKG_SUFFIX@ 909 | PAYLOAD_ONLY 910 | 911 | TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING 912 | 913 | 914 | 915 | TYPE 916 | 0 917 | VERSION 918 | 2 919 | 920 | 921 | -------------------------------------------------------------------------------- /installer/obs-command-source.pkgproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PACKAGES 6 | 7 | 8 | MUST-CLOSE-APPLICATION-ITEMS 9 | 10 | MUST-CLOSE-APPLICATIONS 11 | 12 | PACKAGE_FILES 13 | 14 | DEFAULT_INSTALL_LOCATION 15 | / 16 | HIERARCHY 17 | 18 | CHILDREN 19 | 20 | 21 | CHILDREN 22 | 23 | GID 24 | 80 25 | PATH 26 | Applications 27 | PATH_TYPE 28 | 0 29 | PERMISSIONS 30 | 509 31 | TYPE 32 | 1 33 | UID 34 | 0 35 | 36 | 37 | CHILDREN 38 | 39 | 40 | CHILDREN 41 | 42 | 43 | CHILDREN 44 | 45 | 46 | CHILDREN 47 | 48 | 49 | CHILDREN 50 | 51 | 52 | CHILDREN 53 | 54 | 55 | CHILDREN 56 | 57 | GID 58 | 80 59 | PATH 60 | ../build/obs-command-source.so 61 | PATH_TYPE 62 | 1 63 | PERMISSIONS 64 | 493 65 | TYPE 66 | 3 67 | UID 68 | 0 69 | 70 | 71 | GID 72 | 80 73 | PATH 74 | bin 75 | PATH_TYPE 76 | 0 77 | PERMISSIONS 78 | 493 79 | TYPE 80 | 2 81 | UID 82 | 0 83 | 84 | 85 | CHILDREN 86 | 87 | GID 88 | 80 89 | PATH 90 | ../data 91 | PATH_TYPE 92 | 1 93 | PERMISSIONS 94 | 493 95 | TYPE 96 | 3 97 | UID 98 | 0 99 | 100 | 101 | GID 102 | 80 103 | PATH 104 | obs-command-source 105 | PATH_TYPE 106 | 0 107 | PERMISSIONS 108 | 493 109 | TYPE 110 | 2 111 | UID 112 | 0 113 | 114 | 115 | GID 116 | 80 117 | PATH 118 | plugins 119 | PATH_TYPE 120 | 0 121 | PERMISSIONS 122 | 493 123 | TYPE 124 | 2 125 | UID 126 | 0 127 | 128 | 129 | GID 130 | 80 131 | PATH 132 | obs-studio 133 | PATH_TYPE 134 | 0 135 | PERMISSIONS 136 | 493 137 | TYPE 138 | 2 139 | UID 140 | 0 141 | 142 | 143 | GID 144 | 80 145 | PATH 146 | Application Support 147 | PATH_TYPE 148 | 0 149 | PERMISSIONS 150 | 493 151 | TYPE 152 | 1 153 | UID 154 | 0 155 | 156 | 157 | GID 158 | 0 159 | PATH 160 | Library 161 | PATH_TYPE 162 | 0 163 | PERMISSIONS 164 | 493 165 | TYPE 166 | 1 167 | UID 168 | 0 169 | 170 | 171 | GID 172 | 0 173 | PATH 174 | / 175 | PATH_TYPE 176 | 0 177 | PERMISSIONS 178 | 493 179 | TYPE 180 | 1 181 | UID 182 | 0 183 | 184 | PAYLOAD_TYPE 185 | 0 186 | SHOW_INVISIBLE 187 | 188 | SPLIT_FORKS 189 | 190 | TREAT_MISSING_FILES_AS_WARNING 191 | 192 | VERSION 193 | 5 194 | 195 | PACKAGE_SCRIPTS 196 | 197 | RESOURCES 198 | 199 | 200 | PACKAGE_SETTINGS 201 | 202 | AUTHENTICATION 203 | 1 204 | CONCLUSION_ACTION 205 | 0 206 | FOLLOW_SYMBOLIC_LINKS 207 | 208 | IDENTIFIER 209 | net.nagater.obs-command-source 210 | LOCATION 211 | 0 212 | NAME 213 | obs-command-source plugin 214 | OVERWRITE_PERMISSIONS 215 | 216 | PAYLOAD_SIZE 217 | -1 218 | REFERENCE_PATH 219 | 220 | RELOCATABLE 221 | 222 | USE_HFS+_COMPRESSION 223 | 224 | VERSION 225 | 4.9.0 226 | 227 | TYPE 228 | 0 229 | UUID 230 | B687A502-5EF9-4574-854F-64FE86F50ED6 231 | 232 | 233 | PROJECT 234 | 235 | PROJECT_COMMENTS 236 | 237 | NOTES 238 | 239 | PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M 240 | IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv 241 | c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l 242 | cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 243 | IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 244 | ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp 245 | dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u 246 | dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD 247 | b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg 248 | dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 249 | Pgo8L2JvZHk+CjwvaHRtbD4K 250 | 251 | 252 | PROJECT_PRESENTATION 253 | 254 | BACKGROUND 255 | 256 | APPAREANCES 257 | 258 | DARK_AQUA 259 | 260 | LIGHT_AQUA 261 | 262 | 263 | SHARED_SETTINGS_FOR_ALL_APPAREANCES 264 | 265 | 266 | INSTALLATION_STEPS 267 | 268 | 269 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 270 | ICPresentationViewIntroductionController 271 | INSTALLER_PLUGIN 272 | Introduction 273 | LIST_TITLE_KEY 274 | InstallerSectionTitle 275 | 276 | 277 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 278 | ICPresentationViewReadMeController 279 | INSTALLER_PLUGIN 280 | ReadMe 281 | LIST_TITLE_KEY 282 | InstallerSectionTitle 283 | 284 | 285 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 286 | ICPresentationViewLicenseController 287 | INSTALLER_PLUGIN 288 | License 289 | LIST_TITLE_KEY 290 | InstallerSectionTitle 291 | 292 | 293 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 294 | ICPresentationViewDestinationSelectController 295 | INSTALLER_PLUGIN 296 | TargetSelect 297 | LIST_TITLE_KEY 298 | InstallerSectionTitle 299 | 300 | 301 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 302 | ICPresentationViewInstallationTypeController 303 | INSTALLER_PLUGIN 304 | PackageSelection 305 | LIST_TITLE_KEY 306 | InstallerSectionTitle 307 | 308 | 309 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 310 | ICPresentationViewInstallationController 311 | INSTALLER_PLUGIN 312 | Install 313 | LIST_TITLE_KEY 314 | InstallerSectionTitle 315 | 316 | 317 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 318 | ICPresentationViewSummaryController 319 | INSTALLER_PLUGIN 320 | Summary 321 | LIST_TITLE_KEY 322 | InstallerSectionTitle 323 | 324 | 325 | INTRODUCTION 326 | 327 | LOCALIZATIONS 328 | 329 | 330 | LICENSE 331 | 332 | LOCALIZATIONS 333 | 334 | MODE 335 | 0 336 | 337 | README 338 | 339 | LOCALIZATIONS 340 | 341 | 342 | SUMMARY 343 | 344 | LOCALIZATIONS 345 | 346 | 347 | TITLE 348 | 349 | LOCALIZATIONS 350 | 351 | 352 | LANGUAGE 353 | English 354 | VALUE 355 | obs-command-source 356 | 357 | 358 | 359 | 360 | PROJECT_REQUIREMENTS 361 | 362 | LIST 363 | 364 | 365 | BEHAVIOR 366 | 3 367 | DICTIONARY 368 | 369 | IC_REQUIREMENT_CPU_ARCHITECTURE_FAMILY 370 | 2 371 | IC_REQUIREMENT_CPU_INTEL_ARCHITECTURE_TYPE 372 | 2 373 | IC_REQUIREMENT_CPU_MINIMUM_CPU_CORES_COUNT 374 | 1 375 | IC_REQUIREMENT_CPU_MINIMUM_FREQUENCY 376 | 866666 377 | IC_REQUIREMENT_CPU_POWERPC_ARCHITECTURE_TYPE 378 | 0 379 | 380 | IC_REQUIREMENT_CHECK_TYPE 381 | 0 382 | IDENTIFIER 383 | fr.whitebox.Packages.requirement.cpu 384 | MESSAGE 385 | 386 | 387 | LANGUAGE 388 | English 389 | SECONDARY_VALUE 390 | 391 | VALUE 392 | Your processor is not supported by obs-command-source to function. 393 | 394 | 395 | NAME 396 | Processor 397 | STATE 398 | 399 | 400 | 401 | RESOURCES 402 | 403 | ROOT_VOLUME_ONLY 404 | 405 | 406 | PROJECT_SETTINGS 407 | 408 | BUILD_FORMAT 409 | 0 410 | BUILD_PATH 411 | 412 | PATH 413 | build 414 | PATH_TYPE 415 | 1 416 | 417 | EXCLUDED_FILES 418 | 419 | 420 | PATTERNS_ARRAY 421 | 422 | 423 | REGULAR_EXPRESSION 424 | 425 | STRING 426 | .DS_Store 427 | TYPE 428 | 0 429 | 430 | 431 | PROTECTED 432 | 433 | PROXY_NAME 434 | Remove .DS_Store files 435 | PROXY_TOOLTIP 436 | Remove ".DS_Store" files created by the Finder. 437 | STATE 438 | 439 | 440 | 441 | PATTERNS_ARRAY 442 | 443 | 444 | REGULAR_EXPRESSION 445 | 446 | STRING 447 | .pbdevelopment 448 | TYPE 449 | 0 450 | 451 | 452 | PROTECTED 453 | 454 | PROXY_NAME 455 | Remove .pbdevelopment files 456 | PROXY_TOOLTIP 457 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 458 | STATE 459 | 460 | 461 | 462 | PATTERNS_ARRAY 463 | 464 | 465 | REGULAR_EXPRESSION 466 | 467 | STRING 468 | CVS 469 | TYPE 470 | 1 471 | 472 | 473 | REGULAR_EXPRESSION 474 | 475 | STRING 476 | .cvsignore 477 | TYPE 478 | 0 479 | 480 | 481 | REGULAR_EXPRESSION 482 | 483 | STRING 484 | .cvspass 485 | TYPE 486 | 0 487 | 488 | 489 | REGULAR_EXPRESSION 490 | 491 | STRING 492 | .svn 493 | TYPE 494 | 1 495 | 496 | 497 | REGULAR_EXPRESSION 498 | 499 | STRING 500 | .git 501 | TYPE 502 | 1 503 | 504 | 505 | REGULAR_EXPRESSION 506 | 507 | STRING 508 | .gitignore 509 | TYPE 510 | 0 511 | 512 | 513 | PROTECTED 514 | 515 | PROXY_NAME 516 | Remove SCM metadata 517 | PROXY_TOOLTIP 518 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 519 | STATE 520 | 521 | 522 | 523 | PATTERNS_ARRAY 524 | 525 | 526 | REGULAR_EXPRESSION 527 | 528 | STRING 529 | classes.nib 530 | TYPE 531 | 0 532 | 533 | 534 | REGULAR_EXPRESSION 535 | 536 | STRING 537 | designable.db 538 | TYPE 539 | 0 540 | 541 | 542 | REGULAR_EXPRESSION 543 | 544 | STRING 545 | info.nib 546 | TYPE 547 | 0 548 | 549 | 550 | PROTECTED 551 | 552 | PROXY_NAME 553 | Optimize nib files 554 | PROXY_TOOLTIP 555 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 556 | STATE 557 | 558 | 559 | 560 | PATTERNS_ARRAY 561 | 562 | 563 | REGULAR_EXPRESSION 564 | 565 | STRING 566 | Resources Disabled 567 | TYPE 568 | 1 569 | 570 | 571 | PROTECTED 572 | 573 | PROXY_NAME 574 | Remove Resources Disabled folders 575 | PROXY_TOOLTIP 576 | Remove "Resources Disabled" folders. 577 | STATE 578 | 579 | 580 | 581 | SEPARATOR 582 | 583 | 584 | 585 | NAME 586 | obs-command-source 587 | PAYLOAD_ONLY 588 | 589 | TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING 590 | 591 | 592 | 593 | TYPE 594 | 0 595 | VERSION 596 | 2 597 | 598 | 599 | -------------------------------------------------------------------------------- /obs-command-source.c: -------------------------------------------------------------------------------- 1 | /* 2 | * OBS Command Source Plugin 3 | * Copyright (C) 2020-2023 Norihiro Kamae 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18 | */ 19 | 20 | #if !defined(_WIN32) && !defined(__APPLE__) 21 | #define _GNU_SOURCE // close_range for Linux 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #ifdef _WIN32 29 | #include 30 | #else 31 | #ifdef __APPLE__ 32 | #include 33 | #endif 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #endif 40 | #include "plugin-macros.generated.h" 41 | 42 | struct command_source 43 | { 44 | char *cmd_show; 45 | char *cmd_hide; 46 | char *cmd_activate; 47 | char *cmd_deactivate; 48 | char *cmd_previewed; 49 | char *cmd_unpreviewed; 50 | #ifndef _WIN32 51 | int sig_show; 52 | int sig_activate; 53 | int sig_preview; 54 | #endif 55 | 56 | bool is_shown; 57 | bool is_preview, was_preview; 58 | 59 | obs_source_t *self; 60 | 61 | #ifndef _WIN32 62 | pid_t pid_show; 63 | pid_t pid_activate; 64 | pid_t pid_preview; 65 | DARRAY(pid_t) running_pids; 66 | #else 67 | // For Windows, just send dummy information to reuse the code for now. 68 | // TODO: Implement to hold hProcess and kill it. 69 | #define pid_show self 70 | #define pid_activate self 71 | #define pid_preview self 72 | #endif // not _WIN32 73 | }; 74 | 75 | OBS_DECLARE_MODULE() 76 | OBS_MODULE_USE_DEFAULT_LOCALE("obs-command-source", "en-US") 77 | 78 | #ifndef _WIN32 79 | static void setenv_if(const char *name, const char *val) 80 | { 81 | if (val) 82 | setenv(name, val, 1); 83 | } 84 | 85 | static void setenv_int(const char *name, int val) 86 | { 87 | char s[16]; 88 | snprintf(s, sizeof(s), "%d", val); 89 | s[sizeof(s) - 1] = 0; 90 | setenv(name, s, 1); 91 | } 92 | #endif 93 | 94 | #ifndef _WIN32 95 | 96 | #if defined(__APPLE__) 97 | static void closefrom(int lower) 98 | { 99 | #define NFDS 128 100 | struct proc_fdinfo fds[NFDS]; 101 | pid_t pid = getpid(); 102 | int ret; 103 | do { 104 | ret = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds, sizeof(fds)) / sizeof(*fds); 105 | for (int i = 0; i < ret; i++) { 106 | if (fds[i].proc_fd >= lower) 107 | close(fds[i].proc_fd); 108 | } 109 | } while (ret >= NFDS); 110 | } 111 | #endif 112 | 113 | static void fork_exec(const char *cmd, struct command_source *s, pid_t *pid_sig) 114 | { 115 | obs_source_t *current_src = obs_frontend_get_current_scene(); 116 | obs_source_t *preview_src = NULL; 117 | if (obs_frontend_preview_program_mode_active()) 118 | preview_src = obs_frontend_get_current_preview_scene(); 119 | 120 | pid_t pid = fork(); 121 | if (!pid) { 122 | pid_t mypid = getpid(); 123 | setpgid(mypid, mypid); 124 | setenv_if("OBS_CURRENT_SCENE", obs_source_get_name(current_src)); 125 | setenv_if("OBS_PREVIEW_SCENE", obs_source_get_name(preview_src)); 126 | setenv_if("OBS_SOURCE_NAME", obs_source_get_name(s->self)); 127 | setenv_int("OBS_TRANSITION_DURATION", obs_frontend_get_transition_duration()); 128 | 129 | #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__) 130 | closefrom(3); 131 | #else // Linux 132 | close_range(3, 65535, 0); 133 | #endif 134 | 135 | execl("/bin/sh", "sh", "-c", cmd, (char *)NULL); 136 | _exit(1); 137 | } 138 | else if (pid != -1) { 139 | if (pid_sig) { 140 | if (*pid_sig) 141 | da_push_back(s->running_pids, pid_sig); 142 | *pid_sig = pid; 143 | } 144 | else 145 | da_push_back(s->running_pids, &pid); 146 | } 147 | 148 | obs_source_release(current_src); 149 | obs_source_release(preview_src); 150 | } 151 | 152 | #else // _WIN32 153 | 154 | static void fork_exec(const char *cmd, struct command_source *s, void *unused) 155 | { 156 | UNUSED_PARAMETER(unused); 157 | PROCESS_INFORMATION pi = {0}; 158 | STARTUPINFO si = {sizeof(STARTUPINFO)}; 159 | char *p = bstrdup(cmd); 160 | CreateProcess(NULL, p, NULL, NULL, FALSE, BELOW_NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi); 161 | CloseHandle(pi.hThread); 162 | CloseHandle(pi.hProcess); 163 | bfree(p); 164 | } 165 | 166 | #endif 167 | 168 | static void check_notify_preview(struct command_source *s); 169 | static void on_preview_scene_changed(enum obs_frontend_event event, void *param); 170 | 171 | static void cmdsrc_show(void *data) 172 | { 173 | struct command_source *s = data; 174 | if (s->cmd_show) { 175 | fork_exec(s->cmd_show, s, &s->pid_show); 176 | } 177 | 178 | check_notify_preview(s); 179 | if (!s->is_shown) 180 | obs_frontend_add_event_callback(on_preview_scene_changed, data); 181 | s->is_shown = true; 182 | } 183 | 184 | #ifndef _WIN32 185 | static void cmdsrc_kill(const struct command_source *s, pid_t pid, int sig) 186 | { 187 | blog(LOG_DEBUG, "source '%s' sending signal %d to PGID %d", obs_source_get_name(s->self), sig, pid); 188 | killpg(pid, sig); 189 | } 190 | #endif 191 | 192 | static void cmdsrc_hide(void *data) 193 | { 194 | struct command_source *s = data; 195 | if (s->cmd_hide) { 196 | fork_exec(s->cmd_hide, s, NULL); 197 | } 198 | 199 | #ifndef _WIN32 200 | if (s->pid_show && s->sig_show) 201 | cmdsrc_kill(s, s->pid_show, s->sig_show); 202 | #endif 203 | 204 | check_notify_preview(s); 205 | if (s->is_shown) 206 | obs_frontend_remove_event_callback(on_preview_scene_changed, data); 207 | s->is_shown = false; 208 | } 209 | 210 | static void cmdsrc_activate(void *data) 211 | { 212 | struct command_source *s = data; 213 | if (s->cmd_activate) { 214 | fork_exec(s->cmd_activate, s, &s->pid_activate); 215 | } 216 | } 217 | 218 | static void cmdsrc_deactivate(void *data) 219 | { 220 | struct command_source *s = data; 221 | if (s->cmd_deactivate) { 222 | fork_exec(s->cmd_deactivate, s, NULL); 223 | } 224 | 225 | #ifndef _WIN32 226 | if (s->pid_activate && s->sig_activate) 227 | cmdsrc_kill(s, s->pid_activate, s->sig_activate); 228 | #endif 229 | } 230 | 231 | static inline void cmdsrc_previewed(struct command_source *s) 232 | { 233 | if (s->cmd_previewed) 234 | fork_exec(s->cmd_previewed, s, &s->pid_preview); 235 | } 236 | 237 | static inline void cmdsrc_unpreviewed(struct command_source *s) 238 | { 239 | if (s->cmd_unpreviewed) 240 | fork_exec(s->cmd_unpreviewed, s, NULL); 241 | 242 | #ifndef _WIN32 243 | if (s->pid_preview && s->sig_preview) 244 | cmdsrc_kill(s, s->pid_preview, s->sig_preview); 245 | #endif 246 | } 247 | 248 | static void preview_callback(obs_source_t *parent, obs_source_t *child, void *param) 249 | { 250 | UNUSED_PARAMETER(parent); 251 | struct command_source *s = param; 252 | if (child == s->self) 253 | s->is_preview = true; 254 | } 255 | 256 | static void check_notify_preview(struct command_source *s) 257 | { 258 | s->is_preview = false; 259 | obs_source_t *preview_soure = obs_frontend_get_current_preview_scene(); 260 | if (preview_soure) { 261 | obs_source_enum_active_sources(preview_soure, preview_callback, s); 262 | obs_source_release(preview_soure); 263 | } 264 | 265 | if (s->is_preview && !s->was_preview) 266 | cmdsrc_previewed(s); 267 | else if (!s->is_preview && s->was_preview) 268 | cmdsrc_unpreviewed(s); 269 | s->was_preview = s->is_preview; 270 | } 271 | 272 | static void on_preview_scene_changed(enum obs_frontend_event event, void *param) 273 | { 274 | struct command_source *s = param; 275 | switch (event) { 276 | case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED: 277 | case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: 278 | case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED: 279 | case OBS_FRONTEND_EVENT_SCENE_CHANGED: 280 | check_notify_preview(s); 281 | break; 282 | default: 283 | break; 284 | } 285 | } 286 | 287 | static const char *command_source_name(void *unused) 288 | { 289 | UNUSED_PARAMETER(unused); 290 | 291 | return obs_module_text("execute-command"); 292 | } 293 | 294 | static void command_source_get_defaults(obs_data_t *settings) 295 | { 296 | #ifndef _WIN32 297 | obs_data_set_default_int(settings, "sig", SIGTERM); 298 | #endif 299 | } 300 | 301 | static obs_properties_t *command_source_get_properties(void *unused) 302 | { 303 | UNUSED_PARAMETER(unused); 304 | obs_properties_t *props; 305 | #ifndef _WIN32 306 | obs_property_t *prop; 307 | #endif 308 | 309 | props = obs_properties_create(); 310 | 311 | obs_properties_add_text(props, "cmd_show", obs_module_text("shown"), OBS_TEXT_DEFAULT); 312 | obs_properties_add_text(props, "cmd_hide", obs_module_text("hidden"), OBS_TEXT_DEFAULT); 313 | obs_properties_add_text(props, "cmd_activate", obs_module_text("activated"), OBS_TEXT_DEFAULT); 314 | obs_properties_add_text(props, "cmd_deactivate", obs_module_text("deactivated"), OBS_TEXT_DEFAULT); 315 | obs_properties_add_text(props, "cmd_previewed", obs_module_text("shown-in-preview"), OBS_TEXT_DEFAULT); 316 | obs_properties_add_text(props, "cmd_unpreviewed", obs_module_text("hidden-from-preview"), OBS_TEXT_DEFAULT); 317 | 318 | #ifndef _WIN32 319 | obs_properties_add_bool(props, "sigen_show", obs_module_text("prop-sig-show")); 320 | obs_properties_add_bool(props, "sigen_activate", obs_module_text("prop-sig-active")); 321 | obs_properties_add_bool(props, "sigen_preview", obs_module_text("prop-sig-preview")); 322 | prop = obs_properties_add_list(props, "sig", obs_module_text("prop-sig"), OBS_COMBO_TYPE_LIST, 323 | OBS_COMBO_FORMAT_INT); 324 | obs_property_list_add_int(prop, "SIGABRT", SIGABRT); 325 | obs_property_list_add_int(prop, "SIGINT", SIGINT); 326 | obs_property_list_add_int(prop, "SIGKILL", SIGKILL); 327 | obs_property_list_add_int(prop, "SIGTERM", SIGTERM); 328 | obs_property_list_add_int(prop, "SIGHUP", SIGHUP); 329 | obs_property_list_add_int(prop, "SIGUSR1", SIGUSR1); 330 | obs_property_list_add_int(prop, "SIGUSR2", SIGUSR2); 331 | #endif 332 | 333 | return props; 334 | } 335 | 336 | static void command_source_destroy(void *data) 337 | { 338 | struct command_source *s = data; 339 | 340 | if (s->is_shown) 341 | obs_frontend_remove_event_callback(on_preview_scene_changed, data); 342 | 343 | if (s->cmd_show) 344 | bfree(s->cmd_show); 345 | if (s->cmd_hide) 346 | bfree(s->cmd_hide); 347 | if (s->cmd_activate) 348 | bfree(s->cmd_activate); 349 | if (s->cmd_deactivate) 350 | bfree(s->cmd_deactivate); 351 | if (s->cmd_previewed) 352 | bfree(s->cmd_previewed); 353 | if (s->cmd_unpreviewed) 354 | bfree(s->cmd_unpreviewed); 355 | #ifndef _WIN32 356 | da_free(s->running_pids); 357 | #endif // not _WIN32 358 | 359 | bfree(s); 360 | } 361 | 362 | static inline char *bstrdup_nonzero(const char *s) 363 | { 364 | if (!*s) 365 | return NULL; 366 | return bstrdup(s); 367 | } 368 | 369 | static void command_source_update(void *data, obs_data_t *settings) 370 | { 371 | struct command_source *s = data; 372 | 373 | if (s->cmd_show) 374 | bfree(s->cmd_show); 375 | if (s->cmd_hide) 376 | bfree(s->cmd_hide); 377 | if (s->cmd_activate) 378 | bfree(s->cmd_activate); 379 | if (s->cmd_deactivate) 380 | bfree(s->cmd_deactivate); 381 | if (s->cmd_previewed) 382 | bfree(s->cmd_previewed); 383 | if (s->cmd_unpreviewed) 384 | bfree(s->cmd_unpreviewed); 385 | s->cmd_show = bstrdup_nonzero(obs_data_get_string(settings, "cmd_show")); 386 | s->cmd_hide = bstrdup_nonzero(obs_data_get_string(settings, "cmd_hide")); 387 | s->cmd_activate = bstrdup_nonzero(obs_data_get_string(settings, "cmd_activate")); 388 | s->cmd_deactivate = bstrdup_nonzero(obs_data_get_string(settings, "cmd_deactivate")); 389 | s->cmd_previewed = bstrdup_nonzero(obs_data_get_string(settings, "cmd_previewed")); 390 | s->cmd_unpreviewed = bstrdup_nonzero(obs_data_get_string(settings, "cmd_unpreviewed")); 391 | #ifndef _WIN32 392 | int sig = obs_data_get_int(settings, "sig"); 393 | s->sig_show = obs_data_get_bool(settings, "sigen_show") ? sig : 0; 394 | s->sig_activate = obs_data_get_bool(settings, "sigen_activate") ? sig : 0; 395 | s->sig_preview = obs_data_get_bool(settings, "sigen_preview") ? sig : 0; 396 | #endif 397 | } 398 | 399 | static void *command_source_create(obs_data_t *settings, obs_source_t *source) 400 | { 401 | struct command_source *s = bzalloc(sizeof(struct command_source)); 402 | s->self = source; 403 | #ifndef _WIN32 404 | da_init(s->running_pids); 405 | #endif // not _WIN32 406 | 407 | command_source_update(s, settings); 408 | 409 | return s; 410 | } 411 | 412 | #ifndef _WIN32 413 | static bool cmdsrc_waitpid(struct command_source *s, pid_t pid) 414 | { 415 | int wstatus = 0; 416 | pid_t ret = waitpid(pid, &wstatus, WNOHANG); 417 | if (ret == pid) { 418 | char st[32] = {0}; 419 | if (WIFSIGNALED(wstatus)) 420 | snprintf(st, sizeof(st) - 1, " by signal %d", WTERMSIG(wstatus)); 421 | else if (WCOREDUMP(wstatus)) 422 | snprintf(st, sizeof(st) - 1, " with core dump"); 423 | else if (WIFEXITED(wstatus)) 424 | snprintf(st, sizeof(st) - 1, " with status %d", WEXITSTATUS(wstatus)); 425 | blog(LOG_DEBUG, "source '%s': PID %d exited%s", obs_source_get_name(s->self), pid, st); 426 | return true; 427 | } 428 | return false; 429 | } 430 | 431 | static void cmdsrc_tick(void *data, float seconds) 432 | { 433 | UNUSED_PARAMETER(seconds); 434 | struct command_source *s = data; 435 | 436 | if (s->pid_show) { 437 | if (cmdsrc_waitpid(s, s->pid_show)) 438 | s->pid_show = 0; 439 | } 440 | 441 | if (s->pid_activate) { 442 | if (cmdsrc_waitpid(s, s->pid_activate)) 443 | s->pid_activate = 0; 444 | } 445 | 446 | if (s->pid_preview) { 447 | if (cmdsrc_waitpid(s, s->pid_preview)) 448 | s->pid_preview = 0; 449 | } 450 | 451 | for (size_t i = 0; i < s->running_pids.num; i++) { 452 | if (cmdsrc_waitpid(s, s->running_pids.array[i])) { 453 | da_erase(s->running_pids, i); 454 | i--; 455 | } 456 | } 457 | } 458 | #endif // not _WIN32 459 | 460 | static struct obs_source_info command_source_info = { 461 | .id = "command_source", 462 | .type = OBS_SOURCE_TYPE_INPUT, 463 | .get_name = command_source_name, 464 | .create = command_source_create, 465 | .destroy = command_source_destroy, 466 | .update = command_source_update, 467 | .show = cmdsrc_show, 468 | .hide = cmdsrc_hide, 469 | .activate = cmdsrc_activate, 470 | .deactivate = cmdsrc_deactivate, 471 | .get_defaults = command_source_get_defaults, 472 | .get_properties = command_source_get_properties, 473 | #ifndef _WIN32 474 | .video_tick = cmdsrc_tick, 475 | #endif // not _WIN32 476 | }; 477 | 478 | bool obs_module_load() 479 | { 480 | obs_register_source(&command_source_info); 481 | 482 | return true; 483 | } 484 | -------------------------------------------------------------------------------- /plugin-macros.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * OBS Command Source Plugin 3 | * Copyright (C) 2020-2023 Norihiro Kamae 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18 | */ 19 | 20 | #ifndef PLUGIN_MACROS_H 21 | #define PLUGIN_MACROS_H 22 | 23 | #define PLUGIN_NAME "@PROJECT_NAME@" 24 | #define PLUGIN_VERSION "@PROJECT_VERSION@" 25 | 26 | #define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) 27 | 28 | #endif // PLUGIN_MACROS_H 29 | -------------------------------------------------------------------------------- /tools/request-websocket.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | import os 4 | import sys 5 | import asyncio 6 | import json 7 | import simpleobsws 8 | 9 | _exitcode = 0 10 | 11 | async def connect_and_send(rr): 12 | # NOTE: User has to edit host, port, and password. 13 | try: 14 | obsws_host = os.environ['OBSWS_HOST'] 15 | except: 16 | obsws_host = '127.0.0.1' 17 | try: 18 | obsws_port = int(os.environ['OBSWS_PORT']) 19 | except: 20 | obsws_port = 4455 21 | try: 22 | obsws_passwd = os.environ['OBSWS_PASSWD'] 23 | except: 24 | obsws_passwd = None 25 | ws = simpleobsws.WebSocketClient(url=f'ws://{obsws_host}:{obsws_port}', password=obsws_passwd) 26 | await ws.connect() 27 | await ws.wait_until_identified() 28 | for req, data in rr: 29 | if req=='sleep': 30 | await asyncio.sleep(data) 31 | else: 32 | req = simpleobsws.Request(req, data) 33 | res = await ws.call(req) 34 | if res.ok(): 35 | if res.responseData != None: 36 | print(json.dumps(res.responseData, indent="\t", ensure_ascii=None)) 37 | else: 38 | global _exitcode 39 | _exitcode = 1 40 | print('Error: %s' % str(res.responseData), file=sys.stderr) 41 | await ws.disconnect() 42 | 43 | help_str='''NAME 44 | request-websocket.py - a helper command to send requests to OBS Studio 45 | 46 | SYNOPSIS 47 | request-websocket.py [request_type data] ... 48 | request-websocket.py --help 49 | 50 | DESCRIPTION 51 | This script will take pairs of request_type and data and send each pair to OBS Studio. 52 | The last pair can skip data if you don't need to send any data. 53 | 54 | request_type 55 | This is a request-type supported by obs-websocket protocol or 'sleep'. 56 | If request_type is 'sleep', data has to be a non-negative number to sleep in seconds. 57 | Otherwise, the request-type will be directly send to obs-websocket. 58 | data 59 | The data is a JSON text. The data can be an empty string if you don't need to send any data. 60 | 61 | EXAMPLES 62 | request-websocket.py GetVersion 63 | request-websocket.py SetPreviewScene '{"scene-name": "Scene"}' 64 | request-websocket.py SetCurrentScene '{"scene-name": "Scene1"}' sleep 10 SetCurrentScene '{"scene-name": "Scene2"}' 65 | ''' 66 | 67 | def main(): 68 | import sys 69 | req = None 70 | rr = list() 71 | st = 0 72 | for a in sys.argv[1:]: 73 | if a=='--help': 74 | print(help_str) 75 | return 0 76 | if st==0: 77 | req = a 78 | st = 1 79 | elif st==1: 80 | if len(a): 81 | data = json.loads(a) 82 | else: 83 | data = None 84 | rr.append((req, data)) 85 | st = 0 86 | if st==1: 87 | rr.append((req, None)) 88 | loop = asyncio.new_event_loop() 89 | loop.run_until_complete(connect_and_send(rr)) 90 | 91 | if __name__ == '__main__': 92 | main() 93 | sys.exit(_exitcode) 94 | --------------------------------------------------------------------------------