mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 12:45:28 -05:00
Compare commits
48 Commits
1.23.1
...
1.24.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efa34d080e | ||
|
|
19a6277841 | ||
|
|
107e1413ed | ||
|
|
94416dd881 | ||
|
|
8efc39958f | ||
|
|
569f794e33 | ||
|
|
fc1460e592 | ||
|
|
d6e4ee0203 | ||
|
|
0c8135078d | ||
|
|
24f4b864c4 | ||
|
|
41e47f32b6 | ||
|
|
a73ccb15a1 | ||
|
|
36a78f49a5 | ||
|
|
cbd459b0a5 | ||
|
|
03bb1fd089 | ||
|
|
4e2c254717 | ||
|
|
fffb6c5f29 | ||
|
|
81f669e226 | ||
|
|
6484ae54c0 | ||
|
|
1c52caf5e9 | ||
|
|
ce1d4cce57 | ||
|
|
8246197ae6 | ||
|
|
8f90bf8846 | ||
|
|
b955e8a0ea | ||
|
|
81da32f7a2 | ||
|
|
5324fa9350 | ||
|
|
c375daa258 | ||
|
|
4f3bd699c8 | ||
|
|
f5024621c0 | ||
|
|
8308506d71 | ||
|
|
6605c19202 | ||
|
|
fdf1a94f97 | ||
|
|
6b6e37da92 | ||
|
|
0f0fc8ae4e | ||
|
|
4d7e2992e5 | ||
|
|
a5050d4810 | ||
|
|
9df6963f08 | ||
|
|
b833dd4576 | ||
|
|
39378ae20f | ||
|
|
5a2cb943f7 | ||
|
|
b88209f63d | ||
|
|
9c848938f8 | ||
|
|
07b86dda41 | ||
|
|
49e8bf7639 | ||
|
|
a2d0a7f544 | ||
|
|
1a8c894ddf | ||
|
|
3f51f63298 | ||
|
|
a0b4df574b |
79
.github/actions/build-dependencies/action.yml
vendored
Normal file
79
.github/actions/build-dependencies/action.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: 'Setup plugin build dependencies'
|
||||
description: 'Builds the plugin build dependencies'
|
||||
inputs:
|
||||
target:
|
||||
description: 'Build target for dependencies'
|
||||
required: true
|
||||
config:
|
||||
description: 'Build configuration'
|
||||
required: false
|
||||
default: 'Release'
|
||||
visualStudio:
|
||||
description: 'Visual Studio version (Windows only)'
|
||||
required: false
|
||||
default: 'Visual Studio 16 2019'
|
||||
workingDirectory:
|
||||
description: 'Working directory for packaging'
|
||||
required: false
|
||||
default: ${{ github.workspace }}
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.24.x'
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: restore-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.DEP_DIR }}
|
||||
key: ${{ env.DEP_DIR }}-${{ runner.os }}-${{ inputs.target }}
|
||||
|
||||
- name: Run macOS Build
|
||||
if: ${{ runner.os == 'macOS' && steps.restore-cache.outputs.cache-hit != 'true' }}
|
||||
shell: zsh {0}
|
||||
run: |
|
||||
build_args=(
|
||||
-c ${{ inputs.config }}
|
||||
-t macos-${{ inputs.target }}
|
||||
)
|
||||
|
||||
if (( ${+CI} && ${+RUNNER_DEBUG} )) build_args+=(--debug)
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-deps-macos.zsh -o ${{ env.DEP_DIR }} ${build_args}
|
||||
|
||||
- name: Run Linux Build
|
||||
if: ${{ runner.os == 'Linux' && steps.restore-cache.outputs.cache-hit != 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
build_args=(
|
||||
-c ${{ inputs.config }}
|
||||
-t linux-${{ inputs.target }}
|
||||
)
|
||||
|
||||
if [[ -n "${CI}" && -n "${RUNNER_DEBUG}" ]]; then
|
||||
build_args+=(--debug)
|
||||
fi
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-deps-linux.sh -o ${{ env.DEP_DIR }} "${build_args[@]}"
|
||||
|
||||
- name: Run Windows Build
|
||||
if: ${{ runner.os == 'Windows' && steps.restore-cache.outputs.cache-hit != 'true' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
$BuildArgs = @{
|
||||
Target = '${{ inputs.target }}'
|
||||
Configuration = '${{ inputs.config }}'
|
||||
CMakeGenerator = '${{ inputs.visualStudio }}'
|
||||
}
|
||||
|
||||
if ( ( Test-Path env:CI ) -and ( Test-Path env:RUNNER_DEBUG ) ) {
|
||||
$BuildArgs += @{
|
||||
Debug = $true
|
||||
}
|
||||
}
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/Build-Deps-Windows.ps1 -OutDirName ${{ env.DEP_DIR }} @BuildArgs
|
||||
|
||||
6
.github/actions/build-plugin/action.yml
vendored
6
.github/actions/build-plugin/action.yml
vendored
@@ -49,7 +49,7 @@ runs:
|
||||
if [[ '${{ inputs.codesign }}' == 'true' ]] build_args+=(-s)
|
||||
if (( ${+CI} && ${+RUNNER_DEBUG} )) build_args+=(--debug)
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-macos.zsh ${build_args}
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-macos.zsh -d ${{ env.DEP_DIR }} ${build_args}
|
||||
|
||||
- name: Run Linux Build
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
@@ -68,7 +68,7 @@ runs:
|
||||
build_args+=(-p)
|
||||
fi
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-linux.sh "${build_args[@]}"
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-linux.sh -d ${{ env.DEP_DIR }} "${build_args[@]}"
|
||||
|
||||
- name: Run Windows Build
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
@@ -86,4 +86,4 @@ runs:
|
||||
}
|
||||
}
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/Build-Windows.ps1 @BuildArgs
|
||||
${{ inputs.workingDirectory }}/.github/scripts/Build-Windows.ps1 -ADVSSDepName ${{ env.DEP_DIR }} @BuildArgs
|
||||
|
||||
1
.github/scripts/.Wingetfile
vendored
1
.github/scripts/.Wingetfile
vendored
@@ -1,3 +1,4 @@
|
||||
package '7zip.7zip', path: '7-zip', bin: '7z'
|
||||
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
|
||||
package 'innosetup', path: 'Inno Setup 6', bin: 'iscc'
|
||||
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl'
|
||||
|
||||
324
.github/scripts/.build-deps.zsh
vendored
Executable file
324
.github/scripts/.build-deps.zsh
vendored
Executable file
@@ -0,0 +1,324 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
builtin emulate -L zsh
|
||||
setopt EXTENDED_GLOB
|
||||
setopt PUSHD_SILENT
|
||||
setopt ERR_EXIT
|
||||
setopt ERR_RETURN
|
||||
setopt NO_UNSET
|
||||
setopt PIPE_FAIL
|
||||
setopt NO_AUTO_PUSHD
|
||||
setopt NO_PUSHD_IGNORE_DUPS
|
||||
setopt FUNCTION_ARGZERO
|
||||
|
||||
## Enable for script debugging
|
||||
# setopt WARN_CREATE_GLOBAL
|
||||
# setopt WARN_NESTED_VAR
|
||||
# setopt XTRACE
|
||||
|
||||
autoload -Uz is-at-least && if ! is-at-least 5.2; then
|
||||
print -u2 -PR "%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_trap_error() {
|
||||
print -u2 -PR '%F{1} ✖︎ script execution error%f'
|
||||
print -PR -e "
|
||||
Callstack:
|
||||
${(j:\n :)funcfiletrace}
|
||||
"
|
||||
exit 2
|
||||
}
|
||||
|
||||
build() {
|
||||
if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
|
||||
local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[3]}
|
||||
local target="${host_os}-${CPUTYPE}"
|
||||
local project_root=${SCRIPT_HOME:A:h:h}
|
||||
local buildspec_file="${project_root}/buildspec.json"
|
||||
|
||||
trap '_trap_error' ZERR
|
||||
|
||||
fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
|
||||
autoload -Uz log_info log_error log_output set_loglevel check_${host_os} setup_${host_os} setup_obs setup_ccache
|
||||
|
||||
if [[ ! -r ${buildspec_file} ]] {
|
||||
log_error \
|
||||
'No buildspec.json found. Please create a build specification for your project.' \
|
||||
'A buildspec.json.template file is provided in the repository to get you started.'
|
||||
return 2
|
||||
}
|
||||
|
||||
typeset -g -a skips=()
|
||||
local -i _verbosity=1
|
||||
local -r _version='1.0.0'
|
||||
local -r -a _valid_targets=(
|
||||
macos-x86_64
|
||||
macos-arm64
|
||||
macos-universal
|
||||
linux-x86_64
|
||||
)
|
||||
local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel)
|
||||
if [[ ${host_os} == 'macos' ]] {
|
||||
local -r -a _valid_generators=(Xcode Ninja 'Unix Makefiles')
|
||||
local generator="${${CI:+Ninja}:-Xcode}"
|
||||
} else {
|
||||
local -r -a _valid_generators=(Ninja 'Unix Makefiles')
|
||||
local generator='Ninja'
|
||||
}
|
||||
local -r _usage="
|
||||
Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
%BOptions%b:
|
||||
|
||||
%F{yellow} Build configuration options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-t | --target%b Specify target - default: %B%F{green}${host_os}-${CPUTYPE}%f%b
|
||||
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
|
||||
%B-o | --out%b Output directory - default: %B%F{green}RelWithDebInfo%f%b
|
||||
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
|
||||
Available generators:
|
||||
- Ninja
|
||||
- Unix Makefiles
|
||||
- Xcode (macOS only)
|
||||
|
||||
%F{yellow} Output options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-q | --quiet%b Quiet (error output only)
|
||||
%B-v | --verbose%b Verbose (more detailed output)
|
||||
%B--skip-[all|build|deps|unpack]%b Skip all|building OBS|checking for dependencies|unpacking dependencies
|
||||
%B--debug%b Debug (very detailed and added output)
|
||||
|
||||
%F{yellow} General options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-h | --help%b Print this usage help
|
||||
%B-V | --version%b Print script version information"
|
||||
|
||||
local -a args
|
||||
while (( # )) {
|
||||
case ${1} {
|
||||
-t|--target|-c|--config|--generator)
|
||||
if (( # == 1 )) || [[ ${2:0:1} == '-' ]] {
|
||||
log_error "Missing value for option %B${1}%b"
|
||||
log_output ${_usage}
|
||||
exit 2
|
||||
}
|
||||
;;
|
||||
}
|
||||
case ${1} {
|
||||
--)
|
||||
shift
|
||||
args+=($@)
|
||||
break
|
||||
;;
|
||||
-t|--target)
|
||||
if (( ! ${_valid_targets[(Ie)${2}]} )) {
|
||||
log_error "Invalid value %B${2}%b for option %B${1}%b"
|
||||
log_output ${_usage}
|
||||
exit 2
|
||||
}
|
||||
target=${2}
|
||||
shift 2
|
||||
;;
|
||||
-c|--config)
|
||||
if (( ! ${_valid_configs[(Ie)${2}]} )) {
|
||||
log_error "Invalid value %B${2}%b for option %B${1}%b"
|
||||
log_output ${_usage}
|
||||
exit 2
|
||||
}
|
||||
BUILD_CONFIG=${2}
|
||||
shift 2
|
||||
;;
|
||||
-o|--out)
|
||||
OUT_DIR="${2}"
|
||||
shift 2
|
||||
;;
|
||||
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
||||
-v|--verbose) (( _verbosity += 1 )); shift ;;
|
||||
-h|--help) log_output ${_usage}; exit 0 ;;
|
||||
-V|--version) print -Pr "${_version}"; exit 0 ;;
|
||||
--debug) _verbosity=3; shift ;;
|
||||
--generator)
|
||||
if (( ! ${_valid_generators[(Ie)${2}]} )) {
|
||||
log_error "Invalid value %B${2}%b for option %B${1}%b"
|
||||
log_output ${_usage}
|
||||
exit 2
|
||||
}
|
||||
generator=${2}
|
||||
shift 2
|
||||
;;
|
||||
--skip-*)
|
||||
local _skip="${${(s:-:)1}[-1]}"
|
||||
local _check=(all deps unpack build)
|
||||
(( ${_check[(Ie)${_skip}]} )) || log_warning "Invalid skip mode %B${_skip}%b supplied"
|
||||
typeset -g -a skips=(${skips} ${_skip})
|
||||
shift
|
||||
;;
|
||||
*) log_error "Unknown option: %B${1}%b"; log_output ${_usage}; exit 2 ;;
|
||||
}
|
||||
}
|
||||
|
||||
set -- ${(@)args}
|
||||
set_loglevel ${_verbosity}
|
||||
|
||||
check_${host_os}
|
||||
setup_ccache
|
||||
|
||||
typeset -g QT_VERSION
|
||||
typeset -g DEPLOYMENT_TARGET
|
||||
typeset -g OBS_DEPS_VERSION
|
||||
setup_${host_os}
|
||||
|
||||
local product_name
|
||||
local product_version
|
||||
local git_tag="$(git describe --tags)"
|
||||
|
||||
read -r product_name product_version <<< \
|
||||
"$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
|
||||
|
||||
if [[ "${git_tag}" =~ '^([0-9]+\.){0,2}(\*|[0-9]+)$' ]] {
|
||||
log_info "Using git tag as version identifier '${git_tag}'"
|
||||
product_version="${git_tag}"
|
||||
} else {
|
||||
log_info "Using buildspec.json version identifier '${product_version}'"
|
||||
}
|
||||
|
||||
if [[ -z "${OUT_DIR}" ]] {
|
||||
OUT_DIR="advss-build-dependencies"
|
||||
}
|
||||
mkdir -p "${project_root}/../${OUT_DIR}"
|
||||
local advss_dep_path="$(realpath ${project_root}/../${OUT_DIR})"
|
||||
local _plugin_deps="${project_root:h}/obs-build-dependencies/plugin-deps-${OBS_DEPS_VERSION}-qt${QT_VERSION}-${target##*-}"
|
||||
|
||||
case ${host_os} {
|
||||
macos)
|
||||
local opencv_dir="${project_root}/deps/opencv"
|
||||
local opencv_build_dir="${opencv_dir}/build_${target##*-}"
|
||||
|
||||
local -a opencv_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DBUILD_LIST=core,imgproc,objdetect
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
|
||||
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
|
||||
)
|
||||
|
||||
if [ "${target}" != "macos-x86_64" ]; then
|
||||
opencv_cmake_args+=(-DWITH_IPP=OFF)
|
||||
fi
|
||||
|
||||
pushd ${opencv_dir}
|
||||
log_info "Configure OpenCV ..."
|
||||
cmake -S . -B ${opencv_build_dir} -G ${generator} ${opencv_cmake_args}
|
||||
|
||||
log_info "Building OpenCV ..."
|
||||
cmake --build ${opencv_build_dir} --config Release
|
||||
|
||||
log_info "Installing OpenCV..."
|
||||
cmake --install ${opencv_build_dir} --prefix "${advss_dep_path}" --config Release || true
|
||||
popd
|
||||
|
||||
local leptonica_dir="${project_root}/deps/leptonica"
|
||||
local leptonica_build_dir="${leptonica_dir}/build_${target##*-}"
|
||||
|
||||
local -a leptonica_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DSW_BUILD=OFF
|
||||
-DOPENJPEG_SUPPORT=OFF
|
||||
-DLIBWEBP_SUPPORT=OFF
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=TRUE
|
||||
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
|
||||
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
|
||||
)
|
||||
|
||||
pushd ${leptonica_dir}
|
||||
log_info "Configure Leptonica ..."
|
||||
cmake -S . -B ${leptonica_build_dir} -G ${generator} ${leptonica_cmake_args}
|
||||
|
||||
log_info "Building Leptonica ..."
|
||||
cmake --build ${leptonica_build_dir} --config Release
|
||||
|
||||
log_info "Installing Leptonica..."
|
||||
# Workaround for "unknown file attribute: H" errors when running install
|
||||
cmake --install ${leptonica_build_dir} --prefix "${advss_dep_path}" --config Release || :
|
||||
popd
|
||||
|
||||
local tesseract_dir="${project_root}/deps/tesseract"
|
||||
local tesseract_build_dir="${tesseract_dir}/build_${target##*-}"
|
||||
|
||||
local -a tesseract_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DSW_BUILD=OFF
|
||||
-DBUILD_TRAINING_TOOLS=OFF
|
||||
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
|
||||
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
|
||||
)
|
||||
|
||||
if [ "${target}" != "macos-x86_64" ]; then
|
||||
tesseract_cmake_args+=(
|
||||
-DCMAKE_SYSTEM_PROCESSOR=aarch64
|
||||
-DHAVE_AVX=FALSE
|
||||
-DHAVE_AVX2=FALSE
|
||||
-DHAVE_AVX512F=FALSE
|
||||
-DHAVE_FMA=FALSE
|
||||
-DHAVE_SSE4_1=FALSE
|
||||
-DHAVE_NEON=TRUE
|
||||
)
|
||||
sed -i'.original' 's/HAVE_NEON FALSE/HAVE_NEON TRUE/g' "${tesseract_dir}/CMakeLists.txt"
|
||||
fi
|
||||
|
||||
pushd ${tesseract_dir}
|
||||
log_info "Configure Tesseract ..."
|
||||
cmake -S . -B ${tesseract_build_dir} -G ${generator} ${tesseract_cmake_args}
|
||||
|
||||
log_info "Building Tesseract ..."
|
||||
cmake --build ${tesseract_build_dir} --config Release
|
||||
|
||||
log_info "Installing Tesseract..."
|
||||
cmake --install ${tesseract_build_dir} --prefix "${advss_dep_path}" --config Release
|
||||
popd
|
||||
|
||||
pushd ${advss_dep_path}
|
||||
log_info "Prepare openssl ..."
|
||||
rm -rf openssl
|
||||
git clone git://git.openssl.org/openssl.git --branch openssl-3.1.2 --depth 1
|
||||
mv openssl openssl_x86
|
||||
cp -r openssl_x86 openssl_arm
|
||||
|
||||
log_info "Building openssl x86 ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.9
|
||||
cd openssl_x86
|
||||
./Configure darwin64-x86_64-cc shared
|
||||
make
|
||||
|
||||
log_info "Building openssl arm ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
cd ../openssl_arm
|
||||
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm
|
||||
make
|
||||
|
||||
log_info "Combine arm and x86 openssl binaries ..."
|
||||
cd ..
|
||||
mkdir openssl-combined
|
||||
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output openssl-combined/libcrypto.a
|
||||
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output openssl-combined/libssl.a
|
||||
|
||||
log_info "Clean up openssl dir..."
|
||||
mv openssl_x86 openssl
|
||||
rm -rf openssl_arm
|
||||
;;
|
||||
linux)
|
||||
# Nothing to do for now
|
||||
;;
|
||||
}
|
||||
}
|
||||
|
||||
build ${@}
|
||||
112
.github/scripts/.build.zsh
vendored
112
.github/scripts/.build.zsh
vendored
@@ -36,6 +36,7 @@ build() {
|
||||
local target="${host_os}-${CPUTYPE}"
|
||||
local project_root=${SCRIPT_HOME:A:h:h}
|
||||
local buildspec_file="${project_root}/buildspec.json"
|
||||
local dep_dir=""
|
||||
|
||||
trap '_trap_error' ZERR
|
||||
|
||||
@@ -77,6 +78,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
|
||||
%B-s | --codesign%b Enable codesigning (macOS only)
|
||||
%B-p | --portable%b Enable portable mode (Linux only)
|
||||
%B-d | --dep%b Dependency directory name - default: %B%F{green}advss-build-dependencies%f%b
|
||||
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
|
||||
Available generators:
|
||||
- Ninja
|
||||
@@ -130,6 +132,10 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
BUILD_CONFIG=${2}
|
||||
shift 2
|
||||
;;
|
||||
-d|--dep)
|
||||
dep_dir="${2}"
|
||||
shift 2
|
||||
;;
|
||||
-s|--codesign) CODESIGN=1; shift ;;
|
||||
-p|--portable) typeset -g PORTABLE=1; shift ;;
|
||||
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
||||
@@ -168,6 +174,15 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
typeset -g OBS_DEPS_VERSION
|
||||
setup_${host_os}
|
||||
|
||||
local advss_deps_path
|
||||
if [[ -z "${dep_dir}" ]] {
|
||||
log_info "Building advss deps ..."
|
||||
dep_dir="advss-build-dependencies"
|
||||
${SCRIPT_HOME}/build-deps-${host_os}.zsh -c "${BUILD_CONFIG:-RelWithDebInfo}" -t "${target}" --generator "${generator}" -o "${dep_dir}" --skip-deps --skip-unpack
|
||||
}
|
||||
advss_deps_path=$(realpath ${project_root}/../${dep_dir})
|
||||
log_info "Using advss deps at $advss_deps_path ..."
|
||||
|
||||
local product_name
|
||||
local product_version
|
||||
local git_tag="$(git describe --tags)"
|
||||
@@ -187,96 +202,6 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
sed -i '' \
|
||||
"s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/" \
|
||||
"${project_root}/CMakeLists.txt"
|
||||
|
||||
local opencv_dir="${project_root}/deps/opencv"
|
||||
local opencv_build_dir="${opencv_dir}/build_${target##*-}"
|
||||
|
||||
local -a opencv_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DBUILD_LIST=core,imgproc,objdetect
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
)
|
||||
|
||||
if [ "${target}" != "macos-x86_64" ]; then
|
||||
opencv_cmake_args+=(-DWITH_IPP=OFF)
|
||||
fi
|
||||
|
||||
pushd ${opencv_dir}
|
||||
log_info "Configure OpenCV ..."
|
||||
cmake -S . -B ${opencv_build_dir} -G ${generator} ${opencv_cmake_args}
|
||||
|
||||
log_info "Building OpenCV ..."
|
||||
cmake --build ${opencv_build_dir} --config Release
|
||||
|
||||
log_info "Installing OpenCV..."
|
||||
cmake --install ${opencv_build_dir} --config Release || true
|
||||
popd
|
||||
|
||||
local leptonica_dir="${project_root}/deps/leptonica"
|
||||
local leptonica_build_dir="${leptonica_dir}/build_${target##*-}"
|
||||
|
||||
local -a leptonica_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DSW_BUILD=OFF
|
||||
-DOPENJPEG_SUPPORT=OFF
|
||||
-DLIBWEBP_SUPPORT=OFF
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=TRUE
|
||||
)
|
||||
|
||||
pushd ${leptonica_dir}
|
||||
log_info "Configure Leptonica ..."
|
||||
cmake -S . -B ${leptonica_build_dir} -G ${generator} ${leptonica_cmake_args}
|
||||
|
||||
log_info "Building Leptonica ..."
|
||||
cmake --build ${leptonica_build_dir} --config Release
|
||||
|
||||
log_info "Installing Leptonica..."
|
||||
# Workaround for "unknown file attribute: H" errors when running install
|
||||
cmake --install ${leptonica_build_dir} --config Release || :
|
||||
popd
|
||||
|
||||
local tesseract_dir="${project_root}/deps/tesseract"
|
||||
local tesseract_build_dir="${tesseract_dir}/build_${target##*-}"
|
||||
|
||||
local -a tesseract_cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DSW_BUILD=OFF
|
||||
-DBUILD_TRAINING_TOOLS=OFF
|
||||
)
|
||||
|
||||
if [ "${target}" != "macos-x86_64" ]; then
|
||||
tesseract_cmake_args+=(
|
||||
-DCMAKE_SYSTEM_PROCESSOR=aarch64
|
||||
-DHAVE_AVX=FALSE
|
||||
-DHAVE_AVX2=FALSE
|
||||
-DHAVE_AVX512F=FALSE
|
||||
-DHAVE_FMA=FALSE
|
||||
-DHAVE_SSE4_1=FALSE
|
||||
-DHAVE_NEON=TRUE
|
||||
)
|
||||
sed -i'.original' 's/HAVE_NEON FALSE/HAVE_NEON TRUE/g' "${tesseract_dir}/CMakeLists.txt"
|
||||
fi
|
||||
|
||||
pushd ${tesseract_dir}
|
||||
log_info "Configure Tesseract ..."
|
||||
cmake -S . -B ${tesseract_build_dir} -G ${generator} ${tesseract_cmake_args}
|
||||
|
||||
log_info "Building Tesseract ..."
|
||||
#cmake --build ${tesseract_build_dir} --config Release --target libtesseract
|
||||
cmake --build ${tesseract_build_dir} --config Release
|
||||
|
||||
log_info "Installing Tesseract..."
|
||||
#cmake --install ${tesseract_build_dir} --config Release --component libtesseract
|
||||
cmake --install ${tesseract_build_dir} --config Release
|
||||
popd
|
||||
;;
|
||||
linux)
|
||||
sed -i'' \
|
||||
@@ -295,7 +220,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
local -a cmake_args=(
|
||||
-DCMAKE_BUILD_TYPE=${BUILD_CONFIG:-RelWithDebInfo}
|
||||
-DQT_VERSION=${QT_VERSION}
|
||||
-DCMAKE_PREFIX_PATH="${_plugin_deps}"
|
||||
-DCMAKE_PREFIX_PATH="${_plugin_deps};${advss_deps_path}"
|
||||
)
|
||||
|
||||
if (( _loglevel == 0 )) cmake_args+=(-Wno_deprecated -Wno-dev --log-level=ERROR)
|
||||
@@ -312,12 +237,17 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
num_procs=$(( $(sysctl -n hw.ncpu) + 1 ))
|
||||
|
||||
local openssl_lib_dir="${advss_deps_path}/openssl-combined/"
|
||||
local openssl_include_dir="${advss_deps_path}/openssl/include"
|
||||
|
||||
cmake_args+=(
|
||||
-DCMAKE_FRAMEWORK_PATH="${_plugin_deps}/Frameworks"
|
||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DOBS_CODESIGN_LINKER=ON
|
||||
-DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}"
|
||||
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}"
|
||||
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a"
|
||||
)
|
||||
|
||||
;;
|
||||
|
||||
172
.github/scripts/Build-Deps-Windows.ps1
vendored
Normal file
172
.github/scripts/Build-Deps-Windows.ps1
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')]
|
||||
[string] $Configuration = 'RelWithDebInfo',
|
||||
[ValidateSet('x86', 'x64')]
|
||||
[string] $Target,
|
||||
[ValidateSet('Visual Studio 17 2022', 'Visual Studio 16 2019')]
|
||||
[string] $CMakeGenerator,
|
||||
[string] $OutDirName,
|
||||
[switch] $SkipDeps,
|
||||
[switch] $SkipUnpack
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ( $DebugPreference -eq 'Continue' ) {
|
||||
$VerbosePreference = 'Continue'
|
||||
$InformationPreference = 'Continue'
|
||||
}
|
||||
|
||||
if ( $PSVersionTable.PSVersion -lt '7.0.0' ) {
|
||||
Write-Warning 'The obs-deps PowerShell build script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6'
|
||||
exit 2
|
||||
}
|
||||
|
||||
function Build {
|
||||
trap {
|
||||
Pop-Location -Stack BuildTemp -ErrorAction 'SilentlyContinue'
|
||||
Write-Error $_
|
||||
exit 2
|
||||
}
|
||||
|
||||
$ScriptHome = $PSScriptRoot
|
||||
$ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.."
|
||||
$BuildSpecFile = "${ProjectRoot}/buildspec.json"
|
||||
|
||||
$UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse
|
||||
|
||||
foreach ($Utility in $UtilityFunctions) {
|
||||
Write-Debug "Loading $($Utility.FullName)"
|
||||
. $Utility.FullName
|
||||
}
|
||||
|
||||
$BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json
|
||||
$ProductName = $BuildSpec.name
|
||||
$ProductVersion = $BuildSpec.version
|
||||
|
||||
$script:VisualStudioVersion = ''
|
||||
$script:PlatformSDK = '10.0.18363.657'
|
||||
|
||||
Setup-Host
|
||||
|
||||
if ( $CmakeGenerator -eq '' ) {
|
||||
$CmakeGenerator = $script:VisualStudioVersion
|
||||
}
|
||||
|
||||
$DepsPath = "plugin-deps-${script:DepsVersion}-*-${script:Target}"
|
||||
$OBSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
|
||||
|
||||
if ( $OutDirName -eq '' ) {
|
||||
$OutDirName = "advss-build-dependencies"
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path ${ProjectRoot}/../${OutDirName}
|
||||
|
||||
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/../${OutDirName})"
|
||||
|
||||
$OpenCVPath = "${ProjectRoot}/deps/opencv"
|
||||
$OpenCVBuildPath = "${OpenCVPath}/build"
|
||||
|
||||
Push-Location -Stack BuildOpenCVTemp
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$OpenCVCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=Release"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||
"-DBUILD_LIST=core,imgproc,objdetect"
|
||||
)
|
||||
|
||||
Log-Information "Configuring OpenCV..."
|
||||
Invoke-External cmake -S ${OpenCVPath} -B ${OpenCVBuildPath} @OpenCVCmakeArgs
|
||||
|
||||
$OpenCVCmakeArgs = @(
|
||||
'--config', "Release"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$OpenCVCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building OpenCV..."
|
||||
Invoke-External cmake --build "${OpenCVBuildPath}" @OpenCVCmakeArgs
|
||||
Log-Information "Install OpenCV}..."
|
||||
Invoke-External cmake --install "${OpenCVBuildPath}" --prefix "${ADVSSDepPath}" @OpenCVCmakeArgs
|
||||
|
||||
$LeptonicaPath = "${ProjectRoot}/deps/leptonica"
|
||||
$LeptonicaBuildPath = "${LeptonicaPath}/build"
|
||||
|
||||
Push-Location -Stack BuildLeptonicaTemp
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$LeptonicaCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||
"-DSW_BUILD=OFF"
|
||||
"-DLIBWEBP_SUPPORT=OFF"
|
||||
)
|
||||
|
||||
Log-Information "Configuring leptonica..."
|
||||
Invoke-External cmake -S ${LeptonicaPath} -B ${LeptonicaBuildPath} @LeptonicaCmakeArgs
|
||||
|
||||
$LeptonicaCmakeArgs = @(
|
||||
'--config', "${Configuration}"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$LeptonicaCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building leptonica..."
|
||||
Invoke-External cmake --build "${LeptonicaBuildPath}" @LeptonicaCmakeArgs
|
||||
|
||||
Log-Information "Install leptonica..."
|
||||
Invoke-External cmake --install "${LeptonicaBuildPath}" --prefix "${ADVSSDepPath}" @LeptonicaCmakeArgs
|
||||
|
||||
Push-Location -Stack BuildTesseractTemp
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$TesseractPath = "${ProjectRoot}/deps/tesseract"
|
||||
$TesseractBuildPath = "${TesseractPath}/build"
|
||||
|
||||
# Explicitly disable PkgConfig and tiff as it will lead build errors
|
||||
$TesseractCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||
"-DSW_BUILD=OFF"
|
||||
"-DDISABLE_CURL=ON"
|
||||
"-DBUILD_TRAINING_TOOLS=OFF"
|
||||
"-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE"
|
||||
"-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=TRUE"
|
||||
)
|
||||
|
||||
Log-Information "Configuring tesseract..."
|
||||
Invoke-External cmake -S ${TesseractPath} -B ${TesseractBuildPath} @TesseractCmakeArgs
|
||||
|
||||
$TesseractCmakeArgs = @(
|
||||
'--config', "${Configuration}"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$TesseractCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building tesseract..."
|
||||
Invoke-External cmake --build "${TesseractBuildPath}" @TesseractCmakeArgs
|
||||
|
||||
Log-Information "Install tesseract..."
|
||||
Invoke-External cmake --install "${TesseractBuildPath}" --prefix "${ADVSSDepPath}" @TesseractCmakeArgs
|
||||
}
|
||||
|
||||
Build
|
||||
114
.github/scripts/Build-Windows.ps1
vendored
114
.github/scripts/Build-Windows.ps1
vendored
@@ -6,6 +6,7 @@ param(
|
||||
[string] $Target,
|
||||
[ValidateSet('Visual Studio 17 2022', 'Visual Studio 16 2019')]
|
||||
[string] $CMakeGenerator,
|
||||
[string] $ADVSSDepName,
|
||||
[switch] $SkipAll,
|
||||
[switch] $SkipBuild,
|
||||
[switch] $SkipDeps,
|
||||
@@ -60,112 +61,13 @@ function Build {
|
||||
$DepsPath = "plugin-deps-${script:DepsVersion}-qt${script:QtVersion}-${script:Target}"
|
||||
$DepInstallPath = "$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
|
||||
|
||||
$OpenCVPath = "${ProjectRoot}/deps/opencv"
|
||||
$OpenCVBuildPath = "${OpenCVPath}/build"
|
||||
|
||||
Push-Location -Stack BuildOpenCVTemp
|
||||
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$OpenCVCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=Release"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
|
||||
"-DBUILD_LIST=core,imgproc,objdetect"
|
||||
)
|
||||
|
||||
Log-Information "Configuring OpenCV..."
|
||||
Invoke-External cmake -S ${OpenCVPath} -B ${OpenCVBuildPath} @OpenCVCmakeArgs
|
||||
|
||||
$OpenCVCmakeArgs = @(
|
||||
'--config', "Release"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$OpenCVCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building OpenCV..."
|
||||
Invoke-External cmake --build "${OpenCVBuildPath}" @OpenCVCmakeArgs
|
||||
if ( $ADVSSDepName -eq '' ) {
|
||||
Log-Information "Building advss deps ..."
|
||||
$ADVSSDepName = "advss-build-dependencies"
|
||||
invoke-expression -Command "$PSScriptRoot/Build-Deps-Windows.ps1 -Configuration $Configuration -Target $Target -CMakeGenerator `"$CMakeGenerator`" -OutDirName $ADVSSDepName -SkipDeps -SkipUnpack"
|
||||
}
|
||||
Log-Information "Install OpenCV}..."
|
||||
Invoke-External cmake --install "${OpenCVBuildPath}" --prefix "${DepInstallPath}" @OpenCVCmakeArgs
|
||||
|
||||
$LeptonicaPath = "${ProjectRoot}/deps/leptonica"
|
||||
$LeptonicaBuildPath = "${LeptonicaPath}/build"
|
||||
|
||||
Push-Location -Stack BuildLeptonicaTemp
|
||||
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$LeptonicaCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
|
||||
"-DSW_BUILD=OFF"
|
||||
"-DLIBWEBP_SUPPORT=OFF"
|
||||
)
|
||||
|
||||
Log-Information "Configuring leptonica..."
|
||||
Invoke-External cmake -S ${LeptonicaPath} -B ${LeptonicaBuildPath} @LeptonicaCmakeArgs
|
||||
|
||||
$LeptonicaCmakeArgs = @(
|
||||
'--config', "${Configuration}"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$LeptonicaCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building leptonica..."
|
||||
Invoke-External cmake --build "${LeptonicaBuildPath}" @LeptonicaCmakeArgs
|
||||
}
|
||||
Log-Information "Install leptonica..."
|
||||
Invoke-External cmake --install "${LeptonicaBuildPath}" --prefix "${DepInstallPath}" @LeptonicaCmakeArgs
|
||||
|
||||
Push-Location -Stack BuildTesseractTemp
|
||||
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
|
||||
Ensure-Location $ProjectRoot
|
||||
|
||||
$TesseractPath = "${ProjectRoot}/deps/tesseract"
|
||||
$TesseractBuildPath = "${TesseractPath}/build"
|
||||
|
||||
# Explicitly disable PkgConfig and tiff as it will lead build errors
|
||||
$TesseractCmakeArgs = @(
|
||||
'-G', $CmakeGenerator
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
|
||||
"-DSW_BUILD=OFF"
|
||||
"-DDISABLE_CURL=ON"
|
||||
"-DBUILD_TRAINING_TOOLS=OFF"
|
||||
"-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE"
|
||||
"-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=TRUE"
|
||||
)
|
||||
|
||||
Log-Information "Configuring tesseract..."
|
||||
Invoke-External cmake -S ${TesseractPath} -B ${TesseractBuildPath} @TesseractCmakeArgs
|
||||
|
||||
$TesseractCmakeArgs = @(
|
||||
'--config', "${Configuration}"
|
||||
)
|
||||
|
||||
if ( $VerbosePreference -eq 'Continue' ) {
|
||||
$TesseractCmakeArgs += ('--verbose')
|
||||
}
|
||||
|
||||
Log-Information "Building tesseract..."
|
||||
Invoke-External cmake --build "${TesseractBuildPath}" @TesseractCmakeArgs
|
||||
}
|
||||
Log-Information "Install tesseract..."
|
||||
Invoke-External cmake --install "${TesseractBuildPath}" --prefix "${DepInstallPath}" @TesseractCmakeArgs
|
||||
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/../${script:ADVSSDepName})"
|
||||
Log-Information "Using advss deps at $ADVSSDepPath ..."
|
||||
|
||||
(Get-Content -Path ${ProjectRoot}/CMakeLists.txt -Raw) `
|
||||
-replace "project\((.*) VERSION (.*)\)", "project(${ProductName} VERSION ${ProductVersion})" `
|
||||
@@ -182,7 +84,7 @@ function Build {
|
||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath};${ADVSSDepPath}"
|
||||
"-DQT_VERSION=${script:QtVersion}"
|
||||
)
|
||||
|
||||
|
||||
13
.github/scripts/build-deps-linux.sh
vendored
Executable file
13
.github/scripts/build-deps-linux.sh
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! type zsh > /dev/null 2>&1; then
|
||||
echo ' => Installing script dependency Zsh.'
|
||||
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install zsh
|
||||
fi
|
||||
|
||||
SCRIPT=$(readlink -f "${0}")
|
||||
SCRIPT_DIR=$(dirname "${SCRIPT}")
|
||||
|
||||
zsh ${SCRIPT_DIR}/build-deps-linux.zsh "${@}"
|
||||
1
.github/scripts/build-deps-linux.zsh
vendored
Symbolic link
1
.github/scripts/build-deps-linux.zsh
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
.build-deps.zsh
|
||||
1
.github/scripts/build-deps-macos.zsh
vendored
Symbolic link
1
.github/scripts/build-deps-macos.zsh
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
.build-deps.zsh
|
||||
29
.github/workflows/main.yml
vendored
29
.github/workflows/main.yml
vendored
@@ -17,6 +17,7 @@ on:
|
||||
env:
|
||||
PLUGIN_NAME: SceneSwitcher
|
||||
LIB_NAME: advanced-scene-switcher
|
||||
DEP_DIR: advss-build-dependencies-1
|
||||
|
||||
jobs:
|
||||
clang_check:
|
||||
@@ -115,9 +116,6 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/.ccache
|
||||
${{ github.workspace }}/plugin/deps/opencv/build_*
|
||||
${{ github.workspace }}/plugin/deps/leptonica/build_*
|
||||
${{ github.workspace }}/plugin/deps/tesseract/build*
|
||||
key: macos-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
|
||||
restore-keys: |
|
||||
macos-${{ matrix.arch }}-ccache-plugin-
|
||||
@@ -146,6 +144,13 @@ jobs:
|
||||
print "CODESIGN_IDENT=${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" >> $GITHUB_ENV
|
||||
print "CODESIGN_IDENT_INSTALLER=${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Dependencies
|
||||
uses: ./plugin/.github/actions/build-dependencies
|
||||
with:
|
||||
workingDirectory: ${{ github.workspace }}/plugin
|
||||
target: ${{ matrix.arch }}
|
||||
config: RelWithDebInfo
|
||||
|
||||
- name: Build Plugin
|
||||
uses: ./plugin/.github/actions/build-plugin
|
||||
with:
|
||||
@@ -248,6 +253,13 @@ jobs:
|
||||
echo 'found=false' >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build Dependencies
|
||||
uses: ./plugin/.github/actions/build-dependencies
|
||||
with:
|
||||
workingDirectory: ${{ github.workspace }}/plugin
|
||||
target: ${{ matrix.arch }}
|
||||
config: RelWithDebInfo
|
||||
|
||||
- name: Build Plugin
|
||||
uses: ./plugin/.github/actions/build-plugin
|
||||
with:
|
||||
@@ -329,9 +341,6 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/.ccache
|
||||
${{ github.workspace }}/plugin/deps/opencv/build_*
|
||||
${{ github.workspace }}/plugin/deps/leptonica/build_*
|
||||
${{ github.workspace }}/plugin/deps/tesseract/build*
|
||||
key: windows-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
|
||||
restore-keys: |
|
||||
windows-${{ matrix.arch }}-ccache-plugin-
|
||||
@@ -357,6 +366,14 @@ jobs:
|
||||
|
||||
"found=$(([string]${LabelFound}).ToLower())" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Build Dependencies
|
||||
uses: ./plugin/.github/actions/build-dependencies
|
||||
with:
|
||||
workingDirectory: ${{ github.workspace }}/plugin
|
||||
target: ${{ matrix.arch }}
|
||||
config: RelWithDebInfo
|
||||
visualStudio: 'Visual Studio 17 2022'
|
||||
|
||||
- name: Build Plugin
|
||||
uses: ./plugin/.github/actions/build-plugin
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -91,6 +91,7 @@ Thumbs.db
|
||||
.settings
|
||||
.idea
|
||||
.metadata
|
||||
.vscode
|
||||
*.iml
|
||||
*.ipr
|
||||
*.sublime*
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -19,3 +19,6 @@
|
||||
[submodule "deps/libremidi"]
|
||||
path = deps/libremidi
|
||||
url = https://github.com/jcelerier/libremidi.git
|
||||
[submodule "deps/cpp-httplib"]
|
||||
path = deps/cpp-httplib
|
||||
url = https://github.com/yhirose/cpp-httplib.git
|
||||
|
||||
@@ -254,6 +254,7 @@ target_sources(
|
||||
src/utils/curl-helper.hpp
|
||||
src/utils/duration-control.cpp
|
||||
src/utils/duration-control.hpp
|
||||
src/utils/export-symbol-helper.hpp
|
||||
src/utils/item-selection-helpers.cpp
|
||||
src/utils/item-selection-helpers.hpp
|
||||
src/utils/log-helper.hpp
|
||||
@@ -267,6 +268,8 @@ target_sources(
|
||||
src/utils/macro-export-import-dialog.hpp
|
||||
src/utils/macro-list.cpp
|
||||
src/utils/macro-list.hpp
|
||||
src/utils/macro-run-button.cpp
|
||||
src/utils/macro-run-button.hpp
|
||||
src/utils/macro-segment-selection.cpp
|
||||
src/utils/macro-segment-selection.hpp
|
||||
src/utils/math-helpers.cpp
|
||||
@@ -375,6 +378,8 @@ target_include_directories(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
||||
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE ADVSS_EXPORT_SYMBOLS=1)
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
# --- Windows-specific build settings and tasks ---
|
||||
|
||||
@@ -146,18 +146,15 @@ AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Während des
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||
AdvSceneSwitcher.condition.window="Fenster"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existiert und ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... ist {{fullscreen}} Vollbild {{maximized}} Maximiert {{focused}} Fokusiert {{windowFocusChanged}} Vordergrundfenster geändert"
|
||||
AdvSceneSwitcher.condition.window.entry.line3="Aktuelles Vordergrundfenster: {{focusWindow}}"
|
||||
AdvSceneSwitcher.condition.file="Datei"
|
||||
AdvSceneSwitcher.condition.file.type.match="entspricht"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
||||
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
||||
AdvSceneSwitcher.condition.file.local="Lokale Datei"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medien"
|
||||
AdvSceneSwitcher.condition.media.source="Quelle"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
|
||||
@@ -395,9 +392,9 @@ AdvSceneSwitcher.condition.variable.type.greaterThanVariable="ist größer als d
|
||||
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Szene wechseln"
|
||||
AdvSceneSwitcher.action.scene.entry="Wechsle zu Szene{{scenes}}mittels{{transitions}}mit einer Dauer von{{duration}}Sekunden"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="Wechsle zu Szene{{scenes}}mittels{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene="Szene wechseln"
|
||||
AdvSceneSwitcher.action.scene.entry="Wechsle{{sceneTypes}}Szene zu{{scenes}}mittels{{transitions}}mit einer Dauer von{{duration}}Sekunden"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="Wechsle{{sceneTypes}}Szene zu{{scenes}}mittels{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Warten, bis der Übergang zur Zielszene abgeschlossen ist"
|
||||
AdvSceneSwitcher.action.wait="Warten"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
||||
@@ -440,11 +437,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Stream starten"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Ausführen"
|
||||
AdvSceneSwitcher.action.run.arguments="Argumente:"
|
||||
AdvSceneSwitcher.action.run.addArgument="Argument hinzufügen"
|
||||
AdvSceneSwitcher.action.run.addArgumentDescription="Neues Argument hinzufügen:"
|
||||
AdvSceneSwitcher.action.run.entry="Ausführen {{filePath}}"
|
||||
AdvSceneSwitcher.action.run.entry.workingDirectory="Arbeitsverzeichnis:{{workingDirectory}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Sichtbarkeit von Szenenelementen"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
|
||||
@@ -506,7 +498,6 @@ AdvSceneSwitcher.action.hotkey.leftMeta="Linke Meta-Taste"
|
||||
AdvSceneSwitcher.action.hotkey.rightMeta="Rechte Meta-Taste"
|
||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Tastendruck nur an OBS senden"
|
||||
AdvSceneSwitcher.action.hotkey.disabled="Globale Tastendrücke können nicht simuliert werden - die Funktionalität beschränkt sich auf das Senden von Tastendrücken an OBS!"
|
||||
AdvSceneSwitcher.action.hotkey.entry="Drücke {{keys}} für {{duration}}"
|
||||
AdvSceneSwitcher.action.sceneOrder="Reihenfolge der Szenenelemente"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Nach oben verschieben"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Nach unten verschieben"
|
||||
@@ -567,8 +558,6 @@ AdvSceneSwitcher.action.sequence.continueFrom="Weiter mit ausgewähltem Element"
|
||||
AdvSceneSwitcher.action.websocket="Websocket"
|
||||
AdvSceneSwitcher.action.websocket.type.request="Anfrage"
|
||||
AdvSceneSwitcher.action.websocket.type.event="Ereignis"
|
||||
AdvSceneSwitcher.action.websocket.entry.request="Szenenwechsler {{type}} via {{connection}} senden"
|
||||
AdvSceneSwitcher.action.websocket.entry.event="Szenenwechsler {{type}} an verbundene Clients senden"
|
||||
AdvSceneSwitcher.action.http="HTTP"
|
||||
AdvSceneSwitcher.action.http.type.get="GET"
|
||||
AdvSceneSwitcher.action.http.type.post="POST"
|
||||
@@ -586,7 +575,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="Ungültige Auswahl!"
|
||||
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Aktionen wird nicht unterstützt!"
|
||||
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Bedingungen wird nicht unterstützt!"
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Aktueller Wert:"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}"
|
||||
|
||||
|
||||
; Transition Tab
|
||||
|
||||
@@ -30,6 +30,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose loggi
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable="Disable filtering by typing in drop down menus"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warning if plugins cannot be loaded"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
|
||||
@@ -70,6 +71,7 @@ AdvSceneSwitcher.macroTab.priorityWarning="Note: It is recommended to configure
|
||||
AdvSceneSwitcher.macroTab.help="Macros allow you to execute a string of actions depending on multiple conditions.\n\nClick on the highlighted plus symbol to add a new Macro."
|
||||
AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define Macro conditions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new condition."
|
||||
AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define Macro actions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new action."
|
||||
AdvSceneSwitcher.macroTab.editElseActionHelp="This section allows you to define Macro actions, which are executed if the conditions are *not* met.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new action."
|
||||
AdvSceneSwitcher.macroTab.edit="Edit macro"
|
||||
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
|
||||
AdvSceneSwitcher.macroTab.edit.condition="Condition type:"
|
||||
@@ -77,6 +79,7 @@ AdvSceneSwitcher.macroTab.edit.action="Action type:"
|
||||
AdvSceneSwitcher.macroTab.add="Add new macro"
|
||||
AdvSceneSwitcher.macroTab.name="Name:"
|
||||
AdvSceneSwitcher.macroTab.run="Run macro"
|
||||
AdvSceneSwitcher.macroTab.runElse="Run macro (else)"
|
||||
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already."
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
|
||||
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
|
||||
@@ -154,7 +157,7 @@ AdvSceneSwitcher.condition.audio.type.volume="Configured volume level"
|
||||
AdvSceneSwitcher.condition.audio.type.syncOffset="Sync offset"
|
||||
AdvSceneSwitcher.condition.audio.type.monitor="Audio monitoring"
|
||||
AdvSceneSwitcher.condition.audio.type.balance="Audio balance"
|
||||
AdvSceneSwitcher.condition.audio.entry="{{checkType}} of {{audioSources}} is {{condition}}{{volume}}{{syncOffset}}{{monitorTypes}}"
|
||||
AdvSceneSwitcher.condition.audio.entry="{{checkType}}of{{audioSources}}is{{condition}}{{volume}}{{syncOffset}}{{monitorTypes}}"
|
||||
AdvSceneSwitcher.condition.cursor="Cursor"
|
||||
AdvSceneSwitcher.condition.cursor.type.region="is in region"
|
||||
AdvSceneSwitcher.condition.cursor.type.moving="is moving"
|
||||
@@ -164,15 +167,17 @@ AdvSceneSwitcher.condition.cursor.button.middle="Middle mouse button"
|
||||
AdvSceneSwitcher.condition.cursor.button.right="Right mouse button"
|
||||
AdvSceneSwitcher.condition.cursor.showFrame="Show frame"
|
||||
AdvSceneSwitcher.condition.cursor.hideFrame="Hide frame"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line1="Cursor {{conditions}}{{buttons}}{{minX}}{{minY}}{{maxX}}{{maxY}}{{toggleFrameButton}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at {{xPos}} x {{yPos}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line1="Cursor{{conditions}}{{buttons}}{{minX}}{{minY}}{{maxX}}{{maxY}}{{toggleFrameButton}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at{{xPos}}x{{yPos}}"
|
||||
AdvSceneSwitcher.condition.scene="Scene"
|
||||
AdvSceneSwitcher.condition.scene.type.current="Current scene is"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
|
||||
AdvSceneSwitcher.condition.scene.type.preview="Preview scene is"
|
||||
AdvSceneSwitcher.condition.scene.type.changed="Scene changed"
|
||||
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
|
||||
AdvSceneSwitcher.condition.scene.type.currentPattern="Current scene matches"
|
||||
AdvSceneSwitcher.condition.scene.type.previousPattern="Previous scene matches"
|
||||
AdvSceneSwitcher.condition.scene.type.previewPattern="Preview scene matches"
|
||||
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="During transition check for transition target scene"
|
||||
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||
@@ -192,9 +197,9 @@ AdvSceneSwitcher.condition.file.type.contentChange="content changed"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
||||
AdvSceneSwitcher.condition.file.remote="Remote file"
|
||||
AdvSceneSwitcher.condition.file.local="Local file"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Media"
|
||||
AdvSceneSwitcher.condition.media.source="Source"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
|
||||
@@ -223,7 +228,7 @@ AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will al
|
||||
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
|
||||
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
|
||||
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
|
||||
AdvSceneSwitcher.condition.video.patternMatchMode="Use pattern matching mode {{patternMatchingModes}}"
|
||||
AdvSceneSwitcher.condition.video.patternMatchMode="Use pattern matching mode{{patternMatchingModes}}"
|
||||
AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation="Cross correlation"
|
||||
AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient="Correlation coefficient"
|
||||
AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference="Squared difference"
|
||||
@@ -264,9 +269,9 @@ AdvSceneSwitcher.condition.video.type.main="OBS's main output"
|
||||
AdvSceneSwitcher.condition.video.type.source="Source"
|
||||
AdvSceneSwitcher.condition.video.type.scene="Scene"
|
||||
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier):{{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors:{{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds"
|
||||
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area"
|
||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}"
|
||||
@@ -292,10 +297,10 @@ AdvSceneSwitcher.condition.record.state.pause="Recording paused"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
|
||||
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
|
||||
AdvSceneSwitcher.condition.process="Process"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}} is running {{focused}} and is focused"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process: {{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}is running{{focused}}and is focused"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="Idle"
|
||||
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for {{duration}}"
|
||||
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Plugin state"
|
||||
AdvSceneSwitcher.condition.pluginState.state.start="Plugin started"
|
||||
AdvSceneSwitcher.condition.pluginState.state.restart="Plugin restarted"
|
||||
@@ -310,10 +315,10 @@ AdvSceneSwitcher.condition.timer.type.fixed="Fixed"
|
||||
AdvSceneSwitcher.condition.timer.type.random="Random"
|
||||
AdvSceneSwitcher.condition.timer.pause="Pause"
|
||||
AdvSceneSwitcher.condition.timer.continue="Continue"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}} duration of {{duration}} has passed"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.random="{{type}} duration from {{duration}} to {{duration2}} has passed"
|
||||
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Save time remaining {{autoReset}} Automatically reset timer after duration was reached"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}}duration of{{duration}}has passed"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.random="{{type}}duration from{{duration}}to{{duration2}}has passed"
|
||||
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining:{{remaining}}seconds"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}}{{reset}}{{saveRemaining}}Save time remaining{{autoReset}}Automatically reset timer after duration was reached"
|
||||
AdvSceneSwitcher.condition.timer.reset="Reset"
|
||||
AdvSceneSwitcher.condition.macro="Macro"
|
||||
AdvSceneSwitcher.condition.macro.type.count="Macro run count"
|
||||
@@ -330,16 +335,17 @@ AdvSceneSwitcher.condition.macro.state.type.above="More than"
|
||||
AdvSceneSwitcher.condition.macro.state.type.equal="Exactly"
|
||||
AdvSceneSwitcher.condition.macro.count.reset="Reset"
|
||||
AdvSceneSwitcher.condition.macro.pausedWarning="Selected macro is currently paused!"
|
||||
AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true"
|
||||
AdvSceneSwitcher.condition.macro.multistate.entry="Conditions of {{multiStateConditions}}{{multiStateCount}} of the following macros are true:"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} was executed {{conditions}} {{count}} times"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}"
|
||||
AdvSceneSwitcher.condition.macro.state.entry="Conditions of{{macros}}are true"
|
||||
AdvSceneSwitcher.condition.macro.multistate.entry="Conditions of{{multiStateConditions}}{{multiStateCount}}of the following macros are true:"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}}was executed{{conditions}}{{count}}times"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count:{{currentCount}}{{resetCount}}"
|
||||
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
|
||||
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
|
||||
AdvSceneSwitcher.condition.source="Source"
|
||||
AdvSceneSwitcher.condition.source.type.active="Is active"
|
||||
AdvSceneSwitcher.condition.source.type.showing="Is showing"
|
||||
AdvSceneSwitcher.condition.source.type.settings="Settings match"
|
||||
AdvSceneSwitcher.condition.source.type.settingsChanged="Settings changed"
|
||||
AdvSceneSwitcher.condition.source.sceneVisibilityHint="Scene specific visibility can be checked using the \"Scene item visibility\" condition"
|
||||
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
|
||||
AdvSceneSwitcher.condition.source.entry.line1="{{sources}}{{conditions}}"
|
||||
@@ -353,8 +359,9 @@ AdvSceneSwitcher.condition.filter="Filter"
|
||||
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
|
||||
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
|
||||
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
|
||||
AdvSceneSwitcher.condition.filter.type.settingsChanged="Settings changed"
|
||||
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
|
||||
AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line1="On{{sources}}{{filters}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}}{{getSettings}}"
|
||||
AdvSceneSwitcher.condition.sceneOrder="Scene item order"
|
||||
@@ -367,7 +374,7 @@ AdvSceneSwitcher.condition.hotkey="Hotkey"
|
||||
AdvSceneSwitcher.condition.hotkey.name="Macro trigger hotkey"
|
||||
AdvSceneSwitcher.condition.hotkey.tip="Note: You can configure the keybindings for this hotkey in the OBS settings window"
|
||||
AdvSceneSwitcher.condition.hotkey.entry.line1="Hotkey is pressed"
|
||||
AdvSceneSwitcher.condition.hotkey.entry.line2="Name: {{name}}"
|
||||
AdvSceneSwitcher.condition.hotkey.entry.line2="Name:{{name}}"
|
||||
AdvSceneSwitcher.condition.replay="Replay buffer"
|
||||
AdvSceneSwitcher.condition.replay.state.stopped="Replay buffer stopped"
|
||||
AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
|
||||
@@ -392,17 +399,17 @@ AdvSceneSwitcher.condition.date.ignoreDate="If unchecked the date component will
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="If unchecked the time component will be ignored"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
|
||||
AdvSceneSwitcher.condition.date.entry.simple="On {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
|
||||
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
|
||||
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repeat every {{duration}} on date match"
|
||||
AdvSceneSwitcher.condition.date.entry.pattern="Current date \"{{currentDate}}\" matches pattern {{pattern}}"
|
||||
AdvSceneSwitcher.condition.date.entry.simple="On{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
|
||||
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
|
||||
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Repeat every{{duration}}on date match"
|
||||
AdvSceneSwitcher.condition.date.entry.pattern="Current date \"{{currentDate}}\" matches pattern{{pattern}}"
|
||||
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
|
||||
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
|
||||
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}On repeat update selected date to repeat date"
|
||||
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}}{{getSettings}}"
|
||||
AdvSceneSwitcher.condition.transition="Transition"
|
||||
AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
|
||||
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
|
||||
@@ -426,7 +433,7 @@ AdvSceneSwitcher.condition.openvr="OpenVR"
|
||||
AdvSceneSwitcher.condition.openvr.errorStatus="OpenVR error: "
|
||||
AdvSceneSwitcher.condition.openvr.entry.line1="HMD is in ..."
|
||||
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD is currently at {{xPos}} x {{yPos}} x {{zPos}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD is currently at{{xPos}}x{{yPos}}x{{zPos}}"
|
||||
AdvSceneSwitcher.condition.stats="OBS stats"
|
||||
AdvSceneSwitcher.condition.stats.type.fps="FPS"
|
||||
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU Usage"
|
||||
@@ -445,15 +452,15 @@ AdvSceneSwitcher.condition.stats.condition.above="above"
|
||||
AdvSceneSwitcher.condition.stats.condition.equals="equal to"
|
||||
AdvSceneSwitcher.condition.stats.condition.below="below"
|
||||
AdvSceneSwitcher.condition.stats.dockHint="You can open the \"Stats\" dock to view the current status"
|
||||
AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
|
||||
AdvSceneSwitcher.condition.stats.entry="{{stats}}is{{condition}}{{value}}"
|
||||
AdvSceneSwitcher.condition.profile="Profile"
|
||||
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
|
||||
AdvSceneSwitcher.condition.profile.entry="Current active profile is{{profiles}}"
|
||||
AdvSceneSwitcher.condition.websocket="Websocket"
|
||||
AdvSceneSwitcher.condition.websocket.type.request="Scene Switcher Request"
|
||||
AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
|
||||
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.request="{{type}}was received:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.event="{{type}}was received from{{connection}}:"
|
||||
AdvSceneSwitcher.condition.variable="Variable"
|
||||
AdvSceneSwitcher.condition.variable.type.compare="equals"
|
||||
AdvSceneSwitcher.condition.variable.type.empty="is empty"
|
||||
@@ -466,11 +473,11 @@ AdvSceneSwitcher.condition.variable.type.lessThanVariable="is less than variable
|
||||
AdvSceneSwitcher.condition.variable.type.greaterThanVariable="is greater than variable"
|
||||
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
|
||||
AdvSceneSwitcher.condition.run="Run"
|
||||
AdvSceneSwitcher.condition.run.entry="Process exits before timeout of{{timeout}} seconds"
|
||||
AdvSceneSwitcher.condition.run.entry="Process exits before timeout of{{timeout}}seconds"
|
||||
AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}Check for exit code{{exitCode}}"
|
||||
AdvSceneSwitcher.condition.midi="MIDI"
|
||||
AdvSceneSwitcher.condition.midi.entry="Mesasge was received from {{device}} which matches:"
|
||||
AdvSceneSwitcher.condition.midi.entry.listen="Set MIDI message selection to messages incoming on selected device: {{listenButton}}"
|
||||
AdvSceneSwitcher.condition.midi.entry="Mesasge was received from{{device}}which matches:"
|
||||
AdvSceneSwitcher.condition.midi.entry.listen="Set MIDI message selection to messages incoming on selected device:{{listenButton}}"
|
||||
AdvSceneSwitcher.condition.display="Display"
|
||||
AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches"
|
||||
AdvSceneSwitcher.condition.display.type.displayCount="Number of connected displays is"
|
||||
@@ -483,15 +490,18 @@ AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Information about th
|
||||
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Switch scene"
|
||||
AdvSceneSwitcher.action.scene.entry="Switch to scene{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="Switch to scene{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene="Switch scene"
|
||||
AdvSceneSwitcher.action.scene.type.program="Program"
|
||||
AdvSceneSwitcher.action.scene.type.preview="Preview"
|
||||
AdvSceneSwitcher.action.scene.entry="Switch{{sceneTypes}}scene to{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="Switch{{sceneTypes}}scene to{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene.entry.preview="Switch{{sceneTypes}}scene to{{scenes}}"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
|
||||
AdvSceneSwitcher.action.wait="Wait"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="fixed"
|
||||
AdvSceneSwitcher.action.wait.type.random="random"
|
||||
AdvSceneSwitcher.action.wait.entry.fixed="Wait for {{waitType}} duration of {{duration}}"
|
||||
AdvSceneSwitcher.action.wait.entry.random="Wait for {{waitType}} duration from {{duration}} to {{duration2}}"
|
||||
AdvSceneSwitcher.action.wait.entry.fixed="Wait for{{waitType}}duration of{{duration}}"
|
||||
AdvSceneSwitcher.action.wait.entry.random="Wait for{{waitType}}duration from{{duration}}to{{duration2}}"
|
||||
AdvSceneSwitcher.action.audio="Audio"
|
||||
AdvSceneSwitcher.action.audio.type.mute="Mute"
|
||||
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
|
||||
@@ -503,8 +513,8 @@ AdvSceneSwitcher.action.audio.type.balance="Set balance"
|
||||
AdvSceneSwitcher.action.audio.balance.description="Move the slider in the direction of the audio channel you want to focus on."
|
||||
AdvSceneSwitcher.action.audio.fade.type.duration="over a duration of"
|
||||
AdvSceneSwitcher.action.audio.fade.type.rate="at a rate of"
|
||||
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade {{fadeTypes}} {{duration}} seconds."
|
||||
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}per second."
|
||||
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade{{fadeTypes}}{{duration}}seconds."
|
||||
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade{{fadeTypes}}{{rate}}per second."
|
||||
AdvSceneSwitcher.action.audio.fade.wait="Wait for fade to complete."
|
||||
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
|
||||
AdvSceneSwitcher.action.audio.entry="{{actions}}{{audioSources}}{{volume}}{{syncOffset}}{{monitorTypes}}"
|
||||
@@ -547,7 +557,7 @@ AdvSceneSwitcher.action.filter.type.enable="Enable"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
||||
AdvSceneSwitcher.action.filter.type.toggle="Toggle"
|
||||
AdvSceneSwitcher.action.filter.type.settings="Set settings"
|
||||
AdvSceneSwitcher.action.filter.entry="On {{sources}} {{actions}} {{filters}}"
|
||||
AdvSceneSwitcher.action.filter.entry="On{{sources}}{{actions}}{{filters}}"
|
||||
AdvSceneSwitcher.action.filter.getSettings="Get current settings"
|
||||
AdvSceneSwitcher.action.source="Source"
|
||||
AdvSceneSwitcher.action.source.type.enable="Enable"
|
||||
@@ -558,6 +568,7 @@ AdvSceneSwitcher.action.source.type.pressSettingsButton="Press settings button"
|
||||
AdvSceneSwitcher.action.source.type.refreshSettings.tooltip="Can be used to refresh browser, media, etc. sources"
|
||||
AdvSceneSwitcher.action.source.type.deinterlaceMode="Set deinterlace mode"
|
||||
AdvSceneSwitcher.action.source.type.deinterlaceOrder="Set deinterlace field order"
|
||||
AdvSceneSwitcher.action.source.type.openInteractionDialog="Open interaction dialog"
|
||||
AdvSceneSwitcher.action.source.noSettingsButtons="No buttons found!"
|
||||
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
|
||||
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
|
||||
@@ -647,7 +658,7 @@ AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}
|
||||
AdvSceneSwitcher.action.file="File"
|
||||
AdvSceneSwitcher.action.file.type.write="Write"
|
||||
AdvSceneSwitcher.action.file.type.append="Append"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}} to {{filePath}}:"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}}to{{filePath}}:"
|
||||
AdvSceneSwitcher.action.studioMode="Studio mode"
|
||||
AdvSceneSwitcher.action.studioMode.type.swap="Swap preview and program scene"
|
||||
AdvSceneSwitcher.action.studioMode.type.setScene="Set preview scene to"
|
||||
@@ -659,15 +670,15 @@ AdvSceneSwitcher.action.transition.type.scene="scene transition"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modify {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Set transition duration to {{duration}}seconds"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modify{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Set transition duration to{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.timer="Timer"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continue"
|
||||
AdvSceneSwitcher.action.timer.type.reset="Reset"
|
||||
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Set time remaining of"
|
||||
AdvSceneSwitcher.action.timer.entry="{{timerAction}} timers on {{macros}} {{duration}}"
|
||||
AdvSceneSwitcher.action.timer.entry="{{timerAction}}timers on{{macros}}{{duration}}"
|
||||
AdvSceneSwitcher.action.random="Random"
|
||||
AdvSceneSwitcher.action.random.allowRepeat="Allow consecutive execution of the same macro"
|
||||
AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (paused macros are ignored)"
|
||||
@@ -685,9 +696,9 @@ AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}location"
|
||||
AdvSceneSwitcher.action.profile="Profile"
|
||||
AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}"
|
||||
AdvSceneSwitcher.action.profile.entry="Switch active profile to{{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Scene collection"
|
||||
AdvSceneSwitcher.action.sceneCollection.entry="Switch active scene collection to {{sceneCollections}}"
|
||||
AdvSceneSwitcher.action.sceneCollection.entry="Switch active scene collection to{{sceneCollections}}"
|
||||
AdvSceneSwitcher.action.sceneCollection.warning="Note: Any actions following after this will not be executed as the changing scene collection will also reload the scene switcher settings.\nThe scene collection action will be ignored while the settings window is opened."
|
||||
AdvSceneSwitcher.action.sequence="Sequence"
|
||||
AdvSceneSwitcher.action.sequence.entry="Each time this action is performed run the next macro in the list (paused macros are ignored)"
|
||||
@@ -712,8 +723,8 @@ AdvSceneSwitcher.action.http.headers="Headers:"
|
||||
AdvSceneSwitcher.action.http.addHeader="Add header"
|
||||
AdvSceneSwitcher.action.http.type.get="GET"
|
||||
AdvSceneSwitcher.action.http.type.post="POST"
|
||||
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
|
||||
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
|
||||
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
|
||||
AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
|
||||
AdvSceneSwitcher.action.variable="Variable"
|
||||
AdvSceneSwitcher.action.variable.type.set="Set to fixed value"
|
||||
AdvSceneSwitcher.action.variable.type.append="Append"
|
||||
@@ -727,6 +738,8 @@ AdvSceneSwitcher.action.variable.type.subString="Set to substring of current val
|
||||
AdvSceneSwitcher.action.variable.type.findAndReplace="Find and replace in current value"
|
||||
AdvSceneSwitcher.action.variable.type.mathExpression="Mathematical expression"
|
||||
AdvSceneSwitcher.action.variable.type.askForValue="Get user input"
|
||||
AdvSceneSwitcher.action.variable.type.environmentVariable="Set to environment variable value"
|
||||
AdvSceneSwitcher.action.variable.type.sceneItemCount="Set to scene item count of scene"
|
||||
AdvSceneSwitcher.action.variable.askForValuePromptDefault="Assign value to variable \"%1\":"
|
||||
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
|
||||
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
|
||||
@@ -738,11 +751,12 @@ AdvSceneSwitcher.action.variable.invalidSelection="Invalid selection!"
|
||||
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable values from %1 actions is not supported!"
|
||||
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}"
|
||||
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}} Substring size:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}"
|
||||
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
|
||||
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
|
||||
AdvSceneSwitcher.action.variable.entry.userInput="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
|
||||
AdvSceneSwitcher.action.projector="Projector"
|
||||
AdvSceneSwitcher.action.projector.type.source="Source"
|
||||
AdvSceneSwitcher.action.projector.type.scene="Scene"
|
||||
@@ -755,247 +769,26 @@ AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
|
||||
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
||||
AdvSceneSwitcher.action.midi="MIDI"
|
||||
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on {{listenDevices}}: {{listenButton}}"
|
||||
AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:"
|
||||
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}"
|
||||
AdvSceneSwitcher.action.osc="Open Sound Control"
|
||||
AdvSceneSwitcher.action.sceneLock="Scene item lock"
|
||||
AdvSceneSwitcher.action.sceneLock.type.lock="lock"
|
||||
AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
|
||||
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
|
||||
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
|
||||
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
|
||||
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}} with a duration of {{duration}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition {{defTransitionDelay}} after scene change."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
|
||||
|
||||
; Pause Scenes Tab
|
||||
AdvSceneSwitcher.pauseTab.title="Pause"
|
||||
AdvSceneSwitcher.pauseTab.pauseOnScene="Pause the Scene Switcher on scene"
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pause the Scene Switcher when "
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus2="is in focus"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeScene="scene is active"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeWindow="window is in focus"
|
||||
AdvSceneSwitcher.pauseTab.pauseTargetAll="all"
|
||||
AdvSceneSwitcher.pauseTab.pauseEntry="Pause {{pauseTargets}} checks when {{pauseTypes}} {{scenes}} {{windows}}"
|
||||
AdvSceneSwitcher.pauseTab.help="On this tab you can configure to pause individual switching methods if a scene is active or window is in focus.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Window Title Tab
|
||||
AdvSceneSwitcher.windowTitleTab.title="Title"
|
||||
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Enter either direct window titles or valid regex. You can check syntax and matches for regular expressions using <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignore this window name"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
|
||||
AdvSceneSwitcher.windowTitleTab.fullscreen="if fullscreen"
|
||||
AdvSceneSwitcher.windowTitleTab.maximized="if maximized"
|
||||
AdvSceneSwitcher.windowTitleTab.focused="if focused"
|
||||
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
|
||||
AdvSceneSwitcher.windowTitleTab.windowsHelp="Switch scenes based on the window title of running applications.\nThe following additional conditions can be selected:\nThe window is Fullscreen\nThe window is maximized\nThe window is focused\n\nClick on the highlighted plus symbol to continue."
|
||||
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="If a window title is ignored the scene switcher will act as if the previously selected window is still in focus.\nThis will allow you to avoid scene switches, if you frequently switch to a different window, which shall not trigger a scene change.\n\nChoose a window or enter a window title above and click on the plus symbol below to add it to the list."
|
||||
|
||||
; Executable Tab
|
||||
AdvSceneSwitcher.executableTab.title="Executable"
|
||||
AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven"
|
||||
AdvSceneSwitcher.executableTab.requiresFocus="only if focused"
|
||||
AdvSceneSwitcher.executableTab.entry="When {{processes}} is running switch to {{scenes}} using {{transitions}} {{requiresFocus}}"
|
||||
AdvSceneSwitcher.executableTab.help="This tab will allow you to automatically switch scenes if a process is running.\nThis can be useful in situations where the window name could change or is not known.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Screen Region Tab
|
||||
AdvSceneSwitcher.screenRegionTab.title="Region"
|
||||
AdvSceneSwitcher.screenRegionTab.currentPosition="Cursor is currently at:"
|
||||
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Show guide frames"
|
||||
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hide guide frames"
|
||||
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="No selection"
|
||||
AdvSceneSwitcher.screenRegionTab.entry="If cursor is in {{minX}} {{minY}} x {{maxX}} {{maxY}} switch to {{scenes}} using {{transitions}} unless in {{excludeScenes}}"
|
||||
AdvSceneSwitcher.screenRegionTab.help="This tab will allow you to automatically switch scenes based on the current position of your mouse cursor.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Media Tab
|
||||
AdvSceneSwitcher.mediaTab.title="Media"
|
||||
AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro"
|
||||
AdvSceneSwitcher.mediaTab.states.none="None"
|
||||
AdvSceneSwitcher.mediaTab.states.playing="Playing"
|
||||
AdvSceneSwitcher.mediaTab.states.opening="Opening"
|
||||
AdvSceneSwitcher.mediaTab.states.buffering="Buffering"
|
||||
AdvSceneSwitcher.mediaTab.states.paused="Paused"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Ended"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Error"
|
||||
AdvSceneSwitcher.mediaTab.states.playlistEnd="Ended(Playlist)"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Any"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="None"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Time shorter"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Time longer"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Time remaining shorter"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Time remaining longer"
|
||||
AdvSceneSwitcher.mediaTab.entry="When {{mediaSources}} state is {{states}} and {{timeRestrictions}} {{time}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.mediaTab.help="This tab will allow you to switch scenes based on the states of media sources.\nFor example, you can automatically switch back to the previous scene once the selected media sourced ended its playback.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; File Tab
|
||||
AdvSceneSwitcher.fileTab.title="File"
|
||||
AdvSceneSwitcher.fileTab.readWriteSceneFile="Read / write scene from / to file"
|
||||
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Write the name of the current scene to this file:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Enable switching of scenes based on file input"
|
||||
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Read scene name to be switched to from this file:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Switch scene based on file contents"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning="Please note that if you choose the remote option the scene switcher will try to access the remote location every x ms as specified on the General tab!"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning1="Note that the scene switcher will try to access the remote location every "
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
|
||||
AdvSceneSwitcher.fileTab.libcurlWarning="Failed to load libcurl! Accessing remote files will not be possible!"
|
||||
AdvSceneSwitcher.fileTab.selectWrite="Select a file to write to ..."
|
||||
AdvSceneSwitcher.fileTab.selectRead="Select a file to read from ..."
|
||||
AdvSceneSwitcher.fileTab.textFileType="Text files (*.txt)"
|
||||
AdvSceneSwitcher.fileTab.anyFileType="Any files (*.*)"
|
||||
AdvSceneSwitcher.fileTab.remote="remote file"
|
||||
AdvSceneSwitcher.fileTab.local="local file"
|
||||
AdvSceneSwitcher.fileTab.useRegExp="use regular expressions (pattern matching)"
|
||||
AdvSceneSwitcher.fileTab.checkfileContentTime="if modification date changed"
|
||||
AdvSceneSwitcher.fileTab.checkfileContent="if content changed"
|
||||
AdvSceneSwitcher.fileTab.entry="Switch to {{scenes}} using {{transitions}} if content of {{fileType}} {{filePath}} {{browseButton}} matches:"
|
||||
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
|
||||
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.fileTab.help="This tab will allow you to automatically switch scenes based on the content of remote or local files.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Random Tab
|
||||
AdvSceneSwitcher.randomTab.title="Random"
|
||||
AdvSceneSwitcher.randomTab.randomDisabledWarning="Functionality disabled - To activate select \"If no switch condition is met switch to any scene in Random tab\" on General tab"
|
||||
AdvSceneSwitcher.randomTab.entry="If no switch condition is met switch to {{scenes}} using {{transitions}} for {{delay}}"
|
||||
AdvSceneSwitcher.randomTab.help="The scene switcher will randomly choose an entry on this tab to switch to for the configured time.\nNote that the same entry will not be chosen twice in a row.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Time Tab
|
||||
AdvSceneSwitcher.timeTab.title="Time"
|
||||
AdvSceneSwitcher.timeTab.anyDay="On any day"
|
||||
AdvSceneSwitcher.timeTab.mondays="Mondays"
|
||||
AdvSceneSwitcher.timeTab.tuesdays="Tuesdays"
|
||||
AdvSceneSwitcher.timeTab.wednesdays="Wednesdays"
|
||||
AdvSceneSwitcher.timeTab.thursdays="Thursdays"
|
||||
AdvSceneSwitcher.timeTab.fridays="Fridays"
|
||||
AdvSceneSwitcher.timeTab.saturdays="Saturdays"
|
||||
AdvSceneSwitcher.timeTab.sundays="Sundays"
|
||||
AdvSceneSwitcher.timeTab.afterstart="After streaming/recording start"
|
||||
AdvSceneSwitcher.timeTab.afterstart.tip="The time relative to the start of streaming / recording will be used"
|
||||
AdvSceneSwitcher.timeTab.entry="{{triggers}} at {{time}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.timeTab.help="This tab will allow you to automatically switch to a different scene based on the current local time.\n\nNote that the scene switcher will only switch scenes at the exact time you specified.\nMake sure you have configured the priority settings on the General tab to your liking so the selected time point will not be missed due to other switching methods having a higher priority.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Idle Tab
|
||||
AdvSceneSwitcher.idleTab.title="Idle"
|
||||
AdvSceneSwitcher.idleTab.enable="Enable Idle Detection"
|
||||
AdvSceneSwitcher.idleTab.idleswitch="After {{duration}} of no keyboard or mouse inputs switch to scene {{scenes}} using the {{transitions}}"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Do not switch if"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="is in focus"
|
||||
|
||||
; Scene Sequence Tab
|
||||
AdvSceneSwitcher.sceneSequenceTab.title="Sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.description="A sequence of automatic scene switches can be cancelled by either pausing/stopping the scene switcher or manually switching to a different scene"
|
||||
AdvSceneSwitcher.sceneSequenceTab.save="Save scene sequences to file"
|
||||
AdvSceneSwitcher.sceneSequenceTab.load="Load scene sequences from file"
|
||||
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Save Scene Sequence to file ..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Select a file to read Scene Sequence from ..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadFail="Advanced Scene Switcher failed to import settings!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Advanced Scene Switcher settings imported successfully!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} using {{transitions}} {{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Audio Tab
|
||||
AdvSceneSwitcher.audioTab.title="Audio"
|
||||
AdvSceneSwitcher.audioTab.condition.above="above"
|
||||
AdvSceneSwitcher.audioTab.condition.below="below"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} seconds switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match ..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} seconds switch to {{scenes}} using {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
|
||||
AdvSceneSwitcher.videoTab.condition.match="exactly matches"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="An exact match requires the target and the source image to be of the same resolution.\nAdditionally every single pixel needs to match, which is why use of image formats which use compression (e.g. .JPG) is not recommended!"
|
||||
AdvSceneSwitcher.videoTab.condition.differ="does not match"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="has not changed"
|
||||
AdvSceneSwitcher.videoTab.condition.hasChanged="has changed"
|
||||
AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.videoTab.entry="When {{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
|
||||
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Network"
|
||||
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
|
||||
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
|
||||
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
|
||||
AdvSceneSwitcher.networkTab.server.sendSceneChange="Send messages for scene changes"
|
||||
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches"
|
||||
AdvSceneSwitcher.networkTab.server.sendPreview="Send messages for preview scene change when running in Studio mode"
|
||||
AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
|
||||
AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.server.status.notRunning="Not running"
|
||||
AdvSceneSwitcher.networkTab.server.status.starting="Starting"
|
||||
AdvSceneSwitcher.networkTab.server.status.running="Running"
|
||||
AdvSceneSwitcher.networkTab.server.restart="Restart server"
|
||||
AdvSceneSwitcher.networkTab.client="Start client (Receives scene switches messages)"
|
||||
AdvSceneSwitcher.networkTab.client.address="Hostname or IP address"
|
||||
AdvSceneSwitcher.networkTab.client.port="Port"
|
||||
AdvSceneSwitcher.networkTab.client.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.client.status.disconnected="Disconnected"
|
||||
AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
|
||||
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
|
||||
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
|
||||
|
||||
; Scene Group Tab
|
||||
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
|
||||
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit="Edit Scene Groups"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.name="Name:"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.type="Type: {{type}}"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.count="Count"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.time="Time"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.random="Random"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.count="Advance to next scene in list after {{count}} matches"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.time="Advance to next scene in list after {{time}} has passed"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.random="Choose next scene in list at random"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Start from beginning if end of scene list is reached"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Add scene"
|
||||
AdvSceneSwitcher.sceneGroupTab.add="Add Scene Group"
|
||||
AdvSceneSwitcher.sceneGroupTab.defaultname="Scene Group %1"
|
||||
AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
|
||||
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
|
||||
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
|
||||
|
||||
; Scene Trigger Tab
|
||||
AdvSceneSwitcher.sceneTriggerTab.title="Scene Triggers"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--select trigger--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="is active"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="is not active"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="switched away from"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--select action--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="start recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="pause recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="unpause recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="stop recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="stop streaming"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="start streaming"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="start replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="stop replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="unmute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="start the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="stop the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="start virtual camera"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="stop virtual camera"
|
||||
AdvSceneSwitcher.sceneTriggerTab.entry="When {{scenes}} {{triggers}} {{actions}} {{audioSources}} after {{duration}}"
|
||||
AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on scene changes, like stopping recording or streaming."
|
||||
AdvSceneSwitcher.action.twitch="Twitch"
|
||||
AdvSceneSwitcher.action.twitch.type.title="Set stream title"
|
||||
AdvSceneSwitcher.action.twitch.type.category="Set stream category"
|
||||
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
|
||||
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip"
|
||||
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration"
|
||||
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
|
||||
AdvSceneSwitcher.action.twitch.entry="On{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}"
|
||||
AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
|
||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
||||
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
|
||||
AdvSceneSwitcher.action.twitch.title.title="Enter title"
|
||||
|
||||
; Hotkey
|
||||
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
|
||||
@@ -1009,7 +802,7 @@ AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Move macro segment selection dow
|
||||
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remove selected macro segment"
|
||||
|
||||
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
|
||||
AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
|
||||
AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}"
|
||||
|
||||
AdvSceneSwitcher.close="Close"
|
||||
AdvSceneSwitcher.browse="Browse"
|
||||
@@ -1029,6 +822,7 @@ AdvSceneSwitcher.macroSegmentSelection.invalid="Invalid selection!"
|
||||
AdvSceneSwitcher.variable.select="--select variable--"
|
||||
AdvSceneSwitcher.variable.add="Add new variable"
|
||||
AdvSceneSwitcher.variable.configure="Configure variable settings"
|
||||
AdvSceneSwitcher.variable.invalid="Invalid varialbe selection"
|
||||
AdvSceneSwitcher.variable.name="Name:"
|
||||
AdvSceneSwitcher.variable.value="Current value:"
|
||||
AdvSceneSwitcher.variable.save="Save / load behavior"
|
||||
@@ -1039,6 +833,7 @@ AdvSceneSwitcher.variable.save.default="Set to value"
|
||||
AdvSceneSwitcher.connection.select="--select connection--"
|
||||
AdvSceneSwitcher.connection.add="Add new connection"
|
||||
AdvSceneSwitcher.connection.configure="Configure connection settings"
|
||||
AdvSceneSwitcher.connection.invalid="Invalid connection selection"
|
||||
AdvSceneSwitcher.connection.name="Name:"
|
||||
AdvSceneSwitcher.connection.useCustomURI="Use custom URI"
|
||||
AdvSceneSwitcher.connection.customURI="Address:"
|
||||
@@ -1125,6 +920,41 @@ AdvSceneSwitcher.osc.message.type.false="False"
|
||||
AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
|
||||
AdvSceneSwitcher.osc.message.type.null="Nil"
|
||||
|
||||
AdvSceneSwitcher.twitchToken.name="Account name:"
|
||||
AdvSceneSwitcher.twitchToken.nameNotAvailable="Account already in use"
|
||||
AdvSceneSwitcher.twitchToken.select="--select Twitch connection--"
|
||||
AdvSceneSwitcher.twitchToken.add="Add new connection"
|
||||
AdvSceneSwitcher.twitchToken.configure="Configure Twitch connection settings"
|
||||
AdvSceneSwitcher.twitchToken.value="Token:"
|
||||
AdvSceneSwitcher.twitchToken.invalid="Invalid twitch token"
|
||||
AdvSceneSwitcher.twitchToken.request="Request token"
|
||||
AdvSceneSwitcher.twitchToken.request.waiting="Waiting for token approval ..."
|
||||
AdvSceneSwitcher.twitchToken.request.fail="Failed to get token!"
|
||||
AdvSceneSwitcher.twitchToken.request.fail.browser="Authentication failed! (%1)\nYou can close this window now."
|
||||
AdvSceneSwitcher.twitchToken.request.fail.stateMismatch="State mismatch"
|
||||
AdvSceneSwitcher.twitchToken.request.success="Successfully received token!"
|
||||
AdvSceneSwitcher.twitchToken.request.success.browser="Authentication successful! You can close this window now."
|
||||
AdvSceneSwitcher.twitchToken.request.notSet="No token set - Please request new token!"
|
||||
AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
|
||||
AdvSceneSwitcher.twitchToken.analytics.readExtensions="View analytics data for the Twitch Extensions owned by the authenticated account."
|
||||
AdvSceneSwitcher.twitchToken.analytics.readGames="View analytics data for the games owned by the authenticated account."
|
||||
AdvSceneSwitcher.twitchToken.bits.read="View Bits information for a channel."
|
||||
AdvSceneSwitcher.twitchToken.channel.manageBroadcast="Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
|
||||
AdvSceneSwitcher.twitchToken.channel.startCommercial="Run commercials on a channel."
|
||||
AdvSceneSwitcher.twitchToken.channel.createClip="Create clips from channel's broadcasts."
|
||||
|
||||
AdvSceneSwitcher.twitchCategories.fetchStart="Fetching stream categories ..."
|
||||
AdvSceneSwitcher.twitchCategories.fetchStatus="Got %1 stream categories."
|
||||
AdvSceneSwitcher.twitchCategories.fetchSkip="Skip fetching more stream categories"
|
||||
AdvSceneSwitcher.twitchCategories.fetchStop="Stop"
|
||||
AdvSceneSwitcher.twitchCategories.search="Search for stream category ..."
|
||||
AdvSceneSwitcher.twitchCategories.name="Category name:"
|
||||
AdvSceneSwitcher.twitchCategories.manualSearch="Search for additional category and it add to the selection list"
|
||||
AdvSceneSwitcher.twitchCategories.noViewersCategoriesMissing="Categories without any viewers will have to be searched for manually"
|
||||
AdvSceneSwitcher.twitchCategories.searchFailed="No new categories were found for \"%1\"."
|
||||
AdvSceneSwitcher.twitchCategories.searchSuccess="%1 new categories were found for \"%2\" and were added to the list!"
|
||||
AdvSceneSwitcher.twitchCategories.select="--select category--"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
@@ -1149,6 +979,7 @@ AdvSceneSwitcher.enterPath="--enter path--"
|
||||
AdvSceneSwitcher.enterText="--enter text--"
|
||||
AdvSceneSwitcher.enterURL="--enter URL--"
|
||||
AdvSceneSwitcher.selectHotkey="--select hotkey--"
|
||||
AdvSceneSwitcher.selectDisplay="--select display--"
|
||||
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
|
||||
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
|
||||
|
||||
@@ -1192,3 +1023,237 @@ AdvSceneSwitcher.duration.condition.within="Within the last"
|
||||
AdvSceneSwitcher.audio.monitor.none="Monitor Off"
|
||||
AdvSceneSwitcher.audio.monitor.monitorOnly="Monitor Only (mute output)"
|
||||
AdvSceneSwitcher.audio.monitor.both="Monitor and Output"
|
||||
|
||||
; Legacy tabs below - please don't waste your time adding translations for these :)
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
|
||||
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
|
||||
AdvSceneSwitcher.transitionTab.entry="Switch from{{scenes}}to{{scenes2}}using{{transitions}}with a duration of{{duration}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene{{scenes}}is active change default scene transition to{{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition{{defTransitionDelay}}after scene change."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
|
||||
|
||||
; Pause Scenes Tab
|
||||
AdvSceneSwitcher.pauseTab.title="Pause"
|
||||
AdvSceneSwitcher.pauseTab.pauseOnScene="Pause the Scene Switcher on scene"
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pause the Scene Switcher when "
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus2="is in focus"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeScene="scene is active"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeWindow="window is in focus"
|
||||
AdvSceneSwitcher.pauseTab.pauseTargetAll="all"
|
||||
AdvSceneSwitcher.pauseTab.pauseEntry="Pause{{pauseTargets}}checks when{{pauseTypes}}{{scenes}}{{windows}}"
|
||||
AdvSceneSwitcher.pauseTab.help="On this tab you can configure to pause individual switching methods if a scene is active or window is in focus.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Window Title Tab
|
||||
AdvSceneSwitcher.windowTitleTab.title="Title"
|
||||
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Enter either direct window titles or valid regex. You can check syntax and matches for regular expressions using <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignore this window name"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
|
||||
AdvSceneSwitcher.windowTitleTab.fullscreen="if fullscreen"
|
||||
AdvSceneSwitcher.windowTitleTab.maximized="if maximized"
|
||||
AdvSceneSwitcher.windowTitleTab.focused="if focused"
|
||||
AdvSceneSwitcher.windowTitleTab.entry="{{windows}}{{scenes}}{{transitions}}{{fullscreen}}{{maximized}}{{focused}}"
|
||||
AdvSceneSwitcher.windowTitleTab.windowsHelp="Switch scenes based on the window title of running applications.\nThe following additional conditions can be selected:\nThe window is Fullscreen\nThe window is maximized\nThe window is focused\n\nClick on the highlighted plus symbol to continue."
|
||||
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="If a window title is ignored the scene switcher will act as if the previously selected window is still in focus.\nThis will allow you to avoid scene switches, if you frequently switch to a different window, which shall not trigger a scene change.\n\nChoose a window or enter a window title above and click on the plus symbol below to add it to the list."
|
||||
|
||||
; Executable Tab
|
||||
AdvSceneSwitcher.executableTab.title="Executable"
|
||||
AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven"
|
||||
AdvSceneSwitcher.executableTab.requiresFocus="only if focused"
|
||||
AdvSceneSwitcher.executableTab.entry="When{{processes}}is running switch to{{scenes}}using{{transitions}}{{requiresFocus}}"
|
||||
AdvSceneSwitcher.executableTab.help="This tab will allow you to automatically switch scenes if a process is running.\nThis can be useful in situations where the window name could change or is not known.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Screen Region Tab
|
||||
AdvSceneSwitcher.screenRegionTab.title="Region"
|
||||
AdvSceneSwitcher.screenRegionTab.currentPosition="Cursor is currently at:"
|
||||
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Show guide frames"
|
||||
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hide guide frames"
|
||||
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="No selection"
|
||||
AdvSceneSwitcher.screenRegionTab.entry="If cursor is in{{minX}}{{minY}}x{{maxX}}{{maxY}}switch to{{scenes}}using{{transitions}}unless in{{excludeScenes}}"
|
||||
AdvSceneSwitcher.screenRegionTab.help="This tab will allow you to automatically switch scenes based on the current position of your mouse cursor.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Media Tab
|
||||
AdvSceneSwitcher.mediaTab.title="Media"
|
||||
AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro"
|
||||
AdvSceneSwitcher.mediaTab.states.none="None"
|
||||
AdvSceneSwitcher.mediaTab.states.playing="Playing"
|
||||
AdvSceneSwitcher.mediaTab.states.opening="Opening"
|
||||
AdvSceneSwitcher.mediaTab.states.buffering="Buffering"
|
||||
AdvSceneSwitcher.mediaTab.states.paused="Paused"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Ended"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Error"
|
||||
AdvSceneSwitcher.mediaTab.states.playlistEnd="Ended(Playlist)"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Any"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="None"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Time shorter"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Time longer"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Time remaining shorter"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Time remaining longer"
|
||||
AdvSceneSwitcher.mediaTab.entry="When{{mediaSources}}state is{{states}}and{{timeRestrictions}}{{time}}switch to{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.mediaTab.help="This tab will allow you to switch scenes based on the states of media sources.\nFor example, you can automatically switch back to the previous scene once the selected media sourced ended its playback.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; File Tab
|
||||
AdvSceneSwitcher.fileTab.title="File"
|
||||
AdvSceneSwitcher.fileTab.readWriteSceneFile="Read / write scene from / to file"
|
||||
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Write the name of the current scene to this file:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Enable switching of scenes based on file input"
|
||||
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Read scene name to be switched to from this file:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Switch scene based on file contents"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning="Please note that if you choose the remote option the scene switcher will try to access the remote location every x ms as specified on the General tab!"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning1="Note that the scene switcher will try to access the remote location every "
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
|
||||
AdvSceneSwitcher.fileTab.libcurlWarning="Failed to load libcurl! Accessing remote files will not be possible!"
|
||||
AdvSceneSwitcher.fileTab.selectWrite="Select a file to write to ..."
|
||||
AdvSceneSwitcher.fileTab.selectRead="Select a file to read from ..."
|
||||
AdvSceneSwitcher.fileTab.textFileType="Text files (*.txt)"
|
||||
AdvSceneSwitcher.fileTab.anyFileType="Any files (*.*)"
|
||||
AdvSceneSwitcher.fileTab.remote="remote file"
|
||||
AdvSceneSwitcher.fileTab.local="local file"
|
||||
AdvSceneSwitcher.fileTab.useRegExp="use regular expressions (pattern matching)"
|
||||
AdvSceneSwitcher.fileTab.checkfileContentTime="if modification date changed"
|
||||
AdvSceneSwitcher.fileTab.checkfileContent="if content changed"
|
||||
AdvSceneSwitcher.fileTab.entry="Switch to{{scenes}}using{{transitions}}if content of{{fileType}}{{filePath}}{{browseButton}}matches:"
|
||||
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
|
||||
AdvSceneSwitcher.fileTab.entry3="{{useRegex}}{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.fileTab.help="This tab will allow you to automatically switch scenes based on the content of remote or local files.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Random Tab
|
||||
AdvSceneSwitcher.randomTab.title="Random"
|
||||
AdvSceneSwitcher.randomTab.randomDisabledWarning="Functionality disabled - To activate select \"If no switch condition is met switch to any scene in Random tab\" on General tab"
|
||||
AdvSceneSwitcher.randomTab.entry="If no switch condition is met switch to{{scenes}}using{{transitions}}for{{delay}}"
|
||||
AdvSceneSwitcher.randomTab.help="The scene switcher will randomly choose an entry on this tab to switch to for the configured time.\nNote that the same entry will not be chosen twice in a row.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Time Tab
|
||||
AdvSceneSwitcher.timeTab.title="Time"
|
||||
AdvSceneSwitcher.timeTab.anyDay="On any day"
|
||||
AdvSceneSwitcher.timeTab.mondays="Mondays"
|
||||
AdvSceneSwitcher.timeTab.tuesdays="Tuesdays"
|
||||
AdvSceneSwitcher.timeTab.wednesdays="Wednesdays"
|
||||
AdvSceneSwitcher.timeTab.thursdays="Thursdays"
|
||||
AdvSceneSwitcher.timeTab.fridays="Fridays"
|
||||
AdvSceneSwitcher.timeTab.saturdays="Saturdays"
|
||||
AdvSceneSwitcher.timeTab.sundays="Sundays"
|
||||
AdvSceneSwitcher.timeTab.afterstart="After streaming/recording start"
|
||||
AdvSceneSwitcher.timeTab.afterstart.tip="The time relative to the start of streaming / recording will be used"
|
||||
AdvSceneSwitcher.timeTab.entry="{{triggers}}at{{time}}switch to{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.timeTab.help="This tab will allow you to automatically switch to a different scene based on the current local time.\n\nNote that the scene switcher will only switch scenes at the exact time you specified.\nMake sure you have configured the priority settings on the General tab to your liking so the selected time point will not be missed due to other switching methods having a higher priority.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Idle Tab
|
||||
AdvSceneSwitcher.idleTab.title="Idle"
|
||||
AdvSceneSwitcher.idleTab.enable="Enable Idle Detection"
|
||||
AdvSceneSwitcher.idleTab.idleswitch="After{{duration}}of no keyboard or mouse inputs switch to scene{{scenes}}using the{{transitions}}"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Do not switch if"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="is in focus"
|
||||
|
||||
; Scene Sequence Tab
|
||||
AdvSceneSwitcher.sceneSequenceTab.title="Sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.description="A sequence of automatic scene switches can be cancelled by either pausing/stopping the scene switcher or manually switching to a different scene"
|
||||
AdvSceneSwitcher.sceneSequenceTab.save="Save scene sequences to file"
|
||||
AdvSceneSwitcher.sceneSequenceTab.load="Load scene sequences from file"
|
||||
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Save Scene Sequence to file ..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Select a file to read Scene Sequence from ..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadFail="Advanced Scene Switcher failed to import settings!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Advanced Scene Switcher settings imported successfully!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry="When{{startScenes}}is active switch to{{scenes}}after{{delay}}using{{transitions}}{{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After{{delay}}switch to{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Audio Tab
|
||||
AdvSceneSwitcher.audioTab.title="Audio"
|
||||
AdvSceneSwitcher.audioTab.condition.above="above"
|
||||
AdvSceneSwitcher.audioTab.condition.below="below"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.audioTab.entry="When the volume of{{audioSources}}is{{condition}}{{volumeWidget}}for{{duration}}seconds switch to{{scenes}}using{{transitions}}{{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match ..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... for{{duration}}seconds switch to{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
|
||||
AdvSceneSwitcher.videoTab.condition.match="exactly matches"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="An exact match requires the target and the source image to be of the same resolution.\nAdditionally every single pixel needs to match, which is why use of image formats which use compression (e.g. .JPG) is not recommended!"
|
||||
AdvSceneSwitcher.videoTab.condition.differ="does not match"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="has not changed"
|
||||
AdvSceneSwitcher.videoTab.condition.hasChanged="has changed"
|
||||
AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
|
||||
AdvSceneSwitcher.videoTab.entry="When{{videoSources}}{{condition}}{{filePath}}{{browseButton}}for{{duration}}switch to{{scenes}}using{{transitions}}{{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
|
||||
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Network"
|
||||
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
|
||||
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
|
||||
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
|
||||
AdvSceneSwitcher.networkTab.server.sendSceneChange="Send messages for scene changes"
|
||||
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches"
|
||||
AdvSceneSwitcher.networkTab.server.sendPreview="Send messages for preview scene change when running in Studio mode"
|
||||
AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
|
||||
AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.server.status.notRunning="Not running"
|
||||
AdvSceneSwitcher.networkTab.server.status.starting="Starting"
|
||||
AdvSceneSwitcher.networkTab.server.status.running="Running"
|
||||
AdvSceneSwitcher.networkTab.server.restart="Restart server"
|
||||
AdvSceneSwitcher.networkTab.client="Start client (Receives scene switches messages)"
|
||||
AdvSceneSwitcher.networkTab.client.address="Hostname or IP address"
|
||||
AdvSceneSwitcher.networkTab.client.port="Port"
|
||||
AdvSceneSwitcher.networkTab.client.status.currentStatus="Current status"
|
||||
AdvSceneSwitcher.networkTab.client.status.disconnected="Disconnected"
|
||||
AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
|
||||
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
|
||||
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
|
||||
|
||||
; Scene Group Tab
|
||||
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
|
||||
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit="Edit Scene Groups"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.name="Name:"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.type="Type:{{type}}"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.count="Count"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.time="Time"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.random="Random"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.count="Advance to next scene in list after{{count}}matches"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.time="Advance to next scene in list after{{time}}has passed"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.random="Choose next scene in list at random"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Start from beginning if end of scene list is reached"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Add scene"
|
||||
AdvSceneSwitcher.sceneGroupTab.add="Add Scene Group"
|
||||
AdvSceneSwitcher.sceneGroupTab.defaultname="Scene Group %1"
|
||||
AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
|
||||
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
|
||||
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
|
||||
|
||||
; Scene Trigger Tab
|
||||
AdvSceneSwitcher.sceneTriggerTab.title="Scene Triggers"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--select trigger--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="is active"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="is not active"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="switched away from"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--select action--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="start recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="pause recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="unpause recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="stop recording"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="stop streaming"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="start streaming"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="start replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="stop replay buffer"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="unmute source"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="start the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="stop the scene switcher"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="start virtual camera"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="stop virtual camera"
|
||||
AdvSceneSwitcher.sceneTriggerTab.entry="When{{scenes}}{{triggers}}{{actions}}{{audioSources}}after{{duration}}"
|
||||
AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on scene changes, like stopping recording or streaming."
|
||||
|
||||
@@ -122,12 +122,10 @@ AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Durante la tr
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||
AdvSceneSwitcher.condition.window="Ventana"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existen y ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... es {{fullscreen}} pantalla completa {{maximized}} maximizada {{focused}} enfocada {{windowFocusChanged}} ventana de primer plano cambiada"
|
||||
AdvSceneSwitcher.condition.file="Archivo"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medios"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
|
||||
@@ -324,8 +322,8 @@ AdvSceneSwitcher.condition.stats.dockHint="Puede abrir el panel de \"Estadístic
|
||||
AdvSceneSwitcher.condition.stats.entry="{{stats}} esta {{condition}} {{value}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Cambiar escena"
|
||||
AdvSceneSwitcher.action.scene.entry="Cambiar a la escena {{scenes}} usando {{transitions}} con una duración de {{duration}} segundos"
|
||||
AdvSceneSwitcher.action.scene="Cambiar escena"
|
||||
AdvSceneSwitcher.action.scene.entry="Cambiar a la{{sceneTypes}}escena{{scenes}}usando{{transitions}}con una duración de{{duration}} segundos"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Espere hasta que se complete la transición a la escena de destino"
|
||||
AdvSceneSwitcher.action.wait="Esperar"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="fijo"
|
||||
@@ -362,10 +360,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Ejecutar"
|
||||
AdvSceneSwitcher.action.run.arguments="Argumentos:"
|
||||
AdvSceneSwitcher.action.run.addArgument="Agregar argumento"
|
||||
AdvSceneSwitcher.action.run.addArgumentDescription="Añadir nuevo argumento:"
|
||||
AdvSceneSwitcher.action.run.entry="Ejecutar {{filePath}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
|
||||
@@ -422,7 +416,6 @@ AdvSceneSwitcher.action.hotkey.leftMeta="Meta izquierda"
|
||||
AdvSceneSwitcher.action.hotkey.rightMeta="Meta derecho"
|
||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Enviar pulsación de tecla solo a OBS"
|
||||
AdvSceneSwitcher.action.hotkey.disabled="No se puede simular la pulsación de teclas: ¡funcionalidad desactivada!"
|
||||
AdvSceneSwitcher.action.hotkey.entry="Presione {{keys}} durante {{duration}}"
|
||||
AdvSceneSwitcher.action.sceneOrder="Orden de elementos de escena"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Mover hacia arriba"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Mover hacia abajo"
|
||||
|
||||
1209
data/locale/fr-FR.ini
Normal file
1209
data/locale/fr-FR.ini
Normal file
File diff suppressed because it is too large
Load Diff
@@ -86,10 +86,9 @@ AdvSceneSwitcher.condition.scene="Сцена"
|
||||
AdvSceneSwitcher.condition.scene.type.current="Текущий"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
||||
AdvSceneSwitcher.condition.window="Окно"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..."
|
||||
AdvSceneSwitcher.condition.file="Файл"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Медиа"
|
||||
AdvSceneSwitcher.condition.video="Видео"
|
||||
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
||||
@@ -116,8 +115,8 @@ AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Автоматиче
|
||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Переключить сцену"
|
||||
AdvSceneSwitcher.action.scene.entry="Перейти к сцене {{scenes}} используя {{transitions}} с продолжительностью {{duration}} секунд"
|
||||
AdvSceneSwitcher.action.scene="Переключить сцену"
|
||||
AdvSceneSwitcher.action.scene.entry="Перейти к сцене{{sceneTypes}}{{scenes}}используя{{transitions}}с продолжительностью{{duration}}секунд"
|
||||
AdvSceneSwitcher.action.wait="Подождать"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="фиксированный"
|
||||
AdvSceneSwitcher.action.wait.type.random="случайный"
|
||||
|
||||
@@ -112,12 +112,10 @@ AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Geçiş hedefi
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||
AdvSceneSwitcher.condition.window="Pencere"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} Varolan ve ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... {{fullscreen}} Tam ekran {{maximized}} büyütüldü {{focused}} odaklanıldı {{windowFocusChanged}} ön plan penceresi değişikliği"
|
||||
AdvSceneSwitcher.condition.file="Dosya"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medya"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
|
||||
@@ -261,8 +259,8 @@ AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD mevcut {{xPos}} x {{yPos}} x {{zPos}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Sahne Degistirici"
|
||||
AdvSceneSwitcher.action.scene.entry="Sahneyi {{scenes}} kullanarak {{transitions}} süresi olan {{duration}} saniye"
|
||||
AdvSceneSwitcher.action.scene="Sahne Degistirici"
|
||||
AdvSceneSwitcher.action.scene.entry="Sahneyi{{sceneTypes}}{{scenes}}kullanarak{{transitions}}süresi olan{{duration}}saniye"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Hedef sahneye geçiş tamamlanana kadar bekleyin"
|
||||
AdvSceneSwitcher.action.wait="Bekle"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="sabit"
|
||||
@@ -292,10 +290,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Çalıştır"
|
||||
AdvSceneSwitcher.action.run.arguments="Argümanlar:"
|
||||
AdvSceneSwitcher.action.run.addArgument="Argüman ekle"
|
||||
AdvSceneSwitcher.action.run.addArgumentDescription="Yeni Argüman ekle:"
|
||||
AdvSceneSwitcher.action.run.entry="Çalıştır {{filePath}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Sahne öğesi görünürlüğü"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
|
||||
@@ -350,7 +344,6 @@ AdvSceneSwitcher.action.hotkey.leftMeta="Sol Meta"
|
||||
AdvSceneSwitcher.action.hotkey.rightMeta="Sağ Meta"
|
||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Tuşa basımı yalnızca OBS'ye gönder"
|
||||
AdvSceneSwitcher.action.hotkey.disabled="Tuşa basma simülasyonu yapılamıyor - işlevsellik devre dışı!"
|
||||
AdvSceneSwitcher.action.hotkey.entry="Bas {{keys}} uygun {{duration}}"
|
||||
AdvSceneSwitcher.action.sceneOrder="Sahne öğesi sırası"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Yukarı taşı"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Aşağı indir"
|
||||
|
||||
@@ -178,9 +178,9 @@ AdvSceneSwitcher.condition.file.type.contentChange="内容已更改"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="修改日期已更改"
|
||||
AdvSceneSwitcher.condition.file.remote="远程文件"
|
||||
AdvSceneSwitcher.condition.file.local="本地文件"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="媒体"
|
||||
AdvSceneSwitcher.condition.media.source="源"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
|
||||
@@ -448,9 +448,9 @@ AdvSceneSwitcher.condition.display.type.displayCount="连接的显示器的数
|
||||
AdvSceneSwitcher.condition.display.entry="{{conditions}}{{displays}}{{regex}}{{displayCount}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="切换场景"
|
||||
AdvSceneSwitcher.action.scene.entry="切换场景 {{scenes}} 使用 {{transitions}} 时长 {{duration}} 秒"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="切换到场景{{scenes}}使用{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene="切换场景"
|
||||
AdvSceneSwitcher.action.scene.entry="切换场景{{sceneTypes}}{{scenes}}使用{{transitions}}时长{{duration}}秒"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="切换到场景{{sceneTypes}}{{scenes}}使用{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待目标场景的过渡完成"
|
||||
AdvSceneSwitcher.action.wait="等待"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="固定数值"
|
||||
@@ -559,7 +559,6 @@ AdvSceneSwitcher.action.hotkey.leftMeta="左 Meta"
|
||||
AdvSceneSwitcher.action.hotkey.rightMeta="右 Meta"
|
||||
AdvSceneSwitcher.action.hotkey.onlyOBS="仅向OBS发送按键"
|
||||
AdvSceneSwitcher.action.hotkey.disabled="无法模拟按键-功能已禁用!"
|
||||
AdvSceneSwitcher.action.hotkey.entry="按下 {{keys}} 在 {{duration}} 秒"
|
||||
AdvSceneSwitcher.action.sceneOrder="场景项目顺序"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="上移"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="下移"
|
||||
@@ -624,8 +623,6 @@ AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
|
||||
AdvSceneSwitcher.action.websocket="websocket"
|
||||
AdvSceneSwitcher.action.websocket.type.request="request"
|
||||
AdvSceneSwitcher.action.websocket.type.event="event"
|
||||
AdvSceneSwitcher.action.websocket.entry.request="通过{{connection}} 发送场景切换器 {{type}}"
|
||||
AdvSceneSwitcher.action.websocket.entry.event="发送场景切换器 {{type}} 到连接的客户端"
|
||||
AdvSceneSwitcher.action.http="Http"
|
||||
AdvSceneSwitcher.action.http.setHeaders="设置头信息(SET headers)"
|
||||
AdvSceneSwitcher.action.http.headers="头信息(headers):"
|
||||
@@ -655,7 +652,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="无效选择!"
|
||||
AdvSceneSwitcher.action.variable.actionNoVariableSupport="不支持从 %1 个操作获取变量值!"
|
||||
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条件中获取变量值!"
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}"
|
||||
AdvSceneSwitcher.action.variable.entry.substringIndex="子字符串开始:{{subStringStart}} 子字符串大小:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.entry.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
|
||||
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
|
||||
|
||||
8
data/res/images/DarkSearch.svg
Normal file
8
data/res/images/DarkSearch.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
|
||||
<defs>
|
||||
</defs>
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
|
||||
<path d="M 87.803 77.194 L 68.212 57.602 c 9.5 -14.422 7.912 -34.054 -4.766 -46.732 c 0 0 -0.001 0 -0.001 0 c -14.495 -14.493 -38.08 -14.494 -52.574 0 c -14.494 14.495 -14.494 38.079 0 52.575 c 7.248 7.247 16.767 10.87 26.287 10.87 c 7.134 0 14.267 -2.035 20.445 -6.104 l 19.591 19.591 C 78.659 89.267 80.579 90 82.498 90 s 3.84 -0.733 5.305 -2.197 C 90.732 84.873 90.732 80.124 87.803 77.194 z M 21.48 52.837 c -8.645 -8.646 -8.645 -22.713 0 -31.358 c 4.323 -4.322 10 -6.483 15.679 -6.483 c 5.678 0 11.356 2.161 15.678 6.483 c 8.644 8.644 8.645 22.707 0.005 31.352 c -0.002 0.002 -0.004 0.003 -0.005 0.005 c -0.002 0.002 -0.003 0.003 -0.004 0.005 C 44.184 61.481 30.123 61.48 21.48 52.837 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: #fefefe; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
8
data/res/images/LightSearch.svg
Normal file
8
data/res/images/LightSearch.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
|
||||
<defs>
|
||||
</defs>
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
|
||||
<path d="M 87.803 77.194 L 68.212 57.602 c 9.5 -14.422 7.912 -34.054 -4.766 -46.732 c 0 0 -0.001 0 -0.001 0 c -14.495 -14.493 -38.08 -14.494 -52.574 0 c -14.494 14.495 -14.494 38.079 0 52.575 c 7.248 7.247 16.767 10.87 26.287 10.87 c 7.134 0 14.267 -2.035 20.445 -6.104 l 19.591 19.591 C 78.659 89.267 80.579 90 82.498 90 s 3.84 -0.733 5.305 -2.197 C 90.732 84.873 90.732 80.124 87.803 77.194 z M 21.48 52.837 c -8.645 -8.646 -8.645 -22.713 0 -31.358 c 4.323 -4.322 10 -6.483 15.679 -6.483 c 5.678 0 11.356 2.161 15.678 6.483 c 8.644 8.644 8.645 22.707 0.005 31.352 c -0.002 0.002 -0.004 0.003 -0.005 0.005 c -0.002 0.002 -0.003 0.003 -0.004 0.005 C 44.184 61.481 30.123 61.48 21.48 52.837 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: #202020; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
deps/cpp-httplib
vendored
Submodule
1
deps/cpp-httplib
vendored
Submodule
Submodule deps/cpp-httplib added at c7ed1796a7
@@ -67,8 +67,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>957</width>
|
||||
<height>905</height>
|
||||
<width>962</width>
|
||||
<height>1033</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
@@ -256,6 +256,30 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableComboBoxFilter">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_13">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_26">
|
||||
<item>
|
||||
@@ -805,7 +829,7 @@
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.macroTab.edit</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_33">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_38">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0,0,0,0,0,0,0">
|
||||
<item>
|
||||
@@ -819,7 +843,7 @@
|
||||
<widget class="QLineEdit" name="macroName"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="runMacro">
|
||||
<widget class="advss::MacroRunButton" name="runMacro">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.macroTab.run</string>
|
||||
</property>
|
||||
@@ -912,6 +936,16 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="advss::MacroSegmentList" name="conditionsList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
<property name="leftMargin">
|
||||
@@ -1066,170 +1100,360 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="macroActions" native="true">
|
||||
<layout class="QVBoxLayout" name="macroActionsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionAdd">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">addIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionRemove">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">removeIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_133">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_47">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_132">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionTop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionUp">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionDown">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionBottom">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_14">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QSplitter" name="macroElseActionSplitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="macroActions" native="true">
|
||||
<layout class="QVBoxLayout" name="macroActionsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="advss::MacroSegmentList" name="actionsList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionAdd">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">addIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionRemove">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">removeIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_133">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_47">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_132">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionTop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionUp">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionDown">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionBottom">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_14">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="macroElseActions" native="true">
|
||||
<layout class="QVBoxLayout" name="macroElseActionsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="advss::MacroSegmentList" name="elseActionsList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_33">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionAdd">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">addIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionRemove">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">removeIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_138">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_50">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_139">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionTop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionUp">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionDown">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="elseActionBottom">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_22">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -4619,6 +4843,17 @@
|
||||
<extends>QListView</extends>
|
||||
<header>macro-tree.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>advss::MacroSegmentList</class>
|
||||
<extends>QScrollArea</extends>
|
||||
<header>macro-segment-list.hpp</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>advss::MacroRunButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>macro-run-button.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
#include <QMainWindow>
|
||||
#include <QAction>
|
||||
#include <QFileDialog>
|
||||
#include <QDirIterator>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "switcher-data.hpp"
|
||||
#include "status-control.hpp"
|
||||
@@ -17,6 +7,15 @@
|
||||
#include "utility.hpp"
|
||||
#include "version.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QAction>
|
||||
#include <QFileDialog>
|
||||
#include <QDirIterator>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
||||
@@ -328,12 +327,14 @@ void SwitcherData::SetPreconditions()
|
||||
lastCursorPos = GetCursorPos();
|
||||
}
|
||||
|
||||
void ClearWebsocketMessages();
|
||||
|
||||
void SwitcherData::ResetForNextInterval()
|
||||
{
|
||||
// Core reset functions
|
||||
ClearWebsocketMessages();
|
||||
// Plugin reset functions
|
||||
for (const auto &func : resetForNextIntervalFuncs) {
|
||||
for (const auto &func : resetIntervalSteps) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public slots:
|
||||
void on_saveWindowGeo_stateChanged(int state);
|
||||
void on_showTrayNotifications_stateChanged(int state);
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
void on_disableComboBoxFilter_stateChanged(int state);
|
||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||
void on_hideLegacyTabs_stateChanged(int state);
|
||||
void on_priorityUp_clicked();
|
||||
@@ -83,10 +84,13 @@ public:
|
||||
void SetEditMacro(Macro &m);
|
||||
void SetMacroEditAreaDisabled(bool);
|
||||
void HighlightAction(int idx, QColor color = QColor(Qt::green));
|
||||
void HighlightElseAction(int idx, QColor color = QColor(Qt::green));
|
||||
void HighlightCondition(int idx, QColor color = QColor(Qt::green));
|
||||
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
|
||||
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
|
||||
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
||||
void SetActionData(Macro &m);
|
||||
void SetElseActionData(Macro &m);
|
||||
void SetConditionData(Macro &m);
|
||||
void SwapActions(Macro *m, int pos1, int pos2);
|
||||
void SwapConditions(Macro *m, int pos1, int pos2);
|
||||
@@ -97,7 +101,6 @@ public slots:
|
||||
void on_macroUp_clicked();
|
||||
void on_macroDown_clicked();
|
||||
void on_macroName_editingFinished();
|
||||
void on_runMacro_clicked();
|
||||
void on_runMacroInParallel_stateChanged(int value);
|
||||
void on_runMacroOnChange_stateChanged(int value);
|
||||
void on_conditionAdd_clicked();
|
||||
@@ -112,29 +115,50 @@ public slots:
|
||||
void on_actionUp_clicked();
|
||||
void on_actionDown_clicked();
|
||||
void on_actionBottom_clicked();
|
||||
void on_elseActionAdd_clicked();
|
||||
void on_elseActionRemove_clicked();
|
||||
void on_elseActionTop_clicked();
|
||||
void on_elseActionUp_clicked();
|
||||
void on_elseActionDown_clicked();
|
||||
void on_elseActionBottom_clicked();
|
||||
void MacroSelectionAboutToChange();
|
||||
void MacroSelectionChanged();
|
||||
void UpMacroSegementHotkey();
|
||||
void DownMacroSegementHotkey();
|
||||
void DeleteMacroSegementHotkey();
|
||||
void ShowMacroContextMenu(const QPoint &);
|
||||
void ShowMacroActionsContextMenu(const QPoint &);
|
||||
void ShowMacroElseActionsContextMenu(const QPoint &);
|
||||
void ShowMacroConditionsContextMenu(const QPoint &);
|
||||
void CopyMacro();
|
||||
void RenameCurrentMacro();
|
||||
void ExportMacros();
|
||||
void ImportMacros();
|
||||
void ExpandAllActions();
|
||||
void ExpandAllElseActions();
|
||||
void ExpandAllConditions();
|
||||
void CollapseAllActions();
|
||||
void CollapseAllElseActions();
|
||||
void CollapseAllConditions();
|
||||
void MinimizeActions();
|
||||
void MaximizeActions();
|
||||
void MinimizeElseActions();
|
||||
void MaximizeElseActions();
|
||||
void MinimizeConditions();
|
||||
void MaximizeConditions();
|
||||
void MacroActionSelectionChanged(int idx);
|
||||
void MacroActionReorder(int to, int target);
|
||||
void AddMacroAction(int idx);
|
||||
void RemoveMacroAction(int idx);
|
||||
void MoveMacroActionUp(int idx);
|
||||
void MoveMacroActionDown(int idx);
|
||||
void MacroElseActionSelectionChanged(int idx);
|
||||
void MacroElseActionReorder(int to, int target);
|
||||
void AddMacroElseAction(int idx);
|
||||
void RemoveMacroElseAction(int idx);
|
||||
void SwapElseActions(Macro *m, int pos1, int pos2);
|
||||
void MoveMacroElseActionUp(int idx);
|
||||
void MoveMacroElseActionDown(int idx);
|
||||
void MacroConditionSelectionChanged(int idx);
|
||||
void MacroConditionReorder(int to, int target);
|
||||
void AddMacroCondition(int idx);
|
||||
@@ -156,6 +180,7 @@ signals:
|
||||
void MacroSegmentOrderChanged();
|
||||
void HighlightMacrosChanged(bool value);
|
||||
void HighlightActionsChanged(bool value);
|
||||
void HighlightElseActionsChanged(bool value);
|
||||
void HighlightConditionsChanged(bool value);
|
||||
|
||||
void ConnectionAdded(const QString &);
|
||||
@@ -166,19 +191,16 @@ signals:
|
||||
void VariableRemoved(const QString &);
|
||||
|
||||
private:
|
||||
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
|
||||
|
||||
void SetupMacroSegmentSelection(MacroSection type, int idx);
|
||||
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
|
||||
bool MacroTabIsInFocus();
|
||||
|
||||
MacroSegmentList *conditionsList = nullptr;
|
||||
MacroSegmentList *actionsList = nullptr;
|
||||
|
||||
enum class MacroSection {
|
||||
CONDITIONS,
|
||||
ACTIONS,
|
||||
};
|
||||
MacroSection lastInteracted = MacroSection::CONDITIONS;
|
||||
int currentConditionIdx = -1;
|
||||
int currentActionIdx = -1;
|
||||
int currentElseActionIdx = -1;
|
||||
|
||||
/* --- End of macro tab section --- */
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "switcher-data.hpp"
|
||||
#include "status-control.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "version.h"
|
||||
|
||||
@@ -131,10 +132,9 @@ void AdvSceneSwitcher::closeEvent(QCloseEvent *)
|
||||
}
|
||||
switcher->windowPos = this->pos();
|
||||
switcher->windowSize = this->size();
|
||||
switcher->macroActionConditionSplitterPosition =
|
||||
ui->macroActionConditionSplitter->sizes();
|
||||
switcher->macroListMacroEditSplitterPosition =
|
||||
ui->macroListMacroEditSplitter->sizes();
|
||||
MacroSelectionAboutToChange(); // Trigger saving of splitter states
|
||||
|
||||
obs_frontend_save();
|
||||
}
|
||||
@@ -175,6 +175,16 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
||||
switcher->disableHints = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_disableComboBoxFilter_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->disableFilterComboboxFilter = state;
|
||||
FilterComboBox::SetFilterBehaviourEnabled(!state);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
@@ -494,6 +504,11 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
||||
loadSceneGroups(obj);
|
||||
LoadVariables(obj);
|
||||
LoadConnections(obj);
|
||||
|
||||
for (const auto &func : loadSteps) {
|
||||
func(obj);
|
||||
}
|
||||
|
||||
LoadMacros(obj);
|
||||
loadWindowTitleSwitches(obj);
|
||||
loadScreenRegionSwitches(obj);
|
||||
@@ -548,6 +563,10 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
||||
SaveHotkeys(obj);
|
||||
SaveUISettings(obj);
|
||||
SaveVersion(obj, g_GIT_SHA1);
|
||||
|
||||
for (const auto &func : saveSteps) {
|
||||
func(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
|
||||
@@ -575,6 +594,8 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_bool(obj, "showSystemTrayNotifications",
|
||||
showSystemTrayNotifications);
|
||||
obs_data_set_bool(obj, "disableHints", disableHints);
|
||||
obs_data_set_bool(obj, "disableFilterComboboxFilter",
|
||||
disableFilterComboboxFilter);
|
||||
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
|
||||
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
|
||||
|
||||
@@ -623,6 +644,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
||||
showSystemTrayNotifications =
|
||||
obs_data_get_bool(obj, "showSystemTrayNotifications");
|
||||
disableHints = obs_data_get_bool(obj, "disableHints");
|
||||
disableFilterComboboxFilter =
|
||||
obs_data_get_bool(obj, "disableFilterComboboxFilter");
|
||||
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
|
||||
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
|
||||
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
|
||||
@@ -652,34 +675,6 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
||||
lastImportPath = obs_data_get_string(obj, "lastImportPath");
|
||||
}
|
||||
|
||||
static void saveSplitterPos(QList<int> &sizes, obs_data_t *obj,
|
||||
const std::string name)
|
||||
{
|
||||
auto array = obs_data_array_create();
|
||||
for (int i = 0; i < sizes.count(); ++i) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
obs_data_set_int(array_obj, "pos", sizes[i]);
|
||||
obs_data_array_push_back(array, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, name.c_str(), array);
|
||||
obs_data_array_release(array);
|
||||
}
|
||||
|
||||
static void loadSplitterPos(QList<int> &sizes, obs_data_t *obj,
|
||||
const std::string name)
|
||||
{
|
||||
sizes.clear();
|
||||
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *item = obs_data_array_item(array, i);
|
||||
sizes << obs_data_get_int(item, "pos");
|
||||
obs_data_release(item);
|
||||
}
|
||||
obs_data_array_release(array);
|
||||
}
|
||||
|
||||
void SwitcherData::SaveUISettings(obs_data_t *obj)
|
||||
{
|
||||
obs_data_set_int(obj, "generalTabPos", tabOrder[0]);
|
||||
@@ -707,9 +702,7 @@ void SwitcherData::SaveUISettings(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "windowWidth", windowSize.width());
|
||||
obs_data_set_int(obj, "windowHeight", windowSize.height());
|
||||
|
||||
saveSplitterPos(macroActionConditionSplitterPosition, obj,
|
||||
"macroActionConditionSplitterPosition");
|
||||
saveSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||
SaveSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||
"macroListMacroEditSplitterPosition");
|
||||
}
|
||||
|
||||
@@ -763,9 +756,8 @@ void SwitcherData::LoadUISettings(obs_data_t *obj)
|
||||
(int)obs_data_get_int(obj, "windowPosY")};
|
||||
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
|
||||
(int)obs_data_get_int(obj, "windowHeight")};
|
||||
loadSplitterPos(macroActionConditionSplitterPosition, obj,
|
||||
"macroActionConditionSplitterPosition");
|
||||
loadSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||
|
||||
LoadSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||
"macroListMacroEditSplitterPosition");
|
||||
}
|
||||
|
||||
@@ -997,6 +989,10 @@ void AdvSceneSwitcher::SetupGeneralTab()
|
||||
ui->showTrayNotifications->setChecked(
|
||||
switcher->showSystemTrayNotifications);
|
||||
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
||||
ui->disableComboBoxFilter->setChecked(
|
||||
switcher->disableFilterComboboxFilter);
|
||||
FilterComboBox::SetFilterBehaviourEnabled(
|
||||
!switcher->disableFilterComboboxFilter);
|
||||
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ bool IsFullscreen(const std::string &title)
|
||||
return windowStatesAreSet(title, states);
|
||||
}
|
||||
|
||||
std::optional<std::string> GetTextInWindow(const std::string &window)
|
||||
std::optional<std::string> GetTextInWindow(const std::string &)
|
||||
{
|
||||
// Not implemented
|
||||
return {};
|
||||
|
||||
@@ -239,7 +239,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
|
||||
obs_data_release(data);
|
||||
}
|
||||
macro->UpdateActionIndices();
|
||||
actionsList->Insert(
|
||||
ui->actionsList->Insert(
|
||||
idx,
|
||||
new MacroActionEdit(this, ¯o->Actions()[idx], id));
|
||||
SetActionData(*macro);
|
||||
@@ -263,7 +263,7 @@ void AdvSceneSwitcher::on_actionAdd_clicked()
|
||||
if (currentActionIdx != -1) {
|
||||
MacroActionSelectionChanged(currentActionIdx + 1);
|
||||
}
|
||||
actionsList->SetHelpMsgVisible(false);
|
||||
ui->actionsList->SetHelpMsgVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||
@@ -279,7 +279,7 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
actionsList->Remove(idx);
|
||||
ui->actionsList->Remove(idx);
|
||||
macro->Actions().erase(macro->Actions().begin() + idx);
|
||||
switcher->abortMacroWait = true;
|
||||
switcher->macroWaitCv.notify_all();
|
||||
@@ -322,10 +322,11 @@ void AdvSceneSwitcher::on_actionUp_clicked()
|
||||
MoveMacroActionUp(currentActionIdx);
|
||||
MacroActionSelectionChanged(currentActionIdx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_actionDown_clicked()
|
||||
{
|
||||
if (currentActionIdx == -1 ||
|
||||
currentActionIdx == actionsList->ContentLayout()->count() - 1) {
|
||||
currentActionIdx == ui->actionsList->ContentLayout()->count() - 1) {
|
||||
return;
|
||||
}
|
||||
MoveMacroActionDown(currentActionIdx);
|
||||
@@ -337,11 +338,82 @@ void AdvSceneSwitcher::on_actionBottom_clicked()
|
||||
if (currentActionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
const int newIdx = actionsList->ContentLayout()->count() - 1;
|
||||
const int newIdx = ui->actionsList->ContentLayout()->count() - 1;
|
||||
MacroActionReorder(newIdx, currentActionIdx);
|
||||
MacroActionSelectionChanged(newIdx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionAdd_clicked()
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentElseActionIdx == -1) {
|
||||
AddMacroElseAction((int)macro->ElseActions().size());
|
||||
} else {
|
||||
AddMacroElseAction(currentElseActionIdx + 1);
|
||||
}
|
||||
if (currentElseActionIdx != -1) {
|
||||
MacroElseActionSelectionChanged(currentElseActionIdx + 1);
|
||||
}
|
||||
ui->elseActionsList->SetHelpMsgVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionRemove_clicked()
|
||||
{
|
||||
if (currentElseActionIdx == -1) {
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
RemoveMacroElseAction((int)macro->Actions().size() - 1);
|
||||
} else {
|
||||
RemoveMacroElseAction(currentElseActionIdx);
|
||||
}
|
||||
MacroElseActionSelectionChanged(-1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionTop_clicked()
|
||||
{
|
||||
if (currentElseActionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
MacroElseActionReorder(0, currentElseActionIdx);
|
||||
MacroElseActionSelectionChanged(0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionUp_clicked()
|
||||
{
|
||||
if (currentElseActionIdx == -1 || currentElseActionIdx == 0) {
|
||||
return;
|
||||
}
|
||||
MoveMacroElseActionUp(currentElseActionIdx);
|
||||
MacroElseActionSelectionChanged(currentElseActionIdx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionDown_clicked()
|
||||
{
|
||||
if (currentElseActionIdx == -1 ||
|
||||
currentElseActionIdx ==
|
||||
ui->elseActionsList->ContentLayout()->count() - 1) {
|
||||
return;
|
||||
}
|
||||
MoveMacroElseActionDown(currentElseActionIdx);
|
||||
MacroElseActionSelectionChanged(currentElseActionIdx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_elseActionBottom_clicked()
|
||||
{
|
||||
if (currentElseActionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
const int newIdx = ui->elseActionsList->ContentLayout()->count() - 1;
|
||||
MacroElseActionReorder(newIdx, currentElseActionIdx);
|
||||
MacroElseActionSelectionChanged(newIdx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
|
||||
{
|
||||
if (pos1 == pos2) {
|
||||
@@ -355,11 +427,11 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
|
||||
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
|
||||
m->UpdateActionIndices();
|
||||
auto widget1 = static_cast<MacroActionEdit *>(
|
||||
actionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
ui->actionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
auto widget2 = static_cast<MacroActionEdit *>(
|
||||
actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
actionsList->Insert(pos1, widget2);
|
||||
actionsList->Insert(pos2, widget1);
|
||||
ui->actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
ui->actionsList->Insert(pos1, widget2);
|
||||
ui->actionsList->Insert(pos2, widget1);
|
||||
SetActionData(*m);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
}
|
||||
@@ -394,24 +466,158 @@ void AdvSceneSwitcher::MoveMacroActionDown(int idx)
|
||||
HighlightAction(idx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
|
||||
void AdvSceneSwitcher::MacroElseActionSelectionChanged(int idx)
|
||||
{
|
||||
SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroElseActionReorder(int to, int from)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionsList->SetSelection(idx);
|
||||
conditionsList->SetSelection(-1);
|
||||
|
||||
if (idx < 0 || (unsigned)idx >= macro->Actions().size()) {
|
||||
currentActionIdx = -1;
|
||||
} else {
|
||||
currentActionIdx = idx;
|
||||
lastInteracted = MacroSection::ACTIONS;
|
||||
if (to == from || from < 0 || from > (int)macro->ElseActions().size() ||
|
||||
to < 0 || to > (int)macro->ElseActions().size()) {
|
||||
return;
|
||||
}
|
||||
currentConditionIdx = -1;
|
||||
HighlightControls();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto action = macro->ElseActions().at(from);
|
||||
macro->ElseActions().erase(macro->ElseActions().begin() + from);
|
||||
macro->ElseActions().insert(macro->ElseActions().begin() + to,
|
||||
action);
|
||||
macro->UpdateElseActionIndices();
|
||||
ui->elseActionsList->ContentLayout()->insertItem(
|
||||
to, ui->elseActionsList->ContentLayout()->takeAt(from));
|
||||
SetElseActionData(*macro);
|
||||
}
|
||||
HighlightElseAction(to);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::AddMacroElseAction(int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
if (idx - 1 >= 0) {
|
||||
id = macro->ElseActions().at(idx - 1)->GetId();
|
||||
} else {
|
||||
MacroActionSwitchScene temp(nullptr);
|
||||
id = temp.GetId();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->ElseActions().emplace(
|
||||
macro->ElseActions().begin() + idx,
|
||||
MacroActionFactory::Create(id, macro.get()));
|
||||
if (idx - 1 >= 0) {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
macro->ElseActions().at(idx - 1)->Save(data);
|
||||
macro->ElseActions().at(idx)->Load(data);
|
||||
}
|
||||
macro->UpdateElseActionIndices();
|
||||
ui->elseActionsList->Insert(
|
||||
idx, new MacroActionEdit(
|
||||
this, ¯o->ElseActions()[idx], id));
|
||||
SetElseActionData(*macro);
|
||||
}
|
||||
HighlightElseAction(idx);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacroElseAction(int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 0 || idx >= (int)macro->ElseActions().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
ui->elseActionsList->Remove(idx);
|
||||
macro->ElseActions().erase(macro->ElseActions().begin() + idx);
|
||||
switcher->abortMacroWait = true;
|
||||
switcher->macroWaitCv.notify_all();
|
||||
macro->UpdateElseActionIndices();
|
||||
SetActionData(*macro);
|
||||
}
|
||||
MacroElseActionSelectionChanged(-1);
|
||||
lastInteracted = MacroSection::ELSE_ACTIONS;
|
||||
emit(MacroSegmentOrderChanged());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SwapElseActions(Macro *m, int pos1, int pos2)
|
||||
{
|
||||
if (pos1 == pos2) {
|
||||
return;
|
||||
}
|
||||
if (pos1 > pos2) {
|
||||
std::swap(pos1, pos2);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
iter_swap(m->ElseActions().begin() + pos1,
|
||||
m->ElseActions().begin() + pos2);
|
||||
m->UpdateElseActionIndices();
|
||||
auto widget1 = static_cast<MacroActionEdit *>(
|
||||
ui->elseActionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
auto widget2 = static_cast<MacroActionEdit *>(
|
||||
ui->elseActionsList->ContentLayout()
|
||||
->takeAt(pos2 - 1)
|
||||
->widget());
|
||||
ui->elseActionsList->Insert(pos1, widget2);
|
||||
ui->elseActionsList->Insert(pos2, widget1);
|
||||
SetElseActionData(*m);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroElseActionUp(int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 1 || idx >= (int)macro->ElseActions().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SwapElseActions(macro.get(), idx, idx - 1);
|
||||
HighlightElseAction(idx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroElseActionDown(int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx < 0 || idx >= (int)macro->ElseActions().size() - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
SwapElseActions(macro.get(), idx, idx + 1);
|
||||
HighlightElseAction(idx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
|
||||
{
|
||||
SetupMacroSegmentSelection(MacroSection::ACTIONS, idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroActionReorder(int to, int from)
|
||||
@@ -431,8 +637,8 @@ void AdvSceneSwitcher::MacroActionReorder(int to, int from)
|
||||
macro->Actions().erase(macro->Actions().begin() + from);
|
||||
macro->Actions().insert(macro->Actions().begin() + to, action);
|
||||
macro->UpdateActionIndices();
|
||||
actionsList->ContentLayout()->insertItem(
|
||||
to, actionsList->ContentLayout()->takeAt(from));
|
||||
ui->actionsList->ContentLayout()->insertItem(
|
||||
to, ui->actionsList->ContentLayout()->takeAt(from));
|
||||
SetActionData(*macro);
|
||||
}
|
||||
HighlightAction(to);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "platform-funcs.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <obs-interaction.h>
|
||||
|
||||
namespace advss {
|
||||
@@ -723,7 +724,7 @@ static QString getHotkeyDescriptionByName(const std::string &name)
|
||||
QString description = "";
|
||||
} params;
|
||||
|
||||
auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) {
|
||||
auto func = [](void *param, obs_hotkey_id, obs_hotkey_t *hotkey) {
|
||||
auto params = static_cast<Parameters *>(param);
|
||||
std::string name = obs_hotkey_get_name(hotkey);
|
||||
addNamePrefix(name, hotkey);
|
||||
|
||||
@@ -49,7 +49,7 @@ bool MacroActionMacro::PerformAction()
|
||||
break;
|
||||
case Action::RUN:
|
||||
if (!macro->Paused()) {
|
||||
macro->PerformActions();
|
||||
macro->PerformActions(true, false, true);
|
||||
}
|
||||
break;
|
||||
case Action::STOP:
|
||||
|
||||
@@ -57,6 +57,12 @@ bool MacroActionProjector::PerformAction()
|
||||
break;
|
||||
}
|
||||
|
||||
if (_fullscreen && _monitor == -1) {
|
||||
blog(LOG_INFO, "refusing to open fullscreen projector"
|
||||
" with invalid display selection");
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_frontend_open_projector(type, _fullscreen ? _monitor : -1, "",
|
||||
name.c_str());
|
||||
|
||||
@@ -85,6 +91,7 @@ bool MacroActionProjector::Save(obs_data_t *obj) const
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(obj, "monitor", _monitor);
|
||||
obs_data_set_string(obj, "monitorName", _monitorName.c_str());
|
||||
obs_data_set_bool(obj, "fullscreen", _fullscreen);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
@@ -96,12 +103,50 @@ bool MacroActionProjector::Load(obs_data_t *obj)
|
||||
MacroAction::Load(obj);
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||
_monitor = obs_data_get_int(obj, "monitor");
|
||||
_monitorName = obs_data_get_string(obj, "monitorName");
|
||||
_fullscreen = obs_data_get_bool(obj, "fullscreen");
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
|
||||
if (MonitorSetupChanged()) {
|
||||
blog(LOG_INFO, "monitor setup seems to have changed! "
|
||||
"resetting projector action monitor selection!");
|
||||
_monitor = -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionProjector::SetMonitor(int idx)
|
||||
{
|
||||
_monitor = idx;
|
||||
auto monitorNames = GetMonitorNames();
|
||||
if (_monitor < 0 || _monitor >= monitorNames.size()) {
|
||||
// Monitor setup changed while settings were selected?
|
||||
_monitorName = "";
|
||||
return;
|
||||
}
|
||||
_monitorName = monitorNames.at(_monitor).toStdString();
|
||||
}
|
||||
|
||||
int MacroActionProjector::GetMonitor() const
|
||||
{
|
||||
return _monitor;
|
||||
}
|
||||
|
||||
bool MacroActionProjector::MonitorSetupChanged()
|
||||
{
|
||||
if (_monitorName.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto monitorNames = GetMonitorNames();
|
||||
if (_monitor < 0 || _monitor >= monitorNames.size()) {
|
||||
return true;
|
||||
}
|
||||
return monitorNames.at(_monitor) !=
|
||||
QString::fromStdString(_monitorName);
|
||||
}
|
||||
|
||||
static inline void populateSelectionTypes(QComboBox *list)
|
||||
{
|
||||
for (auto entry : selectionTypes) {
|
||||
@@ -134,6 +179,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
sources.sort();
|
||||
_sources->SetSourceNameList(sources);
|
||||
_monitors->addItems(GetMonitorNames());
|
||||
_monitors->setPlaceholderText(
|
||||
obs_module_text("AdvSceneSwitcher.selectDisplay"));
|
||||
|
||||
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(WindowTypeChanged(int)));
|
||||
@@ -177,7 +224,7 @@ void MacroActionProjectorEdit::UpdateEntryData()
|
||||
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_monitors->setCurrentIndex(_entryData->_monitor);
|
||||
_monitors->setCurrentIndex(_entryData->GetMonitor());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -208,7 +255,7 @@ void MacroActionProjectorEdit::MonitorChanged(int value)
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_monitor = value;
|
||||
_entryData->SetMonitor(value);
|
||||
}
|
||||
|
||||
void MacroActionProjectorEdit::WindowTypeChanged(int)
|
||||
|
||||
@@ -17,6 +17,8 @@ public:
|
||||
{
|
||||
return std::make_shared<MacroActionProjector>(m);
|
||||
}
|
||||
void SetMonitor(int);
|
||||
int GetMonitor() const;
|
||||
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
@@ -29,10 +31,15 @@ public:
|
||||
Type _type = Type::SCENE;
|
||||
SourceSelection _source;
|
||||
SceneSelection _scene;
|
||||
int _monitor = 0;
|
||||
bool _fullscreen = true;
|
||||
|
||||
private:
|
||||
bool MonitorSetupChanged();
|
||||
|
||||
int _monitor = -1;
|
||||
// Only used to detect display setup changes
|
||||
std::string _monitorName = "";
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
@@ -54,12 +54,12 @@ bool MacroActionRandom::PerformAction()
|
||||
}
|
||||
if (macros.size() == 1) {
|
||||
lastRandomMacro = macros[0];
|
||||
return macros[0]->PerformActions();
|
||||
return macros[0]->PerformActions(true);
|
||||
}
|
||||
srand((unsigned int)time(0));
|
||||
size_t idx = std::rand() % (macros.size());
|
||||
lastRandomMacro = macros[idx];
|
||||
return macros[idx]->PerformActions();
|
||||
return macros[idx]->PerformActions(true);
|
||||
}
|
||||
|
||||
void MacroActionRandom::LogAction() const
|
||||
|
||||
@@ -12,7 +12,15 @@ const std::string MacroActionSwitchScene::id = "scene_switch";
|
||||
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
|
||||
MacroActionSwitchScene::id,
|
||||
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
|
||||
"AdvSceneSwitcher.action.switchScene"});
|
||||
"AdvSceneSwitcher.action.scene"});
|
||||
|
||||
const static std::map<MacroActionSwitchScene::SceneType, std::string>
|
||||
sceneTypes = {
|
||||
{MacroActionSwitchScene::SceneType::PROGRAM,
|
||||
"AdvSceneSwitcher.action.scene.type.program"},
|
||||
{MacroActionSwitchScene::SceneType::PREVIEW,
|
||||
"AdvSceneSwitcher.action.scene.type.preview"},
|
||||
};
|
||||
|
||||
static void waitForTransitionChange(OBSWeakSource &transition,
|
||||
std::unique_lock<std::mutex> *lock,
|
||||
@@ -126,6 +134,14 @@ bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
|
||||
bool MacroActionSwitchScene::PerformAction()
|
||||
{
|
||||
auto scene = _scene.GetScene();
|
||||
|
||||
if (_sceneType == SceneType::PREVIEW) {
|
||||
OBSSourceAutoRelease previewScneSource =
|
||||
obs_weak_source_get_source(scene);
|
||||
obs_frontend_set_current_preview_scene(previewScneSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto transition = _transition.GetTransition();
|
||||
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
|
||||
obs_frontend_preview_program_mode_active());
|
||||
@@ -137,24 +153,9 @@ bool MacroActionSwitchScene::PerformAction()
|
||||
|
||||
void MacroActionSwitchScene::LogAction() const
|
||||
{
|
||||
auto t = _scene.GetType();
|
||||
auto sceneName = GetWeakSourceName(_scene.GetScene(false));
|
||||
switch (t) {
|
||||
case SceneSelection::Type::SCENE:
|
||||
vblog(LOG_INFO, "switch to scene '%s'",
|
||||
_scene.ToString(true).c_str());
|
||||
break;
|
||||
case SceneSelection::Type::GROUP:
|
||||
vblog(LOG_INFO, "switch to scene '%s' (scene group '%s')",
|
||||
sceneName.c_str(), _scene.ToString(true).c_str());
|
||||
break;
|
||||
case SceneSelection::Type::PREVIOUS:
|
||||
vblog(LOG_INFO, "switch to previous scene '%s'",
|
||||
sceneName.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
vblog(LOG_INFO, "switch%s scene to '%s'",
|
||||
_sceneType == SceneType::PREVIEW ? " preview" : "",
|
||||
_scene.ToString(true).c_str());
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
||||
@@ -165,6 +166,7 @@ bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
||||
_duration.Save(obj);
|
||||
obs_data_set_bool(obj, "blockUntilTransitionDone",
|
||||
_blockUntilTransitionDone);
|
||||
obs_data_set_int(obj, "sceneType", static_cast<int>(_sceneType));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,6 +178,7 @@ bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
||||
_duration.Load(obj);
|
||||
_blockUntilTransitionDone =
|
||||
obs_data_get_bool(obj, "blockUntilTransitionDone");
|
||||
_sceneType = static_cast<SceneType>(obs_data_get_int(obj, "sceneType"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -184,6 +187,13 @@ std::string MacroActionSwitchScene::GetShortDesc() const
|
||||
return _scene.ToString();
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : sceneTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
|
||||
: QWidget(parent),
|
||||
@@ -192,9 +202,11 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
_duration(new DurationSelection(parent, false)),
|
||||
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
|
||||
_sceneTypes(new QComboBox()),
|
||||
_entryLayout(new QHBoxLayout())
|
||||
{
|
||||
_duration->SpinBox()->setSpecialValueText("-");
|
||||
populateTypeSelection(_sceneTypes);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
@@ -206,28 +218,29 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(BlockUntilTransitionDoneChanged(int)));
|
||||
QWidget::connect(_sceneTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SceneTypeChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{blockUntilTransitionDone}}", _blockUntilTransitionDone},
|
||||
};
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
_entryLayout,
|
||||
{{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{sceneTypes}}", _sceneTypes}});
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
auto mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(_entryLayout);
|
||||
mainLayout->addWidget(_blockUntilTransitionDone);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
_sceneTypes->setCurrentIndex(static_cast<int>(_entryData->_sceneType));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_transitions->SetTransition(_entryData->_transition);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_blockUntilTransitionDone->setChecked(
|
||||
_entryData->_blockUntilTransitionDone);
|
||||
SetDurationVisibility();
|
||||
SetWidgetVisibility();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
@@ -251,8 +264,44 @@ void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
|
||||
_entryData->_blockUntilTransitionDone = state;
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SetDurationVisibility()
|
||||
void MacroActionSwitchSceneEdit::SceneTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_sceneType =
|
||||
static_cast<MacroActionSwitchScene::SceneType>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SetWidgetVisibility()
|
||||
{
|
||||
_entryLayout->removeWidget(_scenes);
|
||||
_entryLayout->removeWidget(_transitions);
|
||||
_entryLayout->removeWidget(_duration);
|
||||
_entryLayout->removeWidget(_sceneTypes);
|
||||
ClearLayout(_entryLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{sceneTypes}}", _sceneTypes},
|
||||
};
|
||||
|
||||
if (_entryData->_sceneType ==
|
||||
MacroActionSwitchScene::SceneType::PREVIEW) {
|
||||
_transitions->hide();
|
||||
_duration->hide();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.entry.preview"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
return;
|
||||
}
|
||||
|
||||
_transitions->show();
|
||||
if (_entryData->_transition.GetType() !=
|
||||
TransitionSelection::Type::TRANSITION) {
|
||||
_duration->show();
|
||||
@@ -261,15 +310,6 @@ void MacroActionSwitchSceneEdit::SetDurationVisibility()
|
||||
_entryData->_transition.GetTransition());
|
||||
_duration->setVisible(!fixedDuration);
|
||||
|
||||
_entryLayout->removeWidget(_scenes);
|
||||
_entryLayout->removeWidget(_transitions);
|
||||
_entryLayout->removeWidget(_duration);
|
||||
ClearLayout(_entryLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
};
|
||||
if (fixedDuration) {
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
@@ -302,7 +342,7 @@ void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_transition = t;
|
||||
SetDurationVisibility();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
{
|
||||
return std::make_shared<MacroActionSwitchScene>(m);
|
||||
}
|
||||
|
||||
enum class SceneType { PROGRAM, PREVIEW };
|
||||
SceneType _sceneType = SceneType::PROGRAM;
|
||||
SceneSelection _scene;
|
||||
TransitionSelection _transition;
|
||||
Duration _duration;
|
||||
@@ -55,20 +58,21 @@ private slots:
|
||||
void TransitionChanged(const TransitionSelection &);
|
||||
void DurationChanged(const Duration &seconds);
|
||||
void BlockUntilTransitionDoneChanged(int state);
|
||||
void SceneTypeChanged(int);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
SceneSelectionWidget *_scenes;
|
||||
TransitionSelectionWidget *_transitions;
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_blockUntilTransitionDone;
|
||||
QComboBox *_sceneTypes;
|
||||
QHBoxLayout *_entryLayout;
|
||||
|
||||
std::shared_ptr<MacroActionSwitchScene> _entryData;
|
||||
|
||||
private:
|
||||
void SetDurationVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ bool MacroActionSequence::PerformAction()
|
||||
return true;
|
||||
}
|
||||
|
||||
return macro->PerformActions();
|
||||
return macro->PerformActions(true);
|
||||
}
|
||||
|
||||
void MacroActionSequence::LogAction() const
|
||||
|
||||
@@ -27,6 +27,8 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
|
||||
"AdvSceneSwitcher.action.source.type.deinterlaceMode"},
|
||||
{MacroActionSource::Action::DEINTERLACE_FIELD_ORDER,
|
||||
"AdvSceneSwitcher.action.source.type.deinterlaceOrder"},
|
||||
{MacroActionSource::Action::OPEN_INTERACTION_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.openInteractionDialog"},
|
||||
};
|
||||
|
||||
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
|
||||
@@ -110,6 +112,12 @@ static void refreshSourceSettings(obs_source_t *s)
|
||||
}
|
||||
}
|
||||
|
||||
static bool isInteractable(obs_source_t *source)
|
||||
{
|
||||
uint32_t flags = obs_source_get_output_flags(source);
|
||||
return (flags & OBS_SOURCE_INTERACTION) != 0;
|
||||
}
|
||||
|
||||
bool MacroActionSource::PerformAction()
|
||||
{
|
||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||
@@ -135,6 +143,16 @@ bool MacroActionSource::PerformAction()
|
||||
case Action::DEINTERLACE_FIELD_ORDER:
|
||||
obs_source_set_deinterlace_field_order(s, _deinterlaceOrder);
|
||||
break;
|
||||
case Action::OPEN_INTERACTION_DIALOG:
|
||||
if (isInteractable(s)) {
|
||||
obs_frontend_open_source_interaction(s);
|
||||
} else {
|
||||
blog(LOG_INFO,
|
||||
"refusing to open interaction dialog "
|
||||
"for non intractable source \"%s\"",
|
||||
_source.ToString().c_str());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
SETTINGS_BUTTON,
|
||||
DEINTERLACE_MODE,
|
||||
DEINTERLACE_FIELD_ORDER,
|
||||
OPEN_INTERACTION_DIALOG,
|
||||
};
|
||||
Action _action = Action::ENABLE;
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ bool MacroActionSudioMode::_registered = MacroActionFactory::Register(
|
||||
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
|
||||
"AdvSceneSwitcher.action.studioMode"});
|
||||
|
||||
const static std::map<StudioModeAction, std::string> actionTypes = {
|
||||
{StudioModeAction::SWAP_SCENE,
|
||||
const static std::map<MacroActionSudioMode::Action, std::string> actionTypes = {
|
||||
{MacroActionSudioMode::Action::SWAP_SCENE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.swap"},
|
||||
{StudioModeAction::SET_SCENE,
|
||||
{MacroActionSudioMode::Action::SET_SCENE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.setScene"},
|
||||
{StudioModeAction::ENABLE_STUDIO_MODE,
|
||||
{MacroActionSudioMode::Action::ENABLE_STUDIO_MODE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.enable"},
|
||||
{StudioModeAction::DISABLE_STUDIO_MODE,
|
||||
{MacroActionSudioMode::Action::DISABLE_STUDIO_MODE,
|
||||
"AdvSceneSwitcher.action.studioMode.type.disable"},
|
||||
};
|
||||
|
||||
@@ -41,19 +41,19 @@ static void enableStudioMode(bool enable)
|
||||
bool MacroActionSudioMode::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
case StudioModeAction::SWAP_SCENE:
|
||||
case Action::SWAP_SCENE:
|
||||
obs_frontend_preview_program_trigger_transition();
|
||||
break;
|
||||
case StudioModeAction::SET_SCENE: {
|
||||
case Action::SET_SCENE: {
|
||||
auto s = obs_weak_source_get_source(_scene.GetScene());
|
||||
obs_frontend_set_current_preview_scene(s);
|
||||
obs_source_release(s);
|
||||
break;
|
||||
}
|
||||
case StudioModeAction::ENABLE_STUDIO_MODE:
|
||||
case Action::ENABLE_STUDIO_MODE:
|
||||
enableStudioMode(true);
|
||||
break;
|
||||
case StudioModeAction::DISABLE_STUDIO_MODE:
|
||||
case Action::DISABLE_STUDIO_MODE:
|
||||
enableStudioMode(false);
|
||||
break;
|
||||
default:
|
||||
@@ -86,15 +86,14 @@ bool MacroActionSudioMode::Save(obs_data_t *obj) const
|
||||
bool MacroActionSudioMode::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action =
|
||||
static_cast<StudioModeAction>(obs_data_get_int(obj, "action"));
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_scene.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionSudioMode::GetShortDesc() const
|
||||
{
|
||||
if (_action == StudioModeAction::SET_SCENE) {
|
||||
if (_action == Action::SET_SCENE) {
|
||||
return _scene.ToString();
|
||||
}
|
||||
return "";
|
||||
@@ -102,8 +101,9 @@ std::string MacroActionSudioMode::GetShortDesc() const
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
for (const auto &[id, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,9 +139,10 @@ void MacroActionSudioModeEdit::UpdateEntryData()
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
||||
@@ -156,15 +157,27 @@ void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::ActionChanged(int value)
|
||||
void MacroActionSudioModeEdit::SetWidgetVisibility()
|
||||
{
|
||||
_scenes->setVisible(_entryData->_action ==
|
||||
MacroActionSudioMode::Action::SET_SCENE);
|
||||
|
||||
if (_entryData->_action != MacroActionSudioMode::Action::SET_SCENE) {
|
||||
_actions->removeItem(_actions->findData(static_cast<int>(
|
||||
MacroActionSudioMode::Action::SET_SCENE)));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSudioModeEdit::ActionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<StudioModeAction>(value);
|
||||
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
|
||||
_entryData->_action = static_cast<MacroActionSudioMode::Action>(
|
||||
_actions->itemData(index).toInt());
|
||||
SetWidgetVisibility();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class StudioModeAction {
|
||||
SWAP_SCENE,
|
||||
SET_SCENE,
|
||||
ENABLE_STUDIO_MODE,
|
||||
DISABLE_STUDIO_MODE,
|
||||
};
|
||||
|
||||
class MacroActionSudioMode : public MacroAction {
|
||||
public:
|
||||
MacroActionSudioMode(Macro *m) : MacroAction(m) {}
|
||||
@@ -25,7 +18,14 @@ public:
|
||||
return std::make_shared<MacroActionSudioMode>(m);
|
||||
}
|
||||
|
||||
StudioModeAction _action = StudioModeAction::SWAP_SCENE;
|
||||
enum class Action {
|
||||
SWAP_SCENE,
|
||||
SET_SCENE, // TODO: Remove in future version as the
|
||||
// functionality moved to the scene switch action
|
||||
ENABLE_STUDIO_MODE,
|
||||
DISABLE_STUDIO_MODE,
|
||||
};
|
||||
Action _action = Action::SWAP_SCENE;
|
||||
SceneSelection _scene;
|
||||
|
||||
private:
|
||||
@@ -55,12 +55,13 @@ private slots:
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QComboBox *_actions;
|
||||
SceneSelectionWidget *_scenes;
|
||||
std::shared_ptr<MacroActionSudioMode> _entryData;
|
||||
|
||||
private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ const static std::map<MacroActionVariable::Type, std::string> actionTypes = {
|
||||
"AdvSceneSwitcher.action.variable.type.mathExpression"},
|
||||
{MacroActionVariable::Type::USER_INPUT,
|
||||
"AdvSceneSwitcher.action.variable.type.askForValue"},
|
||||
{MacroActionVariable::Type::ENV_VARIABLE,
|
||||
"AdvSceneSwitcher.action.variable.type.environmentVariable"},
|
||||
{MacroActionVariable::Type::SCENE_ITEM_COUNT,
|
||||
"AdvSceneSwitcher.action.variable.type.sceneItemCount"},
|
||||
};
|
||||
|
||||
static void apppend(Variable &var, const std::string &value)
|
||||
@@ -126,6 +130,7 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
|
||||
|
||||
struct AskForInputParams {
|
||||
QString prompt;
|
||||
QString placeholder;
|
||||
std::optional<std::string> result;
|
||||
};
|
||||
|
||||
@@ -134,6 +139,7 @@ static void askForInput(void *param)
|
||||
auto parameters = static_cast<AskForInputParams *>(param);
|
||||
auto dialog = new NonModalMessageDialog(
|
||||
parameters->prompt, NonModalMessageDialog::Type::INPUT);
|
||||
dialog->SetInput(parameters->placeholder);
|
||||
parameters->result = dialog->GetInput();
|
||||
}
|
||||
|
||||
@@ -213,6 +219,9 @@ bool MacroActionVariable::PerformAction()
|
||||
"AdvSceneSwitcher.action.variable.askForValuePromptDefault"))
|
||||
.arg(QString::fromStdString(
|
||||
var->Name())),
|
||||
_useCustomPrompt && _useInputPlaceholder
|
||||
? QString::fromStdString(_inputPlaceholder)
|
||||
: "",
|
||||
{}};
|
||||
obs_queue_task(OBS_TASK_UI, askForInput, ¶ms, true);
|
||||
if (!params.result.has_value()) {
|
||||
@@ -221,6 +230,14 @@ bool MacroActionVariable::PerformAction()
|
||||
var->SetValue(*params.result);
|
||||
return true;
|
||||
}
|
||||
case Type::ENV_VARIABLE: {
|
||||
var->SetValue(std::getenv(_envVariableName.c_str()));
|
||||
return true;
|
||||
}
|
||||
case Type::SCENE_ITEM_COUNT: {
|
||||
var->SetValue(GetSceneItemCount(_scene.GetScene(false)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -247,6 +264,10 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
|
||||
_mathExpression.Save(obj, "mathExpression");
|
||||
obs_data_set_bool(obj, "useCustomPrompt", _useCustomPrompt);
|
||||
_inputPrompt.Save(obj, "inputPrompt");
|
||||
obs_data_set_bool(obj, "useInputPlaceholder", _useInputPlaceholder);
|
||||
_inputPlaceholder.Save(obj, "inputPlaceholder");
|
||||
_envVariableName.Save(obj, "environmentVariableName");
|
||||
_scene.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -271,6 +292,10 @@ bool MacroActionVariable::Load(obs_data_t *obj)
|
||||
_mathExpression.Load(obj, "mathExpression");
|
||||
_useCustomPrompt = obs_data_get_bool(obj, "useCustomPrompt");
|
||||
_inputPrompt.Load(obj, "inputPrompt");
|
||||
_useInputPlaceholder = obs_data_get_bool(obj, "useInputPlaceholder");
|
||||
_inputPlaceholder.Load(obj, "inputPlaceholder");
|
||||
_envVariableName.Load(obj, "environmentVariableName");
|
||||
_scene.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -392,7 +417,12 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_mathExpressionResult(new QLabel()),
|
||||
_promptLayout(new QHBoxLayout()),
|
||||
_useCustomPrompt(new QCheckBox()),
|
||||
_inputPrompt(new VariableLineEdit(this))
|
||||
_inputPrompt(new VariableLineEdit(this)),
|
||||
_placeholderLayout(new QHBoxLayout()),
|
||||
_useInputPlaceholder(new QCheckBox()),
|
||||
_inputPlaceholder(new VariableLineEdit(this)),
|
||||
_envVariable(new VariableLineEdit(this)),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, true, true, true))
|
||||
{
|
||||
_numValue->setMinimum(-9999999999);
|
||||
_numValue->setMaximum(9999999999);
|
||||
@@ -447,6 +477,14 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
SLOT(UseCustomPromptChanged(int)));
|
||||
QWidget::connect(_inputPrompt, SIGNAL(editingFinished()), this,
|
||||
SLOT(InputPromptChanged()));
|
||||
QWidget::connect(_useInputPlaceholder, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseInputPlaceholderChanged(int)));
|
||||
QWidget::connect(_inputPlaceholder, SIGNAL(editingFinished()), this,
|
||||
SLOT(InputPlaceholderChanged()));
|
||||
QWidget::connect(_envVariable, SIGNAL(editingFinished()), this,
|
||||
SLOT(EnvVariableChanged()));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{variables}}", _variables},
|
||||
@@ -463,6 +501,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
{"{{mathExpression}}", _mathExpression},
|
||||
{"{{useCustomPrompt}}", _useCustomPrompt},
|
||||
{"{{inputPrompt}}", _inputPrompt},
|
||||
{"{{useInputPlaceholder}}", _useInputPlaceholder},
|
||||
{"{{inputPlaceholder}}", _inputPlaceholder},
|
||||
{"{{envVariableName}}", _envVariable},
|
||||
{"{{scenes}}", _scenes},
|
||||
};
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
|
||||
@@ -485,8 +527,12 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.entry.userInput"),
|
||||
"AdvSceneSwitcher.action.variable.entry.userInput.customPrompt"),
|
||||
_promptLayout, widgetPlaceholders);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.entry.userInput.placeholder"),
|
||||
_placeholderLayout, widgetPlaceholders);
|
||||
|
||||
auto regexConfigLayout = new QHBoxLayout;
|
||||
regexConfigLayout->addWidget(_regex);
|
||||
@@ -505,6 +551,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
layout->addLayout(_findReplaceLayout);
|
||||
layout->addWidget(_mathExpressionResult);
|
||||
layout->addLayout(_promptLayout);
|
||||
layout->addLayout(_placeholderLayout);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -547,6 +594,10 @@ void MacroActionVariableEdit::UpdateEntryData()
|
||||
_mathExpression->setText(_entryData->_mathExpression);
|
||||
_useCustomPrompt->setChecked(_entryData->_useCustomPrompt);
|
||||
_inputPrompt->setText(_entryData->_inputPrompt);
|
||||
_useInputPlaceholder->setChecked(_entryData->_useInputPlaceholder);
|
||||
_inputPlaceholder->setText(_entryData->_inputPlaceholder);
|
||||
_envVariable->setText(_entryData->_envVariableName);
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -818,15 +869,9 @@ void MacroActionVariableEdit::UseCustomPromptChanged(int value)
|
||||
return;
|
||||
}
|
||||
|
||||
_inputPrompt->setVisible(value);
|
||||
if (value) {
|
||||
RemoveStretchIfPresent(_promptLayout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_promptLayout);
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_useCustomPrompt = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::InputPromptChanged()
|
||||
@@ -839,6 +884,47 @@ void MacroActionVariableEdit::InputPromptChanged()
|
||||
_entryData->_inputPrompt = _inputPrompt->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::UseInputPlaceholderChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_useInputPlaceholder = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::InputPlaceholderChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_inputPlaceholder = _inputPlaceholder->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::EnvVariableChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_envVariableName = _envVariable->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::SceneChanged(const SceneSelection &scene)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_scene = scene;
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
@@ -887,7 +973,34 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
SetLayoutVisible(_promptLayout,
|
||||
_entryData->_type ==
|
||||
MacroActionVariable::Type::USER_INPUT);
|
||||
_inputPrompt->setVisible(_entryData->_useCustomPrompt);
|
||||
_inputPrompt->setVisible(
|
||||
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
|
||||
_entryData->_useCustomPrompt);
|
||||
if (_entryData->_useCustomPrompt) {
|
||||
RemoveStretchIfPresent(_promptLayout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_promptLayout);
|
||||
}
|
||||
SetLayoutVisible(
|
||||
_placeholderLayout,
|
||||
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
|
||||
_entryData->_useCustomPrompt);
|
||||
_useInputPlaceholder->setVisible(
|
||||
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
|
||||
_entryData->_useCustomPrompt);
|
||||
_inputPlaceholder->setVisible(
|
||||
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
|
||||
_entryData->_useCustomPrompt &&
|
||||
_entryData->_useInputPlaceholder);
|
||||
if (_entryData->_useInputPlaceholder) {
|
||||
RemoveStretchIfPresent(_placeholderLayout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_placeholderLayout);
|
||||
}
|
||||
_envVariable->setVisible(_entryData->_type ==
|
||||
MacroActionVariable::Type::ENV_VARIABLE);
|
||||
_scenes->setVisible(_entryData->_type ==
|
||||
MacroActionVariable::Type::SCENE_ITEM_COUNT);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "macro-segment-selection.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
namespace advss {
|
||||
@@ -37,6 +38,8 @@ public:
|
||||
FIND_AND_REPLACE,
|
||||
MATH_EXPRESSION,
|
||||
USER_INPUT,
|
||||
ENV_VARIABLE,
|
||||
SCENE_ITEM_COUNT,
|
||||
};
|
||||
|
||||
Type _type = Type::SET_FIXED_VALUE;
|
||||
@@ -58,6 +61,15 @@ public:
|
||||
bool _useCustomPrompt = false;
|
||||
StringVariable _inputPrompt = obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.askForValuePrompt");
|
||||
bool _useInputPlaceholder = false;
|
||||
StringVariable _inputPlaceholder =
|
||||
obs_module_text("AdvSceneSwitcher.enterText");
|
||||
#ifdef _WIN32
|
||||
StringVariable _envVariableName = "USERPROFILE";
|
||||
#else
|
||||
StringVariable _envVariableName = "HOME";
|
||||
#endif
|
||||
SceneSelection _scene;
|
||||
|
||||
private:
|
||||
void DecrementCurrentSegmentVariableRef();
|
||||
@@ -107,11 +119,18 @@ private slots:
|
||||
void MathExpressionChanged();
|
||||
void UseCustomPromptChanged(int);
|
||||
void InputPromptChanged();
|
||||
void UseInputPlaceholderChanged(int);
|
||||
void InputPlaceholderChanged();
|
||||
void EnvVariableChanged();
|
||||
void SceneChanged(const SceneSelection &);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
void SetSegmentValueError(const QString &);
|
||||
|
||||
VariableSelection *_variables;
|
||||
VariableSelection *_variables2;
|
||||
QComboBox *_actions;
|
||||
@@ -136,12 +155,13 @@ protected:
|
||||
QHBoxLayout *_promptLayout;
|
||||
QCheckBox *_useCustomPrompt;
|
||||
VariableLineEdit *_inputPrompt;
|
||||
QHBoxLayout *_placeholderLayout;
|
||||
QCheckBox *_useInputPlaceholder;
|
||||
VariableLineEdit *_inputPlaceholder;
|
||||
VariableLineEdit *_envVariable;
|
||||
SceneSelectionWidget *_scenes;
|
||||
|
||||
std::shared_ptr<MacroActionVariable> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
void SetSegmentValueError(const QString &);
|
||||
|
||||
QTimer _timer;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -385,7 +385,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
|
||||
}
|
||||
(*cond)->SetLogicType(logic);
|
||||
macro->UpdateConditionIndices();
|
||||
conditionsList->Insert(
|
||||
ui->conditionsList->Insert(
|
||||
idx,
|
||||
new MacroConditionEdit(this, ¯o->Conditions()[idx],
|
||||
id, idx == 0));
|
||||
@@ -410,7 +410,7 @@ void AdvSceneSwitcher::on_conditionAdd_clicked()
|
||||
if (currentConditionIdx != -1) {
|
||||
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
||||
}
|
||||
conditionsList->SetHelpMsgVisible(false);
|
||||
ui->conditionsList->SetHelpMsgVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
@@ -426,14 +426,14 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
|
||||
{
|
||||
auto lock = LockContext();
|
||||
conditionsList->Remove(idx);
|
||||
ui->conditionsList->Remove(idx);
|
||||
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
||||
macro->UpdateConditionIndices();
|
||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||
auto newRoot = macro->Conditions().at(0);
|
||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(0))
|
||||
ui->conditionsList->WidgetAt(0))
|
||||
->SetRootNode(true);
|
||||
}
|
||||
SetConditionData(*macro);
|
||||
@@ -479,7 +479,7 @@ void AdvSceneSwitcher::on_conditionDown_clicked()
|
||||
{
|
||||
if (currentConditionIdx == -1 ||
|
||||
currentConditionIdx ==
|
||||
conditionsList->ContentLayout()->count() - 1) {
|
||||
ui->conditionsList->ContentLayout()->count() - 1) {
|
||||
return;
|
||||
}
|
||||
MoveMacroConditionDown(currentConditionIdx);
|
||||
@@ -491,7 +491,7 @@ void AdvSceneSwitcher::on_conditionBottom_clicked()
|
||||
if (currentConditionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
const int newIdx = conditionsList->ContentLayout()->count() - 1;
|
||||
const int newIdx = ui->conditionsList->ContentLayout()->count() - 1;
|
||||
MacroConditionReorder(newIdx, currentConditionIdx);
|
||||
MacroConditionSelectionChanged(newIdx);
|
||||
}
|
||||
@@ -521,11 +521,11 @@ void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
|
||||
}
|
||||
|
||||
auto widget1 = static_cast<MacroConditionEdit *>(
|
||||
conditionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
ui->conditionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
auto widget2 = static_cast<MacroConditionEdit *>(
|
||||
conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
conditionsList->Insert(pos1, widget2);
|
||||
conditionsList->Insert(pos2, widget1);
|
||||
ui->conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
ui->conditionsList->Insert(pos1, widget2);
|
||||
ui->conditionsList->Insert(pos2, widget1);
|
||||
SetConditionData(*m);
|
||||
widget2->SetRootNode(root);
|
||||
widget1->SetRootNode(false);
|
||||
@@ -564,22 +564,7 @@ void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
|
||||
|
||||
void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
conditionsList->SetSelection(idx);
|
||||
actionsList->SetSelection(-1);
|
||||
|
||||
if (idx < 0 || (unsigned)idx >= macro->Conditions().size()) {
|
||||
currentConditionIdx = -1;
|
||||
} else {
|
||||
currentConditionIdx = idx;
|
||||
lastInteracted = MacroSection::CONDITIONS;
|
||||
}
|
||||
currentActionIdx = -1;
|
||||
HighlightControls();
|
||||
SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
||||
@@ -599,30 +584,30 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
||||
if (to == 0) {
|
||||
condition->SetLogicType(LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(from))
|
||||
ui->conditionsList->WidgetAt(from))
|
||||
->SetRootNode(true);
|
||||
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(0))
|
||||
ui->conditionsList->WidgetAt(0))
|
||||
->SetRootNode(false);
|
||||
}
|
||||
if (from == 0) {
|
||||
condition->SetLogicType(LogicType::AND);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(from))
|
||||
ui->conditionsList->WidgetAt(from))
|
||||
->SetRootNode(false);
|
||||
macro->Conditions().at(1)->SetLogicType(
|
||||
LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(1))
|
||||
ui->conditionsList->WidgetAt(1))
|
||||
->SetRootNode(true);
|
||||
}
|
||||
macro->Conditions().erase(macro->Conditions().begin() + from);
|
||||
macro->Conditions().insert(macro->Conditions().begin() + to,
|
||||
condition);
|
||||
macro->UpdateConditionIndices();
|
||||
conditionsList->ContentLayout()->insertItem(
|
||||
to, conditionsList->ContentLayout()->takeAt(from));
|
||||
ui->conditionsList->ContentLayout()->insertItem(
|
||||
to, ui->conditionsList->ContentLayout()->takeAt(from));
|
||||
SetConditionData(*macro);
|
||||
}
|
||||
HighlightCondition(to);
|
||||
|
||||
@@ -18,8 +18,10 @@ const static std::map<MacroConditionFilter::Condition, std::string>
|
||||
"AdvSceneSwitcher.condition.filter.type.active"},
|
||||
{MacroConditionFilter::Condition::DISABLED,
|
||||
"AdvSceneSwitcher.condition.filter.type.showing"},
|
||||
{MacroConditionFilter::Condition::SETTINGS,
|
||||
{MacroConditionFilter::Condition::SETTINGS_MATCH,
|
||||
"AdvSceneSwitcher.condition.filter.type.settings"},
|
||||
{MacroConditionFilter::Condition::SETTINGS_CHANGED,
|
||||
"AdvSceneSwitcher.condition.filter.type.settingsChanged"},
|
||||
};
|
||||
|
||||
bool MacroConditionFilter::CheckCondition()
|
||||
@@ -38,13 +40,20 @@ bool MacroConditionFilter::CheckCondition()
|
||||
case Condition::DISABLED:
|
||||
ret = !obs_source_enabled(filterSource);
|
||||
break;
|
||||
case Condition::SETTINGS:
|
||||
case Condition::SETTINGS_MATCH:
|
||||
ret = CompareSourceSettings(filterWeakSource, _settings,
|
||||
_regex);
|
||||
if (IsReferencedInVars()) {
|
||||
SetVariableValue(GetSourceSettings(filterWeakSource));
|
||||
}
|
||||
break;
|
||||
case Condition::SETTINGS_CHANGED: {
|
||||
std::string settings = GetSourceSettings(_source.GetSource());
|
||||
ret = !_currentSettings.empty() && settings != _currentSettings;
|
||||
_currentSettings = settings;
|
||||
SetVariableValue(settings);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -95,8 +104,8 @@ std::string MacroConditionFilter::GetShortDesc() const
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : filterConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
for (const auto &[_, name] : filterConditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +203,9 @@ void MacroConditionFilterEdit::ConditionChanged(int index)
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionFilter::Condition>(index);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS);
|
||||
SetSettingsSelectionVisible(
|
||||
_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS_MATCH);
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::GetSettingsClicked()
|
||||
@@ -259,8 +269,9 @@ void MacroConditionFilterEdit::UpdateEntryData()
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_settings->setPlainText(_entryData->_settings);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS);
|
||||
SetSettingsSelectionVisible(
|
||||
_entryData->_condition ==
|
||||
MacroConditionFilter::Condition::SETTINGS_MATCH);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
@@ -27,7 +27,8 @@ public:
|
||||
enum class Condition {
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
SETTINGS,
|
||||
SETTINGS_MATCH,
|
||||
SETTINGS_CHANGED,
|
||||
};
|
||||
|
||||
SourceSelection _source;
|
||||
@@ -37,6 +38,8 @@ public:
|
||||
RegexConfig _regex;
|
||||
|
||||
private:
|
||||
std::string _currentSettings;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "process-config.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ const static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
|
||||
"AdvSceneSwitcher.condition.scene.type.current"},
|
||||
{MacroConditionScene::Type::PREVIOUS,
|
||||
"AdvSceneSwitcher.condition.scene.type.previous"},
|
||||
{MacroConditionScene::Type::PREVIEW,
|
||||
"AdvSceneSwitcher.condition.scene.type.preview"},
|
||||
{MacroConditionScene::Type::CHANGED,
|
||||
"AdvSceneSwitcher.condition.scene.type.changed"},
|
||||
{MacroConditionScene::Type::NOT_CHANGED,
|
||||
@@ -24,6 +26,8 @@ const static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
|
||||
"AdvSceneSwitcher.condition.scene.type.currentPattern"},
|
||||
{MacroConditionScene::Type::PREVIOUS_PATTERN,
|
||||
"AdvSceneSwitcher.condition.scene.type.previousPattern"},
|
||||
{MacroConditionScene::Type::PREVIEW_PATTERN,
|
||||
"AdvSceneSwitcher.condition.scene.type.previewPattern"},
|
||||
};
|
||||
|
||||
static bool sceneNameMatchesRegex(const OBSWeakSource &scene,
|
||||
@@ -79,6 +83,14 @@ bool MacroConditionScene::CheckCondition()
|
||||
SetVariableValue(GetWeakSourceName(scene));
|
||||
return scene == _scene.GetScene(false);
|
||||
}
|
||||
case Type::PREVIEW: {
|
||||
OBSSourceAutoRelease source =
|
||||
obs_frontend_get_current_preview_scene();
|
||||
OBSWeakSourceAutoRelease scene =
|
||||
obs_source_get_weak_source(source);
|
||||
SetVariableValue(GetWeakSourceName(scene));
|
||||
return scene == _scene.GetScene(false);
|
||||
}
|
||||
case Type::CHANGED:
|
||||
SetVariableValue(GetWeakSourceName(switcher->currentScene));
|
||||
return sceneChanged;
|
||||
@@ -95,6 +107,14 @@ bool MacroConditionScene::CheckCondition()
|
||||
SetVariableValue(GetWeakSourceName(scene));
|
||||
return sceneNameMatchesRegex(scene, _pattern);
|
||||
}
|
||||
case Type::PREVIEW_PATTERN: {
|
||||
OBSSourceAutoRelease source =
|
||||
obs_frontend_get_current_preview_scene();
|
||||
OBSWeakSourceAutoRelease scene =
|
||||
obs_source_get_weak_source(source);
|
||||
SetVariableValue(GetWeakSourceName(scene));
|
||||
return sceneNameMatchesRegex(scene.Get(), _pattern);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -108,6 +128,7 @@ bool MacroConditionScene::Save(obs_data_t *obj) const
|
||||
obs_data_set_string(obj, "pattern", _pattern.c_str());
|
||||
obs_data_set_bool(obj, "useTransitionTargetScene",
|
||||
_useTransitionTargetScene);
|
||||
obs_data_set_int(obj, "version", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -124,6 +145,46 @@ bool MacroConditionScene::Load(obs_data_t *obj)
|
||||
_useTransitionTargetScene =
|
||||
obs_data_get_bool(obj, "useTransitionTargetScene");
|
||||
}
|
||||
|
||||
// TODO: Remove fallback in future version
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
enum {
|
||||
CURRENT,
|
||||
PREVIOUS,
|
||||
CHANGED,
|
||||
NOT_CHANGED,
|
||||
CURRENT_PATTERN,
|
||||
PREVIOUS_PATTERN,
|
||||
};
|
||||
|
||||
int oldType = obs_data_get_int(obj, "type");
|
||||
switch (oldType) {
|
||||
case CURRENT:
|
||||
_type = Type::CURRENT;
|
||||
break;
|
||||
case PREVIOUS:
|
||||
_type = Type::PREVIOUS;
|
||||
break;
|
||||
case CHANGED:
|
||||
_type = Type::CHANGED;
|
||||
break;
|
||||
case NOT_CHANGED:
|
||||
_type = Type::NOT_CHANGED;
|
||||
break;
|
||||
case CURRENT_PATTERN:
|
||||
_type = Type::CURRENT_PATTERN;
|
||||
break;
|
||||
case PREVIOUS_PATTERN:
|
||||
_type = Type::PREVIOUS_PATTERN;
|
||||
break;
|
||||
default:
|
||||
blog(LOG_WARNING,
|
||||
"failed to convert scene condition type (%d) for macro %s",
|
||||
oldType, GetMacro()->Name().c_str());
|
||||
_type = Type::CURRENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,8 +198,9 @@ std::string MacroConditionScene::GetShortDesc() const
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : sceneTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
for (const auto &[id, name] : sceneTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,14 +261,15 @@ void MacroConditionSceneEdit::SceneChanged(const SceneSelection &s)
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionSceneEdit::TypeChanged(int value)
|
||||
void MacroConditionSceneEdit::TypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_type = static_cast<MacroConditionScene::Type>(value);
|
||||
_entryData->_type = static_cast<MacroConditionScene::Type>(
|
||||
_sceneType->itemData(index).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -234,7 +297,8 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
||||
{
|
||||
_scenes->setVisible(
|
||||
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS);
|
||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||
_entryData->_type == MacroConditionScene::Type::PREVIEW);
|
||||
_useTransitionTargetScene->setVisible(
|
||||
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||
@@ -246,7 +310,9 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
||||
_entryData->_type ==
|
||||
MacroConditionScene::Type::CURRENT_PATTERN ||
|
||||
_entryData->_type ==
|
||||
MacroConditionScene::Type::PREVIOUS_PATTERN);
|
||||
MacroConditionScene::Type::PREVIOUS_PATTERN ||
|
||||
_entryData->_type ==
|
||||
MacroConditionScene::Type::PREVIEW_PATTERN);
|
||||
|
||||
if (_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS_PATTERN) {
|
||||
@@ -259,6 +325,7 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
||||
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
||||
}
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneEdit::UpdateEntryData()
|
||||
@@ -268,7 +335,8 @@ void MacroConditionSceneEdit::UpdateEntryData()
|
||||
}
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_sceneType->setCurrentIndex(
|
||||
_sceneType->findData(static_cast<int>(_entryData->_type)));
|
||||
_pattern->setText(QString::fromStdString(_entryData->_pattern));
|
||||
_useTransitionTargetScene->setChecked(
|
||||
_entryData->_useTransitionTargetScene);
|
||||
|
||||
@@ -23,12 +23,14 @@ public:
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
CURRENT,
|
||||
PREVIOUS,
|
||||
CHANGED,
|
||||
NOT_CHANGED,
|
||||
CURRENT_PATTERN,
|
||||
PREVIOUS_PATTERN,
|
||||
CURRENT = 10,
|
||||
PREVIOUS = 20,
|
||||
PREVIEW = 30,
|
||||
CHANGED = 40,
|
||||
NOT_CHANGED = 50,
|
||||
CURRENT_PATTERN = 60,
|
||||
PREVIOUS_PATTERN = 70,
|
||||
PREVIEW_PATTERN = 80,
|
||||
};
|
||||
|
||||
SceneSelection _scene;
|
||||
|
||||
@@ -6,10 +6,13 @@ namespace advss {
|
||||
|
||||
const std::string MacroConditionSlideshow::id = "slideshow";
|
||||
|
||||
bool MacroConditionSlideshow::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionSlideshow::id,
|
||||
{MacroConditionSlideshow::Create, MacroConditionSlideshowEdit::Create,
|
||||
"AdvSceneSwitcher.condition.slideshow"});
|
||||
bool MacroConditionSlideshow::_registered =
|
||||
obs_get_version() >= MAKE_SEMANTIC_VERSION(29, 1, 0) &&
|
||||
MacroConditionFactory::Register(
|
||||
MacroConditionSlideshow::id,
|
||||
{MacroConditionSlideshow::Create,
|
||||
MacroConditionSlideshowEdit::Create,
|
||||
"AdvSceneSwitcher.condition.slideshow"});
|
||||
|
||||
static const std::map<MacroConditionSlideshow::Condition, std::string>
|
||||
conditions = {
|
||||
@@ -200,9 +203,9 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionSlideshow> entryData)
|
||||
: QWidget(parent),
|
||||
_conditions(new QComboBox(this)),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
||||
_index(new VariableSpinBox(this)),
|
||||
_path(new VariableLineEdit(this))
|
||||
_path(new VariableLineEdit(this)),
|
||||
_sources(new SourceSelectionWidget(this, QStringList(), true))
|
||||
{
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip"));
|
||||
|
||||
@@ -10,13 +10,16 @@ bool MacroConditionSource::_registered = MacroConditionFactory::Register(
|
||||
{MacroConditionSource::Create, MacroConditionSourceEdit::Create,
|
||||
"AdvSceneSwitcher.condition.source"});
|
||||
|
||||
const static std::map<SourceCondition, std::string> sourceConditionTypes = {
|
||||
{SourceCondition::ACTIVE,
|
||||
"AdvSceneSwitcher.condition.source.type.active"},
|
||||
{SourceCondition::SHOWING,
|
||||
"AdvSceneSwitcher.condition.source.type.showing"},
|
||||
{SourceCondition::SETTINGS,
|
||||
"AdvSceneSwitcher.condition.source.type.settings"},
|
||||
const static std::map<MacroConditionSource::Condition, std::string>
|
||||
sourceCnditionTypes = {
|
||||
{MacroConditionSource::Condition::ACTIVE,
|
||||
"AdvSceneSwitcher.condition.source.type.active"},
|
||||
{MacroConditionSource::Condition::SHOWING,
|
||||
"AdvSceneSwitcher.condition.source.type.showing"},
|
||||
{MacroConditionSource::Condition::SETTINGS_MATCH,
|
||||
"AdvSceneSwitcher.condition.source.type.settings"},
|
||||
{MacroConditionSource::Condition::SETTINGS_CHANGED,
|
||||
"AdvSceneSwitcher.condition.source.type.settingsChanged"},
|
||||
};
|
||||
|
||||
bool MacroConditionSource::CheckCondition()
|
||||
@@ -29,13 +32,13 @@ bool MacroConditionSource::CheckCondition()
|
||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||
|
||||
switch (_condition) {
|
||||
case SourceCondition::ACTIVE:
|
||||
case Condition::ACTIVE:
|
||||
ret = obs_source_active(s);
|
||||
break;
|
||||
case SourceCondition::SHOWING:
|
||||
case Condition::SHOWING:
|
||||
ret = obs_source_showing(s);
|
||||
break;
|
||||
case SourceCondition::SETTINGS:
|
||||
case Condition::SETTINGS_MATCH:
|
||||
ret = CompareSourceSettings(_source.GetSource(), _settings,
|
||||
_regex);
|
||||
if (IsReferencedInVars()) {
|
||||
@@ -43,6 +46,13 @@ bool MacroConditionSource::CheckCondition()
|
||||
GetSourceSettings(_source.GetSource()));
|
||||
}
|
||||
break;
|
||||
case Condition::SETTINGS_CHANGED: {
|
||||
std::string settings = GetSourceSettings(_source.GetSource());
|
||||
ret = !_currentSettings.empty() && settings != _currentSettings;
|
||||
_currentSettings = settings;
|
||||
SetVariableValue(settings);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -70,8 +80,7 @@ bool MacroConditionSource::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_source.Load(obj);
|
||||
_condition = static_cast<SourceCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||
_settings.Load(obj, "settings");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
@@ -89,8 +98,8 @@ std::string MacroConditionSource::GetShortDesc() const
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : sourceConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
for (const auto &[_, name] : sourceCnditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,8 @@ void MacroConditionSourceEdit::ConditionChanged(int index)
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_condition = static_cast<SourceCondition>(index);
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionSource::Condition>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -220,14 +230,18 @@ void MacroConditionSourceEdit::RegexChanged(RegexConfig conf)
|
||||
void MacroConditionSourceEdit::SetWidgetVisibility()
|
||||
{
|
||||
_settings->setVisible(_entryData->_condition ==
|
||||
SourceCondition::SETTINGS);
|
||||
_getSettings->setVisible(_entryData->_condition ==
|
||||
SourceCondition::SETTINGS);
|
||||
_regex->setVisible(_entryData->_condition == SourceCondition::SETTINGS);
|
||||
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||
_getSettings->setVisible(
|
||||
_entryData->_condition ==
|
||||
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||
_regex->setVisible(_entryData->_condition ==
|
||||
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||
|
||||
setToolTip(
|
||||
(_entryData->_condition == SourceCondition::ACTIVE ||
|
||||
_entryData->_condition == SourceCondition::SHOWING)
|
||||
(_entryData->_condition ==
|
||||
MacroConditionSource::Condition::ACTIVE ||
|
||||
_entryData->_condition ==
|
||||
MacroConditionSource::Condition::SHOWING)
|
||||
? obs_module_text(
|
||||
"AdvSceneSwitcher.condition.source.sceneVisibilityHint")
|
||||
: "");
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
enum class SourceCondition {
|
||||
ACTIVE,
|
||||
SHOWING,
|
||||
SETTINGS,
|
||||
};
|
||||
|
||||
class MacroConditionSource : public MacroCondition {
|
||||
public:
|
||||
MacroConditionSource(Macro *m) : MacroCondition(m, true) {}
|
||||
@@ -29,12 +23,21 @@ public:
|
||||
return std::make_shared<MacroConditionSource>(m);
|
||||
}
|
||||
|
||||
enum class Condition {
|
||||
ACTIVE,
|
||||
SHOWING,
|
||||
SETTINGS_MATCH,
|
||||
SETTINGS_CHANGED,
|
||||
};
|
||||
|
||||
SourceSelection _source;
|
||||
SourceCondition _condition = SourceCondition::ACTIVE;
|
||||
Condition _condition = Condition::ACTIVE;
|
||||
StringVariable _settings = "";
|
||||
RegexConfig _regex;
|
||||
|
||||
private:
|
||||
std::string _currentSettings;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
@@ -155,6 +155,13 @@ void MacroConditionStudioModeEdit::SetWidgetVisibility()
|
||||
|
||||
_scenes->setVisible(_entryData->_condition ==
|
||||
StudioModeCondition::PREVIEW_SCENE);
|
||||
|
||||
// TODO: Remove this workaround once the PREVIEW_SCENE condition type
|
||||
// has been removed
|
||||
if (_entryData->_condition != StudioModeCondition::PREVIEW_SCENE) {
|
||||
_condition->removeItem(
|
||||
static_cast<int>(StudioModeCondition::PREVIEW_SCENE));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace advss {
|
||||
enum class StudioModeCondition {
|
||||
STUDIO_MODE_ACTIVE,
|
||||
STUDIO_MODE_NOT_ACTIVE,
|
||||
PREVIEW_SCENE,
|
||||
PREVIEW_SCENE, // TODO: Remove in future version as the functionality
|
||||
// moved to the scene condition
|
||||
};
|
||||
|
||||
class MacroConditionStudioMode : public MacroCondition {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
MacroDock::MacroDock(Macro *m, QWidget *parent,
|
||||
MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
|
||||
const StringVariable &runButtonText,
|
||||
const StringVariable &pauseButtonText,
|
||||
const StringVariable &unpauseButtonText,
|
||||
@@ -25,11 +25,12 @@ MacroDock::MacroDock(Macro *m, QWidget *parent,
|
||||
_statusText(new QLabel(conditionsFalseText.c_str())),
|
||||
_macro(m)
|
||||
{
|
||||
if (_macro) {
|
||||
setWindowTitle(QString::fromStdString(_macro->Name()));
|
||||
_run->setVisible(_macro->DockHasRunButton());
|
||||
_pauseToggle->setVisible(_macro->DockHasPauseButton());
|
||||
_statusText->setVisible(_macro->DockHasStatusLabel());
|
||||
auto macro = _macro.lock();
|
||||
if (macro) {
|
||||
setWindowTitle(QString::fromStdString(macro->Name()));
|
||||
_run->setVisible(macro->DockHasRunButton());
|
||||
_pauseToggle->setVisible(macro->DockHasPauseButton());
|
||||
_statusText->setVisible(macro->DockHasStatusLabel());
|
||||
} else {
|
||||
setWindowTitle("<deleted macro>");
|
||||
}
|
||||
@@ -120,49 +121,52 @@ void MacroDock::EnableHighlight(bool value)
|
||||
|
||||
void MacroDock::RunClicked()
|
||||
{
|
||||
if (!_macro) {
|
||||
auto macro = _macro.lock();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = _macro->PerformActions();
|
||||
auto ret = macro->PerformActions(true);
|
||||
if (!ret) {
|
||||
QString err =
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||
DisplayMessage(err.arg(QString::fromStdString(_macro->Name())));
|
||||
DisplayMessage(err.arg(QString::fromStdString(macro->Name())));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroDock::PauseToggleClicked()
|
||||
{
|
||||
if (!_macro) {
|
||||
auto macro = _macro.lock();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
_macro->SetPaused(!_macro->Paused());
|
||||
macro->SetPaused(!macro->Paused());
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void MacroDock::UpdateText()
|
||||
{
|
||||
_run->setText(_runButtonText.c_str());
|
||||
|
||||
if (!_macro) {
|
||||
auto macro = _macro.lock();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pauseToggle->setText(_macro->Paused() ? _unpauseButtonText.c_str()
|
||||
: _pauseButtonText.c_str());
|
||||
_statusText->setText(_macro->Matched() ? _conditionsTrueText.c_str()
|
||||
: _conditionsFalseText.c_str());
|
||||
_pauseToggle->setText(macro->Paused() ? _unpauseButtonText.c_str()
|
||||
: _pauseButtonText.c_str());
|
||||
_statusText->setText(macro->Matched() ? _conditionsTrueText.c_str()
|
||||
: _conditionsFalseText.c_str());
|
||||
}
|
||||
|
||||
void MacroDock::Highlight()
|
||||
{
|
||||
if (!_highlight || !_macro) {
|
||||
auto macro = _macro.lock();
|
||||
if (!_highlight || !macro) {
|
||||
return;
|
||||
}
|
||||
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
|
||||
_macro->ExecutedSince(_lastHighlightCheckTime)) {
|
||||
macro->ExecutedSince(_lastHighlightCheckTime)) {
|
||||
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -16,7 +16,8 @@ class MacroDock : public OBSDock {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroDock(Macro *, QWidget *parent, const StringVariable &runButtonText,
|
||||
MacroDock(std::weak_ptr<Macro>, QWidget *parent,
|
||||
const StringVariable &runButtonText,
|
||||
const StringVariable &pauseButtonText,
|
||||
const StringVariable &unpauseButtonText,
|
||||
const StringVariable &conditionsTrueText,
|
||||
@@ -53,7 +54,7 @@ private:
|
||||
QTimer _timer;
|
||||
std::chrono::high_resolution_clock::time_point _lastHighlightCheckTime{};
|
||||
|
||||
Macro *_macro;
|
||||
std::weak_ptr<Macro> _macro;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -253,6 +253,76 @@ void AdvSceneSwitcher::ExportMacros()
|
||||
MacroExportImportDialog::ExportMacros(exportString);
|
||||
}
|
||||
|
||||
static bool
|
||||
isValidMacroSegmentIdx(const std::deque<std::shared_ptr<MacroSegment>> &list,
|
||||
int idx)
|
||||
{
|
||||
return (idx > 0 || (unsigned)idx < list.size());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetupMacroSegmentSelection(MacroSection type, int idx)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
MacroSegmentList *setList, *resetList1, *resetList2;
|
||||
int *setIdx, *resetIdx1, *resetIdx2;
|
||||
std::deque<std::shared_ptr<MacroSegment>> segements;
|
||||
|
||||
switch (type) {
|
||||
case AdvSceneSwitcher::MacroSection::CONDITIONS:
|
||||
setList = ui->conditionsList;
|
||||
setIdx = ¤tConditionIdx;
|
||||
segements = {macro->Conditions().begin(),
|
||||
macro->Conditions().end()};
|
||||
|
||||
resetList1 = ui->actionsList;
|
||||
resetList2 = ui->elseActionsList;
|
||||
resetIdx1 = ¤tActionIdx;
|
||||
resetIdx2 = ¤tElseActionIdx;
|
||||
break;
|
||||
case AdvSceneSwitcher::MacroSection::ACTIONS:
|
||||
setList = ui->actionsList;
|
||||
setIdx = ¤tActionIdx;
|
||||
segements = {macro->Actions().begin(), macro->Actions().end()};
|
||||
|
||||
resetList1 = ui->conditionsList;
|
||||
resetList2 = ui->elseActionsList;
|
||||
resetIdx1 = ¤tConditionIdx;
|
||||
resetIdx2 = ¤tElseActionIdx;
|
||||
break;
|
||||
case AdvSceneSwitcher::MacroSection::ELSE_ACTIONS:
|
||||
setList = ui->elseActionsList;
|
||||
setIdx = ¤tElseActionIdx;
|
||||
segements = {macro->ElseActions().begin(),
|
||||
macro->ElseActions().end()};
|
||||
|
||||
resetList1 = ui->actionsList;
|
||||
resetList2 = ui->conditionsList;
|
||||
resetIdx1 = ¤tActionIdx;
|
||||
resetIdx2 = ¤tConditionIdx;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
setList->SetSelection(idx);
|
||||
resetList1->SetSelection(-1);
|
||||
resetList2->SetSelection(-1);
|
||||
if (isValidMacroSegmentIdx(segements, idx)) {
|
||||
*setIdx = idx;
|
||||
} else {
|
||||
*setIdx = -1;
|
||||
}
|
||||
*resetIdx1 = -1;
|
||||
*resetIdx2 = -1;
|
||||
|
||||
lastInteracted = type;
|
||||
HighlightControls();
|
||||
}
|
||||
|
||||
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
|
||||
std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
@@ -381,21 +451,6 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
|
||||
RenameMacro(macro, newName);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_runMacro_clicked()
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = macro->PerformActions(true, true);
|
||||
if (!ret) {
|
||||
QString err =
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||
DisplayMessage(err.arg(QString::fromStdString(macro->Name())));
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value)
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
@@ -422,9 +477,20 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
|
||||
for (; afterIdx < actions.size(); afterIdx++) {
|
||||
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
||||
actions[afterIdx]->GetId());
|
||||
actionsList->Add(newEntry);
|
||||
ui->actionsList->Add(newEntry);
|
||||
}
|
||||
actionsList->SetHelpMsgVisible(actions.size() == 0);
|
||||
ui->actionsList->SetHelpMsgVisible(actions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
|
||||
{
|
||||
auto &actions = m.ElseActions();
|
||||
for (; afterIdx < actions.size(); afterIdx++) {
|
||||
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
||||
actions[afterIdx]->GetId());
|
||||
ui->elseActionsList->Add(newEntry);
|
||||
}
|
||||
ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
||||
@@ -435,17 +501,35 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
||||
auto newEntry = new MacroConditionEdit(
|
||||
this, &conditions[afterIdx],
|
||||
conditions[afterIdx]->GetId(), root);
|
||||
conditionsList->Add(newEntry);
|
||||
ui->conditionsList->Add(newEntry);
|
||||
root = false;
|
||||
}
|
||||
conditionsList->SetHelpMsgVisible(conditions.size() == 0);
|
||||
ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetActionData(Macro &m)
|
||||
{
|
||||
auto &actions = m.Actions();
|
||||
for (int idx = 0; idx < actionsList->ContentLayout()->count(); idx++) {
|
||||
auto item = actionsList->ContentLayout()->itemAt(idx);
|
||||
for (int idx = 0; idx < ui->actionsList->ContentLayout()->count();
|
||||
idx++) {
|
||||
auto item = ui->actionsList->ContentLayout()->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
auto widget = static_cast<MacroActionEdit *>(item->widget());
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
widget->SetEntryData(&*(actions.begin() + idx));
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetElseActionData(Macro &m)
|
||||
{
|
||||
auto &actions = m.ElseActions();
|
||||
for (int idx = 0; idx < ui->elseActionsList->ContentLayout()->count();
|
||||
idx++) {
|
||||
auto item = ui->elseActionsList->ContentLayout()->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
@@ -460,9 +544,9 @@ void AdvSceneSwitcher::SetActionData(Macro &m)
|
||||
void AdvSceneSwitcher::SetConditionData(Macro &m)
|
||||
{
|
||||
auto &conditions = m.Conditions();
|
||||
for (int idx = 0; idx < conditionsList->ContentLayout()->count();
|
||||
for (int idx = 0; idx < ui->conditionsList->ContentLayout()->count();
|
||||
idx++) {
|
||||
auto item = conditionsList->ContentLayout()->itemAt(idx);
|
||||
auto item = ui->conditionsList->ContentLayout()->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
@@ -474,6 +558,21 @@ void AdvSceneSwitcher::SetConditionData(Macro &m)
|
||||
}
|
||||
}
|
||||
|
||||
static void maximizeFirstSplitterEntry(QSplitter *splitter)
|
||||
{
|
||||
QList<int> newSizes;
|
||||
newSizes << 999999;
|
||||
for (int i = 0; i < splitter->sizes().size() - 1; i++) {
|
||||
newSizes << 0;
|
||||
}
|
||||
splitter->setSizes(newSizes);
|
||||
}
|
||||
|
||||
static void centerSplitterPosition(QSplitter *splitter)
|
||||
{
|
||||
splitter->setSizes(QList<int>() << 999999 << 999999);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||
{
|
||||
{
|
||||
@@ -484,22 +583,40 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||
ui->runMacroInParallel->setChecked(m.RunInParallel());
|
||||
ui->runMacroOnChange->setChecked(m.MatchOnChange());
|
||||
}
|
||||
conditionsList->Clear();
|
||||
actionsList->Clear();
|
||||
ui->conditionsList->Clear();
|
||||
ui->actionsList->Clear();
|
||||
ui->elseActionsList->Clear();
|
||||
|
||||
m.ResetUIHelpers();
|
||||
|
||||
PopulateMacroConditions(m);
|
||||
PopulateMacroActions(m);
|
||||
PopulateMacroElseActions(m);
|
||||
SetMacroEditAreaDisabled(false);
|
||||
|
||||
currentActionIdx = -1;
|
||||
currentElseActionIdx = -1;
|
||||
currentConditionIdx = -1;
|
||||
HighlightControls();
|
||||
|
||||
if (m.IsGroup()) {
|
||||
SetMacroEditAreaDisabled(true);
|
||||
ui->macroName->setEnabled(true);
|
||||
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
return;
|
||||
}
|
||||
|
||||
currentActionIdx = -1;
|
||||
currentConditionIdx = -1;
|
||||
HighlightControls();
|
||||
if (!m.HasValidSplitterPositions()) {
|
||||
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
return;
|
||||
}
|
||||
|
||||
ui->macroActionConditionSplitter->setSizes(
|
||||
m.GetActionConditionSplitterPosition());
|
||||
ui->macroElseActionSplitter->setSizes(
|
||||
m.GetElseActionSplitterPosition());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
||||
@@ -515,12 +632,17 @@ void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
||||
|
||||
void AdvSceneSwitcher::HighlightAction(int idx, QColor color)
|
||||
{
|
||||
actionsList->Highlight(idx, color);
|
||||
ui->actionsList->Highlight(idx, color);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightElseAction(int idx, QColor color)
|
||||
{
|
||||
ui->elseActionsList->Highlight(idx, color);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightCondition(int idx, QColor color)
|
||||
{
|
||||
conditionsList->Highlight(idx, color);
|
||||
ui->conditionsList->Highlight(idx, color);
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro> AdvSceneSwitcher::GetSelectedMacro()
|
||||
@@ -533,6 +655,37 @@ std::vector<std::shared_ptr<Macro>> AdvSceneSwitcher::GetSelectedMacros()
|
||||
return ui->macros->GetCurrentMacros();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroSelectionAboutToChange()
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ui->macroName->isEnabled()) { // No macro is selected
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = GetMacroByQString(ui->macroName->text());
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
macro->SetActionConditionSplitterPosition(
|
||||
ui->macroActionConditionSplitter->sizes());
|
||||
|
||||
auto elsePos = ui->macroElseActionSplitter->sizes();
|
||||
// If only conditions are visible maximize the actions to avoid neither
|
||||
// actions nor elseActions being visible when the condition <-> action
|
||||
// splitter is moved
|
||||
if (elsePos[0] == 0 && elsePos[1] == 0) {
|
||||
macro->SetElseActionSplitterPosition(QList<int>()
|
||||
<< 999999 << 0);
|
||||
return;
|
||||
}
|
||||
macro->SetElseActionSplitterPosition(
|
||||
ui->macroElseActionSplitter->sizes());
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroSelectionChanged()
|
||||
{
|
||||
if (loading) {
|
||||
@@ -542,10 +695,14 @@ void AdvSceneSwitcher::MacroSelectionChanged()
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
SetMacroEditAreaDisabled(true);
|
||||
conditionsList->Clear();
|
||||
actionsList->Clear();
|
||||
conditionsList->SetHelpMsgVisible(true);
|
||||
actionsList->SetHelpMsgVisible(true);
|
||||
ui->conditionsList->Clear();
|
||||
ui->actionsList->Clear();
|
||||
ui->elseActionsList->Clear();
|
||||
ui->conditionsList->SetHelpMsgVisible(true);
|
||||
ui->actionsList->SetHelpMsgVisible(true);
|
||||
ui->elseActionsList->SetHelpMsgVisible(true);
|
||||
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
return;
|
||||
}
|
||||
SetEditMacro(*macro);
|
||||
@@ -583,8 +740,19 @@ void AdvSceneSwitcher::on_macroProperties_clicked()
|
||||
emit HighlightConditionsChanged(prop._highlightConditions);
|
||||
}
|
||||
|
||||
// Don't restore splitter pos if an element is not visible at all
|
||||
bool shouldResotreSplitterPos(const QList<int> &pos)
|
||||
static void moveControlsToSplitter(QSplitter *splitter, int idx,
|
||||
QLayoutItem *item)
|
||||
{
|
||||
static int splitterHandleWidth = 38;
|
||||
auto handle = splitter->handle(idx);
|
||||
auto layout = item->layout();
|
||||
layout->setContentsMargins(7, 7, 7, 7);
|
||||
handle->setLayout(layout);
|
||||
splitter->setHandleWidth(splitterHandleWidth);
|
||||
splitter->setStyleSheet("QSplitter::handle {background: transparent;}");
|
||||
}
|
||||
|
||||
bool shouldRestoreSplitter(const QList<int> &pos)
|
||||
{
|
||||
if (pos.size() == 0) {
|
||||
return false;
|
||||
@@ -605,37 +773,44 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
}
|
||||
ui->macros->Reset(switcher->macros,
|
||||
switcher->macroProperties._highlightExecuted);
|
||||
connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this,
|
||||
SLOT(MacroSelectionAboutToChange()));
|
||||
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
|
||||
SLOT(MacroSelectionChanged()));
|
||||
ui->runMacro->SetMacroTree(ui->macros);
|
||||
|
||||
delete conditionsList;
|
||||
conditionsList = new MacroSegmentList(this);
|
||||
conditionsList->SetHelpMsg(
|
||||
ui->conditionsList->SetHelpMsg(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
|
||||
connect(conditionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
connect(ui->conditionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
&AdvSceneSwitcher::MacroConditionSelectionChanged);
|
||||
connect(conditionsList, &MacroSegmentList::Reorder, this,
|
||||
connect(ui->conditionsList, &MacroSegmentList::Reorder, this,
|
||||
&AdvSceneSwitcher::MacroConditionReorder);
|
||||
ui->macroConditionsLayout->insertWidget(0, conditionsList);
|
||||
|
||||
delete actionsList;
|
||||
actionsList = new MacroSegmentList(this);
|
||||
actionsList->SetHelpMsg(
|
||||
ui->actionsList->SetHelpMsg(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
|
||||
connect(actionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
connect(ui->actionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
&AdvSceneSwitcher::MacroActionSelectionChanged);
|
||||
connect(actionsList, &MacroSegmentList::Reorder, this,
|
||||
connect(ui->actionsList, &MacroSegmentList::Reorder, this,
|
||||
&AdvSceneSwitcher::MacroActionReorder);
|
||||
ui->macroActionsLayout->insertWidget(0, actionsList);
|
||||
|
||||
ui->elseActionsList->SetHelpMsg(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.editElseActionHelp"));
|
||||
connect(ui->elseActionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
&AdvSceneSwitcher::MacroElseActionSelectionChanged);
|
||||
connect(ui->elseActionsList, &MacroSegmentList::Reorder, this,
|
||||
&AdvSceneSwitcher::MacroElseActionReorder);
|
||||
|
||||
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->macros, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroContextMenu);
|
||||
actionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(actionsList, &QWidget::customContextMenuRequested, this,
|
||||
ui->actionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->actionsList, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroActionsContextMenu);
|
||||
conditionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(conditionsList, &QWidget::customContextMenuRequested, this,
|
||||
ui->elseActionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->elseActionsList, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroElseActionsContextMenu);
|
||||
ui->conditionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->conditionsList, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroConditionsContextMenu);
|
||||
|
||||
SetMacroEditAreaDisabled(true);
|
||||
@@ -648,16 +823,10 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
onChangeHighlightTimer.start();
|
||||
|
||||
// Move condition controls into splitter handle layout
|
||||
auto handle = ui->macroActionConditionSplitter->handle(1);
|
||||
auto item = ui->macroConditionsLayout->takeAt(1);
|
||||
if (item) {
|
||||
auto layout = item->layout();
|
||||
layout->setContentsMargins(7, 7, 7, 7);
|
||||
handle->setLayout(layout);
|
||||
ui->macroActionConditionSplitter->setHandleWidth(38);
|
||||
}
|
||||
ui->macroActionConditionSplitter->setStyleSheet(
|
||||
"QSplitter::handle {background: transparent;}");
|
||||
moveControlsToSplitter(ui->macroActionConditionSplitter, 1,
|
||||
ui->macroConditionsLayout->takeAt(1));
|
||||
moveControlsToSplitter(ui->macroElseActionSplitter, 1,
|
||||
ui->macroActionsLayout->takeAt(1));
|
||||
|
||||
// Set action and condition control icons
|
||||
const std::string pathPrefix =
|
||||
@@ -665,6 +834,9 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
||||
SetButtonIcon(ui->actionBottom,
|
||||
(pathPrefix + "DoubleDown.svg").c_str());
|
||||
SetButtonIcon(ui->elseActionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
||||
SetButtonIcon(ui->elseActionBottom,
|
||||
(pathPrefix + "DoubleDown.svg").c_str());
|
||||
SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
||||
SetButtonIcon(ui->conditionBottom,
|
||||
(pathPrefix + "DoubleDown.svg").c_str());
|
||||
@@ -673,13 +845,11 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
|
||||
|
||||
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
|
||||
if (switcher->saveWindowGeo) {
|
||||
if (shouldResotreSplitterPos(
|
||||
switcher->macroActionConditionSplitterPosition)) {
|
||||
ui->macroActionConditionSplitter->setSizes(
|
||||
switcher->macroActionConditionSplitterPosition);
|
||||
}
|
||||
if (shouldResotreSplitterPos(
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
@@ -731,41 +901,55 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.export"), this,
|
||||
&AdvSceneSwitcher::ExportMacros);
|
||||
exportAction->setDisabled(ui->macros->SelectionEmpty());
|
||||
auto import = menu.addAction(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.import"), this,
|
||||
&AdvSceneSwitcher::ImportMacros);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.import"),
|
||||
this, &AdvSceneSwitcher::ImportMacros);
|
||||
|
||||
menu.exec(globalPos);
|
||||
}
|
||||
|
||||
static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos,
|
||||
std::function<void(AdvSceneSwitcher *)> expand,
|
||||
std::function<void(AdvSceneSwitcher *)> collapse,
|
||||
std::function<void(AdvSceneSwitcher *)> maximize,
|
||||
std::function<void(AdvSceneSwitcher *)> minimize)
|
||||
{
|
||||
QMenu menu;
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
||||
ss, [ss, expand]() { expand(ss); });
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
|
||||
ss, [ss, collapse]() { collapse(ss); });
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
||||
ss, [ss, maximize]() { maximize(ss); });
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
||||
ss, [ss, minimize]() { minimize(ss); });
|
||||
menu.exec(pos);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos)
|
||||
{
|
||||
QPoint globalPos = actionsList->mapToGlobal(pos);
|
||||
QMenu menu;
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
||||
this, &AdvSceneSwitcher::ExpandAllActions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
|
||||
this, &AdvSceneSwitcher::CollapseAllActions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
||||
this, &AdvSceneSwitcher::MinimizeConditions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
||||
this, &AdvSceneSwitcher::MinimizeActions);
|
||||
menu.exec(globalPos);
|
||||
setupConextMenu(this, ui->actionsList->mapToGlobal(pos),
|
||||
&AdvSceneSwitcher::ExpandAllActions,
|
||||
&AdvSceneSwitcher::CollapseAllActions,
|
||||
&AdvSceneSwitcher::MaximizeActions,
|
||||
&AdvSceneSwitcher::MinimizeActions);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroElseActionsContextMenu(const QPoint &pos)
|
||||
{
|
||||
setupConextMenu(this, ui->elseActionsList->mapToGlobal(pos),
|
||||
&AdvSceneSwitcher::ExpandAllElseActions,
|
||||
&AdvSceneSwitcher::CollapseAllElseActions,
|
||||
&AdvSceneSwitcher::MaximizeElseActions,
|
||||
&AdvSceneSwitcher::MinimizeElseActions);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos)
|
||||
{
|
||||
QPoint globalPos = conditionsList->mapToGlobal(pos);
|
||||
QMenu menu;
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
||||
this, &AdvSceneSwitcher::ExpandAllConditions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
|
||||
this, &AdvSceneSwitcher::CollapseAllConditions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
||||
this, &AdvSceneSwitcher::MinimizeActions);
|
||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
||||
this, &AdvSceneSwitcher::MinimizeConditions);
|
||||
menu.exec(globalPos);
|
||||
setupConextMenu(this, ui->conditionsList->mapToGlobal(pos),
|
||||
&AdvSceneSwitcher::ExpandAllConditions,
|
||||
&AdvSceneSwitcher::CollapseAllConditions,
|
||||
&AdvSceneSwitcher::MaximizeConditions,
|
||||
&AdvSceneSwitcher::MinimizeConditions);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CopyMacro()
|
||||
@@ -795,60 +979,103 @@ void AdvSceneSwitcher::CopyMacro()
|
||||
emit MacroAdded(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllActions()
|
||||
void setCollapsedHelper(const std::shared_ptr<Macro> &m, MacroSegmentList *list,
|
||||
bool collapsed)
|
||||
{
|
||||
auto m = GetSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
actionsList->SetCollapsed(false);
|
||||
list->SetCollapsed(collapsed);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllActions()
|
||||
{
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->actionsList, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllElseActions()
|
||||
{
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllConditions()
|
||||
{
|
||||
auto m = GetSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
conditionsList->SetCollapsed(false);
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllActions()
|
||||
{
|
||||
auto m = GetSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
actionsList->SetCollapsed(true);
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->actionsList, true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllElseActions()
|
||||
{
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllConditions()
|
||||
{
|
||||
auto m = GetSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
conditionsList->SetCollapsed(true);
|
||||
setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, true);
|
||||
}
|
||||
|
||||
static void reduceSizeOfSplitterIdx(QSplitter *splitter, int idx)
|
||||
{
|
||||
auto sizes = splitter->sizes();
|
||||
int sum = sizes[0] + sizes[1];
|
||||
int reducedSize = sum / 10;
|
||||
sizes[idx] = reducedSize;
|
||||
sizes[(idx + 1) % 2] = sum - reducedSize;
|
||||
splitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MinimizeActions()
|
||||
{
|
||||
QList<int> sizes = ui->macroActionConditionSplitter->sizes();
|
||||
int sum = sizes[0] + sizes[1];
|
||||
int actionsHeight = sum / 10;
|
||||
sizes[1] = actionsHeight;
|
||||
sizes[0] = sum - actionsHeight;
|
||||
ui->macroActionConditionSplitter->setSizes(sizes);
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
if (macro->ElseActions().size() == 0) {
|
||||
reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1);
|
||||
} else {
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MaximizeActions()
|
||||
{
|
||||
MinimizeElseActions();
|
||||
MinimizeConditions();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MinimizeElseActions()
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
if (macro->ElseActions().size() == 0) {
|
||||
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||
} else {
|
||||
reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MaximizeElseActions()
|
||||
{
|
||||
MinimizeConditions();
|
||||
reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MinimizeConditions()
|
||||
{
|
||||
QList<int> sizes = ui->macroActionConditionSplitter->sizes();
|
||||
int sum = sizes[0] + sizes[1];
|
||||
int conditionsHeight = sum / 10;
|
||||
sizes[0] = conditionsHeight;
|
||||
sizes[1] = sum - conditionsHeight;
|
||||
ui->macroActionConditionSplitter->setSizes(sizes);
|
||||
reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MaximizeConditions()
|
||||
{
|
||||
MinimizeElseActions();
|
||||
MinimizeActions();
|
||||
}
|
||||
|
||||
bool AdvSceneSwitcher::MacroTabIsInFocus()
|
||||
|
||||
@@ -609,7 +609,7 @@ bool MacroTreeModel::IsLastItem(std::shared_ptr<Macro> item) const
|
||||
bool MacroTreeModel::IsInValidState()
|
||||
{
|
||||
// Check for reordering erros
|
||||
for (int i = 0, j = 0; i < _macros.size(); i++) {
|
||||
for (size_t i = 0, j = 0; i < _macros.size(); i++) {
|
||||
const auto &m = _macros[i];
|
||||
if (QString::fromStdString(m->Name()) !=
|
||||
data(index(j, 0), Qt::AccessibleTextRole)) {
|
||||
@@ -622,7 +622,7 @@ bool MacroTreeModel::IsInValidState()
|
||||
}
|
||||
|
||||
// Check for group errors
|
||||
for (int i = 0; i < _macros.size(); i++) {
|
||||
for (size_t i = 0; i < _macros.size(); i++) {
|
||||
const auto &m = _macros[i];
|
||||
if (!m->IsGroup()) {
|
||||
continue;
|
||||
@@ -1260,6 +1260,7 @@ void MacroTree::UngroupSelectedGroups()
|
||||
void MacroTree::SelectionChangedHelper(const QItemSelection &,
|
||||
const QItemSelection &)
|
||||
{
|
||||
emit MacroSelectionAboutToChange();
|
||||
emit MacroSelectionChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ public slots:
|
||||
const QItemSelection &);
|
||||
|
||||
signals:
|
||||
void MacroSelectionAboutToChange();
|
||||
void MacroSelectionChanged();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -176,24 +176,26 @@ bool Macro::CeckMatch()
|
||||
}
|
||||
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
||||
|
||||
bool matchedBeforeOnChangeCheck = _matched;
|
||||
if (_matched && _matchOnChange && _lastMatched) {
|
||||
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
_matched = false;
|
||||
SetOnChangeHighlight();
|
||||
_conditionSateChanged = _lastMatched != _matched;
|
||||
if (!_conditionSateChanged && _performActionsOnChange) {
|
||||
_onPreventedActionExecution = true;
|
||||
}
|
||||
_lastMatched = matchedBeforeOnChangeCheck;
|
||||
_lastMatched = _matched;
|
||||
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
||||
return _matched;
|
||||
}
|
||||
|
||||
bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
||||
bool Macro::PerformActions(bool match, bool forceParallel, bool ignorePause)
|
||||
{
|
||||
if (!_done) {
|
||||
vblog(LOG_INFO, "macro %s already running", _name.c_str());
|
||||
return !forceParallel;
|
||||
}
|
||||
std::function<bool(bool)> runFunc =
|
||||
match ? std::bind(&Macro::RunActions, this,
|
||||
std::placeholders::_1)
|
||||
: std::bind(&Macro::RunElseActions, this,
|
||||
std::placeholders::_1);
|
||||
_stop = false;
|
||||
_done = false;
|
||||
bool ret = true;
|
||||
@@ -202,9 +204,9 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
||||
_backgroundThread.join();
|
||||
}
|
||||
_backgroundThread = std::thread(
|
||||
[this, ignorePause] { RunActions(ignorePause); });
|
||||
[this, runFunc, ignorePause] { runFunc(ignorePause); });
|
||||
} else {
|
||||
RunActions(ret, ignorePause);
|
||||
ret = runFunc(ignorePause);
|
||||
}
|
||||
_lastExecutionTime = std::chrono::high_resolution_clock::now();
|
||||
auto group = _parent.lock();
|
||||
@@ -223,6 +225,28 @@ bool Macro::ExecutedSince(
|
||||
return _lastExecutionTime > time;
|
||||
}
|
||||
|
||||
bool Macro::ShouldRunActions() const
|
||||
{
|
||||
const bool hasActionsToExecute =
|
||||
(_matched || _elseActions.size() > 0) &&
|
||||
(!_performActionsOnChange || _conditionSateChanged);
|
||||
|
||||
if (VerboseLoggingEnabled() && _performActionsOnChange &&
|
||||
!_conditionSateChanged) {
|
||||
if (_matched && _actions.size() > 0) {
|
||||
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
}
|
||||
if (!_matched && _elseActions.size() > 0) {
|
||||
blog(LOG_INFO,
|
||||
"skip else actions for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return hasActionsToExecute;
|
||||
}
|
||||
|
||||
int64_t Macro::MsSinceLastCheck() const
|
||||
{
|
||||
if (_lastCheckTime.time_since_epoch().count() == 0) {
|
||||
@@ -251,37 +275,43 @@ void Macro::ResetTimers()
|
||||
_lastExecutionTime = {};
|
||||
}
|
||||
|
||||
void Macro::RunActions(bool &retVal, bool ignorePause)
|
||||
bool Macro::RunActionsHelper(
|
||||
const std::deque<std::shared_ptr<MacroAction>> &actions,
|
||||
bool ignorePause)
|
||||
{
|
||||
bool ret = true;
|
||||
for (auto &a : _actions) {
|
||||
if (a->Enabled()) {
|
||||
a->LogAction();
|
||||
ret = ret && a->PerformAction();
|
||||
bool actionsExecutedSuccessfully = true;
|
||||
for (auto &action : actions) {
|
||||
if (action->Enabled()) {
|
||||
action->LogAction();
|
||||
actionsExecutedSuccessfully =
|
||||
actionsExecutedSuccessfully &&
|
||||
action->PerformAction();
|
||||
} else {
|
||||
vblog(LOG_INFO, "skipping disabled action %s",
|
||||
a->GetId().c_str());
|
||||
action->GetId().c_str());
|
||||
}
|
||||
if (!ret || (_paused && !ignorePause) || _stop || _die) {
|
||||
retVal = ret;
|
||||
if (!actionsExecutedSuccessfully || (_paused && !ignorePause) ||
|
||||
_stop || _die) {
|
||||
break;
|
||||
}
|
||||
if (a->Enabled()) {
|
||||
a->SetHighlight();
|
||||
if (action->Enabled()) {
|
||||
action->SetHighlight();
|
||||
}
|
||||
}
|
||||
_done = true;
|
||||
return actionsExecutedSuccessfully;
|
||||
}
|
||||
|
||||
void Macro::RunActions(bool ignorePause)
|
||||
bool Macro::RunActions(bool ignorePause)
|
||||
{
|
||||
bool unused;
|
||||
RunActions(unused, ignorePause);
|
||||
vblog(LOG_INFO, "running actions of %s", _name.c_str());
|
||||
return RunActionsHelper(_actions, ignorePause);
|
||||
}
|
||||
|
||||
void Macro::SetOnChangeHighlight()
|
||||
bool Macro::RunElseActions(bool ignorePause)
|
||||
{
|
||||
_onChangeTriggered = true;
|
||||
vblog(LOG_INFO, "running else actions of %s", _name.c_str());
|
||||
return RunActionsHelper(_elseActions, ignorePause);
|
||||
}
|
||||
|
||||
bool Macro::DockIsVisible() const
|
||||
@@ -289,6 +319,11 @@ bool Macro::DockIsVisible() const
|
||||
return _dock && _dockAction && _dock->isVisible();
|
||||
}
|
||||
|
||||
void Macro::SetMatchOnChange(bool onChange)
|
||||
{
|
||||
_performActionsOnChange = onChange;
|
||||
}
|
||||
|
||||
void Macro::SetPaused(bool pause)
|
||||
{
|
||||
if (_paused && !pause) {
|
||||
@@ -332,22 +367,39 @@ std::deque<std::shared_ptr<MacroAction>> &Macro::Actions()
|
||||
return _actions;
|
||||
}
|
||||
|
||||
void Macro::UpdateActionIndices()
|
||||
std::deque<std::shared_ptr<MacroAction>> &Macro::ElseActions()
|
||||
{
|
||||
return _elseActions;
|
||||
}
|
||||
|
||||
static void updateIndicesHelper(std::deque<std::shared_ptr<MacroSegment>> &list)
|
||||
{
|
||||
int idx = 0;
|
||||
for (auto a : _actions) {
|
||||
a->SetIndex(idx);
|
||||
for (auto segment : list) {
|
||||
segment->SetIndex(idx);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
void Macro::UpdateActionIndices()
|
||||
{
|
||||
std::deque<std::shared_ptr<MacroSegment>> list(_actions.begin(),
|
||||
_actions.end());
|
||||
updateIndicesHelper(list);
|
||||
}
|
||||
|
||||
void Macro::UpdateElseActionIndices()
|
||||
{
|
||||
std::deque<std::shared_ptr<MacroSegment>> list(_elseActions.begin(),
|
||||
_elseActions.end());
|
||||
updateIndicesHelper(list);
|
||||
}
|
||||
|
||||
void Macro::UpdateConditionIndices()
|
||||
{
|
||||
int idx = 0;
|
||||
for (auto c : _conditions) {
|
||||
c->SetIndex(idx);
|
||||
idx++;
|
||||
}
|
||||
std::deque<std::shared_ptr<MacroSegment>> list(_conditions.begin(),
|
||||
_conditions.end());
|
||||
updateIndicesHelper(list);
|
||||
}
|
||||
|
||||
std::shared_ptr<Macro> Macro::Parent() const
|
||||
@@ -360,56 +412,57 @@ bool Macro::Save(obs_data_t *obj) const
|
||||
obs_data_set_string(obj, "name", _name.c_str());
|
||||
obs_data_set_bool(obj, "pause", _paused);
|
||||
obs_data_set_bool(obj, "parallel", _runInParallel);
|
||||
obs_data_set_bool(obj, "onChange", _matchOnChange);
|
||||
obs_data_set_bool(obj, "onChange", _performActionsOnChange);
|
||||
obs_data_set_bool(obj, "skipExecOnStart", _skipExecOnStart);
|
||||
|
||||
obs_data_set_bool(obj, "group", _isGroup);
|
||||
if (_isGroup) {
|
||||
auto groupData = obs_data_create();
|
||||
OBSDataAutoRelease groupData = obs_data_create();
|
||||
obs_data_set_bool(groupData, "collapsed", _isCollapsed);
|
||||
obs_data_set_int(groupData, "size", _groupSize);
|
||||
obs_data_set_obj(obj, "groupData", groupData);
|
||||
obs_data_release(groupData);
|
||||
return true;
|
||||
}
|
||||
|
||||
SaveDockSettings(obj);
|
||||
|
||||
SaveSplitterPos(_actionConditionSplitterPosition, obj,
|
||||
"macroActionConditionSplitterPosition");
|
||||
SaveSplitterPos(_elseActionSplitterPosition, obj,
|
||||
"macroElseActionSplitterPosition");
|
||||
|
||||
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
|
||||
obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey);
|
||||
OBSDataArrayAutoRelease pauseHotkey = obs_hotkey_save(_pauseHotkey);
|
||||
obs_data_set_array(obj, "pauseHotkey", pauseHotkey);
|
||||
obs_data_array_release(pauseHotkey);
|
||||
obs_data_array_t *unpauseHotkey = obs_hotkey_save(_unpauseHotkey);
|
||||
OBSDataArrayAutoRelease unpauseHotkey = obs_hotkey_save(_unpauseHotkey);
|
||||
obs_data_set_array(obj, "unpauseHotkey", unpauseHotkey);
|
||||
obs_data_array_release(unpauseHotkey);
|
||||
obs_data_array_t *togglePauseHotkey =
|
||||
OBSDataArrayAutoRelease togglePauseHotkey =
|
||||
obs_hotkey_save(_togglePauseHotkey);
|
||||
obs_data_set_array(obj, "togglePauseHotkey", togglePauseHotkey);
|
||||
obs_data_array_release(togglePauseHotkey);
|
||||
|
||||
obs_data_array_t *conditions = obs_data_array_create();
|
||||
OBSDataArrayAutoRelease conditions = obs_data_array_create();
|
||||
for (auto &c : _conditions) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
c->Save(array_obj);
|
||||
obs_data_array_push_back(conditions, array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||
c->Save(arrayObj);
|
||||
obs_data_array_push_back(conditions, arrayObj);
|
||||
}
|
||||
obs_data_set_array(obj, "conditions", conditions);
|
||||
obs_data_array_release(conditions);
|
||||
|
||||
obs_data_array_t *actions = obs_data_array_create();
|
||||
OBSDataArrayAutoRelease actions = obs_data_array_create();
|
||||
for (auto &a : _actions) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
a->Save(array_obj);
|
||||
obs_data_array_push_back(actions, array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||
a->Save(arrayObj);
|
||||
obs_data_array_push_back(actions, arrayObj);
|
||||
}
|
||||
obs_data_set_array(obj, "actions", actions);
|
||||
obs_data_array_release(actions);
|
||||
|
||||
OBSDataArrayAutoRelease elseActions = obs_data_array_create();
|
||||
for (auto &a : _elseActions) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||
a->Save(arrayObj);
|
||||
obs_data_array_push_back(elseActions, arrayObj);
|
||||
}
|
||||
obs_data_set_array(obj, "elseActions", elseActions);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -453,73 +506,69 @@ bool Macro::Load(obs_data_t *obj)
|
||||
_name = obs_data_get_string(obj, "name");
|
||||
_paused = obs_data_get_bool(obj, "pause");
|
||||
_runInParallel = obs_data_get_bool(obj, "parallel");
|
||||
_matchOnChange = obs_data_get_bool(obj, "onChange");
|
||||
_performActionsOnChange = obs_data_get_bool(obj, "onChange");
|
||||
_skipExecOnStart = obs_data_get_bool(obj, "skipExecOnStart");
|
||||
|
||||
_isGroup = obs_data_get_bool(obj, "group");
|
||||
if (_isGroup) {
|
||||
auto groupData = obs_data_get_obj(obj, "groupData");
|
||||
OBSDataAutoRelease groupData =
|
||||
obs_data_get_obj(obj, "groupData");
|
||||
_isCollapsed = obs_data_get_bool(groupData, "collapsed");
|
||||
_groupSize = obs_data_get_int(groupData, "size");
|
||||
obs_data_release(groupData);
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadDockSettings(obj);
|
||||
|
||||
LoadSplitterPos(_actionConditionSplitterPosition, obj,
|
||||
"macroActionConditionSplitterPosition");
|
||||
LoadSplitterPos(_elseActionSplitterPosition, obj,
|
||||
"macroElseActionSplitterPosition");
|
||||
|
||||
obs_data_set_default_bool(obj, "registerHotkeys", true);
|
||||
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
|
||||
if (_registerHotkeys) {
|
||||
SetupHotkeys();
|
||||
}
|
||||
obs_data_array_t *pauseHotkey = obs_data_get_array(obj, "pauseHotkey");
|
||||
OBSDataArrayAutoRelease pauseHotkey =
|
||||
obs_data_get_array(obj, "pauseHotkey");
|
||||
obs_hotkey_load(_pauseHotkey, pauseHotkey);
|
||||
obs_data_array_release(pauseHotkey);
|
||||
obs_data_array_t *unpauseHotkey =
|
||||
OBSDataArrayAutoRelease unpauseHotkey =
|
||||
obs_data_get_array(obj, "unpauseHotkey");
|
||||
obs_hotkey_load(_unpauseHotkey, unpauseHotkey);
|
||||
obs_data_array_release(unpauseHotkey);
|
||||
obs_data_array_t *togglePauseHotkey =
|
||||
OBSDataArrayAutoRelease togglePauseHotkey =
|
||||
obs_data_get_array(obj, "togglePauseHotkey");
|
||||
obs_hotkey_load(_togglePauseHotkey, togglePauseHotkey);
|
||||
obs_data_array_release(togglePauseHotkey);
|
||||
SetHotkeysDesc();
|
||||
|
||||
bool root = true;
|
||||
obs_data_array_t *conditions = obs_data_get_array(obj, "conditions");
|
||||
OBSDataArrayAutoRelease conditions =
|
||||
obs_data_get_array(obj, "conditions");
|
||||
size_t count = obs_data_array_count(conditions);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(conditions, i);
|
||||
|
||||
std::string id = obs_data_get_string(array_obj, "id");
|
||||
|
||||
OBSDataAutoRelease arrayObj =
|
||||
obs_data_array_item(conditions, i);
|
||||
std::string id = obs_data_get_string(arrayObj, "id");
|
||||
auto newEntry = MacroConditionFactory::Create(id, this);
|
||||
if (newEntry) {
|
||||
_conditions.emplace_back(newEntry);
|
||||
auto c = _conditions.back().get();
|
||||
c->Load(array_obj);
|
||||
c->Load(arrayObj);
|
||||
setValidLogic(c, root, _name);
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"discarding condition entry with unknown id (%s) for macro %s",
|
||||
id.c_str(), _name.c_str());
|
||||
}
|
||||
|
||||
obs_data_release(array_obj);
|
||||
root = false;
|
||||
}
|
||||
obs_data_array_release(conditions);
|
||||
UpdateConditionIndices();
|
||||
|
||||
obs_data_array_t *actions = obs_data_get_array(obj, "actions");
|
||||
OBSDataArrayAutoRelease actions = obs_data_get_array(obj, "actions");
|
||||
count = obs_data_array_count(actions);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(actions, i);
|
||||
|
||||
OBSDataAutoRelease array_obj = obs_data_array_item(actions, i);
|
||||
std::string id = obs_data_get_string(array_obj, "id");
|
||||
|
||||
auto newEntry = MacroActionFactory::Create(id, this);
|
||||
if (newEntry) {
|
||||
_actions.emplace_back(newEntry);
|
||||
@@ -529,11 +578,27 @@ bool Macro::Load(obs_data_t *obj)
|
||||
"discarding action entry with unknown id (%s) for macro %s",
|
||||
id.c_str(), _name.c_str());
|
||||
}
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(actions);
|
||||
UpdateActionIndices();
|
||||
|
||||
OBSDataArrayAutoRelease elseActions =
|
||||
obs_data_get_array(obj, "elseActions");
|
||||
count = obs_data_array_count(elseActions);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease array_obj =
|
||||
obs_data_array_item(elseActions, i);
|
||||
std::string id = obs_data_get_string(array_obj, "id");
|
||||
auto newEntry = MacroActionFactory::Create(id, this);
|
||||
if (newEntry) {
|
||||
_elseActions.emplace_back(newEntry);
|
||||
_elseActions.back()->Load(array_obj);
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"discarding elseAction entry with unknown id (%s) for macro %s",
|
||||
id.c_str(), _name.c_str());
|
||||
}
|
||||
}
|
||||
UpdateElseActionIndices();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -552,7 +617,12 @@ bool Macro::SwitchesScene() const
|
||||
{
|
||||
MacroActionSwitchScene temp(nullptr);
|
||||
auto sceneSwitchId = temp.GetId();
|
||||
for (auto &a : _actions) {
|
||||
for (const auto &a : _actions) {
|
||||
if (a->GetId() == sceneSwitchId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &a : _elseActions) {
|
||||
if (a->GetId() == sceneSwitchId) {
|
||||
return true;
|
||||
}
|
||||
@@ -560,18 +630,44 @@ bool Macro::SwitchesScene() const
|
||||
return false;
|
||||
}
|
||||
|
||||
const QList<int> &Macro::GetActionConditionSplitterPosition() const
|
||||
{
|
||||
return _actionConditionSplitterPosition;
|
||||
}
|
||||
|
||||
void Macro::SetActionConditionSplitterPosition(const QList<int> sizes)
|
||||
{
|
||||
_actionConditionSplitterPosition = sizes;
|
||||
}
|
||||
|
||||
const QList<int> &Macro::GetElseActionSplitterPosition() const
|
||||
{
|
||||
return _elseActionSplitterPosition;
|
||||
}
|
||||
|
||||
void Macro::SetElseActionSplitterPosition(const QList<int> sizes)
|
||||
{
|
||||
_elseActionSplitterPosition = sizes;
|
||||
}
|
||||
|
||||
bool Macro::HasValidSplitterPositions() const
|
||||
{
|
||||
return !_actionConditionSplitterPosition.empty() &&
|
||||
!_elseActionSplitterPosition.empty();
|
||||
}
|
||||
|
||||
bool Macro::OnChangePreventedActionsRecently()
|
||||
{
|
||||
if (_onChangeTriggered) {
|
||||
_onChangeTriggered = false;
|
||||
return true;
|
||||
if (_onPreventedActionExecution) {
|
||||
_onPreventedActionExecution = false;
|
||||
return _matched ? _actions.size() > 0 : _elseActions.size() > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Macro::ResetUIHelpers()
|
||||
{
|
||||
_onChangeTriggered = false;
|
||||
_onPreventedActionExecution = false;
|
||||
for (auto c : _conditions) {
|
||||
c->Highlight();
|
||||
}
|
||||
@@ -710,7 +806,8 @@ void Macro::EnableDock(bool value)
|
||||
// Create new dock widget
|
||||
auto window =
|
||||
static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
_dock = new MacroDock(this, window, _runButtonText, _pauseButtonText,
|
||||
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), window,
|
||||
_runButtonText, _pauseButtonText,
|
||||
_unpauseButtonText, _conditionsTrueStatusText,
|
||||
_conditionsFalseStatusText, _dockHighlight);
|
||||
SetDockWidgetName(); // Used by OBS to restore position
|
||||
@@ -1014,7 +1111,7 @@ bool SwitcherData::CheckMacros()
|
||||
{
|
||||
bool ret = false;
|
||||
for (auto &m : macros) {
|
||||
if (m->CeckMatch()) {
|
||||
if (m->CeckMatch() || m->ElseActions().size() > 0) {
|
||||
ret = true;
|
||||
// This has to be performed here for now as actions are
|
||||
// not performed immediately after checking conditions.
|
||||
@@ -1049,7 +1146,7 @@ bool SwitcherData::RunMacros()
|
||||
}
|
||||
|
||||
for (auto &m : runPhaseMacros) {
|
||||
if (!m || !m->Matched()) {
|
||||
if (!m || !m->ShouldRunActions()) {
|
||||
continue;
|
||||
}
|
||||
if (firstInterval && m->SkipExecOnStart()) {
|
||||
@@ -1059,7 +1156,7 @@ bool SwitcherData::RunMacros()
|
||||
continue;
|
||||
}
|
||||
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
|
||||
if (!m->PerformActions()) {
|
||||
if (!m->PerformActions(m->Matched())) {
|
||||
blog(LOG_WARNING, "abort macro: %s", m->Name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ public:
|
||||
Macro(const std::string &name = "", const bool addHotkey = false);
|
||||
virtual ~Macro();
|
||||
bool CeckMatch();
|
||||
bool PerformActions(bool forceParallel = false,
|
||||
bool PerformActions(bool match, bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
bool Matched() const { return _matched; }
|
||||
bool ShouldRunActions() const;
|
||||
int64_t MsSinceLastCheck() const;
|
||||
std::string Name() const { return _name; }
|
||||
void SetName(const std::string &name);
|
||||
@@ -35,8 +36,8 @@ public:
|
||||
bool RunInParallel() const { return _runInParallel; }
|
||||
void SetPaused(bool pause = true);
|
||||
bool Paused() const { return _paused; }
|
||||
void SetMatchOnChange(bool onChange) { _matchOnChange = onChange; }
|
||||
bool MatchOnChange() const { return _matchOnChange; }
|
||||
void SetMatchOnChange(bool onChange);
|
||||
bool MatchOnChange() const { return _performActionsOnChange; }
|
||||
void SetSkipExecOnStart(bool skip) { _skipExecOnStart = skip; }
|
||||
bool SkipExecOnStart() const { return _skipExecOnStart; }
|
||||
int RunCount() const { return _runCount; };
|
||||
@@ -48,7 +49,9 @@ public:
|
||||
|
||||
std::deque<std::shared_ptr<MacroCondition>> &Conditions();
|
||||
std::deque<std::shared_ptr<MacroAction>> &Actions();
|
||||
std::deque<std::shared_ptr<MacroAction>> &ElseActions();
|
||||
void UpdateActionIndices();
|
||||
void UpdateElseActionIndices();
|
||||
void UpdateConditionIndices();
|
||||
|
||||
// Group controls
|
||||
@@ -80,6 +83,11 @@ public:
|
||||
bool SwitchesScene() const;
|
||||
|
||||
// UI helpers
|
||||
const QList<int> &GetActionConditionSplitterPosition() const;
|
||||
void SetActionConditionSplitterPosition(const QList<int>);
|
||||
const QList<int> &GetElseActionSplitterPosition() const;
|
||||
void SetElseActionSplitterPosition(const QList<int>);
|
||||
bool HasValidSplitterPositions() const;
|
||||
bool
|
||||
ExecutedSince(const std::chrono::high_resolution_clock::time_point &);
|
||||
bool OnChangePreventedActionsRecently();
|
||||
@@ -113,9 +121,11 @@ private:
|
||||
void SetupHotkeys();
|
||||
void ClearHotkeys() const;
|
||||
void SetHotkeysDesc() const;
|
||||
void RunActions(bool &ret, bool ignorePause);
|
||||
void RunActions(bool ignorePause);
|
||||
void SetOnChangeHighlight();
|
||||
bool RunActionsHelper(
|
||||
const std::deque<std::shared_ptr<MacroAction>> &actions,
|
||||
bool ignorePause);
|
||||
bool RunActions(bool ignorePause);
|
||||
bool RunElseActions(bool ignorePause);
|
||||
bool DockIsVisible() const;
|
||||
void SetDockWidgetName() const;
|
||||
void SaveDockSettings(obs_data_t *obj) const;
|
||||
@@ -133,6 +143,7 @@ private:
|
||||
|
||||
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
||||
std::deque<std::shared_ptr<MacroAction>> _actions;
|
||||
std::deque<std::shared_ptr<MacroAction>> _elseActions;
|
||||
|
||||
std::weak_ptr<Macro> _parent;
|
||||
uint32_t _groupSize = 0;
|
||||
@@ -142,7 +153,8 @@ private:
|
||||
bool _runInParallel = false;
|
||||
bool _matched = false;
|
||||
bool _lastMatched = false;
|
||||
bool _matchOnChange = true;
|
||||
bool _conditionSateChanged = false;
|
||||
bool _performActionsOnChange = true;
|
||||
bool _skipExecOnStart = false;
|
||||
bool _paused = false;
|
||||
int _runCount = 0;
|
||||
@@ -151,7 +163,11 @@ private:
|
||||
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
|
||||
bool _onChangeTriggered = false;
|
||||
// UI helpers
|
||||
bool _onPreventedActionExecution = false;
|
||||
|
||||
QList<int> _actionConditionSplitterPosition;
|
||||
QList<int> _elseActionSplitterPosition;
|
||||
|
||||
bool _registerDock = false;
|
||||
bool _dockHasRunButton = true;
|
||||
|
||||
@@ -27,6 +27,11 @@ if(ENABLE_OPENVR_PLUGIN)
|
||||
add_subdirectory(openvr)
|
||||
endif()
|
||||
|
||||
option(ENABLE_TWITCH_PLUGIN "Enable the twitch plugin" ON)
|
||||
if(ENABLE_TWITCH_PLUGIN)
|
||||
add_subdirectory(twitch)
|
||||
endif()
|
||||
|
||||
option(ENABLE_VIDEO_PLUGIN "Enable the video plugin" ON)
|
||||
if(ENABLE_VIDEO_PLUGIN)
|
||||
add_subdirectory(video)
|
||||
|
||||
@@ -5,7 +5,7 @@ project(advanced-scene-switcher-midi)
|
||||
|
||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||
set(LIBREMIDI_DIR "${ADVSS_SOURCE_DIR}/deps/libremidi")
|
||||
if(NOT EXISTS "${LIBREMIDI_DIR}")
|
||||
if(NOT EXISTS "${LIBREMIDI_DIR}/CMakeLists.txt")
|
||||
message(WARNING "libremidi directory \"${LIBREMIDI_DIR}\" not found!\n"
|
||||
"MIDI support will be disabled!")
|
||||
return()
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace advss {
|
||||
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
||||
SetupMidiMessageVector()
|
||||
{
|
||||
GetSwitcher()->AddResetForNextIntervalFunction(
|
||||
GetSwitcher()->AddIntervalResetStep(
|
||||
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
|
||||
return {};
|
||||
}
|
||||
|
||||
79
src/macro-external/twitch/CMakeLists.txt
Normal file
79
src/macro-external/twitch/CMakeLists.txt
Normal file
@@ -0,0 +1,79 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(advanced-scene-switcher-twitch)
|
||||
|
||||
# --- Check requirements ---
|
||||
|
||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||
set(CPP_HTTPLIB_DIR "${ADVSS_SOURCE_DIR}/deps/cpp-httplib")
|
||||
if(NOT EXISTS "${CPP_HTTPLIB_DIR}/CMakeLists.txt")
|
||||
message(WARNING "cpp-httplib directory \"${CPP_HTTPLIB_DIR}\" not found!\n"
|
||||
"Twitch support will be disabled!")
|
||||
return()
|
||||
endif()
|
||||
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
|
||||
EXCLUDE_FROM_ALL)
|
||||
|
||||
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n"
|
||||
"Twitch support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT=1)
|
||||
if(OS_MACOS)
|
||||
target_compile_definitions(
|
||||
${PROJECT_NAME} PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN=1)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework Security")
|
||||
endif()
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE category-selection.cpp
|
||||
category-selection.hpp
|
||||
macro-action-twitch.cpp
|
||||
macro-action-twitch.hpp
|
||||
token.cpp
|
||||
token.hpp
|
||||
twitch-helpers.cpp
|
||||
twitch-helpers.hpp)
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${CPP_HTTPLIB_DIR}/"
|
||||
"${OPENSSL_INCLUDE_DIR}")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
|
||||
install_advss_plugin(${PROJECT_NAME})
|
||||
if(OS_WINDOWS)
|
||||
# Couldn't really find a better way to install runtime dependencies for
|
||||
# Windows TODO: Clean this up at some point
|
||||
function(FIND_FILES_WITH_PATTERN result pattern dir)
|
||||
execute_process(
|
||||
COMMAND
|
||||
powershell -Command
|
||||
"Get-ChildItem -Path '${dir}' -Recurse -Include ${pattern} |"
|
||||
"Select-Object -First 1 |"
|
||||
"ForEach-Object { $_.FullName -replace '\\\\', '\\\\' }"
|
||||
OUTPUT_VARIABLE files
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
set(${result}
|
||||
${files}
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(OPENSSL_DLL_SEARCH_DIR "${OPENSSL_INCLUDE_DIR}/..")
|
||||
find_files_with_pattern(CRYPTO_DLL_FILES "libcrypto*.dll"
|
||||
"${OPENSSL_DLL_SEARCH_DIR}")
|
||||
find_files_with_pattern(SSL_DLL_FILES "libssl*.dll"
|
||||
"${OPENSSL_DLL_SEARCH_DIR}")
|
||||
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
|
||||
"${CRYPTO_DLL_FILES}" "${SSL_DLL_FILES}")
|
||||
endif()
|
||||
364
src/macro-external/twitch/category-selection.cpp
Normal file
364
src/macro-external/twitch/category-selection.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "category-selection.hpp"
|
||||
#include "token.hpp"
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <utility.hpp>
|
||||
#include <name-dialog.hpp>
|
||||
#include <obs-module-helper.hpp>
|
||||
#include <obs.hpp>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void TwitchCategory::Load(obs_data_t *obj)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_get_obj(obj, "category");
|
||||
id = obs_data_get_int(data, "id");
|
||||
name = obs_data_get_string(data, "name");
|
||||
}
|
||||
|
||||
void TwitchCategory::Save(obs_data_t *obj) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_int(data, "id", id);
|
||||
obs_data_set_string(data, "name", name.c_str());
|
||||
obs_data_set_obj(obj, "category", data);
|
||||
}
|
||||
|
||||
bool TwitchCategorySelection::_fetchingCategoriesDone = false;
|
||||
std::map<QString, int> TwitchCategorySelection::_streamingCategories;
|
||||
|
||||
void TwitchCategorySelection::PopulateCategorySelection()
|
||||
{
|
||||
auto token = _token.lock();
|
||||
if (!token && _streamingCategories.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_fetchingCategoriesDone && token) {
|
||||
_categoryGrabber.Start(token);
|
||||
if (_progressDialog->exec() == QDialog::Accepted) {
|
||||
_fetchingCategoriesDone = true;
|
||||
}
|
||||
_categoryGrabber.Stop();
|
||||
_categoryGrabber.wait();
|
||||
}
|
||||
UpdateCategoryList();
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::UpdateCategoryList()
|
||||
{
|
||||
_streamingCategories = _categoryGrabber.GetCategories();
|
||||
QString currentSelection = currentText();
|
||||
|
||||
const QSignalBlocker b(this);
|
||||
clear();
|
||||
for (const auto &[name, id] : _streamingCategories) {
|
||||
addItem(name, id);
|
||||
}
|
||||
|
||||
setCurrentText(currentSelection);
|
||||
}
|
||||
|
||||
TwitchCategorySelection::TwitchCategorySelection(QWidget *parent)
|
||||
: FilterComboBox(
|
||||
parent,
|
||||
obs_module_text("AdvSceneSwitcher.twitchCategories.select")),
|
||||
_progressDialog(new ProgressDialog(this))
|
||||
{
|
||||
_progressDialog->setWindowModality(Qt::WindowModal);
|
||||
setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
|
||||
QWidget::connect(this, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SelectionChanged(int)));
|
||||
QWidget::connect(&_categoryGrabber, SIGNAL(CategoryCountUpdated(int)),
|
||||
_progressDialog, SLOT(CategoryCountUpdated(int)));
|
||||
QWidget::connect(&_categoryGrabber, SIGNAL(Finished()), this,
|
||||
SLOT(PopulateFinished()));
|
||||
QWidget::connect(TwitchCategorySignalManager::Instance(),
|
||||
SIGNAL(RepopulateRequired()), this,
|
||||
SLOT(UpdateCategoryList()));
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::SetToken(const std::weak_ptr<TwitchToken> &token)
|
||||
{
|
||||
_token = token;
|
||||
const bool expired = token.expired();
|
||||
setDisabled(expired);
|
||||
if (expired) {
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.categorySelectionDisabled"));
|
||||
} else {
|
||||
setToolTip("");
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::PopulateFinished()
|
||||
{
|
||||
_progressDialog->accept();
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::showPopup()
|
||||
{
|
||||
if (!IsPopulated()) {
|
||||
PopulateCategorySelection();
|
||||
}
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
FilterComboBox::showPopup();
|
||||
}
|
||||
|
||||
bool TwitchCategorySelection::IsPopulated()
|
||||
{
|
||||
return count() == _categoryGrabber.GetCategories().size() &&
|
||||
_fetchingCategoriesDone;
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::SetCategory(const TwitchCategory &id)
|
||||
{
|
||||
// If the list is populated already try to find id ...
|
||||
int index = findData(id.id);
|
||||
if (index != -1) {
|
||||
setCurrentIndex(index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (id.id == -1) {
|
||||
setCurrentIndex(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
// ... otherwise just add a dummy entry with the category name
|
||||
addItem(QString::fromStdString(id.name), id.id);
|
||||
setCurrentIndex(findData(id.id));
|
||||
}
|
||||
|
||||
void TwitchCategorySelection::SelectionChanged(int index)
|
||||
{
|
||||
TwitchCategory category{itemData(index).toInt(),
|
||||
currentText().toStdString()};
|
||||
emit CategoreyChanged(category);
|
||||
}
|
||||
|
||||
std::map<QString, int> CategoryGrabber::_categoryMap = {};
|
||||
std::mutex CategoryGrabber::_mtx = {};
|
||||
|
||||
void CategoryGrabber::Start(const std::shared_ptr<TwitchToken> &token,
|
||||
const std::string search)
|
||||
{
|
||||
_searchString = search;
|
||||
_token = token;
|
||||
_stop = false;
|
||||
start();
|
||||
}
|
||||
|
||||
void CategoryGrabber::Stop()
|
||||
{
|
||||
_stop = true;
|
||||
}
|
||||
|
||||
const std::map<QString, int> &CategoryGrabber::GetCategories()
|
||||
{
|
||||
return _categoryMap;
|
||||
}
|
||||
|
||||
void CategoryGrabber::run()
|
||||
{
|
||||
if (!_token) {
|
||||
return;
|
||||
emit Failed();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
if (_searchString.empty()) {
|
||||
GetAll();
|
||||
} else {
|
||||
Search(_searchString);
|
||||
}
|
||||
}
|
||||
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
void CategoryGrabber::Search(const std::string &)
|
||||
{
|
||||
static const std::string uri = "https://api.twitch.tv";
|
||||
const std::string path = "/helix/search/categories";
|
||||
|
||||
int startCount = _categoryMap.size();
|
||||
std::string cursor;
|
||||
httplib::Params params = {
|
||||
{"first", "100"}, {"after", cursor}, {"query", _searchString}};
|
||||
auto response = SendGetRequest(uri, path, *_token, params);
|
||||
|
||||
while (response.status == 200 && !_stop) {
|
||||
cursor = ParseReply(response.data);
|
||||
if (cursor.empty()) {
|
||||
break; // End of category list
|
||||
}
|
||||
params = {{"first", "100"},
|
||||
{"after", cursor},
|
||||
{"query", _searchString}};
|
||||
response = SendGetRequest(uri, path, *_token, params);
|
||||
emit CategoryCountUpdated(_categoryMap.size() - startCount);
|
||||
}
|
||||
}
|
||||
|
||||
void CategoryGrabber::GetAll()
|
||||
{
|
||||
static const std::string uri = "https://api.twitch.tv";
|
||||
const std::string path = "/helix/games/top";
|
||||
|
||||
// Declare static to "save" progress in case of cancel
|
||||
static std::string cursor;
|
||||
|
||||
httplib::Params params = {{"first", "100"}, {"after", cursor}};
|
||||
auto response = SendGetRequest(uri, path, *_token, params);
|
||||
|
||||
while (response.status == 200 && !_stop) {
|
||||
cursor = ParseReply(response.data);
|
||||
if (cursor.empty()) {
|
||||
break; // End of category list
|
||||
}
|
||||
params = {{"first", "100"}, {"after", cursor}};
|
||||
response = SendGetRequest(uri, path, *_token, params);
|
||||
emit CategoryCountUpdated(_categoryMap.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::string CategoryGrabber::ParseReply(obs_data_t *data) const
|
||||
{
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "data");
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
|
||||
int id = std::stoi(obs_data_get_string(arrayObj, "id"));
|
||||
QString name = obs_data_get_string(arrayObj, "name");
|
||||
_categoryMap.emplace(name, id);
|
||||
}
|
||||
OBSDataAutoRelease pagination = obs_data_get_obj(data, "pagination");
|
||||
return obs_data_get_string(pagination, "cursor");
|
||||
}
|
||||
|
||||
ProgressDialog::ProgressDialog(QWidget *parent, bool showSkip)
|
||||
: QDialog(parent),
|
||||
_skipFetchCheckBox(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.fetchSkip"))),
|
||||
_status(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.fetchStart")))
|
||||
{
|
||||
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
_skipFetchCheckBox->setVisible(showSkip);
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(_status);
|
||||
auto cancelButton = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.twitchCategories.fetchStop"),
|
||||
this);
|
||||
layout->addWidget(_skipFetchCheckBox);
|
||||
layout->addWidget(cancelButton);
|
||||
setLayout(layout);
|
||||
|
||||
QWidget::connect(_skipFetchCheckBox, &QCheckBox::stateChanged, this,
|
||||
[this](int value) { _skipFetch = value; });
|
||||
QWidget::connect(cancelButton, &QPushButton::clicked, this,
|
||||
[this]() { _skipFetch ? accept() : reject(); });
|
||||
|
||||
if (_skipFetch) {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressDialog::CategoryCountUpdated(int value)
|
||||
{
|
||||
_status->setText(
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.fetchStatus"))
|
||||
.arg(value));
|
||||
}
|
||||
|
||||
TwitchCategorySearchButton::TwitchCategorySearchButton()
|
||||
{
|
||||
setMaximumWidth(22);
|
||||
const std::string pathPrefix =
|
||||
GetDataFilePath("res/images/" + GetThemeTypeName());
|
||||
SetButtonIcon(this, (pathPrefix + "Search.svg").c_str());
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.manualSearch"));
|
||||
QWidget::connect(this, SIGNAL(clicked()), this,
|
||||
SLOT(StartManualCategorySearch()));
|
||||
QWidget::connect(this, SIGNAL(RequestRepopulate()),
|
||||
TwitchCategorySignalManager::Instance(),
|
||||
SIGNAL(RepopulateRequired()));
|
||||
}
|
||||
|
||||
void TwitchCategorySearchButton::SetToken(
|
||||
const std::weak_ptr<TwitchToken> &token)
|
||||
{
|
||||
_token = token;
|
||||
const bool expired = token.expired();
|
||||
setDisabled(expired);
|
||||
if (expired) {
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.categorySelectionDisabled"));
|
||||
} else {
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.manualSearch"));
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchCategorySearchButton::StartManualCategorySearch()
|
||||
{
|
||||
std::string category;
|
||||
bool accepted = AdvSSNameDialog::AskForName(
|
||||
this,
|
||||
obs_module_text("AdvSceneSwitcher.twitchCategories.search"),
|
||||
obs_module_text("AdvSceneSwitcher.twitchCategories.name"),
|
||||
category);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
CategoryGrabber categoryGrabber;
|
||||
auto *progressDialog = new ProgressDialog(this, false);
|
||||
|
||||
QWidget::connect(&categoryGrabber, SIGNAL(CategoryCountUpdated(int)),
|
||||
progressDialog, SLOT(CategoryCountUpdated(int)));
|
||||
QWidget::connect(&categoryGrabber, &CategoryGrabber::Finished, this,
|
||||
[progressDialog]() { progressDialog->accept(); });
|
||||
QWidget::connect(&categoryGrabber, &CategoryGrabber::Failed, this,
|
||||
[progressDialog]() { progressDialog->reject(); });
|
||||
|
||||
auto previousCategoryCount = categoryGrabber.GetCategories().size();
|
||||
|
||||
categoryGrabber.Start(_token.lock(), category);
|
||||
progressDialog->exec();
|
||||
categoryGrabber.Stop();
|
||||
categoryGrabber.wait();
|
||||
|
||||
emit RequestRepopulate();
|
||||
progressDialog->deleteLater();
|
||||
|
||||
auto newCategoryCount =
|
||||
categoryGrabber.GetCategories().size() - previousCategoryCount;
|
||||
if (newCategoryCount == 0) {
|
||||
DisplayMessage(
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.searchFailed"))
|
||||
.arg(QString::fromStdString(category)));
|
||||
} else {
|
||||
DisplayMessage(
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchCategories.searchSuccess"))
|
||||
.arg(QString::number(newCategoryCount),
|
||||
QString::fromStdString(category)));
|
||||
}
|
||||
}
|
||||
|
||||
TwitchCategorySignalManager *TwitchCategorySignalManager::Instance()
|
||||
{
|
||||
static TwitchCategorySignalManager manager;
|
||||
return &manager;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
129
src/macro-external/twitch/category-selection.hpp
Normal file
129
src/macro-external/twitch/category-selection.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
#include <filter-combo-box.hpp>
|
||||
#include <string>
|
||||
#include <obs-data.h>
|
||||
#include <QThread>
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class TwitchToken;
|
||||
|
||||
struct TwitchCategory {
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
|
||||
int id = -1;
|
||||
std::string name = "-";
|
||||
};
|
||||
|
||||
class CategoryGrabber : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
void Start(const std::shared_ptr<TwitchToken> &token,
|
||||
const std::string searchString = "");
|
||||
void Stop();
|
||||
const std::map<QString, int> &GetCategories();
|
||||
|
||||
private:
|
||||
signals:
|
||||
void CategoryCountUpdated(int value);
|
||||
void Finished();
|
||||
void Failed();
|
||||
|
||||
private:
|
||||
void run() override;
|
||||
|
||||
void Search(const std::string &);
|
||||
void GetAll();
|
||||
std::string ParseReply(obs_data_t *) const;
|
||||
|
||||
std::shared_ptr<TwitchToken> _token;
|
||||
static std::map<QString, int> _categoryMap;
|
||||
std::string _searchString = "";
|
||||
bool _stop = false;
|
||||
|
||||
// Don't allow parallel search requests to not spam Twitch API
|
||||
static std::mutex _mtx;
|
||||
};
|
||||
|
||||
class ProgressDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProgressDialog(QWidget *parent, bool showSkip = true);
|
||||
|
||||
private slots:
|
||||
void CategoryCountUpdated(int);
|
||||
|
||||
private:
|
||||
QCheckBox *_skipFetchCheckBox;
|
||||
QLabel *_status;
|
||||
bool _skipFetch = false;
|
||||
};
|
||||
|
||||
class TwitchCategorySelection : public FilterComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchCategorySelection(QWidget *parent);
|
||||
|
||||
// Will *not* verify if ID is still valid or populate the selection
|
||||
// list as that would take too long
|
||||
void SetCategory(const TwitchCategory &);
|
||||
|
||||
// Used for populating the category list
|
||||
void SetToken(const std::weak_ptr<TwitchToken> &);
|
||||
|
||||
private slots:
|
||||
void SelectionChanged(int);
|
||||
void PopulateFinished();
|
||||
void UpdateCategoryList();
|
||||
|
||||
signals:
|
||||
void CategoreyChanged(const TwitchCategory &);
|
||||
|
||||
protected:
|
||||
void showPopup() override;
|
||||
|
||||
private:
|
||||
void PopulateCategorySelection();
|
||||
bool IsPopulated();
|
||||
|
||||
ProgressDialog *_progressDialog;
|
||||
CategoryGrabber _categoryGrabber;
|
||||
std::weak_ptr<TwitchToken> _token;
|
||||
static bool _fetchingCategoriesDone;
|
||||
static std::map<QString, int> _streamingCategories;
|
||||
};
|
||||
|
||||
class TwitchCategorySearchButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchCategorySearchButton();
|
||||
void SetToken(const std::weak_ptr<TwitchToken> &);
|
||||
|
||||
private slots:
|
||||
void StartManualCategorySearch();
|
||||
|
||||
signals:
|
||||
void RequestRepopulate();
|
||||
|
||||
private:
|
||||
std::weak_ptr<TwitchToken> _token;
|
||||
};
|
||||
|
||||
// Helper class to ease singal / slot handling
|
||||
class TwitchCategorySignalManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static TwitchCategorySignalManager *Instance();
|
||||
|
||||
signals:
|
||||
void RepopulateRequired();
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
433
src/macro-external/twitch/macro-action-twitch.cpp
Normal file
433
src/macro-external/twitch/macro-action-twitch.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#include "macro-action-twitch.hpp"
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <log-helper.hpp>
|
||||
#include <utility.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionTwitch::id = "twitch";
|
||||
|
||||
bool MacroActionTwitch::_registered = MacroActionFactory::Register(
|
||||
MacroActionTwitch::id,
|
||||
{MacroActionTwitch::Create, MacroActionTwitchEdit::Create,
|
||||
"AdvSceneSwitcher.action.twitch"});
|
||||
|
||||
const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
|
||||
{MacroActionTwitch::Action::TITLE,
|
||||
"AdvSceneSwitcher.action.twitch.type.title"},
|
||||
{MacroActionTwitch::Action::CATEGORY,
|
||||
"AdvSceneSwitcher.action.twitch.type.category"},
|
||||
{MacroActionTwitch::Action::MARKER,
|
||||
"AdvSceneSwitcher.action.twitch.type.marker"},
|
||||
{MacroActionTwitch::Action::CLIP,
|
||||
"AdvSceneSwitcher.action.twitch.type.clip"},
|
||||
{MacroActionTwitch::Action::COMMERCIAL,
|
||||
"AdvSceneSwitcher.action.twitch.type.commercial"},
|
||||
};
|
||||
|
||||
void MacroActionTwitch::SetStreamTitle(
|
||||
const std::shared_ptr<TwitchToken> &token) const
|
||||
{
|
||||
if (std::string(_streamTitle).empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "title", _streamTitle.c_str());
|
||||
auto result = SendPatchRequest(
|
||||
"https://api.twitch.tv",
|
||||
std::string("/helix/channels?broadcaster_id=") +
|
||||
token->GetUserID(),
|
||||
*token, data.Get());
|
||||
|
||||
if (result.status != 204) {
|
||||
blog(LOG_INFO, "Failed to set stream title! (%d)",
|
||||
result.status);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTwitch::SetStreamCategory(
|
||||
const std::shared_ptr<TwitchToken> &token) const
|
||||
{
|
||||
if (_category.id == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "game_id",
|
||||
std::to_string(_category.id).c_str());
|
||||
auto result = SendPatchRequest(
|
||||
"https://api.twitch.tv",
|
||||
std::string("/helix/channels?broadcaster_id=") +
|
||||
token->GetUserID(),
|
||||
*token, data.Get());
|
||||
if (result.status != 204) {
|
||||
blog(LOG_INFO, "Failed to set stream category! (%d)",
|
||||
result.status);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTwitch::CreateStreamMarker(
|
||||
const std::shared_ptr<TwitchToken> &token) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "user_id", token->GetUserID().c_str());
|
||||
|
||||
if (!std::string(_markerDescription).empty()) {
|
||||
obs_data_set_string(data, "description",
|
||||
_markerDescription.c_str());
|
||||
}
|
||||
|
||||
auto result = SendPostRequest("https://api.twitch.tv",
|
||||
"/helix/streams/markers", *token,
|
||||
data.Get());
|
||||
|
||||
if (result.status != 200) {
|
||||
blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTwitch::CreateStreamClip(
|
||||
const std::shared_ptr<TwitchToken> &token) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
auto hasDelay = _clipHasDelay ? "true" : "false";
|
||||
auto result = SendPostRequest(
|
||||
"https://api.twitch.tv",
|
||||
"/helix/clips?broadcaster_id=" + token->GetUserID() +
|
||||
"&has_delay=" + hasDelay,
|
||||
*token, data.Get());
|
||||
|
||||
if (result.status != 202) {
|
||||
blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionTwitch::StartCommercial(
|
||||
const std::shared_ptr<TwitchToken> &token) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
|
||||
obs_data_set_int(data, "length", _duration.Seconds());
|
||||
auto result = SendPostRequest("https://api.twitch.tv",
|
||||
"/helix/channels/commercial", *token,
|
||||
data.Get());
|
||||
if (result.status != 200) {
|
||||
OBSDataArrayAutoRelease replyArray =
|
||||
obs_data_get_array(result.data, "data");
|
||||
OBSDataAutoRelease replyData =
|
||||
obs_data_array_item(replyArray, 0);
|
||||
blog(LOG_INFO,
|
||||
"Failed to start commercial! (%d)\n"
|
||||
"length: %d\n"
|
||||
"message: %s\n"
|
||||
"retry_after: %d\n",
|
||||
result.status, obs_data_get_int(replyData, "length"),
|
||||
obs_data_get_string(replyData, "message"),
|
||||
obs_data_get_int(replyData, "retry_after"));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionTwitch::PerformAction()
|
||||
{
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_action) {
|
||||
case MacroActionTwitch::Action::TITLE:
|
||||
SetStreamTitle(token);
|
||||
break;
|
||||
case MacroActionTwitch::Action::CATEGORY:
|
||||
SetStreamCategory(token);
|
||||
break;
|
||||
case MacroActionTwitch::Action::MARKER:
|
||||
CreateStreamMarker(token);
|
||||
break;
|
||||
case MacroActionTwitch::Action::CLIP:
|
||||
CreateStreamClip(token);
|
||||
break;
|
||||
case MacroActionTwitch::Action::COMMERCIAL:
|
||||
StartCommercial(token);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionTwitch::LogAction() const
|
||||
{
|
||||
auto it = actionTypes.find(_action);
|
||||
if (it != actionTypes.end()) {
|
||||
vblog(LOG_INFO, "performed action \"%s\" with token for \"%s\"",
|
||||
it->second.c_str(),
|
||||
GetWeakTwitchTokenName(_token).c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignored unknown twitch action %d",
|
||||
static_cast<int>(_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionTwitch::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_string(obj, "token",
|
||||
GetWeakTwitchTokenName(_token).c_str());
|
||||
_streamTitle.Save(obj, "streamTitle");
|
||||
_category.Save(obj);
|
||||
_markerDescription.Save(obj, "markerDescription");
|
||||
obs_data_set_bool(obj, "clipHasDelay", _clipHasDelay);
|
||||
_duration.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionTwitch::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||
_token = GetWeakTwitchTokenByName(obs_data_get_string(obj, "token"));
|
||||
_streamTitle.Load(obj, "streamTitle");
|
||||
_category.Load(obj);
|
||||
_markerDescription.Load(obj, "markerDescription");
|
||||
_clipHasDelay = obs_data_get_bool(obj, "clipHasDelay");
|
||||
_duration.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionTwitch::GetShortDesc() const
|
||||
{
|
||||
return GetWeakTwitchTokenName(_token);
|
||||
}
|
||||
|
||||
bool MacroActionTwitch::ActionIsSupportedByToken()
|
||||
{
|
||||
static const std::unordered_map<Action, TokenOption> requiredOption = {
|
||||
{Action::TITLE, {"channel:manage:broadcast"}},
|
||||
{Action::CATEGORY, {"channel:manage:broadcast"}},
|
||||
{Action::MARKER, {"channel:manage:broadcast"}},
|
||||
{Action::CLIP, {"clips:edit"}},
|
||||
{Action::COMMERCIAL, {"channel:edit:commercial"}},
|
||||
};
|
||||
auto token = _token.lock();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
auto option = requiredOption.find(_action);
|
||||
assert(option != requiredOption.end());
|
||||
return token->OptionIsEnabled(option->second);
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[_, name] : actionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionTwitchEdit::MacroActionTwitchEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionTwitch> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_tokens(new TwitchConnectionSelection()),
|
||||
_streamTitle(new VariableLineEdit(this)),
|
||||
_category(new TwitchCategorySelection(this)),
|
||||
_manualCategorySearch(new TwitchCategorySearchButton()),
|
||||
_markerDescription(new VariableLineEdit(this)),
|
||||
_clipHasDelay(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.clip.hasDelay"))),
|
||||
_duration(new DurationSelection(this, false, 0)),
|
||||
_layout(new QHBoxLayout()),
|
||||
_tokenPermissionWarning(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient")))
|
||||
{
|
||||
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_streamTitle->setMaxLength(140);
|
||||
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_markerDescription->setMaxLength(140);
|
||||
|
||||
auto spinBox = _duration->SpinBox();
|
||||
spinBox->setSuffix("s");
|
||||
spinBox->setMaximum(180);
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
|
||||
this, SLOT(TwitchTokenChanged(const QString &)));
|
||||
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
|
||||
SLOT(StreamTitleChanged()));
|
||||
QWidget::connect(_category,
|
||||
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
|
||||
SLOT(CategoreyChanged(const TwitchCategory &)));
|
||||
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
|
||||
SLOT(MarkerDescriptionChanged()));
|
||||
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(HasClipDelayChanged(const Duration &)));
|
||||
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
|
||||
this, SLOT(DurationChanged(const Duration &)));
|
||||
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
|
||||
SLOT(CheckTokenPermissions()));
|
||||
|
||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.twitch.entry"),
|
||||
_layout,
|
||||
{{"{{account}}", _tokens},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{streamTitle}}", _streamTitle},
|
||||
{"{{category}}", _category},
|
||||
{"{{manualCategorySearch}}", _manualCategorySearch},
|
||||
{"{{markerDescription}}", _markerDescription},
|
||||
{"{{clipHasDelay}}", _clipHasDelay},
|
||||
{"{{duration}}", _duration}});
|
||||
_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(_layout);
|
||||
mainLayout->addWidget(_tokenPermissionWarning);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_tokenPermissionCheckTimer.start(1000);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_token = GetWeakTwitchTokenByQString(token);
|
||||
_category->SetToken(_entryData->_token);
|
||||
_manualCategorySearch->SetToken(_entryData->_token);
|
||||
SetupWidgetVisibility();
|
||||
emit(HeaderInfoChanged(token));
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::StreamTitleChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_streamTitle = _streamTitle->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_category = category;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::MarkerDescriptionChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_markerDescription =
|
||||
_markerDescription->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::ClipHasDelayChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_clipHasDelay = state;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::DurationChanged(const Duration &duration)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_duration = duration;
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::CheckTokenPermissions()
|
||||
{
|
||||
_tokenPermissionWarning->setVisible(
|
||||
_entryData && !_entryData->ActionIsSupportedByToken());
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::SetupWidgetVisibility()
|
||||
{
|
||||
_streamTitle->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::TITLE);
|
||||
_category->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::CATEGORY);
|
||||
_manualCategorySearch->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::CATEGORY);
|
||||
_markerDescription->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::MARKER);
|
||||
_clipHasDelay->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::CLIP);
|
||||
_duration->setVisible(_entryData->_action ==
|
||||
MacroActionTwitch::Action::COMMERCIAL);
|
||||
|
||||
if (_entryData->_action == MacroActionTwitch::Action::TITLE ||
|
||||
_entryData->_action == MacroActionTwitch::Action::MARKER) {
|
||||
RemoveStretchIfPresent(_layout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_layout);
|
||||
}
|
||||
|
||||
_tokenPermissionWarning->setVisible(
|
||||
!_entryData->ActionIsSupportedByToken());
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_tokens->SetToken(_entryData->_token);
|
||||
_streamTitle->setText(_entryData->_streamTitle);
|
||||
_category->SetToken(_entryData->_token);
|
||||
_manualCategorySearch->SetToken(_entryData->_token);
|
||||
_category->SetCategory(_entryData->_category);
|
||||
_markerDescription->setText(_entryData->_markerDescription);
|
||||
_clipHasDelay->setChecked(_entryData->_clipHasDelay);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
SetupWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTwitchEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_entryData->_action = static_cast<MacroActionTwitch::Action>(value);
|
||||
SetupWidgetVisibility();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
104
src/macro-external/twitch/macro-action-twitch.hpp
Normal file
104
src/macro-external/twitch/macro-action-twitch.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "token.hpp"
|
||||
#include "category-selection.hpp"
|
||||
|
||||
#include <variable-line-edit.hpp>
|
||||
#include <duration-control.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroActionTwitch : public MacroAction {
|
||||
public:
|
||||
MacroActionTwitch(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionTwitch>(m);
|
||||
}
|
||||
bool ActionIsSupportedByToken();
|
||||
|
||||
enum class Action {
|
||||
TITLE,
|
||||
CATEGORY,
|
||||
MARKER,
|
||||
CLIP,
|
||||
COMMERCIAL,
|
||||
};
|
||||
|
||||
Action _action = Action::TITLE;
|
||||
std::weak_ptr<TwitchToken> _token;
|
||||
StringVariable _streamTitle =
|
||||
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
|
||||
TwitchCategory _category;
|
||||
StringVariable _markerDescription = obs_module_text(
|
||||
"AdvSceneSwitcher.action.twitch.marker.description");
|
||||
bool _clipHasDelay = false;
|
||||
Duration _duration = 60;
|
||||
|
||||
private:
|
||||
void SetStreamTitle(const std::shared_ptr<TwitchToken> &) const;
|
||||
void SetStreamCategory(const std::shared_ptr<TwitchToken> &) const;
|
||||
void CreateStreamMarker(const std::shared_ptr<TwitchToken> &) const;
|
||||
void CreateStreamClip(const std::shared_ptr<TwitchToken> &) const;
|
||||
void StartCommercial(const std::shared_ptr<TwitchToken> &) const;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionTwitchEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionTwitchEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionTwitch> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionTwitchEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionTwitch>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int);
|
||||
void TwitchTokenChanged(const QString &);
|
||||
void StreamTitleChanged();
|
||||
void CategoreyChanged(const TwitchCategory &);
|
||||
void MarkerDescriptionChanged();
|
||||
void ClipHasDelayChanged(int state);
|
||||
void DurationChanged(const Duration &);
|
||||
void CheckTokenPermissions();
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<MacroActionTwitch> _entryData;
|
||||
|
||||
private:
|
||||
void SetupWidgetVisibility();
|
||||
|
||||
QComboBox *_actions;
|
||||
TwitchConnectionSelection *_tokens;
|
||||
VariableLineEdit *_streamTitle;
|
||||
TwitchCategorySelection *_category;
|
||||
TwitchCategorySearchButton *_manualCategorySearch;
|
||||
VariableLineEdit *_markerDescription;
|
||||
QCheckBox *_clipHasDelay;
|
||||
DurationSelection *_duration;
|
||||
QHBoxLayout *_layout;
|
||||
QLabel *_tokenPermissionWarning;
|
||||
QTimer _tokenPermissionCheckTimer;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
640
src/macro-external/twitch/token.cpp
Normal file
640
src/macro-external/twitch/token.cpp
Normal file
@@ -0,0 +1,640 @@
|
||||
#include "token.hpp"
|
||||
#include "twitch-helpers.hpp"
|
||||
|
||||
#include <switcher-data.hpp>
|
||||
#include <utility.hpp>
|
||||
#include <QScrollArea>
|
||||
#include <QDesktopServices>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::deque<std::shared_ptr<Item>> twitchTokens;
|
||||
|
||||
const std::unordered_map<std::string, std::string> TokenOption::apiIdToLocale{
|
||||
// Add necessary token permissions here
|
||||
/*
|
||||
{"analytics:read:extensions",
|
||||
"AdvSceneSwitcher.twitchToken.analytics.readExtensions"},
|
||||
{"analytics:read:games",
|
||||
"AdvSceneSwitcher.twitchToken.analytics.readGames"},
|
||||
{"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"},
|
||||
*/
|
||||
|
||||
{"channel:manage:broadcast",
|
||||
"AdvSceneSwitcher.twitchToken.channel.manageBroadcast"},
|
||||
{"clips:edit", "AdvSceneSwitcher.twitchToken.channel.createClip"},
|
||||
{"channel:edit:commercial",
|
||||
"AdvSceneSwitcher.twitchToken.channel.startCommercial"},
|
||||
};
|
||||
|
||||
static void saveConnections(obs_data_t *obj);
|
||||
static void loadConnections(obs_data_t *obj);
|
||||
|
||||
bool setupTwitchTokenSupport()
|
||||
{
|
||||
GetSwitcher()->AddSaveStep(saveConnections);
|
||||
GetSwitcher()->AddLoadStep(loadConnections);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TwitchToken::_setup = setupTwitchTokenSupport();
|
||||
|
||||
static void saveConnections(obs_data_t *obj)
|
||||
{
|
||||
OBSDataArrayAutoRelease connectionArray = obs_data_array_create();
|
||||
for (const auto &c : twitchTokens) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||
c->Save(arrayObj);
|
||||
obs_data_array_push_back(connectionArray, arrayObj);
|
||||
}
|
||||
obs_data_set_array(obj, "twitchConnections", connectionArray);
|
||||
}
|
||||
|
||||
static void loadConnections(obs_data_t *obj)
|
||||
{
|
||||
twitchTokens.clear();
|
||||
|
||||
OBSDataArrayAutoRelease connectionArray =
|
||||
obs_data_get_array(obj, "twitchConnections");
|
||||
size_t count = obs_data_array_count(connectionArray);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease arrayObj =
|
||||
obs_data_array_item(connectionArray, i);
|
||||
auto con = TwitchToken::Create();
|
||||
twitchTokens.emplace_back(con);
|
||||
twitchTokens.back()->Load(arrayObj);
|
||||
}
|
||||
}
|
||||
|
||||
void TokenOption::Load(obs_data_t *obj)
|
||||
{
|
||||
apiId = obs_data_get_string(obj, "apiID");
|
||||
}
|
||||
|
||||
void TokenOption::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_string(obj, "apiID", apiId.c_str());
|
||||
}
|
||||
|
||||
std::string TokenOption::GetLocale() const
|
||||
{
|
||||
return apiIdToLocale.at(apiId);
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::string> &
|
||||
TokenOption::GetTokenOptionMap()
|
||||
{
|
||||
return apiIdToLocale;
|
||||
}
|
||||
|
||||
bool TokenOption::operator<(const TokenOption &other) const
|
||||
{
|
||||
return apiId < other.apiId;
|
||||
}
|
||||
|
||||
void TwitchToken::Load(obs_data_t *obj)
|
||||
{
|
||||
Item::Load(obj);
|
||||
_token = obs_data_get_string(obj, "token");
|
||||
_userID = obs_data_get_string(obj, "userID");
|
||||
_tokenOptions.clear();
|
||||
OBSDataArrayAutoRelease options = obs_data_get_array(obj, "options");
|
||||
size_t count = obs_data_array_count(options);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_array_item(options, i);
|
||||
TokenOption tokenOption;
|
||||
tokenOption.Load(arrayObj);
|
||||
_tokenOptions.insert(tokenOption);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchToken::Save(obs_data_t *obj) const
|
||||
{
|
||||
Item::Save(obj);
|
||||
obs_data_set_string(obj, "token", _token.c_str());
|
||||
obs_data_set_string(obj, "userID", _userID.c_str());
|
||||
OBSDataArrayAutoRelease options = obs_data_array_create();
|
||||
for (auto &option : _tokenOptions) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||
option.Save(arrayObj);
|
||||
obs_data_array_push_back(options, arrayObj);
|
||||
}
|
||||
obs_data_set_array(obj, "options", options);
|
||||
}
|
||||
|
||||
bool TwitchToken::OptionIsEnabled(const TokenOption &option) const
|
||||
{
|
||||
for (const auto &activeOption : _tokenOptions) {
|
||||
if (activeOption.apiId == option.apiId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TwitchToken::SetToken(const std::string &value)
|
||||
{
|
||||
_token = value;
|
||||
auto res =
|
||||
SendGetRequest("https://api.twitch.tv", "/helix/users", *this);
|
||||
if (res.status != 200) {
|
||||
blog(LOG_WARNING, "failed to get Twitch user id from token!");
|
||||
_userID = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
|
||||
_userID = obs_data_get_string(arrayObj, "id");
|
||||
_name = obs_data_get_string(arrayObj, "display_name");
|
||||
}
|
||||
}
|
||||
|
||||
TwitchToken *GetTwitchTokenByName(const QString &name)
|
||||
{
|
||||
return GetTwitchTokenByName(name.toStdString());
|
||||
}
|
||||
|
||||
TwitchToken *GetTwitchTokenByName(const std::string &name)
|
||||
{
|
||||
for (auto &t : twitchTokens) {
|
||||
if (t->Name() == name) {
|
||||
return dynamic_cast<TwitchToken *>(t.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::weak_ptr<TwitchToken> GetWeakTwitchTokenByName(const std::string &name)
|
||||
{
|
||||
for (const auto &t : twitchTokens) {
|
||||
if (t->Name() == name) {
|
||||
std::weak_ptr<TwitchToken> wp =
|
||||
std::dynamic_pointer_cast<TwitchToken>(t);
|
||||
return wp;
|
||||
}
|
||||
}
|
||||
return std::weak_ptr<TwitchToken>();
|
||||
}
|
||||
|
||||
std::weak_ptr<TwitchToken> GetWeakTwitchTokenByQString(const QString &name)
|
||||
{
|
||||
return GetWeakTwitchTokenByName(name.toStdString());
|
||||
}
|
||||
|
||||
std::string GetWeakTwitchTokenName(std::weak_ptr<TwitchToken> token)
|
||||
{
|
||||
auto con = token.lock();
|
||||
if (!con) {
|
||||
return obs_module_text("AdvSceneSwitcher.twitchToken.invalid");
|
||||
}
|
||||
return con->Name();
|
||||
}
|
||||
|
||||
static bool ConnectionNameAvailable(const QString &name)
|
||||
{
|
||||
return !GetTwitchTokenByName(name);
|
||||
}
|
||||
|
||||
static bool ConnectionNameAvailable(const std::string &name)
|
||||
{
|
||||
return ConnectionNameAvailable(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
|
||||
{
|
||||
TwitchToken &ConnectionSettings = dynamic_cast<TwitchToken &>(settings);
|
||||
return TwitchTokenSettingsDialog::AskForSettings(parent,
|
||||
ConnectionSettings);
|
||||
}
|
||||
|
||||
TwitchConnectionSelection::TwitchConnectionSelection(QWidget *parent)
|
||||
: ItemSelection(twitchTokens, TwitchToken::Create,
|
||||
AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.twitchToken.select",
|
||||
"AdvSceneSwitcher.twitchToken.add",
|
||||
"AdvSceneSwitcher.twitchToken.nameNotAvailable",
|
||||
"AdvSceneSwitcher.twitchToken.configure", parent)
|
||||
{
|
||||
ShowRenameContextMenu(false);
|
||||
|
||||
// Connect to slots
|
||||
QWidget::connect(TwitchConnectionSignalManager::Instance(),
|
||||
SIGNAL(Add(const QString &)), this,
|
||||
SLOT(AddItem(const QString &)));
|
||||
QWidget::connect(TwitchConnectionSignalManager::Instance(),
|
||||
SIGNAL(Remove(const QString &)), this,
|
||||
SLOT(RemoveItem(const QString &)));
|
||||
|
||||
// Forward signals
|
||||
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)),
|
||||
TwitchConnectionSignalManager::Instance(),
|
||||
SIGNAL(Add(const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)),
|
||||
TwitchConnectionSignalManager::Instance(),
|
||||
SIGNAL(Remove(const QString &)));
|
||||
}
|
||||
|
||||
void TwitchConnectionSelection::SetToken(const std::string &token)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
if (!!GetTwitchTokenByName(token)) {
|
||||
_selection->setCurrentText(QString::fromStdString(token));
|
||||
} else {
|
||||
_selection->setCurrentIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchConnectionSelection::SetToken(
|
||||
const std::weak_ptr<TwitchToken> &token_)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
auto token = token_.lock();
|
||||
if (token) {
|
||||
SetToken(token->Name());
|
||||
} else {
|
||||
_selection->setCurrentIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static QCheckBox *addOption(const TokenOption &option, const TwitchToken &token,
|
||||
QGridLayout *layout, int &row)
|
||||
{
|
||||
auto label = new QLabel(obs_module_text(option.GetLocale().c_str()));
|
||||
label->setWordWrap(true);
|
||||
layout->addWidget(label, row, 1);
|
||||
auto checkBox = new QCheckBox();
|
||||
checkBox->setChecked(token.OptionIsEnabled(option));
|
||||
layout->addWidget(checkBox, row, 0);
|
||||
row++;
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
|
||||
QWidget *parent, const TwitchToken &settings)
|
||||
: ItemSettingsDialog(settings, twitchTokens,
|
||||
"AdvSceneSwitcher.twitchToken.select",
|
||||
"AdvSceneSwitcher.twitchToken.add",
|
||||
"AdvSceneSwitcher.twitchToken.nameNotAvailable",
|
||||
parent),
|
||||
_requestToken(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.twitchToken.request"))),
|
||||
_showToken(new QPushButton()),
|
||||
_currentTokenValue(new QLineEdit()),
|
||||
_tokenStatus(new QLabel())
|
||||
{
|
||||
_showToken->setMaximumWidth(22);
|
||||
_showToken->setFlat(true);
|
||||
_showToken->setStyleSheet(
|
||||
"QPushButton { background-color: transparent; border: 0px }");
|
||||
|
||||
_currentTokenValue->setReadOnly(true);
|
||||
_currentTokenValue->setText(QString::fromStdString(settings._token));
|
||||
|
||||
_name->setReadOnly(true);
|
||||
|
||||
QWidget::connect(_requestToken, SIGNAL(clicked()), this,
|
||||
SLOT(RequestToken()));
|
||||
QWidget::connect(_showToken, SIGNAL(pressed()), this,
|
||||
SLOT(ShowToken()));
|
||||
QWidget::connect(_showToken, SIGNAL(released()), this,
|
||||
SLOT(HideToken()));
|
||||
QWidget::connect(&_tokenGrabber, &TokenGrabberThread::GotToken, this,
|
||||
&TwitchTokenSettingsDialog::GotToken);
|
||||
|
||||
auto generalSettingsGrid = new QGridLayout();
|
||||
int row = 0;
|
||||
generalSettingsGrid->addWidget(
|
||||
new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.twitchToken.name")),
|
||||
row, 0);
|
||||
auto nameLayout = new QHBoxLayout;
|
||||
nameLayout->addWidget(_name);
|
||||
nameLayout->addWidget(_nameHint);
|
||||
generalSettingsGrid->addLayout(nameLayout, row, 1);
|
||||
++row;
|
||||
generalSettingsGrid->addWidget(
|
||||
new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.twitchToken.value")),
|
||||
row, 0);
|
||||
auto tokenValueLayout = new QHBoxLayout;
|
||||
tokenValueLayout->addWidget(_currentTokenValue);
|
||||
tokenValueLayout->addWidget(_showToken);
|
||||
generalSettingsGrid->addLayout(tokenValueLayout, row, 1);
|
||||
++row;
|
||||
generalSettingsGrid->addWidget(_requestToken, row, 0);
|
||||
generalSettingsGrid->addWidget(_tokenStatus, row, 1);
|
||||
|
||||
auto optionsGrid = new QGridLayout();
|
||||
row = 0;
|
||||
auto optionsBox = new QGroupBox(
|
||||
obs_module_text("AdvSceneSwitcher.twitchToken.permissions"));
|
||||
for (const auto &[id, _] : TokenOption::GetTokenOptionMap()) {
|
||||
auto checkBox = addOption({id}, settings, optionsGrid, row);
|
||||
QWidget::connect(checkBox, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(TokenOptionChanged(int)));
|
||||
_optionWidgets[id] = checkBox;
|
||||
}
|
||||
MinimizeSizeOfColumn(optionsGrid, 0);
|
||||
optionsBox->setLayout(optionsGrid);
|
||||
|
||||
auto scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
auto contentWidget = new QWidget(scrollArea);
|
||||
auto layout = new QVBoxLayout(contentWidget);
|
||||
layout->addLayout(generalSettingsGrid);
|
||||
layout->addWidget(optionsBox);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
scrollArea->setWidget(contentWidget);
|
||||
|
||||
auto dialogLayout = new QVBoxLayout();
|
||||
dialogLayout->addWidget(scrollArea);
|
||||
dialogLayout->addWidget(_buttonbox);
|
||||
setLayout(dialogLayout);
|
||||
|
||||
_currentTokenValue->setText(QString::fromStdString(settings._token));
|
||||
if (settings._token.empty()) {
|
||||
_tokenStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.notSet"));
|
||||
}
|
||||
HideToken();
|
||||
|
||||
if (_name->text().isEmpty()) {
|
||||
PulseWidget(_requestToken, Qt::green, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
|
||||
_currentToken = settings;
|
||||
}
|
||||
|
||||
void TwitchTokenSettingsDialog::ShowToken()
|
||||
{
|
||||
SetButtonIcon(_showToken, ":res/images/visible.svg");
|
||||
_currentTokenValue->setEchoMode(QLineEdit::Normal);
|
||||
}
|
||||
|
||||
void TwitchTokenSettingsDialog::HideToken()
|
||||
{
|
||||
SetButtonIcon(_showToken, ":res/images/invisible.svg");
|
||||
_currentTokenValue->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
}
|
||||
|
||||
void TwitchTokenSettingsDialog::TokenOptionChanged(int)
|
||||
{
|
||||
if (!_name->text().isEmpty()) {
|
||||
PulseWidget(_requestToken, Qt::green, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
_name->setText("");
|
||||
QMetaObject::invokeMethod(this, "NameChanged",
|
||||
Q_ARG(const QString &, ""));
|
||||
_currentTokenValue->setText("");
|
||||
}
|
||||
|
||||
static std::string generateStateString()
|
||||
{
|
||||
const char *chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
||||
const size_t stateStringLen = 32;
|
||||
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_int_distribution<size_t> dis(0, sizeof(chars) - 2);
|
||||
|
||||
std::string state;
|
||||
state.reserve(stateStringLen);
|
||||
|
||||
for (size_t i = 0; i < stateStringLen; ++i) {
|
||||
state += chars[dis(gen)];
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static std::string generateScopeString(const std::set<TokenOption> &options)
|
||||
{
|
||||
if (options.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string scope;
|
||||
for (const auto &option : options) {
|
||||
scope += option.apiId + "+";
|
||||
}
|
||||
scope.pop_back(); // Remove trailing +
|
||||
return scope;
|
||||
}
|
||||
|
||||
static std::string getHtml(const QString &redirect)
|
||||
{
|
||||
const char *html = R"(
|
||||
<html>
|
||||
<head>
|
||||
<title>Advanced scene switcher</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="output">Please click this link to continue if not automatically redirected</div>
|
||||
<p><a href="%1">Login with Twitch</a></a></p>
|
||||
<script type="text/javascript">
|
||||
if (document.location.hash && document.location.hash != '') {
|
||||
var parsedHash = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (parsedHash.get('access_token')) {
|
||||
window.location.replace(`http://localhost:8080/token?access_token=${parsedHash.get('access_token')}&state=${parsedHash.get('state')}`);
|
||||
output.textContent = 'It is safe to close this window';
|
||||
}
|
||||
} else {
|
||||
window.location.replace('%1');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>)";
|
||||
|
||||
return QString(html).arg(redirect).toStdString();
|
||||
}
|
||||
|
||||
void TwitchTokenSettingsDialog::RequestToken()
|
||||
{
|
||||
// Don't allow parallel RequestToken() calls
|
||||
_requestToken->setDisabled(true);
|
||||
|
||||
auto scope = QString::fromStdString(
|
||||
generateScopeString(GetEnabledOptions()));
|
||||
_tokenGrabber.SetTokenScope(scope);
|
||||
_tokenGrabber.start();
|
||||
_tokenStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.waiting"));
|
||||
}
|
||||
|
||||
void TwitchTokenSettingsDialog::GotToken(const std::optional<QString> &value)
|
||||
{
|
||||
_currentTokenValue->setText(value.value_or(""));
|
||||
if (value.has_value()) {
|
||||
_tokenStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.success"));
|
||||
_currentToken.SetToken(value.value().toStdString());
|
||||
auto name = QString::fromStdString(_currentToken._name);
|
||||
_name->setText(name);
|
||||
_name->textEdited(name);
|
||||
} else {
|
||||
_tokenStatus->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.fail"));
|
||||
_name->setText("");
|
||||
}
|
||||
_requestToken->setEnabled(true);
|
||||
}
|
||||
|
||||
std::set<TokenOption> TwitchTokenSettingsDialog::GetEnabledOptions()
|
||||
{
|
||||
std::set<TokenOption> result;
|
||||
for (const auto &[id, checkBox] : _optionWidgets) {
|
||||
if (checkBox->isChecked()) {
|
||||
TokenOption option;
|
||||
option.apiId = id;
|
||||
result.emplace(option);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TwitchTokenSettingsDialog::AskForSettings(QWidget *parent,
|
||||
TwitchToken &settings)
|
||||
{
|
||||
TwitchTokenSettingsDialog dialog(parent, settings);
|
||||
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
settings = dialog._currentToken;
|
||||
settings._tokenOptions = dialog.GetEnabledOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
int TokenGrabberThread::_timeout = 15;
|
||||
|
||||
TokenGrabberThread::~TokenGrabberThread()
|
||||
{
|
||||
_stopWaiting = true;
|
||||
_cv.notify_all();
|
||||
Stop();
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
static std::string getAuthErrorString(const char *errDetail)
|
||||
{
|
||||
QString err = obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.fail.browser");
|
||||
return err.arg(obs_module_text(errDetail)).toStdString();
|
||||
}
|
||||
|
||||
void TokenGrabberThread::run()
|
||||
{
|
||||
// Reset
|
||||
_server.stop();
|
||||
_server.~Server();
|
||||
new (&_server) httplib::Server();
|
||||
if (_serverThread.joinable()) {
|
||||
_serverThread.join();
|
||||
}
|
||||
_stopWaiting = {false};
|
||||
|
||||
// Generate URI to request token
|
||||
auto state = generateStateString();
|
||||
auto getTokenURI = "https://id.twitch.tv/oauth2/authorize"
|
||||
"?response_type=token"
|
||||
"&client_id=" +
|
||||
QString(GetClientID()) +
|
||||
"&redirect_uri=http://localhost:8080/auth"
|
||||
"&scope=" +
|
||||
_scope + "&state=" + QString::fromStdString(state);
|
||||
|
||||
// Setup server receiving token string
|
||||
auto html = getHtml(getTokenURI);
|
||||
_server.Get("/auth", [html, state](const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
// Check for errors
|
||||
if (req.has_param("error")) {
|
||||
auto recvState = req.get_param_value("state");
|
||||
if (recvState != state) {
|
||||
blog(LOG_WARNING,
|
||||
"state string does not match in error handling?! "
|
||||
"Got \"%s\" - expected \"%s\"\n"
|
||||
"ignoring error ...",
|
||||
recvState.c_str(), state.c_str());
|
||||
return;
|
||||
}
|
||||
auto errorStr =
|
||||
req.get_param_value("error_description");
|
||||
res.set_content(getAuthErrorString(errorStr.c_str()),
|
||||
"text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse fragments and redirect to /token with
|
||||
// corresponding parameters.
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
_server.Get("/token", [&](const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
// Check if valid request and grab the token string
|
||||
std::lock_guard<std::mutex> lk(_mutex);
|
||||
auto recvState = req.get_param_value("state");
|
||||
if (recvState != state) {
|
||||
blog(LOG_WARNING,
|
||||
"state string does not match! "
|
||||
"Got \"%s\" - expected \"%s\"",
|
||||
recvState.c_str(), state.c_str());
|
||||
res.set_content(
|
||||
getAuthErrorString(
|
||||
"AdvSceneSwitcher.twitchToken.request.fail.stateMismatch"),
|
||||
"text/plain");
|
||||
} else {
|
||||
_tokenString = QString::fromStdString(
|
||||
req.get_param_value("access_token"));
|
||||
res.set_content(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.twitchToken.request.success.browser"),
|
||||
"text/plain");
|
||||
}
|
||||
_stopWaiting = true;
|
||||
_cv.notify_all();
|
||||
});
|
||||
|
||||
// Request user to grant token
|
||||
QDesktopServices::openUrl(getTokenURI);
|
||||
|
||||
// Start the server and wait
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_serverThread =
|
||||
std::thread([this]() { _server.listen("localhost", 8080); });
|
||||
auto time = std::chrono::high_resolution_clock::now() +
|
||||
std::chrono::seconds(_timeout);
|
||||
while (!_stopWaiting) {
|
||||
if (_cv.wait_until(lock, time) == std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_server.stop();
|
||||
emit GotToken(_tokenString);
|
||||
}
|
||||
|
||||
void TokenGrabberThread::Stop()
|
||||
{
|
||||
if (_server.is_running()) {
|
||||
_server.stop();
|
||||
}
|
||||
if (_serverThread.joinable()) {
|
||||
_serverThread.join();
|
||||
}
|
||||
wait();
|
||||
}
|
||||
|
||||
TwitchConnectionSignalManager *TwitchConnectionSignalManager::Instance()
|
||||
{
|
||||
static TwitchConnectionSignalManager manager;
|
||||
return &manager;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
141
src/macro-external/twitch/token.hpp
Normal file
141
src/macro-external/twitch/token.hpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
#include <item-selection-helpers.hpp>
|
||||
#include <httplib.h>
|
||||
#include <set>
|
||||
#include <QCheckBox>
|
||||
#include <QThread>
|
||||
#include <optional>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class TwitchConnectionSelection;
|
||||
class TwitchTokenSettingsDialog;
|
||||
|
||||
class TokenOption {
|
||||
public:
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
std::string GetLocale() const;
|
||||
|
||||
static const std::unordered_map<std::string, std::string> &
|
||||
GetTokenOptionMap();
|
||||
bool operator<(const TokenOption &other) const;
|
||||
std::string apiId = "";
|
||||
|
||||
private:
|
||||
const static std::unordered_map<std::string, std::string> apiIdToLocale;
|
||||
};
|
||||
|
||||
class TwitchToken : public Item {
|
||||
public:
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
return std::make_shared<TwitchToken>();
|
||||
}
|
||||
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
std::string GetName() { return _name; }
|
||||
bool OptionIsEnabled(const TokenOption &) const;
|
||||
void SetToken(const std::string &);
|
||||
bool IsEmpty() const { return _token.empty(); }
|
||||
std::string GetToken() const { return _token; }
|
||||
std::string GetUserID() const { return _userID; }
|
||||
|
||||
private:
|
||||
std::string _token;
|
||||
std::string _userID;
|
||||
std::set<TokenOption> _tokenOptions = {{"channel:manage:broadcast"}};
|
||||
|
||||
static bool _setup;
|
||||
|
||||
friend TwitchConnectionSelection;
|
||||
friend TwitchTokenSettingsDialog;
|
||||
};
|
||||
|
||||
class TokenGrabberThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~TokenGrabberThread();
|
||||
void SetTokenScope(const QString &value) { _scope = value; }
|
||||
|
||||
signals:
|
||||
void GotToken(const std::optional<QString> &);
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
void Stop();
|
||||
|
||||
QString _scope;
|
||||
std::optional<QString> _tokenString;
|
||||
|
||||
static int _timeout;
|
||||
std::mutex _mutex;
|
||||
std::atomic_bool _stopWaiting = {false};
|
||||
std::condition_variable _cv;
|
||||
std::thread _serverThread;
|
||||
httplib::Server _server;
|
||||
};
|
||||
|
||||
class TwitchTokenSettingsDialog : public ItemSettingsDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchTokenSettingsDialog(QWidget *parent, const TwitchToken &);
|
||||
static bool AskForSettings(QWidget *parent, TwitchToken &settings);
|
||||
|
||||
private slots:
|
||||
void ShowToken();
|
||||
void HideToken();
|
||||
void TokenOptionChanged(int);
|
||||
void RequestToken();
|
||||
void GotToken(const std::optional<QString> &);
|
||||
|
||||
private:
|
||||
std::set<TokenOption> GetEnabledOptions();
|
||||
|
||||
QPushButton *_requestToken;
|
||||
QPushButton *_showToken;
|
||||
QLineEdit *_currentTokenValue;
|
||||
QLabel *_tokenStatus;
|
||||
TokenGrabberThread _tokenGrabber;
|
||||
TwitchToken _currentToken;
|
||||
std::unordered_map<std::string, QCheckBox *> _optionWidgets;
|
||||
};
|
||||
|
||||
class TwitchConnectionSelection : public ItemSelection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TwitchConnectionSelection(QWidget *parent = 0);
|
||||
void SetToken(const std::string &);
|
||||
void SetToken(const std::weak_ptr<TwitchToken> &);
|
||||
};
|
||||
|
||||
// Helper class so that it is not required to add signals to the
|
||||
// AdvSceneSwitcher class for handling adding and removing Twitch connections
|
||||
class TwitchConnectionSignalManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static TwitchConnectionSignalManager *Instance();
|
||||
|
||||
private:
|
||||
signals:
|
||||
// Rename signal not required as name is based on Twitch account name
|
||||
// and item name cannot be manually changed
|
||||
void Add(const QString &);
|
||||
void Remove(const QString &);
|
||||
};
|
||||
|
||||
TwitchToken *GetTwitchTokenByName(const QString &);
|
||||
TwitchToken *GetTwitchTokenByName(const std::string &);
|
||||
std::weak_ptr<TwitchToken> GetWeakTwitchTokenByName(const std::string &name);
|
||||
std::weak_ptr<TwitchToken> GetWeakTwitchTokenByQString(const QString &name);
|
||||
std::string GetWeakTwitchTokenName(std::weak_ptr<TwitchToken>);
|
||||
|
||||
} // namespace advss
|
||||
|
||||
Q_DECLARE_METATYPE(advss::TwitchToken *);
|
||||
95
src/macro-external/twitch/twitch-helpers.cpp
Normal file
95
src/macro-external/twitch/twitch-helpers.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "twitch-helpers.hpp"
|
||||
#include "token.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
|
||||
|
||||
static httplib::Headers getTokenRequestHeaders(const TwitchToken &token)
|
||||
{
|
||||
return {
|
||||
{"Authorization", "Bearer " + token.GetToken()},
|
||||
{"Client-Id", clientID.data()},
|
||||
};
|
||||
}
|
||||
|
||||
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token,
|
||||
const httplib::Params ¶ms)
|
||||
{
|
||||
httplib::Client cli(uri);
|
||||
auto headers = getTokenRequestHeaders(token);
|
||||
auto response = cli.Get(path, params, headers);
|
||||
if (!response) {
|
||||
auto err = response.error();
|
||||
blog(LOG_INFO, "%s failed - %s", __func__,
|
||||
httplib::to_string(err).c_str());
|
||||
return {};
|
||||
}
|
||||
RequestResult result;
|
||||
result.status = response->status;
|
||||
if (response->body.empty()) {
|
||||
return result;
|
||||
}
|
||||
OBSDataAutoRelease replyData =
|
||||
obs_data_create_from_json(response->body.c_str());
|
||||
result.data = replyData;
|
||||
return result;
|
||||
}
|
||||
|
||||
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token, const OBSData &data)
|
||||
{
|
||||
httplib::Client cli(uri);
|
||||
auto headers = getTokenRequestHeaders(token);
|
||||
auto json = obs_data_get_json(data);
|
||||
std::string body = json ? json : "";
|
||||
auto response = cli.Post(path, headers, body, "application/json");
|
||||
if (!response) {
|
||||
auto err = response.error();
|
||||
blog(LOG_INFO, "%s failed - %s", __func__,
|
||||
httplib::to_string(err).c_str());
|
||||
return {};
|
||||
}
|
||||
RequestResult result;
|
||||
result.status = response->status;
|
||||
if (response->body.empty()) {
|
||||
return result;
|
||||
}
|
||||
OBSDataAutoRelease replyData =
|
||||
obs_data_create_from_json(response->body.c_str());
|
||||
result.data = replyData;
|
||||
return result;
|
||||
}
|
||||
|
||||
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token, const OBSData &data)
|
||||
{
|
||||
httplib::Client cli(uri);
|
||||
auto headers = getTokenRequestHeaders(token);
|
||||
auto json = obs_data_get_json(data);
|
||||
std::string body = json ? json : "";
|
||||
auto response = cli.Patch(path, headers, body, "application/json");
|
||||
if (!response) {
|
||||
auto err = response.error();
|
||||
blog(LOG_INFO, "%s failed - %s", __func__,
|
||||
httplib::to_string(err).c_str());
|
||||
return {};
|
||||
}
|
||||
RequestResult result;
|
||||
result.status = response->status;
|
||||
if (response->body.empty()) {
|
||||
return result;
|
||||
}
|
||||
OBSDataAutoRelease replyData =
|
||||
obs_data_create_from_json(response->body.c_str());
|
||||
result.data = replyData;
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *GetClientID()
|
||||
{
|
||||
return clientID.data();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
24
src/macro-external/twitch/twitch-helpers.hpp
Normal file
24
src/macro-external/twitch/twitch-helpers.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <httplib.h>
|
||||
#include <obs.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class TwitchToken;
|
||||
|
||||
struct RequestResult {
|
||||
int status = 0;
|
||||
OBSData data = nullptr;
|
||||
};
|
||||
|
||||
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token,
|
||||
const httplib::Params & = {});
|
||||
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token, const OBSData &data);
|
||||
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
|
||||
const TwitchToken &token, const OBSData &data);
|
||||
const char *GetClientID();
|
||||
|
||||
} // namespace advss
|
||||
@@ -104,7 +104,9 @@ static bool requiresFileInput(VideoCondition t)
|
||||
bool MacroConditionVideo::CheckShouldBeSkipped()
|
||||
{
|
||||
if (_condition != VideoCondition::PATTERN &&
|
||||
_condition != VideoCondition::OBJECT) {
|
||||
_condition != VideoCondition::OBJECT &&
|
||||
_condition != VideoCondition::HAS_CHANGED &&
|
||||
_condition != VideoCondition::HAS_NOT_CHANGED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -315,19 +317,17 @@ bool MacroConditionVideo::CheckOCR()
|
||||
|
||||
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
|
||||
_ocrParameters.color, _ocrParameters.colorThreshold);
|
||||
|
||||
if (_ocrParameters.regex.Enabled()) {
|
||||
auto expr = _ocrParameters.regex.GetRegularExpression(
|
||||
_ocrParameters.text);
|
||||
if (!expr.isValid()) {
|
||||
return false;
|
||||
}
|
||||
auto match = expr.match(QString::fromStdString(text));
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
SetVariableValue(text);
|
||||
return text == std::string(_ocrParameters.text);
|
||||
if (!_ocrParameters.regex.Enabled()) {
|
||||
return text == std::string(_ocrParameters.text);
|
||||
}
|
||||
auto expr =
|
||||
_ocrParameters.regex.GetRegularExpression(_ocrParameters.text);
|
||||
if (!expr.isValid()) {
|
||||
return false;
|
||||
}
|
||||
auto match = expr.match(QString::fromStdString(text));
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::CheckColor()
|
||||
@@ -1454,7 +1454,9 @@ static bool needsShowMatch(VideoCondition cond)
|
||||
static bool needsThrottleControls(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::PATTERN ||
|
||||
cond == VideoCondition::OBJECT;
|
||||
cond == VideoCondition::OBJECT ||
|
||||
cond == VideoCondition::HAS_CHANGED ||
|
||||
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||
}
|
||||
|
||||
static bool needsThreshold(VideoCondition cond)
|
||||
|
||||
@@ -168,11 +168,22 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
|
||||
obs_data_set_string(obj, "version", currentVersion.c_str());
|
||||
}
|
||||
|
||||
void SwitcherData::AddResetForNextIntervalFunction(
|
||||
std::function<void()> function)
|
||||
void SwitcherData::AddIntervalResetStep(std::function<void()> function)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
resetForNextIntervalFuncs.emplace_back(function);
|
||||
resetIntervalSteps.emplace_back(function);
|
||||
}
|
||||
|
||||
void SwitcherData::AddSaveStep(std::function<void(obs_data_t *)> function)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
saveSteps.emplace_back(function);
|
||||
}
|
||||
|
||||
void SwitcherData::AddLoadStep(std::function<void(obs_data_t *)> function)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
loadSteps.emplace_back(function);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -64,7 +64,9 @@ public:
|
||||
|
||||
void SetPreconditions();
|
||||
void ResetForNextInterval();
|
||||
void AddResetForNextIntervalFunction(std::function<void()>);
|
||||
void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||
void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||
void AddIntervalResetStep(std::function<void()>);
|
||||
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
|
||||
int &linger, bool &setPreviousSceneAsMatch,
|
||||
bool ¯oMatch);
|
||||
@@ -112,7 +114,9 @@ public:
|
||||
std::atomic_bool abortMacroWait = {false};
|
||||
std::condition_variable macroTransitionCv;
|
||||
|
||||
std::vector<std::function<void()>> resetForNextIntervalFuncs;
|
||||
std::vector<std::function<void(obs_data_t *)>> saveSteps;
|
||||
std::vector<std::function<void(obs_data_t *)>> loadSteps;
|
||||
std::vector<std::function<void()>> resetIntervalSteps;
|
||||
|
||||
bool firstBoot = true;
|
||||
bool transitionActive = false;
|
||||
@@ -204,12 +208,12 @@ public:
|
||||
QStringList loadFailureLibs;
|
||||
bool warnPluginLoadFailure = true;
|
||||
bool disableHints = false;
|
||||
bool disableFilterComboboxFilter = false;
|
||||
bool hideLegacyTabs = true;
|
||||
std::vector<int> tabOrder = std::vector<int>(tab_count);
|
||||
bool saveWindowGeo = false;
|
||||
QPoint windowPos = {};
|
||||
QSize windowSize = {};
|
||||
QList<int> macroActionConditionSplitterPosition;
|
||||
QList<int> macroListMacroEditSplitterPosition;
|
||||
|
||||
/* --- End of UI section --- */
|
||||
|
||||
@@ -221,7 +221,7 @@ std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
|
||||
{
|
||||
auto con = connection.lock();
|
||||
if (!con) {
|
||||
return "invalid connection selection";
|
||||
return obs_module_text("AdvSceneSwitcher.connection.invalid");
|
||||
}
|
||||
return con->Name();
|
||||
}
|
||||
@@ -248,6 +248,7 @@ ConnectionSelection::ConnectionSelection(QWidget *parent)
|
||||
AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||
"AdvSceneSwitcher.connection.configure", parent)
|
||||
{
|
||||
// Connect to slots
|
||||
@@ -297,7 +298,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
const Connection &settings)
|
||||
: ItemSettingsDialog(settings, switcher->connections,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add", parent),
|
||||
"AdvSceneSwitcher.connection.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
||||
_useCustomURI(new QCheckBox()),
|
||||
_customUri(new QLineEdit()),
|
||||
_address(new QLineEdit()),
|
||||
|
||||
8
src/utils/export-symbol-helper.hpp
Normal file
8
src/utils/export-symbol-helper.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
// Helpers helper to enable exporting and importing custom Qt widget symbols
|
||||
#ifdef ADVSS_EXPORT_SYMBOLS
|
||||
#define ADVSS_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define ADVSS_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
@@ -7,15 +7,41 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool FilterComboBox::_filteringEnabled = false;
|
||||
|
||||
FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler)
|
||||
: QComboBox(parent)
|
||||
{
|
||||
// If the filtering behaviour of the FilterComboBox is disabled it is
|
||||
// just a regular QComboBox with the option to set a placeholder so exit
|
||||
// the constructor early.
|
||||
|
||||
if (!_filteringEnabled) {
|
||||
if (!placehodler.isEmpty()) {
|
||||
setPlaceholderText(placehodler);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow edit for completer but don't add new entries on pressing enter
|
||||
setEditable(true);
|
||||
setInsertPolicy(InsertPolicy::NoInsert);
|
||||
|
||||
if (!placehodler.isEmpty()) {
|
||||
lineEdit()->setPlaceholderText(placehodler);
|
||||
|
||||
// Make sure that the placeholder text is visible
|
||||
QFontMetrics fontMetrics(font());
|
||||
int textWidth = fontMetrics.boundingRect(placehodler).width();
|
||||
|
||||
QStyleOptionComboBox comboBoxOption;
|
||||
comboBoxOption.initFrom(this);
|
||||
int buttonWidth =
|
||||
style()->subControlRect(QStyle::CC_ComboBox,
|
||||
&comboBoxOption,
|
||||
QStyle::SC_ComboBoxArrow, this)
|
||||
.width();
|
||||
setMinimumWidth(buttonWidth + textWidth);
|
||||
}
|
||||
|
||||
setMaxVisibleItems(30);
|
||||
@@ -31,6 +57,11 @@ FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler)
|
||||
&FilterComboBox::TextChagned);
|
||||
}
|
||||
|
||||
void FilterComboBox::SetFilterBehaviourEnabled(bool value)
|
||||
{
|
||||
FilterComboBox::_filteringEnabled = value;
|
||||
}
|
||||
|
||||
void FilterComboBox::focusOutEvent(QFocusEvent *event)
|
||||
{
|
||||
// Reset on invalid selection
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
#pragma once
|
||||
#include "export-symbol-helper.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
// Helper class which enables user to filter possible selections by typing
|
||||
class FilterComboBox : public QComboBox {
|
||||
class ADVSS_EXPORT FilterComboBox : public QComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FilterComboBox(QWidget *parent = nullptr,
|
||||
const QString &placehodler = "");
|
||||
static void SetFilterBehaviourEnabled(bool);
|
||||
|
||||
protected:
|
||||
void focusOutEvent(QFocusEvent *event) override;
|
||||
@@ -20,6 +23,7 @@ private slots:
|
||||
|
||||
private:
|
||||
int _lastCompleterHighlightRow = -1;
|
||||
static bool _filteringEnabled;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -46,6 +46,7 @@ static bool ItemNameAvailable(const std::string &name,
|
||||
ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
||||
CreateItemFunc create, SettingsCallback callback,
|
||||
std::string_view select, std::string_view add,
|
||||
std::string_view conflict,
|
||||
std::string_view configureTooltip, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_selection(new FilterComboBox(this, obs_module_text(select.data()))),
|
||||
@@ -54,7 +55,8 @@ ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
||||
_askForSettings(callback),
|
||||
_items(items),
|
||||
_selectStr(select),
|
||||
_addStr(add)
|
||||
_addStr(add),
|
||||
_conflictStr(conflict)
|
||||
{
|
||||
_modify->setMaximumWidth(22);
|
||||
SetButtonIcon(_modify, ":/settings/images/settings/general.svg");
|
||||
@@ -94,6 +96,11 @@ void ItemSelection::SetItem(const std::string &item)
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSelection::ShowRenameContextMenu(bool value)
|
||||
{
|
||||
_showRenameContextMenu = value;
|
||||
}
|
||||
|
||||
void ItemSelection::ChangeSelection(const QString &sel)
|
||||
{
|
||||
if (sel == obs_module_text(_addStr.data())) {
|
||||
@@ -139,12 +146,14 @@ void ItemSelection::ModifyButtonClicked()
|
||||
};
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
QAction *action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
|
||||
action->setProperty("connetion", QVariant::fromValue(item));
|
||||
menu.addAction(action);
|
||||
QAction *action;
|
||||
if (_showRenameContextMenu) {
|
||||
action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
|
||||
action->setProperty("item", QVariant::fromValue(item));
|
||||
menu.addAction(action);
|
||||
}
|
||||
|
||||
action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"),
|
||||
&menu);
|
||||
@@ -162,7 +171,7 @@ void ItemSelection::ModifyButtonClicked()
|
||||
void ItemSelection::RenameItem()
|
||||
{
|
||||
QAction *action = reinterpret_cast<QAction *>(sender());
|
||||
QVariant variant = action->property("connetion");
|
||||
QVariant variant = action->property("item");
|
||||
Item *item = variant.value<Item *>();
|
||||
|
||||
std::string name;
|
||||
@@ -174,12 +183,13 @@ void ItemSelection::RenameItem()
|
||||
return;
|
||||
}
|
||||
if (name.empty()) {
|
||||
DisplayMessage("AdvSceneSwitcher.item.emptyName");
|
||||
DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.item.emptyName"));
|
||||
return;
|
||||
}
|
||||
if (_selection->currentText().toStdString() != name &&
|
||||
!ItemNameAvailable(name, _items)) {
|
||||
DisplayMessage("AdvSceneSwitcher.item.nameNotAvailable");
|
||||
DisplayMessage(obs_module_text(_conflictStr.data()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -245,7 +255,9 @@ Item *ItemSelection::GetCurrentItem()
|
||||
ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
||||
std::deque<std::shared_ptr<Item>> &items,
|
||||
std::string_view select,
|
||||
std::string_view add, QWidget *parent)
|
||||
std::string_view add,
|
||||
std::string_view nameConflict,
|
||||
QWidget *parent)
|
||||
: QDialog(parent),
|
||||
_name(new QLineEdit()),
|
||||
_nameHint(new QLabel),
|
||||
@@ -253,18 +265,20 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
||||
QDialogButtonBox::Cancel)),
|
||||
_items(items),
|
||||
_selectStr(select),
|
||||
_addStr(add)
|
||||
_addStr(add),
|
||||
_conflictStr(nameConflict)
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setFixedWidth(555);
|
||||
setMinimumWidth(555);
|
||||
setMinimumHeight(100);
|
||||
|
||||
_buttonbox->setCenterButtons(true);
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
||||
|
||||
_name->setText(QString::fromStdString(settings._name));
|
||||
_originalName = QString::fromStdString(settings._name);
|
||||
_name->setText(_originalName);
|
||||
|
||||
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
|
||||
SLOT(NameChanged(const QString &)));
|
||||
@@ -279,9 +293,8 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
||||
void ItemSettingsDialog::NameChanged(const QString &text)
|
||||
{
|
||||
|
||||
if (text != _name->text() && !ItemNameAvailable(text, _items)) {
|
||||
SetNameWarning(obs_module_text(
|
||||
"AdvSceneSwitcher.item.nameNotAvailable"));
|
||||
if (text != _originalName && !ItemNameAvailable(text, _items)) {
|
||||
SetNameWarning(obs_module_text(_conflictStr.data()));
|
||||
return;
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
#pragma once
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "export-symbol-helper.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
#include <deque>
|
||||
#include <obs.hpp>
|
||||
#include <websocket-helpers.hpp>
|
||||
#include <obs-data.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ItemSelection;
|
||||
class ItemSettingsDialog;
|
||||
class ADVSS_EXPORT ItemSelection;
|
||||
class ADVSS_EXPORT ItemSettingsDialog;
|
||||
|
||||
class Item {
|
||||
public:
|
||||
@@ -41,10 +36,13 @@ class ItemSettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemSettingsDialog(const Item &, std::deque<std::shared_ptr<Item>> &,
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
QWidget *parent = 0);
|
||||
ItemSettingsDialog(
|
||||
const Item &, std::deque<std::shared_ptr<Item>> &,
|
||||
std::string_view selectString = "AdvSceneSwitcher.item.select",
|
||||
std::string_view addString = "AdvSceneSwitcher.item.add",
|
||||
std::string_view conflictString =
|
||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||
QWidget *parent = 0);
|
||||
virtual ~ItemSettingsDialog() = default;
|
||||
|
||||
private slots:
|
||||
@@ -59,6 +57,8 @@ protected:
|
||||
std::deque<std::shared_ptr<Item>> &_items;
|
||||
std::string_view _selectStr;
|
||||
std::string_view _addStr;
|
||||
std::string_view _conflictStr;
|
||||
QString _originalName;
|
||||
};
|
||||
|
||||
typedef bool (*SettingsCallback)(QWidget *, Item &);
|
||||
@@ -73,9 +73,12 @@ public:
|
||||
SettingsCallback,
|
||||
std::string_view selectString = "AdvSceneSwitcher.item.select",
|
||||
std::string_view addString = "AdvSceneSwitcher.item.add",
|
||||
std::string_view conflictString =
|
||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||
std::string_view configureTooltip = "", QWidget *parent = 0);
|
||||
virtual ~ItemSelection() = default;
|
||||
void SetItem(const std::string &);
|
||||
void ShowRenameContextMenu(bool value);
|
||||
|
||||
private slots:
|
||||
void ModifyButtonClicked();
|
||||
@@ -101,6 +104,8 @@ protected:
|
||||
std::deque<std::shared_ptr<Item>> &_items;
|
||||
std::string_view _selectStr;
|
||||
std::string_view _addStr;
|
||||
std::string_view _conflictStr;
|
||||
bool _showRenameContextMenu = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
82
src/utils/macro-run-button.cpp
Normal file
82
src/utils/macro-run-button.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "macro-run-button.hpp"
|
||||
#include "macro-tree.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
namespace advss {
|
||||
|
||||
MacroRunButton::MacroRunButton(QWidget *parent) : QPushButton(parent)
|
||||
{
|
||||
if (window()) {
|
||||
window()->installEventFilter(this);
|
||||
}
|
||||
QWidget::connect(this, SIGNAL(pressed()), this, SLOT(Pressed()));
|
||||
}
|
||||
|
||||
void MacroRunButton::SetMacroTree(MacroTree *macros)
|
||||
{
|
||||
_macros = macros;
|
||||
QWidget::connect(macros, SIGNAL(MacroSelectionChanged()), this,
|
||||
SLOT(MacroSelectionChanged()));
|
||||
QWidget::connect(&_timer, &QTimer::timeout, this,
|
||||
[this]() { MacroSelectionChanged(); });
|
||||
_timer.start(1000);
|
||||
}
|
||||
|
||||
void MacroRunButton::MacroSelectionChanged()
|
||||
{
|
||||
auto macro = _macros->GetCurrentMacro();
|
||||
if (!macro) {
|
||||
_macroHasElseActions = false;
|
||||
return;
|
||||
}
|
||||
_macroHasElseActions = macro->ElseActions().size() > 0;
|
||||
}
|
||||
|
||||
bool MacroRunButton::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (!_macroHasElseActions) {
|
||||
setText(obs_module_text("AdvSceneSwitcher.macroTab.run"));
|
||||
_runElseActionsKeyHeld = false;
|
||||
return QPushButton::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Control) {
|
||||
setText(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.runElse"));
|
||||
_runElseActionsKeyHeld = true;
|
||||
}
|
||||
} else if (event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Control) {
|
||||
setText(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.run"));
|
||||
_runElseActionsKeyHeld = false;
|
||||
}
|
||||
}
|
||||
return QPushButton::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void MacroRunButton::Pressed()
|
||||
{
|
||||
auto macro = _macros->GetCurrentMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = _runElseActionsKeyHeld
|
||||
? macro->PerformActions(false, true, true)
|
||||
: macro->PerformActions(true, true, true);
|
||||
if (!ret) {
|
||||
QString err =
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||
DisplayMessage(err.arg(QString::fromStdString(macro->Name())));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
29
src/utils/macro-run-button.hpp
Normal file
29
src/utils/macro-run-button.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroTree;
|
||||
|
||||
class MacroRunButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MacroRunButton(QWidget *parent = nullptr);
|
||||
void SetMacroTree(MacroTree *);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void MacroSelectionChanged();
|
||||
void Pressed();
|
||||
|
||||
private:
|
||||
MacroTree *_macros = nullptr;
|
||||
bool _macroHasElseActions = false;
|
||||
bool _runElseActionsKeyHeld = false;
|
||||
QTimer _timer;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
@@ -75,7 +75,7 @@ std::optional<std::string> NonModalMessageDialog::GetInput()
|
||||
show();
|
||||
|
||||
// Trigger resize
|
||||
_inputEdit->setPlainText("");
|
||||
_inputEdit->setPlainText(_inputEdit->toPlainText());
|
||||
|
||||
exec();
|
||||
this->deleteLater();
|
||||
@@ -85,6 +85,12 @@ std::optional<std::string> NonModalMessageDialog::GetInput()
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonModalMessageDialog::SetInput(const QString &input)
|
||||
{
|
||||
assert(_type == Type::INPUT);
|
||||
_inputEdit->setPlainText(input);
|
||||
}
|
||||
|
||||
void NonModalMessageDialog::YesClicked()
|
||||
{
|
||||
_answer = QMessageBox::Yes;
|
||||
|
||||
@@ -19,7 +19,8 @@ public:
|
||||
NonModalMessageDialog(const QString &message, bool question);
|
||||
QMessageBox::StandardButton ShowMessage();
|
||||
std::optional<std::string> GetInput();
|
||||
Type GetType() { return _type; }
|
||||
Type GetType() const { return _type; }
|
||||
void SetInput(const QString &);
|
||||
|
||||
private slots:
|
||||
void YesClicked();
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
#include <string.h>
|
||||
#include <QGroupBox>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::unordered_map<size_t, OSCMessageElement::TypeInfo>
|
||||
@@ -360,7 +366,7 @@ OSCMessageElementEdit::OSCMessageElementEdit(QWidget *parent)
|
||||
_text->hide();
|
||||
_binaryText->hide();
|
||||
|
||||
for (int i = 0; i < OSCMessageElement::_typeNames.size() - 1; i++) {
|
||||
for (size_t i = 0; i < OSCMessageElement::_typeNames.size() - 1; i++) {
|
||||
_type->addItem(obs_module_text(
|
||||
OSCMessageElement::_typeNames.at(i).localizedName));
|
||||
}
|
||||
|
||||
@@ -162,29 +162,6 @@ static bool getSceneItemAtIdx(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getTotalSceneItemCountHelper(obs_scene_t *, obs_sceneitem_t *item,
|
||||
void *ptr)
|
||||
{
|
||||
auto count = reinterpret_cast<int *>(ptr);
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, getTotalSceneItemCountHelper, ptr);
|
||||
}
|
||||
*count = *count + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int getTotalSceneItemCountOnScene(const OBSWeakSource &sceneWeakSource)
|
||||
{
|
||||
auto s = obs_weak_source_get_source(sceneWeakSource);
|
||||
auto scene = obs_scene_from_source(s);
|
||||
int count = 0;
|
||||
obs_scene_enum_items(scene, getTotalSceneItemCountHelper, &count);
|
||||
obs_source_release(s);
|
||||
return count;
|
||||
}
|
||||
|
||||
struct GroupData {
|
||||
std::string type;
|
||||
std::vector<OBSSceneItem> items = {};
|
||||
@@ -367,7 +344,6 @@ std::vector<OBSSceneItem> SceneItemSelection::GetSceneItemsByName(
|
||||
} else {
|
||||
name = GetWeakSourceName(_source);
|
||||
}
|
||||
int count = getCountOfSceneItemOccurance(sceneSelection, name, false);
|
||||
auto items = getSceneItemsWithName(scene, name);
|
||||
ReduceBadedOnIndexSelection(items);
|
||||
return items;
|
||||
@@ -409,7 +385,7 @@ std::vector<OBSSceneItem> SceneItemSelection::GetSceneItemsByIdx(
|
||||
}
|
||||
|
||||
auto sceneWeakSource = sceneSelection.GetScene(false);
|
||||
int count = getTotalSceneItemCountOnScene(sceneWeakSource);
|
||||
int count = GetSceneItemCount(sceneWeakSource);
|
||||
if (count == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -751,15 +727,17 @@ void SceneItemSelectionWidget::SetNameConflictVisibility()
|
||||
|
||||
case SceneItemSelection::Type::SOURCE_NAME_PATTERN:
|
||||
case SceneItemSelection::Type::SOURCE_GROUP:
|
||||
sceneItemCount =
|
||||
getTotalSceneItemCountOnScene(_scene.GetScene(false));
|
||||
sceneItemCount = GetSceneItemCount(_scene.GetScene(false));
|
||||
break;
|
||||
case SceneItemSelection::Type::INDEX:
|
||||
case SceneItemSelection::Type::INDEX_RANGE:
|
||||
case SceneItemSelection::Type::ALL:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_currentSelection._type ==
|
||||
SceneItemSelection::Type::SOURCE_NAME_PATTERN) {
|
||||
int sceneItemCount =
|
||||
getTotalSceneItemCountOnScene(_scene.GetScene(false));
|
||||
int sceneItemCount = GetSceneItemCount(_scene.GetScene(false));
|
||||
if (sceneItemCount == 0) {
|
||||
_nameConflictIndex->hide();
|
||||
return;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QLayout>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
||||
@@ -502,6 +502,29 @@ bool SaveTransformState(obs_data_t *obj, const struct obs_transform_info &info,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getTotalSceneItemCountHelper(obs_scene_t *, obs_sceneitem_t *item,
|
||||
void *ptr)
|
||||
{
|
||||
auto count = reinterpret_cast<int *>(ptr);
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, getTotalSceneItemCountHelper, ptr);
|
||||
}
|
||||
*count = *count + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetSceneItemCount(const OBSWeakSource &sceneWeakSource)
|
||||
{
|
||||
auto s = obs_weak_source_get_source(sceneWeakSource);
|
||||
auto scene = obs_scene_from_source(s);
|
||||
int count = 0;
|
||||
obs_scene_enum_items(scene, getTotalSceneItemCountHelper, &count);
|
||||
obs_source_release(s);
|
||||
return count;
|
||||
}
|
||||
|
||||
bool DisplayMessage(const QString &msg, bool question, bool modal)
|
||||
{
|
||||
if (!modal) {
|
||||
@@ -605,11 +628,11 @@ bool IsValidMacroSegmentIndex(Macro *m, const int idx, bool isCondition)
|
||||
return false;
|
||||
}
|
||||
if (isCondition) {
|
||||
if (idx >= m->Conditions().size()) {
|
||||
if (idx >= (int)m->Conditions().size()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (idx >= m->Actions().size()) {
|
||||
if (idx >= (int)m->Actions().size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -651,6 +674,33 @@ QString GetMacroSegmentDescription(Macro *macro, int idx, bool isCondition)
|
||||
return result;
|
||||
}
|
||||
|
||||
void SaveSplitterPos(const QList<int> &sizes, obs_data_t *obj,
|
||||
const std::string name)
|
||||
{
|
||||
auto array = obs_data_array_create();
|
||||
for (int i = 0; i < sizes.count(); ++i) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
obs_data_set_int(array_obj, "pos", sizes[i]);
|
||||
obs_data_array_push_back(array, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, name.c_str(), array);
|
||||
obs_data_array_release(array);
|
||||
}
|
||||
|
||||
void LoadSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
|
||||
{
|
||||
sizes.clear();
|
||||
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *item = obs_data_array_item(array, i);
|
||||
sizes << obs_data_get_int(item, "pos");
|
||||
obs_data_release(item);
|
||||
}
|
||||
obs_data_array_release(array);
|
||||
}
|
||||
|
||||
QStringList GetSourceNames()
|
||||
{
|
||||
auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */
|
||||
|
||||
@@ -38,6 +38,7 @@ void LoadTransformState(obs_data_t *obj, struct obs_transform_info &info,
|
||||
struct obs_sceneitem_crop &crop);
|
||||
bool SaveTransformState(obs_data_t *obj, const struct obs_transform_info &info,
|
||||
const struct obs_sceneitem_crop &crop);
|
||||
int GetSceneItemCount(const OBSWeakSource &);
|
||||
|
||||
/* Scene item helpers */
|
||||
|
||||
@@ -129,6 +130,10 @@ std::string GetPathInProfileDir(const char *filePath);
|
||||
QStringList GetMonitorNames();
|
||||
bool IsValidMacroSegmentIndex(Macro *m, const int idx, bool isCondition);
|
||||
QString GetMacroSegmentDescription(Macro *, int idx, bool isCondition);
|
||||
void SaveSplitterPos(const QList<int> &sizes, obs_data_t *obj,
|
||||
const std::string name);
|
||||
void LoadSplitterPos(QList<int> &sizes, obs_data_t *obj,
|
||||
const std::string name);
|
||||
|
||||
/* Legacy helpers */
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ std::string GetWeakVariableName(std::weak_ptr<Variable> var_)
|
||||
{
|
||||
auto var = var_.lock();
|
||||
if (!var) {
|
||||
return "invalid variable selection";
|
||||
return obs_module_text("AdvSceneSwitcher.variable.invalid");
|
||||
}
|
||||
return var->Name();
|
||||
}
|
||||
@@ -164,7 +164,8 @@ VariableSettingsDialog::VariableSettingsDialog(QWidget *parent,
|
||||
const Variable &settings)
|
||||
: ItemSettingsDialog(settings, switcher->variables,
|
||||
"AdvSceneSwitcher.variable.select",
|
||||
"AdvSceneSwitcher.variable.add", parent),
|
||||
"AdvSceneSwitcher.variable.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
||||
_value(new ResizingPlainTextEdit(this)),
|
||||
_defaultValue(new ResizingPlainTextEdit(this)),
|
||||
_save(new QComboBox())
|
||||
@@ -248,6 +249,7 @@ VariableSelection::VariableSelection(QWidget *parent)
|
||||
AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.variable.select",
|
||||
"AdvSceneSwitcher.variable.add",
|
||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||
"AdvSceneSwitcher.variable.configure", parent)
|
||||
{
|
||||
// Connect to slots
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <mutex>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -102,10 +105,61 @@ static VOID EnumWindowsWithMetro(__in WNDENUMPROC lpEnumFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Asynchronously updates the OBS window list and returns the state of the last
|
||||
// successful update
|
||||
const std::vector<std::string> getOBSWindows()
|
||||
{
|
||||
struct OBSWindowListHelper {
|
||||
std::vector<std::string> windows;
|
||||
std::atomic_bool done;
|
||||
};
|
||||
static OBSWindowListHelper obsWindowListHelper1 = {{}, {true}};
|
||||
static OBSWindowListHelper obsWindowListHelper2 = {{}, {false}};
|
||||
auto getQtWindowList = [](void *param) {
|
||||
auto list = reinterpret_cast<OBSWindowListHelper *>(param);
|
||||
for (auto w : QApplication::topLevelWidgets()) {
|
||||
auto title = w->windowTitle();
|
||||
if (!title.isEmpty()) {
|
||||
list->windows.emplace_back(title.toStdString());
|
||||
}
|
||||
}
|
||||
list->done = true;
|
||||
};
|
||||
|
||||
static OBSWindowListHelper *lastDoneHelper = &obsWindowListHelper2;
|
||||
static OBSWindowListHelper *pendingHelper = &obsWindowListHelper1;
|
||||
static std::mutex mutex;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (pendingHelper->done) { // Check if swap is needed
|
||||
auto temp = lastDoneHelper;
|
||||
lastDoneHelper = pendingHelper;
|
||||
pendingHelper = temp;
|
||||
pendingHelper->done = false;
|
||||
pendingHelper->windows.clear();
|
||||
obs_queue_task(OBS_TASK_UI, getQtWindowList, pendingHelper,
|
||||
false);
|
||||
}
|
||||
|
||||
return lastDoneHelper->windows;
|
||||
}
|
||||
|
||||
void GetWindowList(std::vector<std::string> &windows)
|
||||
{
|
||||
windows.resize(0);
|
||||
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
|
||||
|
||||
// Also add OBS windows
|
||||
for (const auto &window : getOBSWindows()) {
|
||||
if (!window.empty()) {
|
||||
windows.emplace_back(window);
|
||||
}
|
||||
}
|
||||
|
||||
// Add entry for OBS Studio itself - see GetCurrentWindowTitle()
|
||||
windows.emplace_back("OBS");
|
||||
}
|
||||
|
||||
void GetWindowList(QStringList &windows)
|
||||
@@ -117,9 +171,6 @@ void GetWindowList(QStringList &windows)
|
||||
for (auto window : w) {
|
||||
windows << QString::fromStdString(window);
|
||||
}
|
||||
|
||||
// Add entry for OBS Studio itself, see GetCurrentWindowTitle
|
||||
windows << QString("OBS");
|
||||
}
|
||||
|
||||
void GetCurrentWindowTitle(std::string &title)
|
||||
@@ -128,12 +179,24 @@ void GetCurrentWindowTitle(std::string &title)
|
||||
DWORD pid;
|
||||
DWORD thid;
|
||||
thid = GetWindowThreadProcessId(window, &pid);
|
||||
// GetWindowText will freeze if the control it is reading was created in another thread.
|
||||
// It does not directly read the control.
|
||||
// Instead it waits for the thread that created the control to process a WM_GETTEXT message.
|
||||
// So if that thread is frozen in a WaitFor... call you have a deadlock.
|
||||
// Calling GetWindowTitle() on the OBS windows might cause a deadlock in
|
||||
// the following scenario:
|
||||
//
|
||||
// The thread using GetWindowTitle() will send a WM_GETTEXT message to
|
||||
// the main thread that created the window and wait for it to be
|
||||
// processed.
|
||||
// The main thread itself might be blocked from processing the message,
|
||||
// however, when it itself is waiting for the thread using
|
||||
// GetWindowTitle() to return.
|
||||
//
|
||||
// So instead rely on Qt to get the title of the active window.
|
||||
if (GetCurrentProcessId() == pid) {
|
||||
title = "OBS";
|
||||
auto window = QApplication::activeWindow();
|
||||
if (window) {
|
||||
title = window->windowTitle().toStdString();
|
||||
} else {
|
||||
title = "OBS";
|
||||
}
|
||||
return;
|
||||
}
|
||||
GetWindowTitle(window, title);
|
||||
|
||||
Reference in New Issue
Block a user