mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-09-09 10:06:04 -05:00
Compare commits
57 Commits
1.23.0
...
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 | ||
|
|
68c6492c3f | ||
|
|
0ba7ba77d8 | ||
|
|
04b2b3474d | ||
|
|
ecbb5ebbd7 | ||
|
|
b62757b65d | ||
|
|
b1a3ab5493 | ||
|
|
abc3357180 | ||
|
|
29f9cba236 | ||
|
|
9a62522140 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -22,6 +22,7 @@ A clear and concise description of what you expected to happen.
|
|||||||
|
|
||||||
**Logs**
|
**Logs**
|
||||||
Please provide a log of your issue with verbose logging enabled (See General tab of the plugin).
|
Please provide a log of your issue with verbose logging enabled (See General tab of the plugin).
|
||||||
|
In case of a crash, please also include the corresponding crash log.
|
||||||
See [here](https://obsproject.com/forum/threads/please-post-a-log-with-your-issue-heres-how.23074/) for a description where to find the log files and how to share them.
|
See [here](https://obsproject.com/forum/threads/please-post-a-log-with-your-issue-heres-how.23074/) for a description where to find the log files and how to share them.
|
||||||
Please share the currently used plugin settings by exporting them them to a file (See General tab of the plugin).
|
Please share the currently used plugin settings by exporting them them to a file (See General tab of the plugin).
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|||||||
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 [[ '${{ inputs.codesign }}' == 'true' ]] build_args+=(-s)
|
||||||
if (( ${+CI} && ${+RUNNER_DEBUG} )) build_args+=(--debug)
|
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
|
- name: Run Linux Build
|
||||||
if: ${{ runner.os == 'Linux' }}
|
if: ${{ runner.os == 'Linux' }}
|
||||||
@@ -68,7 +68,7 @@ runs:
|
|||||||
build_args+=(-p)
|
build_args+=(-p)
|
||||||
fi
|
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
|
- name: Run Windows Build
|
||||||
if: ${{ runner.os == 'Windows' }}
|
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 '7zip.7zip', path: '7-zip', bin: '7z'
|
||||||
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
|
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
|
||||||
package 'innosetup', path: 'Inno Setup 6', bin: 'iscc'
|
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 target="${host_os}-${CPUTYPE}"
|
||||||
local project_root=${SCRIPT_HOME:A:h:h}
|
local project_root=${SCRIPT_HOME:A:h:h}
|
||||||
local buildspec_file="${project_root}/buildspec.json"
|
local buildspec_file="${project_root}/buildspec.json"
|
||||||
|
local dep_dir=""
|
||||||
|
|
||||||
trap '_trap_error' ZERR
|
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-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
|
||||||
%B-s | --codesign%b Enable codesigning (macOS only)
|
%B-s | --codesign%b Enable codesigning (macOS only)
|
||||||
%B-p | --portable%b Enable portable mode (Linux 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
|
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
|
||||||
Available generators:
|
Available generators:
|
||||||
- Ninja
|
- Ninja
|
||||||
@@ -130,6 +132,10 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
BUILD_CONFIG=${2}
|
BUILD_CONFIG=${2}
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
-d|--dep)
|
||||||
|
dep_dir="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
-s|--codesign) CODESIGN=1; shift ;;
|
-s|--codesign) CODESIGN=1; shift ;;
|
||||||
-p|--portable) typeset -g PORTABLE=1; shift ;;
|
-p|--portable) typeset -g PORTABLE=1; shift ;;
|
||||||
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
||||||
@@ -168,6 +174,15 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
typeset -g OBS_DEPS_VERSION
|
typeset -g OBS_DEPS_VERSION
|
||||||
setup_${host_os}
|
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_name
|
||||||
local product_version
|
local product_version
|
||||||
local git_tag="$(git describe --tags)"
|
local git_tag="$(git describe --tags)"
|
||||||
@@ -187,96 +202,6 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
sed -i '' \
|
sed -i '' \
|
||||||
"s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/" \
|
"s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/" \
|
||||||
"${project_root}/CMakeLists.txt"
|
"${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)
|
linux)
|
||||||
sed -i'' \
|
sed -i'' \
|
||||||
@@ -295,7 +220,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
local -a cmake_args=(
|
local -a cmake_args=(
|
||||||
-DCMAKE_BUILD_TYPE=${BUILD_CONFIG:-RelWithDebInfo}
|
-DCMAKE_BUILD_TYPE=${BUILD_CONFIG:-RelWithDebInfo}
|
||||||
-DQT_VERSION=${QT_VERSION}
|
-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)
|
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 ))
|
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+=(
|
cmake_args+=(
|
||||||
-DCMAKE_FRAMEWORK_PATH="${_plugin_deps}/Frameworks"
|
-DCMAKE_FRAMEWORK_PATH="${_plugin_deps}/Frameworks"
|
||||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||||
-DOBS_CODESIGN_LINKER=ON
|
-DOBS_CODESIGN_LINKER=ON
|
||||||
-DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}"
|
-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,
|
[string] $Target,
|
||||||
[ValidateSet('Visual Studio 17 2022', 'Visual Studio 16 2019')]
|
[ValidateSet('Visual Studio 17 2022', 'Visual Studio 16 2019')]
|
||||||
[string] $CMakeGenerator,
|
[string] $CMakeGenerator,
|
||||||
|
[string] $ADVSSDepName,
|
||||||
[switch] $SkipAll,
|
[switch] $SkipAll,
|
||||||
[switch] $SkipBuild,
|
[switch] $SkipBuild,
|
||||||
[switch] $SkipDeps,
|
[switch] $SkipDeps,
|
||||||
@@ -60,112 +61,13 @@ function Build {
|
|||||||
$DepsPath = "plugin-deps-${script:DepsVersion}-qt${script:QtVersion}-${script:Target}"
|
$DepsPath = "plugin-deps-${script:DepsVersion}-qt${script:QtVersion}-${script:Target}"
|
||||||
$DepInstallPath = "$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
|
$DepInstallPath = "$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
|
||||||
|
|
||||||
$OpenCVPath = "${ProjectRoot}/deps/opencv"
|
if ( $ADVSSDepName -eq '' ) {
|
||||||
$OpenCVBuildPath = "${OpenCVPath}/build"
|
Log-Information "Building advss deps ..."
|
||||||
|
$ADVSSDepName = "advss-build-dependencies"
|
||||||
Push-Location -Stack BuildOpenCVTemp
|
invoke-expression -Command "$PSScriptRoot/Build-Deps-Windows.ps1 -Configuration $Configuration -Target $Target -CMakeGenerator `"$CMakeGenerator`" -OutDirName $ADVSSDepName -SkipDeps -SkipUnpack"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
Log-Information "Install OpenCV}..."
|
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/../${script:ADVSSDepName})"
|
||||||
Invoke-External cmake --install "${OpenCVBuildPath}" --prefix "${DepInstallPath}" @OpenCVCmakeArgs
|
Log-Information "Using advss deps at $ADVSSDepPath ..."
|
||||||
|
|
||||||
$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
|
|
||||||
|
|
||||||
(Get-Content -Path ${ProjectRoot}/CMakeLists.txt -Raw) `
|
(Get-Content -Path ${ProjectRoot}/CMakeLists.txt -Raw) `
|
||||||
-replace "project\((.*) VERSION (.*)\)", "project(${ProductName} VERSION ${ProductVersion})" `
|
-replace "project\((.*) VERSION (.*)\)", "project(${ProductName} VERSION ${ProductVersion})" `
|
||||||
@@ -182,7 +84,7 @@ function Build {
|
|||||||
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
|
||||||
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
|
||||||
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
"-DCMAKE_BUILD_TYPE=${Configuration}"
|
||||||
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
|
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath};${ADVSSDepPath}"
|
||||||
"-DQT_VERSION=${script:QtVersion}"
|
"-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:
|
env:
|
||||||
PLUGIN_NAME: SceneSwitcher
|
PLUGIN_NAME: SceneSwitcher
|
||||||
LIB_NAME: advanced-scene-switcher
|
LIB_NAME: advanced-scene-switcher
|
||||||
|
DEP_DIR: advss-build-dependencies-1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
clang_check:
|
clang_check:
|
||||||
@@ -115,9 +116,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ github.workspace }}/.ccache
|
${{ 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 }}
|
key: macos-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
macos-${{ matrix.arch }}-ccache-plugin-
|
macos-${{ matrix.arch }}-ccache-plugin-
|
||||||
@@ -146,6 +144,13 @@ jobs:
|
|||||||
print "CODESIGN_IDENT=${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" >> $GITHUB_ENV
|
print "CODESIGN_IDENT=${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" >> $GITHUB_ENV
|
||||||
print "CODESIGN_IDENT_INSTALLER=${{ secrets.MACOS_SIGNING_INSTALLER_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
|
- name: Build Plugin
|
||||||
uses: ./plugin/.github/actions/build-plugin
|
uses: ./plugin/.github/actions/build-plugin
|
||||||
with:
|
with:
|
||||||
@@ -248,6 +253,13 @@ jobs:
|
|||||||
echo 'found=false' >> $GITHUB_OUTPUT
|
echo 'found=false' >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Build Dependencies
|
||||||
|
uses: ./plugin/.github/actions/build-dependencies
|
||||||
|
with:
|
||||||
|
workingDirectory: ${{ github.workspace }}/plugin
|
||||||
|
target: ${{ matrix.arch }}
|
||||||
|
config: RelWithDebInfo
|
||||||
|
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
uses: ./plugin/.github/actions/build-plugin
|
uses: ./plugin/.github/actions/build-plugin
|
||||||
with:
|
with:
|
||||||
@@ -329,9 +341,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
${{ github.workspace }}/.ccache
|
${{ 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 }}
|
key: windows-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
windows-${{ matrix.arch }}-ccache-plugin-
|
windows-${{ matrix.arch }}-ccache-plugin-
|
||||||
@@ -357,6 +366,14 @@ jobs:
|
|||||||
|
|
||||||
"found=$(([string]${LabelFound}).ToLower())" >> $env:GITHUB_OUTPUT
|
"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
|
- name: Build Plugin
|
||||||
uses: ./plugin/.github/actions/build-plugin
|
uses: ./plugin/.github/actions/build-plugin
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -91,6 +91,7 @@ Thumbs.db
|
|||||||
.settings
|
.settings
|
||||||
.idea
|
.idea
|
||||||
.metadata
|
.metadata
|
||||||
|
.vscode
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.sublime*
|
*.sublime*
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -19,3 +19,6 @@
|
|||||||
[submodule "deps/libremidi"]
|
[submodule "deps/libremidi"]
|
||||||
path = deps/libremidi
|
path = deps/libremidi
|
||||||
url = https://github.com/jcelerier/libremidi.git
|
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/curl-helper.hpp
|
||||||
src/utils/duration-control.cpp
|
src/utils/duration-control.cpp
|
||||||
src/utils/duration-control.hpp
|
src/utils/duration-control.hpp
|
||||||
|
src/utils/export-symbol-helper.hpp
|
||||||
src/utils/item-selection-helpers.cpp
|
src/utils/item-selection-helpers.cpp
|
||||||
src/utils/item-selection-helpers.hpp
|
src/utils/item-selection-helpers.hpp
|
||||||
src/utils/log-helper.hpp
|
src/utils/log-helper.hpp
|
||||||
@@ -267,6 +268,8 @@ target_sources(
|
|||||||
src/utils/macro-export-import-dialog.hpp
|
src/utils/macro-export-import-dialog.hpp
|
||||||
src/utils/macro-list.cpp
|
src/utils/macro-list.cpp
|
||||||
src/utils/macro-list.hpp
|
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.cpp
|
||||||
src/utils/macro-segment-selection.hpp
|
src/utils/macro-segment-selection.hpp
|
||||||
src/utils/math-helpers.cpp
|
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/obs-websocket/lib"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
||||||
|
|
||||||
|
target_compile_definitions(${LIB_NAME} PRIVATE ADVSS_EXPORT_SYMBOLS=1)
|
||||||
|
|
||||||
# --- End of section ---
|
# --- End of section ---
|
||||||
|
|
||||||
# --- Windows-specific build settings and tasks ---
|
# --- 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.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Fenster"
|
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="Datei"
|
||||||
AdvSceneSwitcher.condition.file.type.match="entspricht"
|
AdvSceneSwitcher.condition.file.type.match="entspricht"
|
||||||
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
|
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
|
||||||
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
||||||
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
||||||
AdvSceneSwitcher.condition.file.local="Lokale 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.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||||
AdvSceneSwitcher.condition.media="Medien"
|
AdvSceneSwitcher.condition.media="Medien"
|
||||||
AdvSceneSwitcher.condition.media.source="Quelle"
|
AdvSceneSwitcher.condition.media.source="Quelle"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
|
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}}"
|
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="Szene wechseln"
|
AdvSceneSwitcher.action.scene="Szene wechseln"
|
||||||
AdvSceneSwitcher.action.scene.entry="Wechsle zu Szene{{scenes}}mittels{{transitions}}mit einer Dauer von{{duration}}Sekunden"
|
AdvSceneSwitcher.action.scene.entry="Wechsle{{sceneTypes}}Szene zu{{scenes}}mittels{{transitions}}mit einer Dauer von{{duration}}Sekunden"
|
||||||
AdvSceneSwitcher.action.scene.entry.noDuration="Wechsle zu Szene{{scenes}}mittels{{transitions}}"
|
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.scene.blockUntilTransitionDone="Warten, bis der Übergang zur Zielszene abgeschlossen ist"
|
||||||
AdvSceneSwitcher.action.wait="Warten"
|
AdvSceneSwitcher.action.wait="Warten"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
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.type.start="Stream starten"
|
||||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||||
AdvSceneSwitcher.action.run="Ausführen"
|
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="Sichtbarkeit von Szenenelementen"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
|
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
|
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.rightMeta="Rechte Meta-Taste"
|
||||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Tastendruck nur an OBS senden"
|
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.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="Reihenfolge der Szenenelemente"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Nach oben verschieben"
|
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Nach oben verschieben"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Nach unten 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="Websocket"
|
||||||
AdvSceneSwitcher.action.websocket.type.request="Anfrage"
|
AdvSceneSwitcher.action.websocket.type.request="Anfrage"
|
||||||
AdvSceneSwitcher.action.websocket.type.event="Ereignis"
|
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="HTTP"
|
||||||
AdvSceneSwitcher.action.http.type.get="GET"
|
AdvSceneSwitcher.action.http.type.get="GET"
|
||||||
AdvSceneSwitcher.action.http.type.post="POST"
|
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.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.conditionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Bedingungen wird nicht unterstützt!"
|
||||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Aktueller Wert:"
|
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
|
; 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.saveWindowGeo="Save window position and size"
|
||||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
|
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
|
||||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
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.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.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"
|
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.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.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.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="Edit macro"
|
||||||
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
|
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
|
||||||
AdvSceneSwitcher.macroTab.edit.condition="Condition 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.add="Add new macro"
|
||||||
AdvSceneSwitcher.macroTab.name="Name:"
|
AdvSceneSwitcher.macroTab.name="Name:"
|
||||||
AdvSceneSwitcher.macroTab.run="Run macro"
|
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.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.runInParallel="Run macro in parallel to other macros"
|
||||||
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
|
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
|
||||||
@@ -104,12 +107,14 @@ AdvSceneSwitcher.macroTab.maximize="Maximize"
|
|||||||
AdvSceneSwitcher.macroTab.minimize="Minimize"
|
AdvSceneSwitcher.macroTab.minimize="Minimize"
|
||||||
AdvSceneSwitcher.macroTab.highlightSettings="Visual settings"
|
AdvSceneSwitcher.macroTab.highlightSettings="Visual settings"
|
||||||
AdvSceneSwitcher.macroTab.hotkeySettings="Hotkey settings"
|
AdvSceneSwitcher.macroTab.hotkeySettings="Hotkey settings"
|
||||||
|
AdvSceneSwitcher.macroTab.generalSettings="General settings"
|
||||||
AdvSceneSwitcher.macroTab.dockSettings="Dock settings"
|
AdvSceneSwitcher.macroTab.dockSettings="Dock settings"
|
||||||
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
|
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
|
||||||
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
|
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
|
||||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
|
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
|
||||||
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control the pause state of new macros"
|
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control the pause state of new macros"
|
||||||
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control the pause state of selected macro"
|
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control the pause state of selected macro"
|
||||||
|
AdvSceneSwitcher.macroTab.currentSkipExecutionOnStartup="Skip execution of actions of current macro on startup"
|
||||||
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
|
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
|
||||||
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
|
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
|
||||||
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
|
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
|
||||||
@@ -152,7 +157,7 @@ AdvSceneSwitcher.condition.audio.type.volume="Configured volume level"
|
|||||||
AdvSceneSwitcher.condition.audio.type.syncOffset="Sync offset"
|
AdvSceneSwitcher.condition.audio.type.syncOffset="Sync offset"
|
||||||
AdvSceneSwitcher.condition.audio.type.monitor="Audio monitoring"
|
AdvSceneSwitcher.condition.audio.type.monitor="Audio monitoring"
|
||||||
AdvSceneSwitcher.condition.audio.type.balance="Audio balance"
|
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="Cursor"
|
||||||
AdvSceneSwitcher.condition.cursor.type.region="is in region"
|
AdvSceneSwitcher.condition.cursor.type.region="is in region"
|
||||||
AdvSceneSwitcher.condition.cursor.type.moving="is moving"
|
AdvSceneSwitcher.condition.cursor.type.moving="is moving"
|
||||||
@@ -162,15 +167,17 @@ AdvSceneSwitcher.condition.cursor.button.middle="Middle mouse button"
|
|||||||
AdvSceneSwitcher.condition.cursor.button.right="Right mouse button"
|
AdvSceneSwitcher.condition.cursor.button.right="Right mouse button"
|
||||||
AdvSceneSwitcher.condition.cursor.showFrame="Show frame"
|
AdvSceneSwitcher.condition.cursor.showFrame="Show frame"
|
||||||
AdvSceneSwitcher.condition.cursor.hideFrame="Hide frame"
|
AdvSceneSwitcher.condition.cursor.hideFrame="Hide frame"
|
||||||
AdvSceneSwitcher.condition.cursor.entry.line1="Cursor {{conditions}}{{buttons}}{{minX}}{{minY}}{{maxX}}{{maxY}}{{toggleFrameButton}}"
|
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.line2="Cursor is currently at{{xPos}}x{{yPos}}"
|
||||||
AdvSceneSwitcher.condition.scene="Scene"
|
AdvSceneSwitcher.condition.scene="Scene"
|
||||||
AdvSceneSwitcher.condition.scene.type.current="Current scene is"
|
AdvSceneSwitcher.condition.scene.type.current="Current scene is"
|
||||||
AdvSceneSwitcher.condition.scene.type.previous="Previous 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.changed="Scene changed"
|
||||||
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
|
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
|
||||||
AdvSceneSwitcher.condition.scene.type.currentPattern="Current scene matches"
|
AdvSceneSwitcher.condition.scene.type.currentPattern="Current scene matches"
|
||||||
AdvSceneSwitcher.condition.scene.type.previousPattern="Previous 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.currentSceneTransitionBehaviour="During transition check for transition target scene"
|
||||||
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
|
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
|
||||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||||
@@ -190,9 +197,9 @@ AdvSceneSwitcher.condition.file.type.contentChange="content changed"
|
|||||||
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
||||||
AdvSceneSwitcher.condition.file.remote="Remote file"
|
AdvSceneSwitcher.condition.file.remote="Remote file"
|
||||||
AdvSceneSwitcher.condition.file.local="Local 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.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||||
AdvSceneSwitcher.condition.media="Media"
|
AdvSceneSwitcher.condition.media="Media"
|
||||||
AdvSceneSwitcher.condition.media.source="Source"
|
AdvSceneSwitcher.condition.media.source="Source"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
|
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
|
||||||
@@ -221,7 +228,7 @@ AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will al
|
|||||||
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
|
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.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.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.crossCorrelation="Cross correlation"
|
||||||
AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient="Correlation coefficient"
|
AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient="Correlation coefficient"
|
||||||
AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference="Squared difference"
|
AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference="Squared difference"
|
||||||
@@ -262,9 +269,9 @@ AdvSceneSwitcher.condition.video.type.main="OBS's main output"
|
|||||||
AdvSceneSwitcher.condition.video.type.source="Source"
|
AdvSceneSwitcher.condition.video.type.source="Source"
|
||||||
AdvSceneSwitcher.condition.video.type.scene="Scene"
|
AdvSceneSwitcher.condition.video.type.scene="Scene"
|
||||||
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
|
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
|
||||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
|
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier):{{modelDataPath}}"
|
||||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
|
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.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.checkAreaEnable="Perform check only in area"
|
||||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||||
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}"
|
||||||
@@ -290,10 +297,10 @@ AdvSceneSwitcher.condition.record.state.pause="Recording paused"
|
|||||||
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
|
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
|
||||||
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
|
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
|
||||||
AdvSceneSwitcher.condition.process="Process"
|
AdvSceneSwitcher.condition.process="Process"
|
||||||
AdvSceneSwitcher.condition.process.entry="{{processes}} is running {{focused}} and is focused"
|
AdvSceneSwitcher.condition.process.entry="{{processes}}is running{{focused}}and is focused"
|
||||||
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process: {{focusProcess}}"
|
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
|
||||||
AdvSceneSwitcher.condition.idle="Idle"
|
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="Plugin state"
|
||||||
AdvSceneSwitcher.condition.pluginState.state.start="Plugin started"
|
AdvSceneSwitcher.condition.pluginState.state.start="Plugin started"
|
||||||
AdvSceneSwitcher.condition.pluginState.state.restart="Plugin restarted"
|
AdvSceneSwitcher.condition.pluginState.state.restart="Plugin restarted"
|
||||||
@@ -308,10 +315,10 @@ AdvSceneSwitcher.condition.timer.type.fixed="Fixed"
|
|||||||
AdvSceneSwitcher.condition.timer.type.random="Random"
|
AdvSceneSwitcher.condition.timer.type.random="Random"
|
||||||
AdvSceneSwitcher.condition.timer.pause="Pause"
|
AdvSceneSwitcher.condition.timer.pause="Pause"
|
||||||
AdvSceneSwitcher.condition.timer.continue="Continue"
|
AdvSceneSwitcher.condition.timer.continue="Continue"
|
||||||
AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}} duration of {{duration}} has passed"
|
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.line1.random="{{type}}duration from{{duration}}to{{duration2}}has passed"
|
||||||
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds"
|
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.line3="{{pauseContinue}}{{reset}}{{saveRemaining}}Save time remaining{{autoReset}}Automatically reset timer after duration was reached"
|
||||||
AdvSceneSwitcher.condition.timer.reset="Reset"
|
AdvSceneSwitcher.condition.timer.reset="Reset"
|
||||||
AdvSceneSwitcher.condition.macro="Macro"
|
AdvSceneSwitcher.condition.macro="Macro"
|
||||||
AdvSceneSwitcher.condition.macro.type.count="Macro run count"
|
AdvSceneSwitcher.condition.macro.type.count="Macro run count"
|
||||||
@@ -328,16 +335,17 @@ AdvSceneSwitcher.condition.macro.state.type.above="More than"
|
|||||||
AdvSceneSwitcher.condition.macro.state.type.equal="Exactly"
|
AdvSceneSwitcher.condition.macro.state.type.equal="Exactly"
|
||||||
AdvSceneSwitcher.condition.macro.count.reset="Reset"
|
AdvSceneSwitcher.condition.macro.count.reset="Reset"
|
||||||
AdvSceneSwitcher.condition.macro.pausedWarning="Selected macro is currently paused!"
|
AdvSceneSwitcher.condition.macro.pausedWarning="Selected macro is currently paused!"
|
||||||
AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true"
|
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.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.line1="{{macros}}was executed{{conditions}}{{count}}times"
|
||||||
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}"
|
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.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
|
||||||
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
|
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
|
||||||
AdvSceneSwitcher.condition.source="Source"
|
AdvSceneSwitcher.condition.source="Source"
|
||||||
AdvSceneSwitcher.condition.source.type.active="Is active"
|
AdvSceneSwitcher.condition.source.type.active="Is active"
|
||||||
AdvSceneSwitcher.condition.source.type.showing="Is showing"
|
AdvSceneSwitcher.condition.source.type.showing="Is showing"
|
||||||
AdvSceneSwitcher.condition.source.type.settings="Settings match"
|
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.sceneVisibilityHint="Scene specific visibility can be checked using the \"Scene item visibility\" condition"
|
||||||
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
|
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
|
||||||
AdvSceneSwitcher.condition.source.entry.line1="{{sources}}{{conditions}}"
|
AdvSceneSwitcher.condition.source.entry.line1="{{sources}}{{conditions}}"
|
||||||
@@ -351,8 +359,9 @@ AdvSceneSwitcher.condition.filter="Filter"
|
|||||||
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
|
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
|
||||||
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
|
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
|
||||||
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
|
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
|
||||||
|
AdvSceneSwitcher.condition.filter.type.settingsChanged="Settings changed"
|
||||||
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
|
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.line2="{{settings}}"
|
||||||
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}}{{getSettings}}"
|
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}}{{getSettings}}"
|
||||||
AdvSceneSwitcher.condition.sceneOrder="Scene item order"
|
AdvSceneSwitcher.condition.sceneOrder="Scene item order"
|
||||||
@@ -365,7 +374,7 @@ AdvSceneSwitcher.condition.hotkey="Hotkey"
|
|||||||
AdvSceneSwitcher.condition.hotkey.name="Macro trigger 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.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.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="Replay buffer"
|
||||||
AdvSceneSwitcher.condition.replay.state.stopped="Replay buffer stopped"
|
AdvSceneSwitcher.condition.replay.state.stopped="Replay buffer stopped"
|
||||||
AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
|
AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
|
||||||
@@ -390,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.ignoreTime="If unchecked the time component will be ignored"
|
||||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
|
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
|
||||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
|
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
|
||||||
AdvSceneSwitcher.condition.date.entry.simple="On {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
|
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.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.repeat="{{repeat}}Repeat every{{duration}}on date match"
|
||||||
AdvSceneSwitcher.condition.date.entry.pattern="Current date \"{{currentDate}}\" matches pattern {{pattern}}"
|
AdvSceneSwitcher.condition.date.entry.pattern="Current date \"{{currentDate}}\" matches pattern{{pattern}}"
|
||||||
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
|
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="Scene item transform"
|
||||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||||
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
|
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
|
||||||
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
|
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="Transition"
|
||||||
AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
|
AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
|
||||||
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
|
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
|
||||||
@@ -424,7 +433,7 @@ AdvSceneSwitcher.condition.openvr="OpenVR"
|
|||||||
AdvSceneSwitcher.condition.openvr.errorStatus="OpenVR error: "
|
AdvSceneSwitcher.condition.openvr.errorStatus="OpenVR error: "
|
||||||
AdvSceneSwitcher.condition.openvr.entry.line1="HMD is in ..."
|
AdvSceneSwitcher.condition.openvr.entry.line1="HMD is in ..."
|
||||||
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
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="OBS stats"
|
||||||
AdvSceneSwitcher.condition.stats.type.fps="FPS"
|
AdvSceneSwitcher.condition.stats.type.fps="FPS"
|
||||||
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU Usage"
|
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU Usage"
|
||||||
@@ -443,15 +452,15 @@ AdvSceneSwitcher.condition.stats.condition.above="above"
|
|||||||
AdvSceneSwitcher.condition.stats.condition.equals="equal to"
|
AdvSceneSwitcher.condition.stats.condition.equals="equal to"
|
||||||
AdvSceneSwitcher.condition.stats.condition.below="below"
|
AdvSceneSwitcher.condition.stats.condition.below="below"
|
||||||
AdvSceneSwitcher.condition.stats.dockHint="You can open the \"Stats\" dock to view the current status"
|
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="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="Websocket"
|
||||||
AdvSceneSwitcher.condition.websocket.type.request="Scene Switcher Request"
|
AdvSceneSwitcher.condition.websocket.type.request="Scene Switcher Request"
|
||||||
AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
|
AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
|
||||||
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
|
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
|
||||||
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
|
AdvSceneSwitcher.condition.websocket.entry.request="{{type}}was received:"
|
||||||
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
|
AdvSceneSwitcher.condition.websocket.entry.event="{{type}}was received from{{connection}}:"
|
||||||
AdvSceneSwitcher.condition.variable="Variable"
|
AdvSceneSwitcher.condition.variable="Variable"
|
||||||
AdvSceneSwitcher.condition.variable.type.compare="equals"
|
AdvSceneSwitcher.condition.variable.type.compare="equals"
|
||||||
AdvSceneSwitcher.condition.variable.type.empty="is empty"
|
AdvSceneSwitcher.condition.variable.type.empty="is empty"
|
||||||
@@ -464,11 +473,11 @@ AdvSceneSwitcher.condition.variable.type.lessThanVariable="is less than variable
|
|||||||
AdvSceneSwitcher.condition.variable.type.greaterThanVariable="is greater than variable"
|
AdvSceneSwitcher.condition.variable.type.greaterThanVariable="is greater than variable"
|
||||||
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
|
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
|
||||||
AdvSceneSwitcher.condition.run="Run"
|
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.run.entry.exit="{{checkExitCode}}Check for exit code{{exitCode}}"
|
||||||
AdvSceneSwitcher.condition.midi="MIDI"
|
AdvSceneSwitcher.condition.midi="MIDI"
|
||||||
AdvSceneSwitcher.condition.midi.entry="Mesasge was received from {{device}} which matches:"
|
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.listen="Set MIDI message selection to messages incoming on selected device:{{listenButton}}"
|
||||||
AdvSceneSwitcher.condition.display="Display"
|
AdvSceneSwitcher.condition.display="Display"
|
||||||
AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches"
|
AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches"
|
||||||
AdvSceneSwitcher.condition.display.type.displayCount="Number of connected displays is"
|
AdvSceneSwitcher.condition.display.type.displayCount="Number of connected displays is"
|
||||||
@@ -481,15 +490,18 @@ AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Information about th
|
|||||||
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
|
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="Switch scene"
|
AdvSceneSwitcher.action.scene="Switch scene"
|
||||||
AdvSceneSwitcher.action.scene.entry="Switch to scene{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
|
AdvSceneSwitcher.action.scene.type.program="Program"
|
||||||
AdvSceneSwitcher.action.scene.entry.noDuration="Switch to scene{{scenes}}using{{transitions}}"
|
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.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
|
||||||
AdvSceneSwitcher.action.wait="Wait"
|
AdvSceneSwitcher.action.wait="Wait"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fixed"
|
AdvSceneSwitcher.action.wait.type.fixed="fixed"
|
||||||
AdvSceneSwitcher.action.wait.type.random="random"
|
AdvSceneSwitcher.action.wait.type.random="random"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Wait for {{waitType}} duration of {{duration}}"
|
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.random="Wait for{{waitType}}duration from{{duration}}to{{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Audio"
|
AdvSceneSwitcher.action.audio="Audio"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Mute"
|
AdvSceneSwitcher.action.audio.type.mute="Mute"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
|
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
|
||||||
@@ -501,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.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.duration="over a duration of"
|
||||||
AdvSceneSwitcher.action.audio.fade.type.rate="at a rate 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.duration="{{fade}}Fade{{fadeTypes}}{{duration}}seconds."
|
||||||
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}per second."
|
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.wait="Wait for fade to complete."
|
||||||
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
|
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
|
||||||
AdvSceneSwitcher.action.audio.entry="{{actions}}{{audioSources}}{{volume}}{{syncOffset}}{{monitorTypes}}"
|
AdvSceneSwitcher.action.audio.entry="{{actions}}{{audioSources}}{{volume}}{{syncOffset}}{{monitorTypes}}"
|
||||||
@@ -545,7 +557,7 @@ AdvSceneSwitcher.action.filter.type.enable="Enable"
|
|||||||
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
||||||
AdvSceneSwitcher.action.filter.type.toggle="Toggle"
|
AdvSceneSwitcher.action.filter.type.toggle="Toggle"
|
||||||
AdvSceneSwitcher.action.filter.type.settings="Set settings"
|
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.filter.getSettings="Get current settings"
|
||||||
AdvSceneSwitcher.action.source="Source"
|
AdvSceneSwitcher.action.source="Source"
|
||||||
AdvSceneSwitcher.action.source.type.enable="Enable"
|
AdvSceneSwitcher.action.source.type.enable="Enable"
|
||||||
@@ -556,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.refreshSettings.tooltip="Can be used to refresh browser, media, etc. sources"
|
||||||
AdvSceneSwitcher.action.source.type.deinterlaceMode="Set deinterlace mode"
|
AdvSceneSwitcher.action.source.type.deinterlaceMode="Set deinterlace mode"
|
||||||
AdvSceneSwitcher.action.source.type.deinterlaceOrder="Set deinterlace field order"
|
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.noSettingsButtons="No buttons found!"
|
||||||
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
|
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\""
|
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\""
|
||||||
@@ -645,7 +658,7 @@ AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}
|
|||||||
AdvSceneSwitcher.action.file="File"
|
AdvSceneSwitcher.action.file="File"
|
||||||
AdvSceneSwitcher.action.file.type.write="Write"
|
AdvSceneSwitcher.action.file.type.write="Write"
|
||||||
AdvSceneSwitcher.action.file.type.append="Append"
|
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="Studio mode"
|
||||||
AdvSceneSwitcher.action.studioMode.type.swap="Swap preview and program scene"
|
AdvSceneSwitcher.action.studioMode.type.swap="Swap preview and program scene"
|
||||||
AdvSceneSwitcher.action.studioMode.type.setScene="Set preview scene to"
|
AdvSceneSwitcher.action.studioMode.type.setScene="Set preview scene to"
|
||||||
@@ -657,15 +670,15 @@ AdvSceneSwitcher.action.transition.type.scene="scene transition"
|
|||||||
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
|
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
|
||||||
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
|
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
|
||||||
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
|
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
|
||||||
AdvSceneSwitcher.action.transition.entry.line1="Modify {{type}}{{scenes}}{{sources}}"
|
AdvSceneSwitcher.action.transition.entry.line1="Modify{{type}}{{scenes}}{{sources}}"
|
||||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to {{transitions}}"
|
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.line3="{{setDuration}}Set transition duration to{{duration}}seconds"
|
||||||
AdvSceneSwitcher.action.timer="Timer"
|
AdvSceneSwitcher.action.timer="Timer"
|
||||||
AdvSceneSwitcher.action.timer.type.pause="Pause"
|
AdvSceneSwitcher.action.timer.type.pause="Pause"
|
||||||
AdvSceneSwitcher.action.timer.type.continue="Continue"
|
AdvSceneSwitcher.action.timer.type.continue="Continue"
|
||||||
AdvSceneSwitcher.action.timer.type.reset="Reset"
|
AdvSceneSwitcher.action.timer.type.reset="Reset"
|
||||||
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Set time remaining of"
|
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="Random"
|
||||||
AdvSceneSwitcher.action.random.allowRepeat="Allow consecutive execution of the same macro"
|
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)"
|
AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (paused macros are ignored)"
|
||||||
@@ -683,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.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.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}location"
|
||||||
AdvSceneSwitcher.action.profile="Profile"
|
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="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.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="Sequence"
|
||||||
AdvSceneSwitcher.action.sequence.entry="Each time this action is performed run the next macro in the list (paused macros are ignored)"
|
AdvSceneSwitcher.action.sequence.entry="Each time this action is performed run the next macro in the list (paused macros are ignored)"
|
||||||
@@ -710,8 +723,8 @@ AdvSceneSwitcher.action.http.headers="Headers:"
|
|||||||
AdvSceneSwitcher.action.http.addHeader="Add header"
|
AdvSceneSwitcher.action.http.addHeader="Add header"
|
||||||
AdvSceneSwitcher.action.http.type.get="GET"
|
AdvSceneSwitcher.action.http.type.get="GET"
|
||||||
AdvSceneSwitcher.action.http.type.post="POST"
|
AdvSceneSwitcher.action.http.type.post="POST"
|
||||||
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
|
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
|
||||||
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
|
AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
|
||||||
AdvSceneSwitcher.action.variable="Variable"
|
AdvSceneSwitcher.action.variable="Variable"
|
||||||
AdvSceneSwitcher.action.variable.type.set="Set to fixed value"
|
AdvSceneSwitcher.action.variable.type.set="Set to fixed value"
|
||||||
AdvSceneSwitcher.action.variable.type.append="Append"
|
AdvSceneSwitcher.action.variable.type.append="Append"
|
||||||
@@ -725,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.findAndReplace="Find and replace in current value"
|
||||||
AdvSceneSwitcher.action.variable.type.mathExpression="Mathematical expression"
|
AdvSceneSwitcher.action.variable.type.mathExpression="Mathematical expression"
|
||||||
AdvSceneSwitcher.action.variable.type.askForValue="Get user input"
|
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.askForValuePromptDefault="Assign value to variable \"%1\":"
|
||||||
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
|
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
|
||||||
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
|
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
|
||||||
@@ -736,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.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.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
|
||||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
|
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
|
||||||
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="Substring start:{{subStringStart}} Substring size:{{subStringSize}}"
|
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.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
|
||||||
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
|
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="Projector"
|
||||||
AdvSceneSwitcher.action.projector.type.source="Source"
|
AdvSceneSwitcher.action.projector.type.source="Source"
|
||||||
AdvSceneSwitcher.action.projector.type.scene="Scene"
|
AdvSceneSwitcher.action.projector.type.scene="Scene"
|
||||||
@@ -753,247 +769,26 @@ AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
|
|||||||
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
|
||||||
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}"
|
||||||
AdvSceneSwitcher.action.midi="MIDI"
|
AdvSceneSwitcher.action.midi="MIDI"
|
||||||
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
|
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.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}"
|
||||||
AdvSceneSwitcher.action.osc="Open Sound Control"
|
AdvSceneSwitcher.action.osc="Open Sound Control"
|
||||||
AdvSceneSwitcher.action.sceneLock="Scene item lock"
|
AdvSceneSwitcher.action.sceneLock="Scene item lock"
|
||||||
AdvSceneSwitcher.action.sceneLock.type.lock="lock"
|
AdvSceneSwitcher.action.sceneLock.type.lock="lock"
|
||||||
AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
|
AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
|
||||||
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
|
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
|
||||||
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
|
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
|
||||||
|
AdvSceneSwitcher.action.twitch="Twitch"
|
||||||
; Transition Tab
|
AdvSceneSwitcher.action.twitch.type.title="Set stream title"
|
||||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
AdvSceneSwitcher.action.twitch.type.category="Set stream category"
|
||||||
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
|
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
|
||||||
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.action.twitch.type.clip="Create stream clip"
|
||||||
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
|
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration"
|
||||||
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}} with a duration of {{duration}}"
|
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
|
||||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
|
AdvSceneSwitcher.action.twitch.entry="On{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}"
|
||||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
|
AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
|
||||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition {{defTransitionDelay}} after scene change."
|
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
||||||
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."
|
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
|
||||||
|
AdvSceneSwitcher.action.twitch.title.title="Enter title"
|
||||||
; 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."
|
|
||||||
|
|
||||||
; Hotkey
|
; Hotkey
|
||||||
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
|
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
|
||||||
@@ -1007,7 +802,7 @@ AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Move macro segment selection dow
|
|||||||
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remove selected macro segment"
|
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.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.close="Close"
|
||||||
AdvSceneSwitcher.browse="Browse"
|
AdvSceneSwitcher.browse="Browse"
|
||||||
@@ -1027,6 +822,7 @@ AdvSceneSwitcher.macroSegmentSelection.invalid="Invalid selection!"
|
|||||||
AdvSceneSwitcher.variable.select="--select variable--"
|
AdvSceneSwitcher.variable.select="--select variable--"
|
||||||
AdvSceneSwitcher.variable.add="Add new variable"
|
AdvSceneSwitcher.variable.add="Add new variable"
|
||||||
AdvSceneSwitcher.variable.configure="Configure variable settings"
|
AdvSceneSwitcher.variable.configure="Configure variable settings"
|
||||||
|
AdvSceneSwitcher.variable.invalid="Invalid varialbe selection"
|
||||||
AdvSceneSwitcher.variable.name="Name:"
|
AdvSceneSwitcher.variable.name="Name:"
|
||||||
AdvSceneSwitcher.variable.value="Current value:"
|
AdvSceneSwitcher.variable.value="Current value:"
|
||||||
AdvSceneSwitcher.variable.save="Save / load behavior"
|
AdvSceneSwitcher.variable.save="Save / load behavior"
|
||||||
@@ -1037,6 +833,7 @@ AdvSceneSwitcher.variable.save.default="Set to value"
|
|||||||
AdvSceneSwitcher.connection.select="--select connection--"
|
AdvSceneSwitcher.connection.select="--select connection--"
|
||||||
AdvSceneSwitcher.connection.add="Add new connection"
|
AdvSceneSwitcher.connection.add="Add new connection"
|
||||||
AdvSceneSwitcher.connection.configure="Configure connection settings"
|
AdvSceneSwitcher.connection.configure="Configure connection settings"
|
||||||
|
AdvSceneSwitcher.connection.invalid="Invalid connection selection"
|
||||||
AdvSceneSwitcher.connection.name="Name:"
|
AdvSceneSwitcher.connection.name="Name:"
|
||||||
AdvSceneSwitcher.connection.useCustomURI="Use custom URI"
|
AdvSceneSwitcher.connection.useCustomURI="Use custom URI"
|
||||||
AdvSceneSwitcher.connection.customURI="Address:"
|
AdvSceneSwitcher.connection.customURI="Address:"
|
||||||
@@ -1123,6 +920,41 @@ AdvSceneSwitcher.osc.message.type.false="False"
|
|||||||
AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
|
AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
|
||||||
AdvSceneSwitcher.osc.message.type.null="Nil"
|
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.selectScene="--select scene--"
|
||||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||||
@@ -1147,6 +979,7 @@ AdvSceneSwitcher.enterPath="--enter path--"
|
|||||||
AdvSceneSwitcher.enterText="--enter text--"
|
AdvSceneSwitcher.enterText="--enter text--"
|
||||||
AdvSceneSwitcher.enterURL="--enter URL--"
|
AdvSceneSwitcher.enterURL="--enter URL--"
|
||||||
AdvSceneSwitcher.selectHotkey="--select hotkey--"
|
AdvSceneSwitcher.selectHotkey="--select hotkey--"
|
||||||
|
AdvSceneSwitcher.selectDisplay="--select display--"
|
||||||
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
|
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
|
||||||
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
|
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
|
||||||
|
|
||||||
@@ -1190,3 +1023,237 @@ AdvSceneSwitcher.duration.condition.within="Within the last"
|
|||||||
AdvSceneSwitcher.audio.monitor.none="Monitor Off"
|
AdvSceneSwitcher.audio.monitor.none="Monitor Off"
|
||||||
AdvSceneSwitcher.audio.monitor.monitorOnly="Monitor Only (mute output)"
|
AdvSceneSwitcher.audio.monitor.monitorOnly="Monitor Only (mute output)"
|
||||||
AdvSceneSwitcher.audio.monitor.both="Monitor and 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.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Ventana"
|
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="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.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||||
AdvSceneSwitcher.condition.media="Medios"
|
AdvSceneSwitcher.condition.media="Medios"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
|
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
|
||||||
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
|
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}}"
|
AdvSceneSwitcher.condition.stats.entry="{{stats}} esta {{condition}} {{value}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="Cambiar escena"
|
AdvSceneSwitcher.action.scene="Cambiar escena"
|
||||||
AdvSceneSwitcher.action.scene.entry="Cambiar a la escena {{scenes}} usando {{transitions}} con una duración de {{duration}} segundos"
|
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.scene.blockUntilTransitionDone="Espere hasta que se complete la transición a la escena de destino"
|
||||||
AdvSceneSwitcher.action.wait="Esperar"
|
AdvSceneSwitcher.action.wait="Esperar"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fijo"
|
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.type.start="Iniciar transmisión"
|
||||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||||
AdvSceneSwitcher.action.run="Ejecutar"
|
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="Visibilidad del elemento de escena"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
|
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
|
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.rightMeta="Meta derecho"
|
||||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Enviar pulsación de tecla solo a OBS"
|
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.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="Orden de elementos de escena"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Mover hacia arriba"
|
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Mover hacia arriba"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Mover hacia abajo"
|
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.current="Текущий"
|
||||||
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
||||||
AdvSceneSwitcher.condition.window="Окно"
|
AdvSceneSwitcher.condition.window="Окно"
|
||||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..."
|
|
||||||
AdvSceneSwitcher.condition.file="Файл"
|
AdvSceneSwitcher.condition.file="Файл"
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
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="Медиа"
|
||||||
AdvSceneSwitcher.condition.video="Видео"
|
AdvSceneSwitcher.condition.video="Видео"
|
||||||
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
||||||
@@ -116,8 +115,8 @@ AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Автоматиче
|
|||||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="Переключить сцену"
|
AdvSceneSwitcher.action.scene="Переключить сцену"
|
||||||
AdvSceneSwitcher.action.scene.entry="Перейти к сцене {{scenes}} используя {{transitions}} с продолжительностью {{duration}} секунд"
|
AdvSceneSwitcher.action.scene.entry="Перейти к сцене{{sceneTypes}}{{scenes}}используя{{transitions}}с продолжительностью{{duration}}секунд"
|
||||||
AdvSceneSwitcher.action.wait="Подождать"
|
AdvSceneSwitcher.action.wait="Подождать"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="фиксированный"
|
AdvSceneSwitcher.action.wait.type.fixed="фиксированный"
|
||||||
AdvSceneSwitcher.action.wait.type.random="случайный"
|
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.line1="{{sceneType}}{{scenes}}{{pattern}}"
|
||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Pencere"
|
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="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.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||||
AdvSceneSwitcher.condition.media="Medya"
|
AdvSceneSwitcher.condition.media="Medya"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
|
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
|
||||||
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
|
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}}"
|
AdvSceneSwitcher.condition.openvr.entry.line3="HMD mevcut {{xPos}} x {{yPos}} x {{zPos}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="Sahne Degistirici"
|
AdvSceneSwitcher.action.scene="Sahne Degistirici"
|
||||||
AdvSceneSwitcher.action.scene.entry="Sahneyi {{scenes}} kullanarak {{transitions}} süresi olan {{duration}} saniye"
|
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.scene.blockUntilTransitionDone="Hedef sahneye geçiş tamamlanana kadar bekleyin"
|
||||||
AdvSceneSwitcher.action.wait="Bekle"
|
AdvSceneSwitcher.action.wait="Bekle"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="sabit"
|
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.type.start="Yayın başlat"
|
||||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||||
AdvSceneSwitcher.action.run="Çalıştır"
|
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="Sahne öğesi görünürlüğü"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
|
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
|
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.rightMeta="Sağ Meta"
|
||||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Tuşa basımı yalnızca OBS'ye gönder"
|
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.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="Sahne öğesi sırası"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Yukarı taşı"
|
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Yukarı taşı"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Aşağı indir"
|
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.type.dateChange="修改日期已更改"
|
||||||
AdvSceneSwitcher.condition.file.remote="远程文件"
|
AdvSceneSwitcher.condition.file.remote="远程文件"
|
||||||
AdvSceneSwitcher.condition.file.local="本地文件"
|
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.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||||
AdvSceneSwitcher.condition.media="媒体"
|
AdvSceneSwitcher.condition.media="媒体"
|
||||||
AdvSceneSwitcher.condition.media.source="源"
|
AdvSceneSwitcher.condition.media.source="源"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
|
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
|
||||||
@@ -448,9 +448,9 @@ AdvSceneSwitcher.condition.display.type.displayCount="连接的显示器的数
|
|||||||
AdvSceneSwitcher.condition.display.entry="{{conditions}}{{displays}}{{regex}}{{displayCount}}"
|
AdvSceneSwitcher.condition.display.entry="{{conditions}}{{displays}}{{regex}}{{displayCount}}"
|
||||||
|
|
||||||
; Macro Actions
|
; Macro Actions
|
||||||
AdvSceneSwitcher.action.switchScene="切换场景"
|
AdvSceneSwitcher.action.scene="切换场景"
|
||||||
AdvSceneSwitcher.action.scene.entry="切换场景 {{scenes}} 使用 {{transitions}} 时长 {{duration}} 秒"
|
AdvSceneSwitcher.action.scene.entry="切换场景{{sceneTypes}}{{scenes}}使用{{transitions}}时长{{duration}}秒"
|
||||||
AdvSceneSwitcher.action.scene.entry.noDuration="切换到场景{{scenes}}使用{{transitions}}"
|
AdvSceneSwitcher.action.scene.entry.noDuration="切换到场景{{sceneTypes}}{{scenes}}使用{{transitions}}"
|
||||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待目标场景的过渡完成"
|
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待目标场景的过渡完成"
|
||||||
AdvSceneSwitcher.action.wait="等待"
|
AdvSceneSwitcher.action.wait="等待"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="固定数值"
|
AdvSceneSwitcher.action.wait.type.fixed="固定数值"
|
||||||
@@ -559,7 +559,6 @@ AdvSceneSwitcher.action.hotkey.leftMeta="左 Meta"
|
|||||||
AdvSceneSwitcher.action.hotkey.rightMeta="右 Meta"
|
AdvSceneSwitcher.action.hotkey.rightMeta="右 Meta"
|
||||||
AdvSceneSwitcher.action.hotkey.onlyOBS="仅向OBS发送按键"
|
AdvSceneSwitcher.action.hotkey.onlyOBS="仅向OBS发送按键"
|
||||||
AdvSceneSwitcher.action.hotkey.disabled="无法模拟按键-功能已禁用!"
|
AdvSceneSwitcher.action.hotkey.disabled="无法模拟按键-功能已禁用!"
|
||||||
AdvSceneSwitcher.action.hotkey.entry="按下 {{keys}} 在 {{duration}} 秒"
|
|
||||||
AdvSceneSwitcher.action.sceneOrder="场景项目顺序"
|
AdvSceneSwitcher.action.sceneOrder="场景项目顺序"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="上移"
|
AdvSceneSwitcher.action.sceneOrder.type.moveUp="上移"
|
||||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="下移"
|
AdvSceneSwitcher.action.sceneOrder.type.moveDown="下移"
|
||||||
@@ -624,8 +623,6 @@ AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
|
|||||||
AdvSceneSwitcher.action.websocket="websocket"
|
AdvSceneSwitcher.action.websocket="websocket"
|
||||||
AdvSceneSwitcher.action.websocket.type.request="request"
|
AdvSceneSwitcher.action.websocket.type.request="request"
|
||||||
AdvSceneSwitcher.action.websocket.type.event="event"
|
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="Http"
|
||||||
AdvSceneSwitcher.action.http.setHeaders="设置头信息(SET headers)"
|
AdvSceneSwitcher.action.http.setHeaders="设置头信息(SET headers)"
|
||||||
AdvSceneSwitcher.action.http.headers="头信息(headers):"
|
AdvSceneSwitcher.action.http.headers="头信息(headers):"
|
||||||
@@ -655,7 +652,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="无效选择!"
|
|||||||
AdvSceneSwitcher.action.variable.actionNoVariableSupport="不支持从 %1 个操作获取变量值!"
|
AdvSceneSwitcher.action.variable.actionNoVariableSupport="不支持从 %1 个操作获取变量值!"
|
||||||
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条件中获取变量值!"
|
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条件中获取变量值!"
|
||||||
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
|
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.substringIndex="子字符串开始:{{subStringStart}} 子字符串大小:{{subStringSize}}"
|
||||||
AdvSceneSwitcher.action.variable.entry.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
|
AdvSceneSwitcher.action.variable.entry.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
|
||||||
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
|
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>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>957</width>
|
<width>962</width>
|
||||||
<height>905</height>
|
<height>1033</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||||
@@ -256,6 +256,30 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_26">
|
<layout class="QHBoxLayout" name="horizontalLayout_26">
|
||||||
<item>
|
<item>
|
||||||
@@ -805,7 +829,7 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>AdvSceneSwitcher.macroTab.edit</string>
|
<string>AdvSceneSwitcher.macroTab.edit</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_33">
|
<layout class="QVBoxLayout" name="verticalLayout_38">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0,0,0,0,0,0,0">
|
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0,0,0,0,0,0,0">
|
||||||
<item>
|
<item>
|
||||||
@@ -819,7 +843,7 @@
|
|||||||
<widget class="QLineEdit" name="macroName"/>
|
<widget class="QLineEdit" name="macroName"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="runMacro">
|
<widget class="advss::MacroRunButton" name="runMacro">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>AdvSceneSwitcher.macroTab.run</string>
|
<string>AdvSceneSwitcher.macroTab.run</string>
|
||||||
</property>
|
</property>
|
||||||
@@ -912,6 +936,16 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="advss::MacroSegmentList" name="conditionsList">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>1</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
@@ -1066,170 +1100,360 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="macroActions" native="true">
|
<widget class="QSplitter" name="macroElseActionSplitter">
|
||||||
<layout class="QVBoxLayout" name="macroActionsLayout">
|
<property name="orientation">
|
||||||
<property name="leftMargin">
|
<enum>Qt::Vertical</enum>
|
||||||
<number>0</number>
|
</property>
|
||||||
</property>
|
<widget class="QWidget" name="macroActions" native="true">
|
||||||
<property name="topMargin">
|
<layout class="QVBoxLayout" name="macroActionsLayout">
|
||||||
<number>0</number>
|
<property name="leftMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
<property name="rightMargin">
|
</property>
|
||||||
<number>0</number>
|
<property name="topMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
<property name="bottomMargin">
|
</property>
|
||||||
<number>0</number>
|
<property name="rightMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
<item>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
<property name="bottomMargin">
|
||||||
<property name="leftMargin">
|
<number>0</number>
|
||||||
<number>9</number>
|
</property>
|
||||||
</property>
|
<item>
|
||||||
<item>
|
<widget class="advss::MacroSegmentList" name="actionsList">
|
||||||
<widget class="QPushButton" name="actionAdd">
|
<property name="minimumSize">
|
||||||
<property name="maximumSize">
|
<size>
|
||||||
<size>
|
<width>0</width>
|
||||||
<width>22</width>
|
<height>1</height>
|
||||||
<height>16777215</height>
|
</size>
|
||||||
</size>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<property name="flat">
|
</item>
|
||||||
<bool>true</bool>
|
<item>
|
||||||
</property>
|
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||||
<property name="themeID" stdset="0">
|
<property name="leftMargin">
|
||||||
<string notr="true">addIconSmall</string>
|
<number>9</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="actionAdd">
|
||||||
<item>
|
<property name="maximumSize">
|
||||||
<widget class="QPushButton" name="actionRemove">
|
<size>
|
||||||
<property name="maximumSize">
|
<width>22</width>
|
||||||
<size>
|
<height>16777215</height>
|
||||||
<width>22</width>
|
</size>
|
||||||
<height>16777215</height>
|
</property>
|
||||||
</size>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
<property name="flat">
|
</property>
|
||||||
<bool>true</bool>
|
<property name="themeID" stdset="0">
|
||||||
</property>
|
<string notr="true">addIconSmall</string>
|
||||||
<property name="themeID" stdset="0">
|
</property>
|
||||||
<string notr="true">removeIconSmall</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="actionRemove">
|
||||||
<item>
|
<property name="maximumSize">
|
||||||
<spacer name="horizontalSpacer_133">
|
<size>
|
||||||
<property name="orientation">
|
<width>22</width>
|
||||||
<enum>Qt::Horizontal</enum>
|
<height>16777215</height>
|
||||||
</property>
|
</size>
|
||||||
<property name="sizeType">
|
</property>
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
<property name="themeID" stdset="0">
|
||||||
<width>5</width>
|
<string notr="true">removeIconSmall</string>
|
||||||
<height>20</height>
|
</property>
|
||||||
</size>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</spacer>
|
<item>
|
||||||
</item>
|
<spacer name="horizontalSpacer_133">
|
||||||
<item>
|
<property name="orientation">
|
||||||
<widget class="Line" name="line_47">
|
<enum>Qt::Horizontal</enum>
|
||||||
<property name="orientation">
|
</property>
|
||||||
<enum>Qt::Vertical</enum>
|
<property name="sizeType">
|
||||||
</property>
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="sizeHint" stdset="0">
|
||||||
<item>
|
<size>
|
||||||
<spacer name="horizontalSpacer_132">
|
<width>5</width>
|
||||||
<property name="orientation">
|
<height>20</height>
|
||||||
<enum>Qt::Horizontal</enum>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
</spacer>
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
<property name="sizeHint" stdset="0">
|
<widget class="Line" name="line_47">
|
||||||
<size>
|
<property name="orientation">
|
||||||
<width>5</width>
|
<enum>Qt::Vertical</enum>
|
||||||
<height>20</height>
|
</property>
|
||||||
</size>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</spacer>
|
<item>
|
||||||
</item>
|
<spacer name="horizontalSpacer_132">
|
||||||
<item>
|
<property name="orientation">
|
||||||
<widget class="QPushButton" name="actionTop">
|
<enum>Qt::Horizontal</enum>
|
||||||
<property name="maximumSize">
|
</property>
|
||||||
<size>
|
<property name="sizeType">
|
||||||
<width>22</width>
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
<height>16777215</height>
|
</property>
|
||||||
</size>
|
<property name="sizeHint" stdset="0">
|
||||||
</property>
|
<size>
|
||||||
<property name="flat">
|
<width>5</width>
|
||||||
<bool>true</bool>
|
<height>20</height>
|
||||||
</property>
|
</size>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</spacer>
|
||||||
<item>
|
</item>
|
||||||
<widget class="QPushButton" name="actionUp">
|
<item>
|
||||||
<property name="maximumSize">
|
<widget class="QPushButton" name="actionTop">
|
||||||
<size>
|
<property name="maximumSize">
|
||||||
<width>22</width>
|
<size>
|
||||||
<height>16777215</height>
|
<width>22</width>
|
||||||
</size>
|
<height>16777215</height>
|
||||||
</property>
|
</size>
|
||||||
<property name="flat">
|
</property>
|
||||||
<bool>true</bool>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
<property name="themeID" stdset="0">
|
</property>
|
||||||
<string notr="true">upArrowIconSmall</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="actionUp">
|
||||||
<item>
|
<property name="maximumSize">
|
||||||
<widget class="QPushButton" name="actionDown">
|
<size>
|
||||||
<property name="maximumSize">
|
<width>22</width>
|
||||||
<size>
|
<height>16777215</height>
|
||||||
<width>22</width>
|
</size>
|
||||||
<height>16777215</height>
|
</property>
|
||||||
</size>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
<property name="flat">
|
</property>
|
||||||
<bool>true</bool>
|
<property name="themeID" stdset="0">
|
||||||
</property>
|
<string notr="true">upArrowIconSmall</string>
|
||||||
<property name="themeID" stdset="0">
|
</property>
|
||||||
<string notr="true">downArrowIconSmall</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QPushButton" name="actionDown">
|
||||||
<item>
|
<property name="maximumSize">
|
||||||
<widget class="QPushButton" name="actionBottom">
|
<size>
|
||||||
<property name="maximumSize">
|
<width>22</width>
|
||||||
<size>
|
<height>16777215</height>
|
||||||
<width>22</width>
|
</size>
|
||||||
<height>16777215</height>
|
</property>
|
||||||
</size>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
<property name="flat">
|
</property>
|
||||||
<bool>true</bool>
|
<property name="themeID" stdset="0">
|
||||||
</property>
|
<string notr="true">downArrowIconSmall</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<spacer name="horizontalSpacer_14">
|
<item>
|
||||||
<property name="orientation">
|
<widget class="QPushButton" name="actionBottom">
|
||||||
<enum>Qt::Horizontal</enum>
|
<property name="maximumSize">
|
||||||
</property>
|
<size>
|
||||||
<property name="sizeHint" stdset="0">
|
<width>22</width>
|
||||||
<size>
|
<height>16777215</height>
|
||||||
<width>40</width>
|
</size>
|
||||||
<height>20</height>
|
</property>
|
||||||
</size>
|
<property name="flat">
|
||||||
</property>
|
<bool>true</bool>
|
||||||
</spacer>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
</layout>
|
<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>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -4619,6 +4843,17 @@
|
|||||||
<extends>QListView</extends>
|
<extends>QListView</extends>
|
||||||
<header>macro-tree.hpp</header>
|
<header>macro-tree.hpp</header>
|
||||||
</customwidget>
|
</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>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<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 "advanced-scene-switcher.hpp"
|
||||||
#include "switcher-data.hpp"
|
#include "switcher-data.hpp"
|
||||||
#include "status-control.hpp"
|
#include "status-control.hpp"
|
||||||
@@ -17,6 +7,15 @@
|
|||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
#include "version.h"
|
#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 {
|
namespace advss {
|
||||||
|
|
||||||
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
||||||
@@ -328,12 +327,14 @@ void SwitcherData::SetPreconditions()
|
|||||||
lastCursorPos = GetCursorPos();
|
lastCursorPos = GetCursorPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClearWebsocketMessages();
|
||||||
|
|
||||||
void SwitcherData::ResetForNextInterval()
|
void SwitcherData::ResetForNextInterval()
|
||||||
{
|
{
|
||||||
// Core reset functions
|
// Core reset functions
|
||||||
ClearWebsocketMessages();
|
ClearWebsocketMessages();
|
||||||
// Plugin reset functions
|
// Plugin reset functions
|
||||||
for (const auto &func : resetForNextIntervalFuncs) {
|
for (const auto &func : resetIntervalSteps) {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public slots:
|
|||||||
void on_saveWindowGeo_stateChanged(int state);
|
void on_saveWindowGeo_stateChanged(int state);
|
||||||
void on_showTrayNotifications_stateChanged(int state);
|
void on_showTrayNotifications_stateChanged(int state);
|
||||||
void on_uiHintsDisable_stateChanged(int state);
|
void on_uiHintsDisable_stateChanged(int state);
|
||||||
|
void on_disableComboBoxFilter_stateChanged(int state);
|
||||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||||
void on_hideLegacyTabs_stateChanged(int state);
|
void on_hideLegacyTabs_stateChanged(int state);
|
||||||
void on_priorityUp_clicked();
|
void on_priorityUp_clicked();
|
||||||
@@ -83,10 +84,13 @@ public:
|
|||||||
void SetEditMacro(Macro &m);
|
void SetEditMacro(Macro &m);
|
||||||
void SetMacroEditAreaDisabled(bool);
|
void SetMacroEditAreaDisabled(bool);
|
||||||
void HighlightAction(int idx, QColor color = QColor(Qt::green));
|
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 HighlightCondition(int idx, QColor color = QColor(Qt::green));
|
||||||
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
|
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 PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
||||||
void SetActionData(Macro &m);
|
void SetActionData(Macro &m);
|
||||||
|
void SetElseActionData(Macro &m);
|
||||||
void SetConditionData(Macro &m);
|
void SetConditionData(Macro &m);
|
||||||
void SwapActions(Macro *m, int pos1, int pos2);
|
void SwapActions(Macro *m, int pos1, int pos2);
|
||||||
void SwapConditions(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_macroUp_clicked();
|
||||||
void on_macroDown_clicked();
|
void on_macroDown_clicked();
|
||||||
void on_macroName_editingFinished();
|
void on_macroName_editingFinished();
|
||||||
void on_runMacro_clicked();
|
|
||||||
void on_runMacroInParallel_stateChanged(int value);
|
void on_runMacroInParallel_stateChanged(int value);
|
||||||
void on_runMacroOnChange_stateChanged(int value);
|
void on_runMacroOnChange_stateChanged(int value);
|
||||||
void on_conditionAdd_clicked();
|
void on_conditionAdd_clicked();
|
||||||
@@ -112,29 +115,50 @@ public slots:
|
|||||||
void on_actionUp_clicked();
|
void on_actionUp_clicked();
|
||||||
void on_actionDown_clicked();
|
void on_actionDown_clicked();
|
||||||
void on_actionBottom_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 MacroSelectionChanged();
|
||||||
void UpMacroSegementHotkey();
|
void UpMacroSegementHotkey();
|
||||||
void DownMacroSegementHotkey();
|
void DownMacroSegementHotkey();
|
||||||
void DeleteMacroSegementHotkey();
|
void DeleteMacroSegementHotkey();
|
||||||
void ShowMacroContextMenu(const QPoint &);
|
void ShowMacroContextMenu(const QPoint &);
|
||||||
void ShowMacroActionsContextMenu(const QPoint &);
|
void ShowMacroActionsContextMenu(const QPoint &);
|
||||||
|
void ShowMacroElseActionsContextMenu(const QPoint &);
|
||||||
void ShowMacroConditionsContextMenu(const QPoint &);
|
void ShowMacroConditionsContextMenu(const QPoint &);
|
||||||
void CopyMacro();
|
void CopyMacro();
|
||||||
void RenameCurrentMacro();
|
void RenameCurrentMacro();
|
||||||
void ExportMacros();
|
void ExportMacros();
|
||||||
void ImportMacros();
|
void ImportMacros();
|
||||||
void ExpandAllActions();
|
void ExpandAllActions();
|
||||||
|
void ExpandAllElseActions();
|
||||||
void ExpandAllConditions();
|
void ExpandAllConditions();
|
||||||
void CollapseAllActions();
|
void CollapseAllActions();
|
||||||
|
void CollapseAllElseActions();
|
||||||
void CollapseAllConditions();
|
void CollapseAllConditions();
|
||||||
void MinimizeActions();
|
void MinimizeActions();
|
||||||
|
void MaximizeActions();
|
||||||
|
void MinimizeElseActions();
|
||||||
|
void MaximizeElseActions();
|
||||||
void MinimizeConditions();
|
void MinimizeConditions();
|
||||||
|
void MaximizeConditions();
|
||||||
void MacroActionSelectionChanged(int idx);
|
void MacroActionSelectionChanged(int idx);
|
||||||
void MacroActionReorder(int to, int target);
|
void MacroActionReorder(int to, int target);
|
||||||
void AddMacroAction(int idx);
|
void AddMacroAction(int idx);
|
||||||
void RemoveMacroAction(int idx);
|
void RemoveMacroAction(int idx);
|
||||||
void MoveMacroActionUp(int idx);
|
void MoveMacroActionUp(int idx);
|
||||||
void MoveMacroActionDown(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 MacroConditionSelectionChanged(int idx);
|
||||||
void MacroConditionReorder(int to, int target);
|
void MacroConditionReorder(int to, int target);
|
||||||
void AddMacroCondition(int idx);
|
void AddMacroCondition(int idx);
|
||||||
@@ -156,6 +180,7 @@ signals:
|
|||||||
void MacroSegmentOrderChanged();
|
void MacroSegmentOrderChanged();
|
||||||
void HighlightMacrosChanged(bool value);
|
void HighlightMacrosChanged(bool value);
|
||||||
void HighlightActionsChanged(bool value);
|
void HighlightActionsChanged(bool value);
|
||||||
|
void HighlightElseActionsChanged(bool value);
|
||||||
void HighlightConditionsChanged(bool value);
|
void HighlightConditionsChanged(bool value);
|
||||||
|
|
||||||
void ConnectionAdded(const QString &);
|
void ConnectionAdded(const QString &);
|
||||||
@@ -166,19 +191,16 @@ signals:
|
|||||||
void VariableRemoved(const QString &);
|
void VariableRemoved(const QString &);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
|
||||||
|
|
||||||
|
void SetupMacroSegmentSelection(MacroSection type, int idx);
|
||||||
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
|
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
|
||||||
bool MacroTabIsInFocus();
|
bool MacroTabIsInFocus();
|
||||||
|
|
||||||
MacroSegmentList *conditionsList = nullptr;
|
|
||||||
MacroSegmentList *actionsList = nullptr;
|
|
||||||
|
|
||||||
enum class MacroSection {
|
|
||||||
CONDITIONS,
|
|
||||||
ACTIONS,
|
|
||||||
};
|
|
||||||
MacroSection lastInteracted = MacroSection::CONDITIONS;
|
MacroSection lastInteracted = MacroSection::CONDITIONS;
|
||||||
int currentConditionIdx = -1;
|
int currentConditionIdx = -1;
|
||||||
int currentActionIdx = -1;
|
int currentActionIdx = -1;
|
||||||
|
int currentElseActionIdx = -1;
|
||||||
|
|
||||||
/* --- End of macro tab section --- */
|
/* --- End of macro tab section --- */
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "switcher-data.hpp"
|
#include "switcher-data.hpp"
|
||||||
#include "status-control.hpp"
|
#include "status-control.hpp"
|
||||||
#include "file-selection.hpp"
|
#include "file-selection.hpp"
|
||||||
|
#include "filter-combo-box.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -131,10 +132,9 @@ void AdvSceneSwitcher::closeEvent(QCloseEvent *)
|
|||||||
}
|
}
|
||||||
switcher->windowPos = this->pos();
|
switcher->windowPos = this->pos();
|
||||||
switcher->windowSize = this->size();
|
switcher->windowSize = this->size();
|
||||||
switcher->macroActionConditionSplitterPosition =
|
|
||||||
ui->macroActionConditionSplitter->sizes();
|
|
||||||
switcher->macroListMacroEditSplitterPosition =
|
switcher->macroListMacroEditSplitterPosition =
|
||||||
ui->macroListMacroEditSplitter->sizes();
|
ui->macroListMacroEditSplitter->sizes();
|
||||||
|
MacroSelectionAboutToChange(); // Trigger saving of splitter states
|
||||||
|
|
||||||
obs_frontend_save();
|
obs_frontend_save();
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,16 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
|||||||
switcher->disableHints = 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)
|
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
|
||||||
{
|
{
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -494,6 +504,11 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
|||||||
loadSceneGroups(obj);
|
loadSceneGroups(obj);
|
||||||
LoadVariables(obj);
|
LoadVariables(obj);
|
||||||
LoadConnections(obj);
|
LoadConnections(obj);
|
||||||
|
|
||||||
|
for (const auto &func : loadSteps) {
|
||||||
|
func(obj);
|
||||||
|
}
|
||||||
|
|
||||||
LoadMacros(obj);
|
LoadMacros(obj);
|
||||||
loadWindowTitleSwitches(obj);
|
loadWindowTitleSwitches(obj);
|
||||||
loadScreenRegionSwitches(obj);
|
loadScreenRegionSwitches(obj);
|
||||||
@@ -548,6 +563,10 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
|||||||
SaveHotkeys(obj);
|
SaveHotkeys(obj);
|
||||||
SaveUISettings(obj);
|
SaveUISettings(obj);
|
||||||
SaveVersion(obj, g_GIT_SHA1);
|
SaveVersion(obj, g_GIT_SHA1);
|
||||||
|
|
||||||
|
for (const auto &func : saveSteps) {
|
||||||
|
func(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwitcherData::SaveGeneralSettings(obs_data_t *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",
|
obs_data_set_bool(obj, "showSystemTrayNotifications",
|
||||||
showSystemTrayNotifications);
|
showSystemTrayNotifications);
|
||||||
obs_data_set_bool(obj, "disableHints", disableHints);
|
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, "warnPluginLoadFailure", warnPluginLoadFailure);
|
||||||
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
|
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
|
||||||
|
|
||||||
@@ -623,6 +644,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
|||||||
showSystemTrayNotifications =
|
showSystemTrayNotifications =
|
||||||
obs_data_get_bool(obj, "showSystemTrayNotifications");
|
obs_data_get_bool(obj, "showSystemTrayNotifications");
|
||||||
disableHints = obs_data_get_bool(obj, "disableHints");
|
disableHints = obs_data_get_bool(obj, "disableHints");
|
||||||
|
disableFilterComboboxFilter =
|
||||||
|
obs_data_get_bool(obj, "disableFilterComboboxFilter");
|
||||||
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
|
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
|
||||||
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
|
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
|
||||||
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
|
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");
|
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)
|
void SwitcherData::SaveUISettings(obs_data_t *obj)
|
||||||
{
|
{
|
||||||
obs_data_set_int(obj, "generalTabPos", tabOrder[0]);
|
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, "windowWidth", windowSize.width());
|
||||||
obs_data_set_int(obj, "windowHeight", windowSize.height());
|
obs_data_set_int(obj, "windowHeight", windowSize.height());
|
||||||
|
|
||||||
saveSplitterPos(macroActionConditionSplitterPosition, obj,
|
SaveSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||||
"macroActionConditionSplitterPosition");
|
|
||||||
saveSplitterPos(macroListMacroEditSplitterPosition, obj,
|
|
||||||
"macroListMacroEditSplitterPosition");
|
"macroListMacroEditSplitterPosition");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,9 +756,8 @@ void SwitcherData::LoadUISettings(obs_data_t *obj)
|
|||||||
(int)obs_data_get_int(obj, "windowPosY")};
|
(int)obs_data_get_int(obj, "windowPosY")};
|
||||||
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
|
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
|
||||||
(int)obs_data_get_int(obj, "windowHeight")};
|
(int)obs_data_get_int(obj, "windowHeight")};
|
||||||
loadSplitterPos(macroActionConditionSplitterPosition, obj,
|
|
||||||
"macroActionConditionSplitterPosition");
|
LoadSplitterPos(macroListMacroEditSplitterPosition, obj,
|
||||||
loadSplitterPos(macroListMacroEditSplitterPosition, obj,
|
|
||||||
"macroListMacroEditSplitterPosition");
|
"macroListMacroEditSplitterPosition");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,6 +989,10 @@ void AdvSceneSwitcher::SetupGeneralTab()
|
|||||||
ui->showTrayNotifications->setChecked(
|
ui->showTrayNotifications->setChecked(
|
||||||
switcher->showSystemTrayNotifications);
|
switcher->showSystemTrayNotifications);
|
||||||
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
||||||
|
ui->disableComboBoxFilter->setChecked(
|
||||||
|
switcher->disableFilterComboboxFilter);
|
||||||
|
FilterComboBox::SetFilterBehaviourEnabled(
|
||||||
|
!switcher->disableFilterComboboxFilter);
|
||||||
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
||||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||||
|
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ bool IsFullscreen(const std::string &title)
|
|||||||
return windowStatesAreSet(title, states);
|
return windowStatesAreSet(title, states);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> GetTextInWindow(const std::string &window)
|
std::optional<std::string> GetTextInWindow(const std::string &)
|
||||||
{
|
{
|
||||||
// Not implemented
|
// Not implemented
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
|
|||||||
obs_data_release(data);
|
obs_data_release(data);
|
||||||
}
|
}
|
||||||
macro->UpdateActionIndices();
|
macro->UpdateActionIndices();
|
||||||
actionsList->Insert(
|
ui->actionsList->Insert(
|
||||||
idx,
|
idx,
|
||||||
new MacroActionEdit(this, ¯o->Actions()[idx], id));
|
new MacroActionEdit(this, ¯o->Actions()[idx], id));
|
||||||
SetActionData(*macro);
|
SetActionData(*macro);
|
||||||
@@ -263,7 +263,7 @@ void AdvSceneSwitcher::on_actionAdd_clicked()
|
|||||||
if (currentActionIdx != -1) {
|
if (currentActionIdx != -1) {
|
||||||
MacroActionSelectionChanged(currentActionIdx + 1);
|
MacroActionSelectionChanged(currentActionIdx + 1);
|
||||||
}
|
}
|
||||||
actionsList->SetHelpMsgVisible(false);
|
ui->actionsList->SetHelpMsgVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||||
@@ -279,7 +279,7 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
|||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
std::lock_guard<std::mutex> lock(switcher->m);
|
||||||
actionsList->Remove(idx);
|
ui->actionsList->Remove(idx);
|
||||||
macro->Actions().erase(macro->Actions().begin() + idx);
|
macro->Actions().erase(macro->Actions().begin() + idx);
|
||||||
switcher->abortMacroWait = true;
|
switcher->abortMacroWait = true;
|
||||||
switcher->macroWaitCv.notify_all();
|
switcher->macroWaitCv.notify_all();
|
||||||
@@ -322,10 +322,11 @@ void AdvSceneSwitcher::on_actionUp_clicked()
|
|||||||
MoveMacroActionUp(currentActionIdx);
|
MoveMacroActionUp(currentActionIdx);
|
||||||
MacroActionSelectionChanged(currentActionIdx - 1);
|
MacroActionSelectionChanged(currentActionIdx - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::on_actionDown_clicked()
|
void AdvSceneSwitcher::on_actionDown_clicked()
|
||||||
{
|
{
|
||||||
if (currentActionIdx == -1 ||
|
if (currentActionIdx == -1 ||
|
||||||
currentActionIdx == actionsList->ContentLayout()->count() - 1) {
|
currentActionIdx == ui->actionsList->ContentLayout()->count() - 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MoveMacroActionDown(currentActionIdx);
|
MoveMacroActionDown(currentActionIdx);
|
||||||
@@ -337,11 +338,82 @@ void AdvSceneSwitcher::on_actionBottom_clicked()
|
|||||||
if (currentActionIdx == -1) {
|
if (currentActionIdx == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int newIdx = actionsList->ContentLayout()->count() - 1;
|
const int newIdx = ui->actionsList->ContentLayout()->count() - 1;
|
||||||
MacroActionReorder(newIdx, currentActionIdx);
|
MacroActionReorder(newIdx, currentActionIdx);
|
||||||
MacroActionSelectionChanged(newIdx);
|
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)
|
void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
|
||||||
{
|
{
|
||||||
if (pos1 == 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);
|
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
|
||||||
m->UpdateActionIndices();
|
m->UpdateActionIndices();
|
||||||
auto widget1 = static_cast<MacroActionEdit *>(
|
auto widget1 = static_cast<MacroActionEdit *>(
|
||||||
actionsList->ContentLayout()->takeAt(pos1)->widget());
|
ui->actionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||||
auto widget2 = static_cast<MacroActionEdit *>(
|
auto widget2 = static_cast<MacroActionEdit *>(
|
||||||
actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
ui->actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||||
actionsList->Insert(pos1, widget2);
|
ui->actionsList->Insert(pos1, widget2);
|
||||||
actionsList->Insert(pos2, widget1);
|
ui->actionsList->Insert(pos2, widget1);
|
||||||
SetActionData(*m);
|
SetActionData(*m);
|
||||||
emit(MacroSegmentOrderChanged());
|
emit(MacroSegmentOrderChanged());
|
||||||
}
|
}
|
||||||
@@ -394,24 +466,158 @@ void AdvSceneSwitcher::MoveMacroActionDown(int idx)
|
|||||||
HighlightAction(idx + 1);
|
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();
|
auto macro = GetSelectedMacro();
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsList->SetSelection(idx);
|
if (to == from || from < 0 || from > (int)macro->ElseActions().size() ||
|
||||||
conditionsList->SetSelection(-1);
|
to < 0 || to > (int)macro->ElseActions().size()) {
|
||||||
|
return;
|
||||||
if (idx < 0 || (unsigned)idx >= macro->Actions().size()) {
|
|
||||||
currentActionIdx = -1;
|
|
||||||
} else {
|
|
||||||
currentActionIdx = idx;
|
|
||||||
lastInteracted = MacroSection::ACTIONS;
|
|
||||||
}
|
}
|
||||||
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)
|
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().erase(macro->Actions().begin() + from);
|
||||||
macro->Actions().insert(macro->Actions().begin() + to, action);
|
macro->Actions().insert(macro->Actions().begin() + to, action);
|
||||||
macro->UpdateActionIndices();
|
macro->UpdateActionIndices();
|
||||||
actionsList->ContentLayout()->insertItem(
|
ui->actionsList->ContentLayout()->insertItem(
|
||||||
to, actionsList->ContentLayout()->takeAt(from));
|
to, ui->actionsList->ContentLayout()->takeAt(from));
|
||||||
SetActionData(*macro);
|
SetActionData(*macro);
|
||||||
}
|
}
|
||||||
HighlightAction(to);
|
HighlightAction(to);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "platform-funcs.hpp"
|
#include "platform-funcs.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include <obs-interaction.h>
|
#include <obs-interaction.h>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
@@ -723,7 +724,7 @@ static QString getHotkeyDescriptionByName(const std::string &name)
|
|||||||
QString description = "";
|
QString description = "";
|
||||||
} params;
|
} 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);
|
auto params = static_cast<Parameters *>(param);
|
||||||
std::string name = obs_hotkey_get_name(hotkey);
|
std::string name = obs_hotkey_get_name(hotkey);
|
||||||
addNamePrefix(name, hotkey);
|
addNamePrefix(name, hotkey);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ bool MacroActionMacro::PerformAction()
|
|||||||
break;
|
break;
|
||||||
case Action::RUN:
|
case Action::RUN:
|
||||||
if (!macro->Paused()) {
|
if (!macro->Paused()) {
|
||||||
macro->PerformActions();
|
macro->PerformActions(true, false, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Action::STOP:
|
case Action::STOP:
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ bool MacroActionProjector::PerformAction()
|
|||||||
break;
|
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, "",
|
obs_frontend_open_projector(type, _fullscreen ? _monitor : -1, "",
|
||||||
name.c_str());
|
name.c_str());
|
||||||
|
|
||||||
@@ -85,6 +91,7 @@ bool MacroActionProjector::Save(obs_data_t *obj) const
|
|||||||
MacroAction::Save(obj);
|
MacroAction::Save(obj);
|
||||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||||
obs_data_set_int(obj, "monitor", _monitor);
|
obs_data_set_int(obj, "monitor", _monitor);
|
||||||
|
obs_data_set_string(obj, "monitorName", _monitorName.c_str());
|
||||||
obs_data_set_bool(obj, "fullscreen", _fullscreen);
|
obs_data_set_bool(obj, "fullscreen", _fullscreen);
|
||||||
_scene.Save(obj);
|
_scene.Save(obj);
|
||||||
_source.Save(obj);
|
_source.Save(obj);
|
||||||
@@ -96,12 +103,50 @@ bool MacroActionProjector::Load(obs_data_t *obj)
|
|||||||
MacroAction::Load(obj);
|
MacroAction::Load(obj);
|
||||||
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||||
_monitor = obs_data_get_int(obj, "monitor");
|
_monitor = obs_data_get_int(obj, "monitor");
|
||||||
|
_monitorName = obs_data_get_string(obj, "monitorName");
|
||||||
_fullscreen = obs_data_get_bool(obj, "fullscreen");
|
_fullscreen = obs_data_get_bool(obj, "fullscreen");
|
||||||
_scene.Load(obj);
|
_scene.Load(obj);
|
||||||
_source.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;
|
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)
|
static inline void populateSelectionTypes(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (auto entry : selectionTypes) {
|
for (auto entry : selectionTypes) {
|
||||||
@@ -134,6 +179,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
|||||||
sources.sort();
|
sources.sort();
|
||||||
_sources->SetSourceNameList(sources);
|
_sources->SetSourceNameList(sources);
|
||||||
_monitors->addItems(GetMonitorNames());
|
_monitors->addItems(GetMonitorNames());
|
||||||
|
_monitors->setPlaceholderText(
|
||||||
|
obs_module_text("AdvSceneSwitcher.selectDisplay"));
|
||||||
|
|
||||||
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
|
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||||
SLOT(WindowTypeChanged(int)));
|
SLOT(WindowTypeChanged(int)));
|
||||||
@@ -177,7 +224,7 @@ void MacroActionProjectorEdit::UpdateEntryData()
|
|||||||
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
|
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||||
_scenes->SetScene(_entryData->_scene);
|
_scenes->SetScene(_entryData->_scene);
|
||||||
_sources->SetSource(_entryData->_source);
|
_sources->SetSource(_entryData->_source);
|
||||||
_monitors->setCurrentIndex(_entryData->_monitor);
|
_monitors->setCurrentIndex(_entryData->GetMonitor());
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +255,7 @@ void MacroActionProjectorEdit::MonitorChanged(int value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_monitor = value;
|
_entryData->SetMonitor(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionProjectorEdit::WindowTypeChanged(int)
|
void MacroActionProjectorEdit::WindowTypeChanged(int)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public:
|
|||||||
{
|
{
|
||||||
return std::make_shared<MacroActionProjector>(m);
|
return std::make_shared<MacroActionProjector>(m);
|
||||||
}
|
}
|
||||||
|
void SetMonitor(int);
|
||||||
|
int GetMonitor() const;
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
SOURCE,
|
SOURCE,
|
||||||
@@ -29,10 +31,15 @@ public:
|
|||||||
Type _type = Type::SCENE;
|
Type _type = Type::SCENE;
|
||||||
SourceSelection _source;
|
SourceSelection _source;
|
||||||
SceneSelection _scene;
|
SceneSelection _scene;
|
||||||
int _monitor = 0;
|
|
||||||
bool _fullscreen = true;
|
bool _fullscreen = true;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool MonitorSetupChanged();
|
||||||
|
|
||||||
|
int _monitor = -1;
|
||||||
|
// Only used to detect display setup changes
|
||||||
|
std::string _monitorName = "";
|
||||||
|
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ bool MacroActionRandom::PerformAction()
|
|||||||
}
|
}
|
||||||
if (macros.size() == 1) {
|
if (macros.size() == 1) {
|
||||||
lastRandomMacro = macros[0];
|
lastRandomMacro = macros[0];
|
||||||
return macros[0]->PerformActions();
|
return macros[0]->PerformActions(true);
|
||||||
}
|
}
|
||||||
srand((unsigned int)time(0));
|
srand((unsigned int)time(0));
|
||||||
size_t idx = std::rand() % (macros.size());
|
size_t idx = std::rand() % (macros.size());
|
||||||
lastRandomMacro = macros[idx];
|
lastRandomMacro = macros[idx];
|
||||||
return macros[idx]->PerformActions();
|
return macros[idx]->PerformActions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionRandom::LogAction() const
|
void MacroActionRandom::LogAction() const
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ const std::string MacroActionSwitchScene::id = "scene_switch";
|
|||||||
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
|
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
|
||||||
MacroActionSwitchScene::id,
|
MacroActionSwitchScene::id,
|
||||||
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
|
{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,
|
static void waitForTransitionChange(OBSWeakSource &transition,
|
||||||
std::unique_lock<std::mutex> *lock,
|
std::unique_lock<std::mutex> *lock,
|
||||||
@@ -126,6 +134,14 @@ bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
|
|||||||
bool MacroActionSwitchScene::PerformAction()
|
bool MacroActionSwitchScene::PerformAction()
|
||||||
{
|
{
|
||||||
auto scene = _scene.GetScene();
|
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();
|
auto transition = _transition.GetTransition();
|
||||||
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
|
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
|
||||||
obs_frontend_preview_program_mode_active());
|
obs_frontend_preview_program_mode_active());
|
||||||
@@ -137,24 +153,9 @@ bool MacroActionSwitchScene::PerformAction()
|
|||||||
|
|
||||||
void MacroActionSwitchScene::LogAction() const
|
void MacroActionSwitchScene::LogAction() const
|
||||||
{
|
{
|
||||||
auto t = _scene.GetType();
|
vblog(LOG_INFO, "switch%s scene to '%s'",
|
||||||
auto sceneName = GetWeakSourceName(_scene.GetScene(false));
|
_sceneType == SceneType::PREVIEW ? " preview" : "",
|
||||||
switch (t) {
|
_scene.ToString(true).c_str());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
||||||
@@ -165,6 +166,7 @@ bool MacroActionSwitchScene::Save(obs_data_t *obj) const
|
|||||||
_duration.Save(obj);
|
_duration.Save(obj);
|
||||||
obs_data_set_bool(obj, "blockUntilTransitionDone",
|
obs_data_set_bool(obj, "blockUntilTransitionDone",
|
||||||
_blockUntilTransitionDone);
|
_blockUntilTransitionDone);
|
||||||
|
obs_data_set_int(obj, "sceneType", static_cast<int>(_sceneType));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +178,7 @@ bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
|||||||
_duration.Load(obj);
|
_duration.Load(obj);
|
||||||
_blockUntilTransitionDone =
|
_blockUntilTransitionDone =
|
||||||
obs_data_get_bool(obj, "blockUntilTransitionDone");
|
obs_data_get_bool(obj, "blockUntilTransitionDone");
|
||||||
|
_sceneType = static_cast<SceneType>(obs_data_get_int(obj, "sceneType"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +187,13 @@ std::string MacroActionSwitchScene::GetShortDesc() const
|
|||||||
return _scene.ToString();
|
return _scene.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void populateTypeSelection(QComboBox *list)
|
||||||
|
{
|
||||||
|
for (const auto &[_, name] : sceneTypes) {
|
||||||
|
list->addItem(obs_module_text(name.c_str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||||
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
|
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
@@ -192,9 +202,11 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
|||||||
_duration(new DurationSelection(parent, false)),
|
_duration(new DurationSelection(parent, false)),
|
||||||
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
|
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
|
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
|
||||||
|
_sceneTypes(new QComboBox()),
|
||||||
_entryLayout(new QHBoxLayout())
|
_entryLayout(new QHBoxLayout())
|
||||||
{
|
{
|
||||||
_duration->SpinBox()->setSpecialValueText("-");
|
_duration->SpinBox()->setSpecialValueText("-");
|
||||||
|
populateTypeSelection(_sceneTypes);
|
||||||
|
|
||||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||||
@@ -206,28 +218,29 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
|||||||
this, SLOT(DurationChanged(const Duration &)));
|
this, SLOT(DurationChanged(const Duration &)));
|
||||||
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
|
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
|
||||||
this, SLOT(BlockUntilTransitionDoneChanged(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"),
|
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->addLayout(_entryLayout);
|
||||||
mainLayout->addWidget(_blockUntilTransitionDone);
|
mainLayout->addWidget(_blockUntilTransitionDone);
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
_entryData = entryData;
|
_entryData = entryData;
|
||||||
|
_sceneTypes->setCurrentIndex(static_cast<int>(_entryData->_sceneType));
|
||||||
_scenes->SetScene(_entryData->_scene);
|
_scenes->SetScene(_entryData->_scene);
|
||||||
_transitions->SetTransition(_entryData->_transition);
|
_transitions->SetTransition(_entryData->_transition);
|
||||||
_duration->SetDuration(_entryData->_duration);
|
_duration->SetDuration(_entryData->_duration);
|
||||||
_blockUntilTransitionDone->setChecked(
|
_blockUntilTransitionDone->setChecked(
|
||||||
_entryData->_blockUntilTransitionDone);
|
_entryData->_blockUntilTransitionDone);
|
||||||
SetDurationVisibility();
|
SetWidgetVisibility();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +264,44 @@ void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
|
|||||||
_entryData->_blockUntilTransitionDone = 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() !=
|
if (_entryData->_transition.GetType() !=
|
||||||
TransitionSelection::Type::TRANSITION) {
|
TransitionSelection::Type::TRANSITION) {
|
||||||
_duration->show();
|
_duration->show();
|
||||||
@@ -261,15 +310,6 @@ void MacroActionSwitchSceneEdit::SetDurationVisibility()
|
|||||||
_entryData->_transition.GetTransition());
|
_entryData->_transition.GetTransition());
|
||||||
_duration->setVisible(!fixedDuration);
|
_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) {
|
if (fixedDuration) {
|
||||||
PlaceWidgets(
|
PlaceWidgets(
|
||||||
obs_module_text(
|
obs_module_text(
|
||||||
@@ -302,7 +342,7 @@ void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
|
|||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_transition = t;
|
_entryData->_transition = t;
|
||||||
SetDurationVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ public:
|
|||||||
{
|
{
|
||||||
return std::make_shared<MacroActionSwitchScene>(m);
|
return std::make_shared<MacroActionSwitchScene>(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SceneType { PROGRAM, PREVIEW };
|
||||||
|
SceneType _sceneType = SceneType::PROGRAM;
|
||||||
SceneSelection _scene;
|
SceneSelection _scene;
|
||||||
TransitionSelection _transition;
|
TransitionSelection _transition;
|
||||||
Duration _duration;
|
Duration _duration;
|
||||||
@@ -55,20 +58,21 @@ private slots:
|
|||||||
void TransitionChanged(const TransitionSelection &);
|
void TransitionChanged(const TransitionSelection &);
|
||||||
void DurationChanged(const Duration &seconds);
|
void DurationChanged(const Duration &seconds);
|
||||||
void BlockUntilTransitionDoneChanged(int state);
|
void BlockUntilTransitionDoneChanged(int state);
|
||||||
|
void SceneTypeChanged(int);
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
void SetWidgetVisibility();
|
||||||
|
|
||||||
SceneSelectionWidget *_scenes;
|
SceneSelectionWidget *_scenes;
|
||||||
TransitionSelectionWidget *_transitions;
|
TransitionSelectionWidget *_transitions;
|
||||||
DurationSelection *_duration;
|
DurationSelection *_duration;
|
||||||
QCheckBox *_blockUntilTransitionDone;
|
QCheckBox *_blockUntilTransitionDone;
|
||||||
|
QComboBox *_sceneTypes;
|
||||||
QHBoxLayout *_entryLayout;
|
QHBoxLayout *_entryLayout;
|
||||||
|
|
||||||
std::shared_ptr<MacroActionSwitchScene> _entryData;
|
std::shared_ptr<MacroActionSwitchScene> _entryData;
|
||||||
|
|
||||||
private:
|
|
||||||
void SetDurationVisibility();
|
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ bool MacroActionSequence::PerformAction()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return macro->PerformActions();
|
return macro->PerformActions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionSequence::LogAction() const
|
void MacroActionSequence::LogAction() const
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
|
|||||||
"AdvSceneSwitcher.action.source.type.deinterlaceMode"},
|
"AdvSceneSwitcher.action.source.type.deinterlaceMode"},
|
||||||
{MacroActionSource::Action::DEINTERLACE_FIELD_ORDER,
|
{MacroActionSource::Action::DEINTERLACE_FIELD_ORDER,
|
||||||
"AdvSceneSwitcher.action.source.type.deinterlaceOrder"},
|
"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 = {
|
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()
|
bool MacroActionSource::PerformAction()
|
||||||
{
|
{
|
||||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||||
@@ -135,6 +143,16 @@ bool MacroActionSource::PerformAction()
|
|||||||
case Action::DEINTERLACE_FIELD_ORDER:
|
case Action::DEINTERLACE_FIELD_ORDER:
|
||||||
obs_source_set_deinterlace_field_order(s, _deinterlaceOrder);
|
obs_source_set_deinterlace_field_order(s, _deinterlaceOrder);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public:
|
|||||||
SETTINGS_BUTTON,
|
SETTINGS_BUTTON,
|
||||||
DEINTERLACE_MODE,
|
DEINTERLACE_MODE,
|
||||||
DEINTERLACE_FIELD_ORDER,
|
DEINTERLACE_FIELD_ORDER,
|
||||||
|
OPEN_INTERACTION_DIALOG,
|
||||||
};
|
};
|
||||||
Action _action = Action::ENABLE;
|
Action _action = Action::ENABLE;
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ bool MacroActionSudioMode::_registered = MacroActionFactory::Register(
|
|||||||
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
|
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
|
||||||
"AdvSceneSwitcher.action.studioMode"});
|
"AdvSceneSwitcher.action.studioMode"});
|
||||||
|
|
||||||
const static std::map<StudioModeAction, std::string> actionTypes = {
|
const static std::map<MacroActionSudioMode::Action, std::string> actionTypes = {
|
||||||
{StudioModeAction::SWAP_SCENE,
|
{MacroActionSudioMode::Action::SWAP_SCENE,
|
||||||
"AdvSceneSwitcher.action.studioMode.type.swap"},
|
"AdvSceneSwitcher.action.studioMode.type.swap"},
|
||||||
{StudioModeAction::SET_SCENE,
|
{MacroActionSudioMode::Action::SET_SCENE,
|
||||||
"AdvSceneSwitcher.action.studioMode.type.setScene"},
|
"AdvSceneSwitcher.action.studioMode.type.setScene"},
|
||||||
{StudioModeAction::ENABLE_STUDIO_MODE,
|
{MacroActionSudioMode::Action::ENABLE_STUDIO_MODE,
|
||||||
"AdvSceneSwitcher.action.studioMode.type.enable"},
|
"AdvSceneSwitcher.action.studioMode.type.enable"},
|
||||||
{StudioModeAction::DISABLE_STUDIO_MODE,
|
{MacroActionSudioMode::Action::DISABLE_STUDIO_MODE,
|
||||||
"AdvSceneSwitcher.action.studioMode.type.disable"},
|
"AdvSceneSwitcher.action.studioMode.type.disable"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,19 +41,19 @@ static void enableStudioMode(bool enable)
|
|||||||
bool MacroActionSudioMode::PerformAction()
|
bool MacroActionSudioMode::PerformAction()
|
||||||
{
|
{
|
||||||
switch (_action) {
|
switch (_action) {
|
||||||
case StudioModeAction::SWAP_SCENE:
|
case Action::SWAP_SCENE:
|
||||||
obs_frontend_preview_program_trigger_transition();
|
obs_frontend_preview_program_trigger_transition();
|
||||||
break;
|
break;
|
||||||
case StudioModeAction::SET_SCENE: {
|
case Action::SET_SCENE: {
|
||||||
auto s = obs_weak_source_get_source(_scene.GetScene());
|
auto s = obs_weak_source_get_source(_scene.GetScene());
|
||||||
obs_frontend_set_current_preview_scene(s);
|
obs_frontend_set_current_preview_scene(s);
|
||||||
obs_source_release(s);
|
obs_source_release(s);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case StudioModeAction::ENABLE_STUDIO_MODE:
|
case Action::ENABLE_STUDIO_MODE:
|
||||||
enableStudioMode(true);
|
enableStudioMode(true);
|
||||||
break;
|
break;
|
||||||
case StudioModeAction::DISABLE_STUDIO_MODE:
|
case Action::DISABLE_STUDIO_MODE:
|
||||||
enableStudioMode(false);
|
enableStudioMode(false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -86,15 +86,14 @@ bool MacroActionSudioMode::Save(obs_data_t *obj) const
|
|||||||
bool MacroActionSudioMode::Load(obs_data_t *obj)
|
bool MacroActionSudioMode::Load(obs_data_t *obj)
|
||||||
{
|
{
|
||||||
MacroAction::Load(obj);
|
MacroAction::Load(obj);
|
||||||
_action =
|
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
|
||||||
static_cast<StudioModeAction>(obs_data_get_int(obj, "action"));
|
|
||||||
_scene.Load(obj);
|
_scene.Load(obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MacroActionSudioMode::GetShortDesc() const
|
std::string MacroActionSudioMode::GetShortDesc() const
|
||||||
{
|
{
|
||||||
if (_action == StudioModeAction::SET_SCENE) {
|
if (_action == Action::SET_SCENE) {
|
||||||
return _scene.ToString();
|
return _scene.ToString();
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@@ -102,8 +101,9 @@ std::string MacroActionSudioMode::GetShortDesc() const
|
|||||||
|
|
||||||
static inline void populateActionSelection(QComboBox *list)
|
static inline void populateActionSelection(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (auto entry : actionTypes) {
|
for (const auto &[id, name] : actionTypes) {
|
||||||
list->addItem(obs_module_text(entry.second.c_str()));
|
list->addItem(obs_module_text(name.c_str()),
|
||||||
|
static_cast<int>(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +139,10 @@ void MacroActionSudioModeEdit::UpdateEntryData()
|
|||||||
if (!_entryData) {
|
if (!_entryData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
_actions->setCurrentIndex(
|
||||||
|
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||||
_scenes->SetScene(_entryData->_scene);
|
_scenes->SetScene(_entryData->_scene);
|
||||||
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
||||||
@@ -156,15 +157,27 @@ void MacroActionSudioModeEdit::SceneChanged(const SceneSelection &s)
|
|||||||
QString::fromStdString(_entryData->GetShortDesc()));
|
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) {
|
if (_loading || !_entryData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_action = static_cast<StudioModeAction>(value);
|
_entryData->_action = static_cast<MacroActionSudioMode::Action>(
|
||||||
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
|
_actions->itemData(index).toInt());
|
||||||
|
SetWidgetVisibility();
|
||||||
emit HeaderInfoChanged(
|
emit HeaderInfoChanged(
|
||||||
QString::fromStdString(_entryData->GetShortDesc()));
|
QString::fromStdString(_entryData->GetShortDesc()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,6 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
enum class StudioModeAction {
|
|
||||||
SWAP_SCENE,
|
|
||||||
SET_SCENE,
|
|
||||||
ENABLE_STUDIO_MODE,
|
|
||||||
DISABLE_STUDIO_MODE,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacroActionSudioMode : public MacroAction {
|
class MacroActionSudioMode : public MacroAction {
|
||||||
public:
|
public:
|
||||||
MacroActionSudioMode(Macro *m) : MacroAction(m) {}
|
MacroActionSudioMode(Macro *m) : MacroAction(m) {}
|
||||||
@@ -25,7 +18,14 @@ public:
|
|||||||
return std::make_shared<MacroActionSudioMode>(m);
|
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;
|
SceneSelection _scene;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -55,12 +55,13 @@ private slots:
|
|||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
void SetWidgetVisibility();
|
||||||
|
|
||||||
QComboBox *_actions;
|
QComboBox *_actions;
|
||||||
SceneSelectionWidget *_scenes;
|
SceneSelectionWidget *_scenes;
|
||||||
std::shared_ptr<MacroActionSudioMode> _entryData;
|
std::shared_ptr<MacroActionSudioMode> _entryData;
|
||||||
|
|
||||||
private:
|
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ const static std::map<MacroActionVariable::Type, std::string> actionTypes = {
|
|||||||
"AdvSceneSwitcher.action.variable.type.mathExpression"},
|
"AdvSceneSwitcher.action.variable.type.mathExpression"},
|
||||||
{MacroActionVariable::Type::USER_INPUT,
|
{MacroActionVariable::Type::USER_INPUT,
|
||||||
"AdvSceneSwitcher.action.variable.type.askForValue"},
|
"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)
|
static void apppend(Variable &var, const std::string &value)
|
||||||
@@ -126,6 +130,7 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
|
|||||||
|
|
||||||
struct AskForInputParams {
|
struct AskForInputParams {
|
||||||
QString prompt;
|
QString prompt;
|
||||||
|
QString placeholder;
|
||||||
std::optional<std::string> result;
|
std::optional<std::string> result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +139,7 @@ static void askForInput(void *param)
|
|||||||
auto parameters = static_cast<AskForInputParams *>(param);
|
auto parameters = static_cast<AskForInputParams *>(param);
|
||||||
auto dialog = new NonModalMessageDialog(
|
auto dialog = new NonModalMessageDialog(
|
||||||
parameters->prompt, NonModalMessageDialog::Type::INPUT);
|
parameters->prompt, NonModalMessageDialog::Type::INPUT);
|
||||||
|
dialog->SetInput(parameters->placeholder);
|
||||||
parameters->result = dialog->GetInput();
|
parameters->result = dialog->GetInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +219,9 @@ bool MacroActionVariable::PerformAction()
|
|||||||
"AdvSceneSwitcher.action.variable.askForValuePromptDefault"))
|
"AdvSceneSwitcher.action.variable.askForValuePromptDefault"))
|
||||||
.arg(QString::fromStdString(
|
.arg(QString::fromStdString(
|
||||||
var->Name())),
|
var->Name())),
|
||||||
|
_useCustomPrompt && _useInputPlaceholder
|
||||||
|
? QString::fromStdString(_inputPlaceholder)
|
||||||
|
: "",
|
||||||
{}};
|
{}};
|
||||||
obs_queue_task(OBS_TASK_UI, askForInput, ¶ms, true);
|
obs_queue_task(OBS_TASK_UI, askForInput, ¶ms, true);
|
||||||
if (!params.result.has_value()) {
|
if (!params.result.has_value()) {
|
||||||
@@ -221,6 +230,14 @@ bool MacroActionVariable::PerformAction()
|
|||||||
var->SetValue(*params.result);
|
var->SetValue(*params.result);
|
||||||
return true;
|
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;
|
return true;
|
||||||
@@ -247,6 +264,10 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
|
|||||||
_mathExpression.Save(obj, "mathExpression");
|
_mathExpression.Save(obj, "mathExpression");
|
||||||
obs_data_set_bool(obj, "useCustomPrompt", _useCustomPrompt);
|
obs_data_set_bool(obj, "useCustomPrompt", _useCustomPrompt);
|
||||||
_inputPrompt.Save(obj, "inputPrompt");
|
_inputPrompt.Save(obj, "inputPrompt");
|
||||||
|
obs_data_set_bool(obj, "useInputPlaceholder", _useInputPlaceholder);
|
||||||
|
_inputPlaceholder.Save(obj, "inputPlaceholder");
|
||||||
|
_envVariableName.Save(obj, "environmentVariableName");
|
||||||
|
_scene.Save(obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +292,10 @@ bool MacroActionVariable::Load(obs_data_t *obj)
|
|||||||
_mathExpression.Load(obj, "mathExpression");
|
_mathExpression.Load(obj, "mathExpression");
|
||||||
_useCustomPrompt = obs_data_get_bool(obj, "useCustomPrompt");
|
_useCustomPrompt = obs_data_get_bool(obj, "useCustomPrompt");
|
||||||
_inputPrompt.Load(obj, "inputPrompt");
|
_inputPrompt.Load(obj, "inputPrompt");
|
||||||
|
_useInputPlaceholder = obs_data_get_bool(obj, "useInputPlaceholder");
|
||||||
|
_inputPlaceholder.Load(obj, "inputPlaceholder");
|
||||||
|
_envVariableName.Load(obj, "environmentVariableName");
|
||||||
|
_scene.Load(obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +417,12 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
_mathExpressionResult(new QLabel()),
|
_mathExpressionResult(new QLabel()),
|
||||||
_promptLayout(new QHBoxLayout()),
|
_promptLayout(new QHBoxLayout()),
|
||||||
_useCustomPrompt(new QCheckBox()),
|
_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->setMinimum(-9999999999);
|
||||||
_numValue->setMaximum(9999999999);
|
_numValue->setMaximum(9999999999);
|
||||||
@@ -447,6 +477,14 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
SLOT(UseCustomPromptChanged(int)));
|
SLOT(UseCustomPromptChanged(int)));
|
||||||
QWidget::connect(_inputPrompt, SIGNAL(editingFinished()), this,
|
QWidget::connect(_inputPrompt, SIGNAL(editingFinished()), this,
|
||||||
SLOT(InputPromptChanged()));
|
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 = {
|
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||||
{"{{variables}}", _variables},
|
{"{{variables}}", _variables},
|
||||||
@@ -463,6 +501,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
{"{{mathExpression}}", _mathExpression},
|
{"{{mathExpression}}", _mathExpression},
|
||||||
{"{{useCustomPrompt}}", _useCustomPrompt},
|
{"{{useCustomPrompt}}", _useCustomPrompt},
|
||||||
{"{{inputPrompt}}", _inputPrompt},
|
{"{{inputPrompt}}", _inputPrompt},
|
||||||
|
{"{{useInputPlaceholder}}", _useInputPlaceholder},
|
||||||
|
{"{{inputPlaceholder}}", _inputPlaceholder},
|
||||||
|
{"{{envVariableName}}", _envVariable},
|
||||||
|
{"{{scenes}}", _scenes},
|
||||||
};
|
};
|
||||||
auto entryLayout = new QHBoxLayout;
|
auto entryLayout = new QHBoxLayout;
|
||||||
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
|
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
|
||||||
@@ -485,8 +527,12 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
|
|
||||||
PlaceWidgets(
|
PlaceWidgets(
|
||||||
obs_module_text(
|
obs_module_text(
|
||||||
"AdvSceneSwitcher.action.variable.entry.userInput"),
|
"AdvSceneSwitcher.action.variable.entry.userInput.customPrompt"),
|
||||||
_promptLayout, widgetPlaceholders);
|
_promptLayout, widgetPlaceholders);
|
||||||
|
PlaceWidgets(
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.action.variable.entry.userInput.placeholder"),
|
||||||
|
_placeholderLayout, widgetPlaceholders);
|
||||||
|
|
||||||
auto regexConfigLayout = new QHBoxLayout;
|
auto regexConfigLayout = new QHBoxLayout;
|
||||||
regexConfigLayout->addWidget(_regex);
|
regexConfigLayout->addWidget(_regex);
|
||||||
@@ -505,6 +551,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
layout->addLayout(_findReplaceLayout);
|
layout->addLayout(_findReplaceLayout);
|
||||||
layout->addWidget(_mathExpressionResult);
|
layout->addWidget(_mathExpressionResult);
|
||||||
layout->addLayout(_promptLayout);
|
layout->addLayout(_promptLayout);
|
||||||
|
layout->addLayout(_placeholderLayout);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
_entryData = entryData;
|
_entryData = entryData;
|
||||||
@@ -547,6 +594,10 @@ void MacroActionVariableEdit::UpdateEntryData()
|
|||||||
_mathExpression->setText(_entryData->_mathExpression);
|
_mathExpression->setText(_entryData->_mathExpression);
|
||||||
_useCustomPrompt->setChecked(_entryData->_useCustomPrompt);
|
_useCustomPrompt->setChecked(_entryData->_useCustomPrompt);
|
||||||
_inputPrompt->setText(_entryData->_inputPrompt);
|
_inputPrompt->setText(_entryData->_inputPrompt);
|
||||||
|
_useInputPlaceholder->setChecked(_entryData->_useInputPlaceholder);
|
||||||
|
_inputPlaceholder->setText(_entryData->_inputPlaceholder);
|
||||||
|
_envVariable->setText(_entryData->_envVariableName);
|
||||||
|
_scenes->SetScene(_entryData->_scene);
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,15 +869,9 @@ void MacroActionVariableEdit::UseCustomPromptChanged(int value)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_inputPrompt->setVisible(value);
|
|
||||||
if (value) {
|
|
||||||
RemoveStretchIfPresent(_promptLayout);
|
|
||||||
} else {
|
|
||||||
AddStretchIfNecessary(_promptLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_useCustomPrompt = value;
|
_entryData->_useCustomPrompt = value;
|
||||||
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionVariableEdit::InputPromptChanged()
|
void MacroActionVariableEdit::InputPromptChanged()
|
||||||
@@ -839,6 +884,47 @@ void MacroActionVariableEdit::InputPromptChanged()
|
|||||||
_entryData->_inputPrompt = _inputPrompt->text().toStdString();
|
_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()
|
void MacroActionVariableEdit::SetWidgetVisibility()
|
||||||
{
|
{
|
||||||
if (!_entryData) {
|
if (!_entryData) {
|
||||||
@@ -887,7 +973,34 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
|||||||
SetLayoutVisible(_promptLayout,
|
SetLayoutVisible(_promptLayout,
|
||||||
_entryData->_type ==
|
_entryData->_type ==
|
||||||
MacroActionVariable::Type::USER_INPUT);
|
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();
|
adjustSize();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "macro-segment-selection.hpp"
|
#include "macro-segment-selection.hpp"
|
||||||
#include "regex-config.hpp"
|
#include "regex-config.hpp"
|
||||||
#include "resizing-text-edit.hpp"
|
#include "resizing-text-edit.hpp"
|
||||||
|
#include "scene-selection.hpp"
|
||||||
#include "variable-line-edit.hpp"
|
#include "variable-line-edit.hpp"
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
@@ -37,6 +38,8 @@ public:
|
|||||||
FIND_AND_REPLACE,
|
FIND_AND_REPLACE,
|
||||||
MATH_EXPRESSION,
|
MATH_EXPRESSION,
|
||||||
USER_INPUT,
|
USER_INPUT,
|
||||||
|
ENV_VARIABLE,
|
||||||
|
SCENE_ITEM_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
Type _type = Type::SET_FIXED_VALUE;
|
Type _type = Type::SET_FIXED_VALUE;
|
||||||
@@ -58,6 +61,15 @@ public:
|
|||||||
bool _useCustomPrompt = false;
|
bool _useCustomPrompt = false;
|
||||||
StringVariable _inputPrompt = obs_module_text(
|
StringVariable _inputPrompt = obs_module_text(
|
||||||
"AdvSceneSwitcher.action.variable.askForValuePrompt");
|
"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:
|
private:
|
||||||
void DecrementCurrentSegmentVariableRef();
|
void DecrementCurrentSegmentVariableRef();
|
||||||
@@ -107,11 +119,18 @@ private slots:
|
|||||||
void MathExpressionChanged();
|
void MathExpressionChanged();
|
||||||
void UseCustomPromptChanged(int);
|
void UseCustomPromptChanged(int);
|
||||||
void InputPromptChanged();
|
void InputPromptChanged();
|
||||||
|
void UseInputPlaceholderChanged(int);
|
||||||
|
void InputPlaceholderChanged();
|
||||||
|
void EnvVariableChanged();
|
||||||
|
void SceneChanged(const SceneSelection &);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
void SetWidgetVisibility();
|
||||||
|
void SetSegmentValueError(const QString &);
|
||||||
|
|
||||||
VariableSelection *_variables;
|
VariableSelection *_variables;
|
||||||
VariableSelection *_variables2;
|
VariableSelection *_variables2;
|
||||||
QComboBox *_actions;
|
QComboBox *_actions;
|
||||||
@@ -136,12 +155,13 @@ protected:
|
|||||||
QHBoxLayout *_promptLayout;
|
QHBoxLayout *_promptLayout;
|
||||||
QCheckBox *_useCustomPrompt;
|
QCheckBox *_useCustomPrompt;
|
||||||
VariableLineEdit *_inputPrompt;
|
VariableLineEdit *_inputPrompt;
|
||||||
|
QHBoxLayout *_placeholderLayout;
|
||||||
|
QCheckBox *_useInputPlaceholder;
|
||||||
|
VariableLineEdit *_inputPlaceholder;
|
||||||
|
VariableLineEdit *_envVariable;
|
||||||
|
SceneSelectionWidget *_scenes;
|
||||||
|
|
||||||
std::shared_ptr<MacroActionVariable> _entryData;
|
std::shared_ptr<MacroActionVariable> _entryData;
|
||||||
|
|
||||||
private:
|
|
||||||
void SetWidgetVisibility();
|
|
||||||
void SetSegmentValueError(const QString &);
|
|
||||||
|
|
||||||
QTimer _timer;
|
QTimer _timer;
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
|
|||||||
}
|
}
|
||||||
(*cond)->SetLogicType(logic);
|
(*cond)->SetLogicType(logic);
|
||||||
macro->UpdateConditionIndices();
|
macro->UpdateConditionIndices();
|
||||||
conditionsList->Insert(
|
ui->conditionsList->Insert(
|
||||||
idx,
|
idx,
|
||||||
new MacroConditionEdit(this, ¯o->Conditions()[idx],
|
new MacroConditionEdit(this, ¯o->Conditions()[idx],
|
||||||
id, idx == 0));
|
id, idx == 0));
|
||||||
@@ -410,7 +410,7 @@ void AdvSceneSwitcher::on_conditionAdd_clicked()
|
|||||||
if (currentConditionIdx != -1) {
|
if (currentConditionIdx != -1) {
|
||||||
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
||||||
}
|
}
|
||||||
conditionsList->SetHelpMsgVisible(false);
|
ui->conditionsList->SetHelpMsgVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||||
@@ -426,14 +426,14 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
conditionsList->Remove(idx);
|
ui->conditionsList->Remove(idx);
|
||||||
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
||||||
macro->UpdateConditionIndices();
|
macro->UpdateConditionIndices();
|
||||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||||
auto newRoot = macro->Conditions().at(0);
|
auto newRoot = macro->Conditions().at(0);
|
||||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||||
static_cast<MacroConditionEdit *>(
|
static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->WidgetAt(0))
|
ui->conditionsList->WidgetAt(0))
|
||||||
->SetRootNode(true);
|
->SetRootNode(true);
|
||||||
}
|
}
|
||||||
SetConditionData(*macro);
|
SetConditionData(*macro);
|
||||||
@@ -479,7 +479,7 @@ void AdvSceneSwitcher::on_conditionDown_clicked()
|
|||||||
{
|
{
|
||||||
if (currentConditionIdx == -1 ||
|
if (currentConditionIdx == -1 ||
|
||||||
currentConditionIdx ==
|
currentConditionIdx ==
|
||||||
conditionsList->ContentLayout()->count() - 1) {
|
ui->conditionsList->ContentLayout()->count() - 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MoveMacroConditionDown(currentConditionIdx);
|
MoveMacroConditionDown(currentConditionIdx);
|
||||||
@@ -491,7 +491,7 @@ void AdvSceneSwitcher::on_conditionBottom_clicked()
|
|||||||
if (currentConditionIdx == -1) {
|
if (currentConditionIdx == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int newIdx = conditionsList->ContentLayout()->count() - 1;
|
const int newIdx = ui->conditionsList->ContentLayout()->count() - 1;
|
||||||
MacroConditionReorder(newIdx, currentConditionIdx);
|
MacroConditionReorder(newIdx, currentConditionIdx);
|
||||||
MacroConditionSelectionChanged(newIdx);
|
MacroConditionSelectionChanged(newIdx);
|
||||||
}
|
}
|
||||||
@@ -521,11 +521,11 @@ void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto widget1 = static_cast<MacroConditionEdit *>(
|
auto widget1 = static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->ContentLayout()->takeAt(pos1)->widget());
|
ui->conditionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||||
auto widget2 = static_cast<MacroConditionEdit *>(
|
auto widget2 = static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
ui->conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||||
conditionsList->Insert(pos1, widget2);
|
ui->conditionsList->Insert(pos1, widget2);
|
||||||
conditionsList->Insert(pos2, widget1);
|
ui->conditionsList->Insert(pos2, widget1);
|
||||||
SetConditionData(*m);
|
SetConditionData(*m);
|
||||||
widget2->SetRootNode(root);
|
widget2->SetRootNode(root);
|
||||||
widget1->SetRootNode(false);
|
widget1->SetRootNode(false);
|
||||||
@@ -564,22 +564,7 @@ void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
|
|||||||
|
|
||||||
void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
|
void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
|
||||||
{
|
{
|
||||||
auto macro = GetSelectedMacro();
|
SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
||||||
@@ -599,30 +584,30 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
|||||||
if (to == 0) {
|
if (to == 0) {
|
||||||
condition->SetLogicType(LogicType::ROOT_NONE);
|
condition->SetLogicType(LogicType::ROOT_NONE);
|
||||||
static_cast<MacroConditionEdit *>(
|
static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->WidgetAt(from))
|
ui->conditionsList->WidgetAt(from))
|
||||||
->SetRootNode(true);
|
->SetRootNode(true);
|
||||||
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
|
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
|
||||||
static_cast<MacroConditionEdit *>(
|
static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->WidgetAt(0))
|
ui->conditionsList->WidgetAt(0))
|
||||||
->SetRootNode(false);
|
->SetRootNode(false);
|
||||||
}
|
}
|
||||||
if (from == 0) {
|
if (from == 0) {
|
||||||
condition->SetLogicType(LogicType::AND);
|
condition->SetLogicType(LogicType::AND);
|
||||||
static_cast<MacroConditionEdit *>(
|
static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->WidgetAt(from))
|
ui->conditionsList->WidgetAt(from))
|
||||||
->SetRootNode(false);
|
->SetRootNode(false);
|
||||||
macro->Conditions().at(1)->SetLogicType(
|
macro->Conditions().at(1)->SetLogicType(
|
||||||
LogicType::ROOT_NONE);
|
LogicType::ROOT_NONE);
|
||||||
static_cast<MacroConditionEdit *>(
|
static_cast<MacroConditionEdit *>(
|
||||||
conditionsList->WidgetAt(1))
|
ui->conditionsList->WidgetAt(1))
|
||||||
->SetRootNode(true);
|
->SetRootNode(true);
|
||||||
}
|
}
|
||||||
macro->Conditions().erase(macro->Conditions().begin() + from);
|
macro->Conditions().erase(macro->Conditions().begin() + from);
|
||||||
macro->Conditions().insert(macro->Conditions().begin() + to,
|
macro->Conditions().insert(macro->Conditions().begin() + to,
|
||||||
condition);
|
condition);
|
||||||
macro->UpdateConditionIndices();
|
macro->UpdateConditionIndices();
|
||||||
conditionsList->ContentLayout()->insertItem(
|
ui->conditionsList->ContentLayout()->insertItem(
|
||||||
to, conditionsList->ContentLayout()->takeAt(from));
|
to, ui->conditionsList->ContentLayout()->takeAt(from));
|
||||||
SetConditionData(*macro);
|
SetConditionData(*macro);
|
||||||
}
|
}
|
||||||
HighlightCondition(to);
|
HighlightCondition(to);
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ const static std::map<MacroConditionFilter::Condition, std::string>
|
|||||||
"AdvSceneSwitcher.condition.filter.type.active"},
|
"AdvSceneSwitcher.condition.filter.type.active"},
|
||||||
{MacroConditionFilter::Condition::DISABLED,
|
{MacroConditionFilter::Condition::DISABLED,
|
||||||
"AdvSceneSwitcher.condition.filter.type.showing"},
|
"AdvSceneSwitcher.condition.filter.type.showing"},
|
||||||
{MacroConditionFilter::Condition::SETTINGS,
|
{MacroConditionFilter::Condition::SETTINGS_MATCH,
|
||||||
"AdvSceneSwitcher.condition.filter.type.settings"},
|
"AdvSceneSwitcher.condition.filter.type.settings"},
|
||||||
|
{MacroConditionFilter::Condition::SETTINGS_CHANGED,
|
||||||
|
"AdvSceneSwitcher.condition.filter.type.settingsChanged"},
|
||||||
};
|
};
|
||||||
|
|
||||||
bool MacroConditionFilter::CheckCondition()
|
bool MacroConditionFilter::CheckCondition()
|
||||||
@@ -38,13 +40,20 @@ bool MacroConditionFilter::CheckCondition()
|
|||||||
case Condition::DISABLED:
|
case Condition::DISABLED:
|
||||||
ret = !obs_source_enabled(filterSource);
|
ret = !obs_source_enabled(filterSource);
|
||||||
break;
|
break;
|
||||||
case Condition::SETTINGS:
|
case Condition::SETTINGS_MATCH:
|
||||||
ret = CompareSourceSettings(filterWeakSource, _settings,
|
ret = CompareSourceSettings(filterWeakSource, _settings,
|
||||||
_regex);
|
_regex);
|
||||||
if (IsReferencedInVars()) {
|
if (IsReferencedInVars()) {
|
||||||
SetVariableValue(GetSourceSettings(filterWeakSource));
|
SetVariableValue(GetSourceSettings(filterWeakSource));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Condition::SETTINGS_CHANGED: {
|
||||||
|
std::string settings = GetSourceSettings(_source.GetSource());
|
||||||
|
ret = !_currentSettings.empty() && settings != _currentSettings;
|
||||||
|
_currentSettings = settings;
|
||||||
|
SetVariableValue(settings);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -95,8 +104,8 @@ std::string MacroConditionFilter::GetShortDesc() const
|
|||||||
|
|
||||||
static inline void populateConditionSelection(QComboBox *list)
|
static inline void populateConditionSelection(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (auto entry : filterConditionTypes) {
|
for (const auto &[_, name] : filterConditionTypes) {
|
||||||
list->addItem(obs_module_text(entry.second.c_str()));
|
list->addItem(obs_module_text(name.c_str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +203,9 @@ void MacroConditionFilterEdit::ConditionChanged(int index)
|
|||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_condition =
|
_entryData->_condition =
|
||||||
static_cast<MacroConditionFilter::Condition>(index);
|
static_cast<MacroConditionFilter::Condition>(index);
|
||||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
SetSettingsSelectionVisible(
|
||||||
MacroConditionFilter::Condition::SETTINGS);
|
_entryData->_condition ==
|
||||||
|
MacroConditionFilter::Condition::SETTINGS_MATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionFilterEdit::GetSettingsClicked()
|
void MacroConditionFilterEdit::GetSettingsClicked()
|
||||||
@@ -259,8 +269,9 @@ void MacroConditionFilterEdit::UpdateEntryData()
|
|||||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||||
_settings->setPlainText(_entryData->_settings);
|
_settings->setPlainText(_entryData->_settings);
|
||||||
_regex->SetRegexConfig(_entryData->_regex);
|
_regex->SetRegexConfig(_entryData->_regex);
|
||||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
SetSettingsSelectionVisible(
|
||||||
MacroConditionFilter::Condition::SETTINGS);
|
_entryData->_condition ==
|
||||||
|
MacroConditionFilter::Condition::SETTINGS_MATCH);
|
||||||
|
|
||||||
adjustSize();
|
adjustSize();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ public:
|
|||||||
enum class Condition {
|
enum class Condition {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED,
|
DISABLED,
|
||||||
SETTINGS,
|
SETTINGS_MATCH,
|
||||||
|
SETTINGS_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
SourceSelection _source;
|
SourceSelection _source;
|
||||||
@@ -37,6 +38,8 @@ public:
|
|||||||
RegexConfig _regex;
|
RegexConfig _regex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string _currentSettings;
|
||||||
|
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "process-config.hpp"
|
#include "process-config.hpp"
|
||||||
#include "duration-control.hpp"
|
#include "duration-control.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
|
|||||||
"AdvSceneSwitcher.condition.scene.type.current"},
|
"AdvSceneSwitcher.condition.scene.type.current"},
|
||||||
{MacroConditionScene::Type::PREVIOUS,
|
{MacroConditionScene::Type::PREVIOUS,
|
||||||
"AdvSceneSwitcher.condition.scene.type.previous"},
|
"AdvSceneSwitcher.condition.scene.type.previous"},
|
||||||
|
{MacroConditionScene::Type::PREVIEW,
|
||||||
|
"AdvSceneSwitcher.condition.scene.type.preview"},
|
||||||
{MacroConditionScene::Type::CHANGED,
|
{MacroConditionScene::Type::CHANGED,
|
||||||
"AdvSceneSwitcher.condition.scene.type.changed"},
|
"AdvSceneSwitcher.condition.scene.type.changed"},
|
||||||
{MacroConditionScene::Type::NOT_CHANGED,
|
{MacroConditionScene::Type::NOT_CHANGED,
|
||||||
@@ -24,6 +26,8 @@ const static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
|
|||||||
"AdvSceneSwitcher.condition.scene.type.currentPattern"},
|
"AdvSceneSwitcher.condition.scene.type.currentPattern"},
|
||||||
{MacroConditionScene::Type::PREVIOUS_PATTERN,
|
{MacroConditionScene::Type::PREVIOUS_PATTERN,
|
||||||
"AdvSceneSwitcher.condition.scene.type.previousPattern"},
|
"AdvSceneSwitcher.condition.scene.type.previousPattern"},
|
||||||
|
{MacroConditionScene::Type::PREVIEW_PATTERN,
|
||||||
|
"AdvSceneSwitcher.condition.scene.type.previewPattern"},
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool sceneNameMatchesRegex(const OBSWeakSource &scene,
|
static bool sceneNameMatchesRegex(const OBSWeakSource &scene,
|
||||||
@@ -79,6 +83,14 @@ bool MacroConditionScene::CheckCondition()
|
|||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
return scene == _scene.GetScene(false);
|
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:
|
case Type::CHANGED:
|
||||||
SetVariableValue(GetWeakSourceName(switcher->currentScene));
|
SetVariableValue(GetWeakSourceName(switcher->currentScene));
|
||||||
return sceneChanged;
|
return sceneChanged;
|
||||||
@@ -95,6 +107,14 @@ bool MacroConditionScene::CheckCondition()
|
|||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
return sceneNameMatchesRegex(scene, _pattern);
|
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;
|
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_string(obj, "pattern", _pattern.c_str());
|
||||||
obs_data_set_bool(obj, "useTransitionTargetScene",
|
obs_data_set_bool(obj, "useTransitionTargetScene",
|
||||||
_useTransitionTargetScene);
|
_useTransitionTargetScene);
|
||||||
|
obs_data_set_int(obj, "version", 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +145,46 @@ bool MacroConditionScene::Load(obs_data_t *obj)
|
|||||||
_useTransitionTargetScene =
|
_useTransitionTargetScene =
|
||||||
obs_data_get_bool(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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +198,9 @@ std::string MacroConditionScene::GetShortDesc() const
|
|||||||
|
|
||||||
static inline void populateTypeSelection(QComboBox *list)
|
static inline void populateTypeSelection(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (auto entry : sceneTypes) {
|
for (const auto &[id, name] : sceneTypes) {
|
||||||
list->addItem(obs_module_text(entry.second.c_str()));
|
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()));
|
QString::fromStdString(_entryData->GetShortDesc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionSceneEdit::TypeChanged(int value)
|
void MacroConditionSceneEdit::TypeChanged(int index)
|
||||||
{
|
{
|
||||||
if (_loading || !_entryData) {
|
if (_loading || !_entryData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_type = static_cast<MacroConditionScene::Type>(value);
|
_entryData->_type = static_cast<MacroConditionScene::Type>(
|
||||||
|
_sceneType->itemData(index).toInt());
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +297,8 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
|||||||
{
|
{
|
||||||
_scenes->setVisible(
|
_scenes->setVisible(
|
||||||
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
||||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS);
|
_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
|
_entryData->_type == MacroConditionScene::Type::PREVIEW);
|
||||||
_useTransitionTargetScene->setVisible(
|
_useTransitionTargetScene->setVisible(
|
||||||
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
_entryData->_type == MacroConditionScene::Type::CURRENT ||
|
||||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
@@ -246,7 +310,9 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
|||||||
_entryData->_type ==
|
_entryData->_type ==
|
||||||
MacroConditionScene::Type::CURRENT_PATTERN ||
|
MacroConditionScene::Type::CURRENT_PATTERN ||
|
||||||
_entryData->_type ==
|
_entryData->_type ==
|
||||||
MacroConditionScene::Type::PREVIOUS_PATTERN);
|
MacroConditionScene::Type::PREVIOUS_PATTERN ||
|
||||||
|
_entryData->_type ==
|
||||||
|
MacroConditionScene::Type::PREVIEW_PATTERN);
|
||||||
|
|
||||||
if (_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
if (_entryData->_type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
_entryData->_type == MacroConditionScene::Type::PREVIOUS_PATTERN) {
|
_entryData->_type == MacroConditionScene::Type::PREVIOUS_PATTERN) {
|
||||||
@@ -259,6 +325,7 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
|
|||||||
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
||||||
}
|
}
|
||||||
adjustSize();
|
adjustSize();
|
||||||
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionSceneEdit::UpdateEntryData()
|
void MacroConditionSceneEdit::UpdateEntryData()
|
||||||
@@ -268,7 +335,8 @@ void MacroConditionSceneEdit::UpdateEntryData()
|
|||||||
}
|
}
|
||||||
|
|
||||||
_scenes->SetScene(_entryData->_scene);
|
_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));
|
_pattern->setText(QString::fromStdString(_entryData->_pattern));
|
||||||
_useTransitionTargetScene->setChecked(
|
_useTransitionTargetScene->setChecked(
|
||||||
_entryData->_useTransitionTargetScene);
|
_entryData->_useTransitionTargetScene);
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
CURRENT,
|
CURRENT = 10,
|
||||||
PREVIOUS,
|
PREVIOUS = 20,
|
||||||
CHANGED,
|
PREVIEW = 30,
|
||||||
NOT_CHANGED,
|
CHANGED = 40,
|
||||||
CURRENT_PATTERN,
|
NOT_CHANGED = 50,
|
||||||
PREVIOUS_PATTERN,
|
CURRENT_PATTERN = 60,
|
||||||
|
PREVIOUS_PATTERN = 70,
|
||||||
|
PREVIEW_PATTERN = 80,
|
||||||
};
|
};
|
||||||
|
|
||||||
SceneSelection _scene;
|
SceneSelection _scene;
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ namespace advss {
|
|||||||
|
|
||||||
const std::string MacroConditionSlideshow::id = "slideshow";
|
const std::string MacroConditionSlideshow::id = "slideshow";
|
||||||
|
|
||||||
bool MacroConditionSlideshow::_registered = MacroConditionFactory::Register(
|
bool MacroConditionSlideshow::_registered =
|
||||||
MacroConditionSlideshow::id,
|
obs_get_version() >= MAKE_SEMANTIC_VERSION(29, 1, 0) &&
|
||||||
{MacroConditionSlideshow::Create, MacroConditionSlideshowEdit::Create,
|
MacroConditionFactory::Register(
|
||||||
"AdvSceneSwitcher.condition.slideshow"});
|
MacroConditionSlideshow::id,
|
||||||
|
{MacroConditionSlideshow::Create,
|
||||||
|
MacroConditionSlideshowEdit::Create,
|
||||||
|
"AdvSceneSwitcher.condition.slideshow"});
|
||||||
|
|
||||||
static const std::map<MacroConditionSlideshow::Condition, std::string>
|
static const std::map<MacroConditionSlideshow::Condition, std::string>
|
||||||
conditions = {
|
conditions = {
|
||||||
@@ -200,9 +203,9 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
|
|||||||
QWidget *parent, std::shared_ptr<MacroConditionSlideshow> entryData)
|
QWidget *parent, std::shared_ptr<MacroConditionSlideshow> entryData)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
_conditions(new QComboBox(this)),
|
_conditions(new QComboBox(this)),
|
||||||
_sources(new SourceSelectionWidget(this, QStringList(), true)),
|
|
||||||
_index(new VariableSpinBox(this)),
|
_index(new VariableSpinBox(this)),
|
||||||
_path(new VariableLineEdit(this))
|
_path(new VariableLineEdit(this)),
|
||||||
|
_sources(new SourceSelectionWidget(this, QStringList(), true))
|
||||||
{
|
{
|
||||||
setToolTip(obs_module_text(
|
setToolTip(obs_module_text(
|
||||||
"AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip"));
|
"AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip"));
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ bool MacroConditionSource::_registered = MacroConditionFactory::Register(
|
|||||||
{MacroConditionSource::Create, MacroConditionSourceEdit::Create,
|
{MacroConditionSource::Create, MacroConditionSourceEdit::Create,
|
||||||
"AdvSceneSwitcher.condition.source"});
|
"AdvSceneSwitcher.condition.source"});
|
||||||
|
|
||||||
const static std::map<SourceCondition, std::string> sourceConditionTypes = {
|
const static std::map<MacroConditionSource::Condition, std::string>
|
||||||
{SourceCondition::ACTIVE,
|
sourceCnditionTypes = {
|
||||||
"AdvSceneSwitcher.condition.source.type.active"},
|
{MacroConditionSource::Condition::ACTIVE,
|
||||||
{SourceCondition::SHOWING,
|
"AdvSceneSwitcher.condition.source.type.active"},
|
||||||
"AdvSceneSwitcher.condition.source.type.showing"},
|
{MacroConditionSource::Condition::SHOWING,
|
||||||
{SourceCondition::SETTINGS,
|
"AdvSceneSwitcher.condition.source.type.showing"},
|
||||||
"AdvSceneSwitcher.condition.source.type.settings"},
|
{MacroConditionSource::Condition::SETTINGS_MATCH,
|
||||||
|
"AdvSceneSwitcher.condition.source.type.settings"},
|
||||||
|
{MacroConditionSource::Condition::SETTINGS_CHANGED,
|
||||||
|
"AdvSceneSwitcher.condition.source.type.settingsChanged"},
|
||||||
};
|
};
|
||||||
|
|
||||||
bool MacroConditionSource::CheckCondition()
|
bool MacroConditionSource::CheckCondition()
|
||||||
@@ -29,13 +32,13 @@ bool MacroConditionSource::CheckCondition()
|
|||||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||||
|
|
||||||
switch (_condition) {
|
switch (_condition) {
|
||||||
case SourceCondition::ACTIVE:
|
case Condition::ACTIVE:
|
||||||
ret = obs_source_active(s);
|
ret = obs_source_active(s);
|
||||||
break;
|
break;
|
||||||
case SourceCondition::SHOWING:
|
case Condition::SHOWING:
|
||||||
ret = obs_source_showing(s);
|
ret = obs_source_showing(s);
|
||||||
break;
|
break;
|
||||||
case SourceCondition::SETTINGS:
|
case Condition::SETTINGS_MATCH:
|
||||||
ret = CompareSourceSettings(_source.GetSource(), _settings,
|
ret = CompareSourceSettings(_source.GetSource(), _settings,
|
||||||
_regex);
|
_regex);
|
||||||
if (IsReferencedInVars()) {
|
if (IsReferencedInVars()) {
|
||||||
@@ -43,6 +46,13 @@ bool MacroConditionSource::CheckCondition()
|
|||||||
GetSourceSettings(_source.GetSource()));
|
GetSourceSettings(_source.GetSource()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Condition::SETTINGS_CHANGED: {
|
||||||
|
std::string settings = GetSourceSettings(_source.GetSource());
|
||||||
|
ret = !_currentSettings.empty() && settings != _currentSettings;
|
||||||
|
_currentSettings = settings;
|
||||||
|
SetVariableValue(settings);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -70,8 +80,7 @@ bool MacroConditionSource::Load(obs_data_t *obj)
|
|||||||
{
|
{
|
||||||
MacroCondition::Load(obj);
|
MacroCondition::Load(obj);
|
||||||
_source.Load(obj);
|
_source.Load(obj);
|
||||||
_condition = static_cast<SourceCondition>(
|
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
||||||
obs_data_get_int(obj, "condition"));
|
|
||||||
_settings.Load(obj, "settings");
|
_settings.Load(obj, "settings");
|
||||||
_regex.Load(obj);
|
_regex.Load(obj);
|
||||||
// TOOD: remove in future version
|
// TOOD: remove in future version
|
||||||
@@ -89,8 +98,8 @@ std::string MacroConditionSource::GetShortDesc() const
|
|||||||
|
|
||||||
static inline void populateConditionSelection(QComboBox *list)
|
static inline void populateConditionSelection(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (auto entry : sourceConditionTypes) {
|
for (const auto &[_, name] : sourceCnditionTypes) {
|
||||||
list->addItem(obs_module_text(entry.second.c_str()));
|
list->addItem(obs_module_text(name.c_str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +182,8 @@ void MacroConditionSourceEdit::ConditionChanged(int index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_condition = static_cast<SourceCondition>(index);
|
_entryData->_condition =
|
||||||
|
static_cast<MacroConditionSource::Condition>(index);
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,14 +230,18 @@ void MacroConditionSourceEdit::RegexChanged(RegexConfig conf)
|
|||||||
void MacroConditionSourceEdit::SetWidgetVisibility()
|
void MacroConditionSourceEdit::SetWidgetVisibility()
|
||||||
{
|
{
|
||||||
_settings->setVisible(_entryData->_condition ==
|
_settings->setVisible(_entryData->_condition ==
|
||||||
SourceCondition::SETTINGS);
|
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||||
_getSettings->setVisible(_entryData->_condition ==
|
_getSettings->setVisible(
|
||||||
SourceCondition::SETTINGS);
|
_entryData->_condition ==
|
||||||
_regex->setVisible(_entryData->_condition == SourceCondition::SETTINGS);
|
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||||
|
_regex->setVisible(_entryData->_condition ==
|
||||||
|
MacroConditionSource::Condition::SETTINGS_MATCH);
|
||||||
|
|
||||||
setToolTip(
|
setToolTip(
|
||||||
(_entryData->_condition == SourceCondition::ACTIVE ||
|
(_entryData->_condition ==
|
||||||
_entryData->_condition == SourceCondition::SHOWING)
|
MacroConditionSource::Condition::ACTIVE ||
|
||||||
|
_entryData->_condition ==
|
||||||
|
MacroConditionSource::Condition::SHOWING)
|
||||||
? obs_module_text(
|
? obs_module_text(
|
||||||
"AdvSceneSwitcher.condition.source.sceneVisibilityHint")
|
"AdvSceneSwitcher.condition.source.sceneVisibilityHint")
|
||||||
: "");
|
: "");
|
||||||
|
|||||||
@@ -10,12 +10,6 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
enum class SourceCondition {
|
|
||||||
ACTIVE,
|
|
||||||
SHOWING,
|
|
||||||
SETTINGS,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacroConditionSource : public MacroCondition {
|
class MacroConditionSource : public MacroCondition {
|
||||||
public:
|
public:
|
||||||
MacroConditionSource(Macro *m) : MacroCondition(m, true) {}
|
MacroConditionSource(Macro *m) : MacroCondition(m, true) {}
|
||||||
@@ -29,12 +23,21 @@ public:
|
|||||||
return std::make_shared<MacroConditionSource>(m);
|
return std::make_shared<MacroConditionSource>(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class Condition {
|
||||||
|
ACTIVE,
|
||||||
|
SHOWING,
|
||||||
|
SETTINGS_MATCH,
|
||||||
|
SETTINGS_CHANGED,
|
||||||
|
};
|
||||||
|
|
||||||
SourceSelection _source;
|
SourceSelection _source;
|
||||||
SourceCondition _condition = SourceCondition::ACTIVE;
|
Condition _condition = Condition::ACTIVE;
|
||||||
StringVariable _settings = "";
|
StringVariable _settings = "";
|
||||||
RegexConfig _regex;
|
RegexConfig _regex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string _currentSettings;
|
||||||
|
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ void MacroConditionStudioModeEdit::SetWidgetVisibility()
|
|||||||
|
|
||||||
_scenes->setVisible(_entryData->_condition ==
|
_scenes->setVisible(_entryData->_condition ==
|
||||||
StudioModeCondition::PREVIEW_SCENE);
|
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
|
} // namespace advss
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ namespace advss {
|
|||||||
enum class StudioModeCondition {
|
enum class StudioModeCondition {
|
||||||
STUDIO_MODE_ACTIVE,
|
STUDIO_MODE_ACTIVE,
|
||||||
STUDIO_MODE_NOT_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 {
|
class MacroConditionStudioMode : public MacroCondition {
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ static bool windowContainsText(const std::string &window,
|
|||||||
return text == matchText;
|
return text == matchText;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionWindow::WindowMatches(const std::string &window)
|
bool MacroConditionWindow::WindowMatchesRequirements(
|
||||||
|
const std::string &window) const
|
||||||
{
|
{
|
||||||
const bool focusCheckOK = (!_focus || window == switcher->currentTitle);
|
const bool focusCheckOK = (!_focus || window == switcher->currentTitle);
|
||||||
if (!focusCheckOK) {
|
if (!focusCheckOK) {
|
||||||
@@ -60,26 +61,52 @@ bool MacroConditionWindow::WindowMatches(const std::string &window)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_checkText) {
|
|
||||||
auto text = GetTextInWindow(window);
|
|
||||||
SetVariableValue(text.value_or(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MacroConditionWindow::WindowMatches(
|
||||||
|
const std::vector<std::string> &windowList)
|
||||||
|
{
|
||||||
|
bool match = !_checkTitle ||
|
||||||
|
std::find(windowList.begin(), windowList.end(),
|
||||||
|
std::string(_window)) != windowList.end();
|
||||||
|
match = match && WindowMatchesRequirements(_window);
|
||||||
|
SetVariableValueBasedOnMatch(_window);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
bool MacroConditionWindow::WindowRegexMatches(
|
bool MacroConditionWindow::WindowRegexMatches(
|
||||||
const std::vector<std::string> &windowList)
|
const std::vector<std::string> &windowList)
|
||||||
{
|
{
|
||||||
|
// No need to test if checking for window title is required as if the
|
||||||
|
// user has disabled window title matching the option will always be
|
||||||
|
// enabled in the backend and use the regular expression ".*".
|
||||||
|
|
||||||
for (const auto &window : windowList) {
|
for (const auto &window : windowList) {
|
||||||
if (matchRegex(_windowRegex, window, _window) &&
|
if (matchRegex(_windowRegex, window, _window) &&
|
||||||
WindowMatches(window)) {
|
WindowMatchesRequirements(window)) {
|
||||||
|
SetVariableValueBasedOnMatch(window);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SetVariableValueBasedOnMatch("");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroConditionWindow::SetVariableValueBasedOnMatch(
|
||||||
|
const std::string &matchWindow)
|
||||||
|
{
|
||||||
|
if (!IsReferencedInVars()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_checkText) {
|
||||||
|
auto text = GetTextInWindow(matchWindow);
|
||||||
|
SetVariableValue(text.value_or(""));
|
||||||
|
} else {
|
||||||
|
SetVariableValue(switcher->currentTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool foregroundWindowChanged()
|
static bool foregroundWindowChanged()
|
||||||
{
|
{
|
||||||
return switcher->currentTitle != switcher->lastTitle;
|
return switcher->currentTitle != switcher->lastTitle;
|
||||||
@@ -87,19 +114,13 @@ static bool foregroundWindowChanged()
|
|||||||
|
|
||||||
bool MacroConditionWindow::CheckCondition()
|
bool MacroConditionWindow::CheckCondition()
|
||||||
{
|
{
|
||||||
SetVariableValue("");
|
|
||||||
if (!_checkText) {
|
|
||||||
SetVariableValue(switcher->currentTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> windowList;
|
std::vector<std::string> windowList;
|
||||||
GetWindowList(windowList);
|
GetWindowList(windowList);
|
||||||
|
|
||||||
bool match = false;
|
bool match = false;
|
||||||
if (_windowRegex.Enabled()) {
|
if (_windowRegex.Enabled()) {
|
||||||
match = WindowRegexMatches(windowList);
|
match = WindowRegexMatches(windowList);
|
||||||
} else {
|
} else {
|
||||||
match = WindowMatches(_window);
|
match = WindowMatches(windowList);
|
||||||
}
|
}
|
||||||
match = match && (!_windowFocusChanged || foregroundWindowChanged());
|
match = match && (!_windowFocusChanged || foregroundWindowChanged());
|
||||||
return match;
|
return match;
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ public:
|
|||||||
return std::make_shared<MacroConditionWindow>(m);
|
return std::make_shared<MacroConditionWindow>(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
bool WindowMatches(const std::string &window);
|
|
||||||
bool WindowRegexMatches(const std::vector<std::string> &windowList);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StringVariable _window;
|
StringVariable _window;
|
||||||
RegexConfig _windowRegex;
|
RegexConfig _windowRegex;
|
||||||
@@ -40,6 +36,11 @@ public:
|
|||||||
RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig();
|
RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool WindowMatchesRequirements(const std::string &window) const;
|
||||||
|
bool WindowMatches(const std::vector<std::string> &windowList);
|
||||||
|
bool WindowRegexMatches(const std::vector<std::string> &windowList);
|
||||||
|
void SetVariableValueBasedOnMatch(const std::string &matchWindow);
|
||||||
|
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
MacroDock::MacroDock(Macro *m, QWidget *parent,
|
MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
|
||||||
const StringVariable &runButtonText,
|
const StringVariable &runButtonText,
|
||||||
const StringVariable &pauseButtonText,
|
const StringVariable &pauseButtonText,
|
||||||
const StringVariable &unpauseButtonText,
|
const StringVariable &unpauseButtonText,
|
||||||
@@ -25,11 +25,12 @@ MacroDock::MacroDock(Macro *m, QWidget *parent,
|
|||||||
_statusText(new QLabel(conditionsFalseText.c_str())),
|
_statusText(new QLabel(conditionsFalseText.c_str())),
|
||||||
_macro(m)
|
_macro(m)
|
||||||
{
|
{
|
||||||
if (_macro) {
|
auto macro = _macro.lock();
|
||||||
setWindowTitle(QString::fromStdString(_macro->Name()));
|
if (macro) {
|
||||||
_run->setVisible(_macro->DockHasRunButton());
|
setWindowTitle(QString::fromStdString(macro->Name()));
|
||||||
_pauseToggle->setVisible(_macro->DockHasPauseButton());
|
_run->setVisible(macro->DockHasRunButton());
|
||||||
_statusText->setVisible(_macro->DockHasStatusLabel());
|
_pauseToggle->setVisible(macro->DockHasPauseButton());
|
||||||
|
_statusText->setVisible(macro->DockHasStatusLabel());
|
||||||
} else {
|
} else {
|
||||||
setWindowTitle("<deleted macro>");
|
setWindowTitle("<deleted macro>");
|
||||||
}
|
}
|
||||||
@@ -120,49 +121,52 @@ void MacroDock::EnableHighlight(bool value)
|
|||||||
|
|
||||||
void MacroDock::RunClicked()
|
void MacroDock::RunClicked()
|
||||||
{
|
{
|
||||||
if (!_macro) {
|
auto macro = _macro.lock();
|
||||||
|
if (!macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ret = _macro->PerformActions();
|
auto ret = macro->PerformActions(true);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
QString err =
|
QString err =
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||||
DisplayMessage(err.arg(QString::fromStdString(_macro->Name())));
|
DisplayMessage(err.arg(QString::fromStdString(macro->Name())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroDock::PauseToggleClicked()
|
void MacroDock::PauseToggleClicked()
|
||||||
{
|
{
|
||||||
if (!_macro) {
|
auto macro = _macro.lock();
|
||||||
|
if (!macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_macro->SetPaused(!_macro->Paused());
|
macro->SetPaused(!macro->Paused());
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroDock::UpdateText()
|
void MacroDock::UpdateText()
|
||||||
{
|
{
|
||||||
_run->setText(_runButtonText.c_str());
|
_run->setText(_runButtonText.c_str());
|
||||||
|
auto macro = _macro.lock();
|
||||||
if (!_macro) {
|
if (!macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pauseToggle->setText(_macro->Paused() ? _unpauseButtonText.c_str()
|
_pauseToggle->setText(macro->Paused() ? _unpauseButtonText.c_str()
|
||||||
: _pauseButtonText.c_str());
|
: _pauseButtonText.c_str());
|
||||||
_statusText->setText(_macro->Matched() ? _conditionsTrueText.c_str()
|
_statusText->setText(macro->Matched() ? _conditionsTrueText.c_str()
|
||||||
: _conditionsFalseText.c_str());
|
: _conditionsFalseText.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroDock::Highlight()
|
void MacroDock::Highlight()
|
||||||
{
|
{
|
||||||
if (!_highlight || !_macro) {
|
auto macro = _macro.lock();
|
||||||
|
if (!_highlight || !macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
|
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
|
||||||
_macro->ExecutedSince(_lastHighlightCheckTime)) {
|
macro->ExecutedSince(_lastHighlightCheckTime)) {
|
||||||
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
|
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
|
||||||
}
|
}
|
||||||
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
|
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class MacroDock : public OBSDock {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MacroDock(Macro *, QWidget *parent, const StringVariable &runButtonText,
|
MacroDock(std::weak_ptr<Macro>, QWidget *parent,
|
||||||
|
const StringVariable &runButtonText,
|
||||||
const StringVariable &pauseButtonText,
|
const StringVariable &pauseButtonText,
|
||||||
const StringVariable &unpauseButtonText,
|
const StringVariable &unpauseButtonText,
|
||||||
const StringVariable &conditionsTrueText,
|
const StringVariable &conditionsTrueText,
|
||||||
@@ -53,7 +54,7 @@ private:
|
|||||||
QTimer _timer;
|
QTimer _timer;
|
||||||
std::chrono::high_resolution_clock::time_point _lastHighlightCheckTime{};
|
std::chrono::high_resolution_clock::time_point _lastHighlightCheckTime{};
|
||||||
|
|
||||||
Macro *_macro;
|
std::weak_ptr<Macro> _macro;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -44,6 +46,8 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
|||||||
"AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))),
|
"AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))),
|
||||||
_currentMacroRegisterHotkeys(new QCheckBox(obs_module_text(
|
_currentMacroRegisterHotkeys(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.macroTab.currentDisableHotkeys"))),
|
"AdvSceneSwitcher.macroTab.currentDisableHotkeys"))),
|
||||||
|
_currentSkipOnStartup(new QCheckBox(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.macroTab.currentSkipExecutionOnStartup"))),
|
||||||
_currentMacroRegisterDock(new QCheckBox(obs_module_text(
|
_currentMacroRegisterDock(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.macroTab.currentRegisterDock"))),
|
"AdvSceneSwitcher.macroTab.currentRegisterDock"))),
|
||||||
_currentMacroDockAddRunButton(new QCheckBox(obs_module_text(
|
_currentMacroDockAddRunButton(new QCheckBox(obs_module_text(
|
||||||
@@ -82,6 +86,12 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
|||||||
hotkeyLayout->addWidget(_currentMacroRegisterHotkeys);
|
hotkeyLayout->addWidget(_currentMacroRegisterHotkeys);
|
||||||
hotkeyOptions->setLayout(hotkeyLayout);
|
hotkeyOptions->setLayout(hotkeyLayout);
|
||||||
|
|
||||||
|
auto generalOptions = new QGroupBox(
|
||||||
|
obs_module_text("AdvSceneSwitcher.macroTab.generalSettings"));
|
||||||
|
auto generalLayout = new QVBoxLayout;
|
||||||
|
generalLayout->addWidget(_currentSkipOnStartup);
|
||||||
|
generalOptions->setLayout(generalLayout);
|
||||||
|
|
||||||
int row = 0;
|
int row = 0;
|
||||||
_dockLayout->addWidget(_currentMacroRegisterDock, row, 1, 1, 2);
|
_dockLayout->addWidget(_currentMacroRegisterDock, row, 1, 1, 2);
|
||||||
row++;
|
row++;
|
||||||
@@ -146,12 +156,23 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
|||||||
connect(_currentMacroDockAddStatusLabel, &QCheckBox::stateChanged, this,
|
connect(_currentMacroDockAddStatusLabel, &QCheckBox::stateChanged, this,
|
||||||
&MacroPropertiesDialog::StatusLabelEnableChanged);
|
&MacroPropertiesDialog::StatusLabelEnableChanged);
|
||||||
|
|
||||||
auto layout = new QVBoxLayout;
|
auto scrollArea = new QScrollArea(this);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
|
||||||
|
auto contentWidget = new QWidget(scrollArea);
|
||||||
|
auto layout = new QVBoxLayout(contentWidget);
|
||||||
layout->addWidget(highlightOptions);
|
layout->addWidget(highlightOptions);
|
||||||
layout->addWidget(hotkeyOptions);
|
layout->addWidget(hotkeyOptions);
|
||||||
|
layout->addWidget(generalOptions);
|
||||||
layout->addWidget(_dockOptions);
|
layout->addWidget(_dockOptions);
|
||||||
layout->addWidget(buttonbox);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
setLayout(layout);
|
scrollArea->setWidget(contentWidget);
|
||||||
|
|
||||||
|
auto dialogLayout = new QVBoxLayout();
|
||||||
|
dialogLayout->addWidget(scrollArea);
|
||||||
|
dialogLayout->addWidget(buttonbox);
|
||||||
|
setLayout(dialogLayout);
|
||||||
|
|
||||||
_executed->setChecked(prop._highlightExecuted);
|
_executed->setChecked(prop._highlightExecuted);
|
||||||
_conditions->setChecked(prop._highlightConditions);
|
_conditions->setChecked(prop._highlightConditions);
|
||||||
@@ -159,10 +180,12 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
|||||||
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
|
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
|
||||||
if (!macro || macro->IsGroup()) {
|
if (!macro || macro->IsGroup()) {
|
||||||
hotkeyOptions->hide();
|
hotkeyOptions->hide();
|
||||||
|
generalOptions->hide();
|
||||||
_dockOptions->hide();
|
_dockOptions->hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_currentMacroRegisterHotkeys->setChecked(macro->PauseHotkeysEnabled());
|
_currentMacroRegisterHotkeys->setChecked(macro->PauseHotkeysEnabled());
|
||||||
|
_currentSkipOnStartup->setChecked(macro->SkipExecOnStart());
|
||||||
const bool dockEnabled = macro->DockEnabled();
|
const bool dockEnabled = macro->DockEnabled();
|
||||||
_currentMacroRegisterDock->setChecked(dockEnabled);
|
_currentMacroRegisterDock->setChecked(dockEnabled);
|
||||||
_currentMacroDockAddRunButton->setChecked(macro->DockHasRunButton());
|
_currentMacroDockAddRunButton->setChecked(macro->DockHasRunButton());
|
||||||
@@ -194,6 +217,20 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
|||||||
dockEnabled && macro->DockHasStatusLabel());
|
dockEnabled && macro->DockHasStatusLabel());
|
||||||
MinimizeSizeOfColumn(_dockLayout, 0);
|
MinimizeSizeOfColumn(_dockLayout, 0);
|
||||||
Resize();
|
Resize();
|
||||||
|
|
||||||
|
// Try to set sensible initial size for the dialog window
|
||||||
|
QSize contentSize = contentWidget->sizeHint();
|
||||||
|
resize(contentSize.width() + layout->contentsMargins().left() +
|
||||||
|
layout->contentsMargins().right() +
|
||||||
|
dialogLayout->contentsMargins().left() +
|
||||||
|
dialogLayout->contentsMargins().right() +
|
||||||
|
scrollArea->verticalScrollBar()->sizeHint().width() + 20,
|
||||||
|
contentSize.height() + dialogLayout->spacing() +
|
||||||
|
buttonbox->sizeHint().height() +
|
||||||
|
dialogLayout->contentsMargins().top() +
|
||||||
|
dialogLayout->contentsMargins().bottom() +
|
||||||
|
scrollArea->horizontalScrollBar()->sizeHint().height() +
|
||||||
|
20);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroPropertiesDialog::DockEnableChanged(int enabled)
|
void MacroPropertiesDialog::DockEnableChanged(int enabled)
|
||||||
@@ -244,8 +281,6 @@ void MacroPropertiesDialog::Resize()
|
|||||||
{
|
{
|
||||||
_dockOptions->adjustSize();
|
_dockOptions->adjustSize();
|
||||||
_dockOptions->updateGeometry();
|
_dockOptions->updateGeometry();
|
||||||
adjustSize();
|
|
||||||
updateGeometry();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
|
bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
|
||||||
@@ -268,6 +303,7 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
|
|||||||
|
|
||||||
macro->EnablePauseHotkeys(
|
macro->EnablePauseHotkeys(
|
||||||
dialog._currentMacroRegisterHotkeys->isChecked());
|
dialog._currentMacroRegisterHotkeys->isChecked());
|
||||||
|
macro->SetSkipExecOnStart(dialog._currentSkipOnStartup->isChecked());
|
||||||
macro->EnableDock(dialog._currentMacroRegisterDock->isChecked());
|
macro->EnableDock(dialog._currentMacroRegisterDock->isChecked());
|
||||||
macro->SetDockHasRunButton(
|
macro->SetDockHasRunButton(
|
||||||
dialog._currentMacroDockAddRunButton->isChecked());
|
dialog._currentMacroDockAddRunButton->isChecked());
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ private:
|
|||||||
QCheckBox *_newMacroRegisterHotkeys;
|
QCheckBox *_newMacroRegisterHotkeys;
|
||||||
// Current macro specific settings
|
// Current macro specific settings
|
||||||
QCheckBox *_currentMacroRegisterHotkeys;
|
QCheckBox *_currentMacroRegisterHotkeys;
|
||||||
|
QCheckBox *_currentSkipOnStartup;
|
||||||
QCheckBox *_currentMacroRegisterDock;
|
QCheckBox *_currentMacroRegisterDock;
|
||||||
QCheckBox *_currentMacroDockAddRunButton;
|
QCheckBox *_currentMacroDockAddRunButton;
|
||||||
QCheckBox *_currentMacroDockAddPauseButton;
|
QCheckBox *_currentMacroDockAddPauseButton;
|
||||||
|
|||||||
@@ -253,6 +253,76 @@ void AdvSceneSwitcher::ExportMacros()
|
|||||||
MacroExportImportDialog::ExportMacros(exportString);
|
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(
|
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
|
||||||
std::shared_ptr<Macro> ¯o)
|
std::shared_ptr<Macro> ¯o)
|
||||||
{
|
{
|
||||||
@@ -381,21 +451,6 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
|
|||||||
RenameMacro(macro, newName);
|
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)
|
void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value)
|
||||||
{
|
{
|
||||||
auto macro = GetSelectedMacro();
|
auto macro = GetSelectedMacro();
|
||||||
@@ -422,9 +477,20 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
|
|||||||
for (; afterIdx < actions.size(); afterIdx++) {
|
for (; afterIdx < actions.size(); afterIdx++) {
|
||||||
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
||||||
actions[afterIdx]->GetId());
|
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)
|
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
||||||
@@ -435,17 +501,35 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
|||||||
auto newEntry = new MacroConditionEdit(
|
auto newEntry = new MacroConditionEdit(
|
||||||
this, &conditions[afterIdx],
|
this, &conditions[afterIdx],
|
||||||
conditions[afterIdx]->GetId(), root);
|
conditions[afterIdx]->GetId(), root);
|
||||||
conditionsList->Add(newEntry);
|
ui->conditionsList->Add(newEntry);
|
||||||
root = false;
|
root = false;
|
||||||
}
|
}
|
||||||
conditionsList->SetHelpMsgVisible(conditions.size() == 0);
|
ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::SetActionData(Macro &m)
|
void AdvSceneSwitcher::SetActionData(Macro &m)
|
||||||
{
|
{
|
||||||
auto &actions = m.Actions();
|
auto &actions = m.Actions();
|
||||||
for (int idx = 0; idx < actionsList->ContentLayout()->count(); idx++) {
|
for (int idx = 0; idx < ui->actionsList->ContentLayout()->count();
|
||||||
auto item = actionsList->ContentLayout()->itemAt(idx);
|
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) {
|
if (!item) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -460,9 +544,9 @@ void AdvSceneSwitcher::SetActionData(Macro &m)
|
|||||||
void AdvSceneSwitcher::SetConditionData(Macro &m)
|
void AdvSceneSwitcher::SetConditionData(Macro &m)
|
||||||
{
|
{
|
||||||
auto &conditions = m.Conditions();
|
auto &conditions = m.Conditions();
|
||||||
for (int idx = 0; idx < conditionsList->ContentLayout()->count();
|
for (int idx = 0; idx < ui->conditionsList->ContentLayout()->count();
|
||||||
idx++) {
|
idx++) {
|
||||||
auto item = conditionsList->ContentLayout()->itemAt(idx);
|
auto item = ui->conditionsList->ContentLayout()->itemAt(idx);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
continue;
|
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)
|
void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -484,22 +583,40 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
|||||||
ui->runMacroInParallel->setChecked(m.RunInParallel());
|
ui->runMacroInParallel->setChecked(m.RunInParallel());
|
||||||
ui->runMacroOnChange->setChecked(m.MatchOnChange());
|
ui->runMacroOnChange->setChecked(m.MatchOnChange());
|
||||||
}
|
}
|
||||||
conditionsList->Clear();
|
ui->conditionsList->Clear();
|
||||||
actionsList->Clear();
|
ui->actionsList->Clear();
|
||||||
|
ui->elseActionsList->Clear();
|
||||||
|
|
||||||
m.ResetUIHelpers();
|
m.ResetUIHelpers();
|
||||||
|
|
||||||
PopulateMacroConditions(m);
|
PopulateMacroConditions(m);
|
||||||
PopulateMacroActions(m);
|
PopulateMacroActions(m);
|
||||||
|
PopulateMacroElseActions(m);
|
||||||
SetMacroEditAreaDisabled(false);
|
SetMacroEditAreaDisabled(false);
|
||||||
|
|
||||||
|
currentActionIdx = -1;
|
||||||
|
currentElseActionIdx = -1;
|
||||||
|
currentConditionIdx = -1;
|
||||||
|
HighlightControls();
|
||||||
|
|
||||||
if (m.IsGroup()) {
|
if (m.IsGroup()) {
|
||||||
SetMacroEditAreaDisabled(true);
|
SetMacroEditAreaDisabled(true);
|
||||||
ui->macroName->setEnabled(true);
|
ui->macroName->setEnabled(true);
|
||||||
|
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||||
|
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentActionIdx = -1;
|
if (!m.HasValidSplitterPositions()) {
|
||||||
currentConditionIdx = -1;
|
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||||
HighlightControls();
|
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->macroActionConditionSplitter->setSizes(
|
||||||
|
m.GetActionConditionSplitterPosition());
|
||||||
|
ui->macroElseActionSplitter->setSizes(
|
||||||
|
m.GetElseActionSplitterPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
||||||
@@ -515,12 +632,17 @@ void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
|||||||
|
|
||||||
void AdvSceneSwitcher::HighlightAction(int idx, QColor color)
|
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)
|
void AdvSceneSwitcher::HighlightCondition(int idx, QColor color)
|
||||||
{
|
{
|
||||||
conditionsList->Highlight(idx, color);
|
ui->conditionsList->Highlight(idx, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Macro> AdvSceneSwitcher::GetSelectedMacro()
|
std::shared_ptr<Macro> AdvSceneSwitcher::GetSelectedMacro()
|
||||||
@@ -533,6 +655,37 @@ std::vector<std::shared_ptr<Macro>> AdvSceneSwitcher::GetSelectedMacros()
|
|||||||
return ui->macros->GetCurrentMacros();
|
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()
|
void AdvSceneSwitcher::MacroSelectionChanged()
|
||||||
{
|
{
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -542,10 +695,14 @@ void AdvSceneSwitcher::MacroSelectionChanged()
|
|||||||
auto macro = GetSelectedMacro();
|
auto macro = GetSelectedMacro();
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
SetMacroEditAreaDisabled(true);
|
SetMacroEditAreaDisabled(true);
|
||||||
conditionsList->Clear();
|
ui->conditionsList->Clear();
|
||||||
actionsList->Clear();
|
ui->actionsList->Clear();
|
||||||
conditionsList->SetHelpMsgVisible(true);
|
ui->elseActionsList->Clear();
|
||||||
actionsList->SetHelpMsgVisible(true);
|
ui->conditionsList->SetHelpMsgVisible(true);
|
||||||
|
ui->actionsList->SetHelpMsgVisible(true);
|
||||||
|
ui->elseActionsList->SetHelpMsgVisible(true);
|
||||||
|
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||||
|
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetEditMacro(*macro);
|
SetEditMacro(*macro);
|
||||||
@@ -583,8 +740,19 @@ void AdvSceneSwitcher::on_macroProperties_clicked()
|
|||||||
emit HighlightConditionsChanged(prop._highlightConditions);
|
emit HighlightConditionsChanged(prop._highlightConditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't restore splitter pos if an element is not visible at all
|
static void moveControlsToSplitter(QSplitter *splitter, int idx,
|
||||||
bool shouldResotreSplitterPos(const QList<int> &pos)
|
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) {
|
if (pos.size() == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -605,37 +773,44 @@ void AdvSceneSwitcher::SetupMacroTab()
|
|||||||
}
|
}
|
||||||
ui->macros->Reset(switcher->macros,
|
ui->macros->Reset(switcher->macros,
|
||||||
switcher->macroProperties._highlightExecuted);
|
switcher->macroProperties._highlightExecuted);
|
||||||
|
connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this,
|
||||||
|
SLOT(MacroSelectionAboutToChange()));
|
||||||
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
|
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
|
||||||
SLOT(MacroSelectionChanged()));
|
SLOT(MacroSelectionChanged()));
|
||||||
|
ui->runMacro->SetMacroTree(ui->macros);
|
||||||
|
|
||||||
delete conditionsList;
|
ui->conditionsList->SetHelpMsg(
|
||||||
conditionsList = new MacroSegmentList(this);
|
|
||||||
conditionsList->SetHelpMsg(
|
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
|
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
|
||||||
connect(conditionsList, &MacroSegmentList::SelectionChagned, this,
|
connect(ui->conditionsList, &MacroSegmentList::SelectionChagned, this,
|
||||||
&AdvSceneSwitcher::MacroConditionSelectionChanged);
|
&AdvSceneSwitcher::MacroConditionSelectionChanged);
|
||||||
connect(conditionsList, &MacroSegmentList::Reorder, this,
|
connect(ui->conditionsList, &MacroSegmentList::Reorder, this,
|
||||||
&AdvSceneSwitcher::MacroConditionReorder);
|
&AdvSceneSwitcher::MacroConditionReorder);
|
||||||
ui->macroConditionsLayout->insertWidget(0, conditionsList);
|
|
||||||
|
|
||||||
delete actionsList;
|
ui->actionsList->SetHelpMsg(
|
||||||
actionsList = new MacroSegmentList(this);
|
|
||||||
actionsList->SetHelpMsg(
|
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
|
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
|
||||||
connect(actionsList, &MacroSegmentList::SelectionChagned, this,
|
connect(ui->actionsList, &MacroSegmentList::SelectionChagned, this,
|
||||||
&AdvSceneSwitcher::MacroActionSelectionChanged);
|
&AdvSceneSwitcher::MacroActionSelectionChanged);
|
||||||
connect(actionsList, &MacroSegmentList::Reorder, this,
|
connect(ui->actionsList, &MacroSegmentList::Reorder, this,
|
||||||
&AdvSceneSwitcher::MacroActionReorder);
|
&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);
|
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(ui->macros, &QWidget::customContextMenuRequested, this,
|
connect(ui->macros, &QWidget::customContextMenuRequested, this,
|
||||||
&AdvSceneSwitcher::ShowMacroContextMenu);
|
&AdvSceneSwitcher::ShowMacroContextMenu);
|
||||||
actionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
ui->actionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(actionsList, &QWidget::customContextMenuRequested, this,
|
connect(ui->actionsList, &QWidget::customContextMenuRequested, this,
|
||||||
&AdvSceneSwitcher::ShowMacroActionsContextMenu);
|
&AdvSceneSwitcher::ShowMacroActionsContextMenu);
|
||||||
conditionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
ui->elseActionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(conditionsList, &QWidget::customContextMenuRequested, this,
|
connect(ui->elseActionsList, &QWidget::customContextMenuRequested, this,
|
||||||
|
&AdvSceneSwitcher::ShowMacroElseActionsContextMenu);
|
||||||
|
ui->conditionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(ui->conditionsList, &QWidget::customContextMenuRequested, this,
|
||||||
&AdvSceneSwitcher::ShowMacroConditionsContextMenu);
|
&AdvSceneSwitcher::ShowMacroConditionsContextMenu);
|
||||||
|
|
||||||
SetMacroEditAreaDisabled(true);
|
SetMacroEditAreaDisabled(true);
|
||||||
@@ -648,16 +823,10 @@ void AdvSceneSwitcher::SetupMacroTab()
|
|||||||
onChangeHighlightTimer.start();
|
onChangeHighlightTimer.start();
|
||||||
|
|
||||||
// Move condition controls into splitter handle layout
|
// Move condition controls into splitter handle layout
|
||||||
auto handle = ui->macroActionConditionSplitter->handle(1);
|
moveControlsToSplitter(ui->macroActionConditionSplitter, 1,
|
||||||
auto item = ui->macroConditionsLayout->takeAt(1);
|
ui->macroConditionsLayout->takeAt(1));
|
||||||
if (item) {
|
moveControlsToSplitter(ui->macroElseActionSplitter, 1,
|
||||||
auto layout = item->layout();
|
ui->macroActionsLayout->takeAt(1));
|
||||||
layout->setContentsMargins(7, 7, 7, 7);
|
|
||||||
handle->setLayout(layout);
|
|
||||||
ui->macroActionConditionSplitter->setHandleWidth(38);
|
|
||||||
}
|
|
||||||
ui->macroActionConditionSplitter->setStyleSheet(
|
|
||||||
"QSplitter::handle {background: transparent;}");
|
|
||||||
|
|
||||||
// Set action and condition control icons
|
// Set action and condition control icons
|
||||||
const std::string pathPrefix =
|
const std::string pathPrefix =
|
||||||
@@ -665,6 +834,9 @@ void AdvSceneSwitcher::SetupMacroTab()
|
|||||||
SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
||||||
SetButtonIcon(ui->actionBottom,
|
SetButtonIcon(ui->actionBottom,
|
||||||
(pathPrefix + "DoubleDown.svg").c_str());
|
(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->conditionTop, (pathPrefix + "DoubleUp.svg").c_str());
|
||||||
SetButtonIcon(ui->conditionBottom,
|
SetButtonIcon(ui->conditionBottom,
|
||||||
(pathPrefix + "DoubleDown.svg").c_str());
|
(pathPrefix + "DoubleDown.svg").c_str());
|
||||||
@@ -673,13 +845,11 @@ void AdvSceneSwitcher::SetupMacroTab()
|
|||||||
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
|
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
|
||||||
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
|
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
|
||||||
|
|
||||||
|
centerSplitterPosition(ui->macroActionConditionSplitter);
|
||||||
|
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
|
||||||
|
|
||||||
if (switcher->saveWindowGeo) {
|
if (switcher->saveWindowGeo) {
|
||||||
if (shouldResotreSplitterPos(
|
if (shouldRestoreSplitter(
|
||||||
switcher->macroActionConditionSplitterPosition)) {
|
|
||||||
ui->macroActionConditionSplitter->setSizes(
|
|
||||||
switcher->macroActionConditionSplitterPosition);
|
|
||||||
}
|
|
||||||
if (shouldResotreSplitterPos(
|
|
||||||
switcher->macroListMacroEditSplitterPosition)) {
|
switcher->macroListMacroEditSplitterPosition)) {
|
||||||
ui->macroListMacroEditSplitter->setSizes(
|
ui->macroListMacroEditSplitter->setSizes(
|
||||||
switcher->macroListMacroEditSplitterPosition);
|
switcher->macroListMacroEditSplitterPosition);
|
||||||
@@ -731,41 +901,55 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
|||||||
obs_module_text("AdvSceneSwitcher.macroTab.export"), this,
|
obs_module_text("AdvSceneSwitcher.macroTab.export"), this,
|
||||||
&AdvSceneSwitcher::ExportMacros);
|
&AdvSceneSwitcher::ExportMacros);
|
||||||
exportAction->setDisabled(ui->macros->SelectionEmpty());
|
exportAction->setDisabled(ui->macros->SelectionEmpty());
|
||||||
auto import = menu.addAction(
|
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.import"),
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.import"), this,
|
this, &AdvSceneSwitcher::ImportMacros);
|
||||||
&AdvSceneSwitcher::ImportMacros);
|
|
||||||
|
|
||||||
menu.exec(globalPos);
|
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)
|
void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos)
|
||||||
{
|
{
|
||||||
QPoint globalPos = actionsList->mapToGlobal(pos);
|
setupConextMenu(this, ui->actionsList->mapToGlobal(pos),
|
||||||
QMenu menu;
|
&AdvSceneSwitcher::ExpandAllActions,
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
&AdvSceneSwitcher::CollapseAllActions,
|
||||||
this, &AdvSceneSwitcher::ExpandAllActions);
|
&AdvSceneSwitcher::MaximizeActions,
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
|
&AdvSceneSwitcher::MinimizeActions);
|
||||||
this, &AdvSceneSwitcher::CollapseAllActions);
|
}
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
|
||||||
this, &AdvSceneSwitcher::MinimizeConditions);
|
void AdvSceneSwitcher::ShowMacroElseActionsContextMenu(const QPoint &pos)
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
{
|
||||||
this, &AdvSceneSwitcher::MinimizeActions);
|
setupConextMenu(this, ui->elseActionsList->mapToGlobal(pos),
|
||||||
menu.exec(globalPos);
|
&AdvSceneSwitcher::ExpandAllElseActions,
|
||||||
|
&AdvSceneSwitcher::CollapseAllElseActions,
|
||||||
|
&AdvSceneSwitcher::MaximizeElseActions,
|
||||||
|
&AdvSceneSwitcher::MinimizeElseActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos)
|
void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos)
|
||||||
{
|
{
|
||||||
QPoint globalPos = conditionsList->mapToGlobal(pos);
|
setupConextMenu(this, ui->conditionsList->mapToGlobal(pos),
|
||||||
QMenu menu;
|
&AdvSceneSwitcher::ExpandAllConditions,
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
&AdvSceneSwitcher::CollapseAllConditions,
|
||||||
this, &AdvSceneSwitcher::ExpandAllConditions);
|
&AdvSceneSwitcher::MaximizeConditions,
|
||||||
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
|
&AdvSceneSwitcher::MinimizeConditions);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::CopyMacro()
|
void AdvSceneSwitcher::CopyMacro()
|
||||||
@@ -795,60 +979,103 @@ void AdvSceneSwitcher::CopyMacro()
|
|||||||
emit MacroAdded(QString::fromStdString(name));
|
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) {
|
if (!m) {
|
||||||
return;
|
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()
|
void AdvSceneSwitcher::ExpandAllConditions()
|
||||||
{
|
{
|
||||||
auto m = GetSelectedMacro();
|
setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, false);
|
||||||
if (!m) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
conditionsList->SetCollapsed(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::CollapseAllActions()
|
void AdvSceneSwitcher::CollapseAllActions()
|
||||||
{
|
{
|
||||||
auto m = GetSelectedMacro();
|
setCollapsedHelper(GetSelectedMacro(), ui->actionsList, true);
|
||||||
if (!m) {
|
}
|
||||||
return;
|
|
||||||
}
|
void AdvSceneSwitcher::CollapseAllElseActions()
|
||||||
actionsList->SetCollapsed(true);
|
{
|
||||||
|
setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::CollapseAllConditions()
|
void AdvSceneSwitcher::CollapseAllConditions()
|
||||||
{
|
{
|
||||||
auto m = GetSelectedMacro();
|
setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, true);
|
||||||
if (!m) {
|
}
|
||||||
return;
|
|
||||||
}
|
static void reduceSizeOfSplitterIdx(QSplitter *splitter, int idx)
|
||||||
conditionsList->SetCollapsed(true);
|
{
|
||||||
|
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()
|
void AdvSceneSwitcher::MinimizeActions()
|
||||||
{
|
{
|
||||||
QList<int> sizes = ui->macroActionConditionSplitter->sizes();
|
auto macro = GetSelectedMacro();
|
||||||
int sum = sizes[0] + sizes[1];
|
if (!macro) {
|
||||||
int actionsHeight = sum / 10;
|
return;
|
||||||
sizes[1] = actionsHeight;
|
}
|
||||||
sizes[0] = sum - actionsHeight;
|
if (macro->ElseActions().size() == 0) {
|
||||||
ui->macroActionConditionSplitter->setSizes(sizes);
|
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()
|
void AdvSceneSwitcher::MinimizeConditions()
|
||||||
{
|
{
|
||||||
QList<int> sizes = ui->macroActionConditionSplitter->sizes();
|
reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 0);
|
||||||
int sum = sizes[0] + sizes[1];
|
}
|
||||||
int conditionsHeight = sum / 10;
|
|
||||||
sizes[0] = conditionsHeight;
|
void AdvSceneSwitcher::MaximizeConditions()
|
||||||
sizes[1] = sum - conditionsHeight;
|
{
|
||||||
ui->macroActionConditionSplitter->setSizes(sizes);
|
MinimizeElseActions();
|
||||||
|
MinimizeActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AdvSceneSwitcher::MacroTabIsInFocus()
|
bool AdvSceneSwitcher::MacroTabIsInFocus()
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ bool MacroTreeModel::IsLastItem(std::shared_ptr<Macro> item) const
|
|||||||
bool MacroTreeModel::IsInValidState()
|
bool MacroTreeModel::IsInValidState()
|
||||||
{
|
{
|
||||||
// Check for reordering erros
|
// 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];
|
const auto &m = _macros[i];
|
||||||
if (QString::fromStdString(m->Name()) !=
|
if (QString::fromStdString(m->Name()) !=
|
||||||
data(index(j, 0), Qt::AccessibleTextRole)) {
|
data(index(j, 0), Qt::AccessibleTextRole)) {
|
||||||
@@ -622,7 +622,7 @@ bool MacroTreeModel::IsInValidState()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for group errors
|
// 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];
|
const auto &m = _macros[i];
|
||||||
if (!m->IsGroup()) {
|
if (!m->IsGroup()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1260,6 +1260,7 @@ void MacroTree::UngroupSelectedGroups()
|
|||||||
void MacroTree::SelectionChangedHelper(const QItemSelection &,
|
void MacroTree::SelectionChangedHelper(const QItemSelection &,
|
||||||
const QItemSelection &)
|
const QItemSelection &)
|
||||||
{
|
{
|
||||||
|
emit MacroSelectionAboutToChange();
|
||||||
emit MacroSelectionChanged();
|
emit MacroSelectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ public slots:
|
|||||||
const QItemSelection &);
|
const QItemSelection &);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void MacroSelectionAboutToChange();
|
||||||
void MacroSelectionChanged();
|
void MacroSelectionChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Macro::~Macro()
|
|||||||
Stop();
|
Stop();
|
||||||
ClearHotkeys();
|
ClearHotkeys();
|
||||||
|
|
||||||
// Keep the dock widgets in case of shutdown so they can be rostored by
|
// Keep the dock widgets in case of shutdown so they can be restored by
|
||||||
// OBS on startup
|
// OBS on startup
|
||||||
if (!switcher->obsIsShuttingDown) {
|
if (!switcher->obsIsShuttingDown) {
|
||||||
RemoveDock();
|
RemoveDock();
|
||||||
@@ -176,24 +176,26 @@ bool Macro::CeckMatch()
|
|||||||
}
|
}
|
||||||
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
||||||
|
|
||||||
bool matchedBeforeOnChangeCheck = _matched;
|
_conditionSateChanged = _lastMatched != _matched;
|
||||||
if (_matched && _matchOnChange && _lastMatched) {
|
if (!_conditionSateChanged && _performActionsOnChange) {
|
||||||
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
|
_onPreventedActionExecution = true;
|
||||||
_name.c_str());
|
|
||||||
_matched = false;
|
|
||||||
SetOnChangeHighlight();
|
|
||||||
}
|
}
|
||||||
_lastMatched = matchedBeforeOnChangeCheck;
|
_lastMatched = _matched;
|
||||||
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
||||||
return _matched;
|
return _matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
bool Macro::PerformActions(bool match, bool forceParallel, bool ignorePause)
|
||||||
{
|
{
|
||||||
if (!_done) {
|
if (!_done) {
|
||||||
vblog(LOG_INFO, "macro %s already running", _name.c_str());
|
vblog(LOG_INFO, "macro %s already running", _name.c_str());
|
||||||
return !forceParallel;
|
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;
|
_stop = false;
|
||||||
_done = false;
|
_done = false;
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
@@ -202,9 +204,9 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
|||||||
_backgroundThread.join();
|
_backgroundThread.join();
|
||||||
}
|
}
|
||||||
_backgroundThread = std::thread(
|
_backgroundThread = std::thread(
|
||||||
[this, ignorePause] { RunActions(ignorePause); });
|
[this, runFunc, ignorePause] { runFunc(ignorePause); });
|
||||||
} else {
|
} else {
|
||||||
RunActions(ret, ignorePause);
|
ret = runFunc(ignorePause);
|
||||||
}
|
}
|
||||||
_lastExecutionTime = std::chrono::high_resolution_clock::now();
|
_lastExecutionTime = std::chrono::high_resolution_clock::now();
|
||||||
auto group = _parent.lock();
|
auto group = _parent.lock();
|
||||||
@@ -223,6 +225,28 @@ bool Macro::ExecutedSince(
|
|||||||
return _lastExecutionTime > time;
|
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
|
int64_t Macro::MsSinceLastCheck() const
|
||||||
{
|
{
|
||||||
if (_lastCheckTime.time_since_epoch().count() == 0) {
|
if (_lastCheckTime.time_since_epoch().count() == 0) {
|
||||||
@@ -251,37 +275,43 @@ void Macro::ResetTimers()
|
|||||||
_lastExecutionTime = {};
|
_lastExecutionTime = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Macro::RunActions(bool &retVal, bool ignorePause)
|
bool Macro::RunActionsHelper(
|
||||||
|
const std::deque<std::shared_ptr<MacroAction>> &actions,
|
||||||
|
bool ignorePause)
|
||||||
{
|
{
|
||||||
bool ret = true;
|
bool actionsExecutedSuccessfully = true;
|
||||||
for (auto &a : _actions) {
|
for (auto &action : actions) {
|
||||||
if (a->Enabled()) {
|
if (action->Enabled()) {
|
||||||
a->LogAction();
|
action->LogAction();
|
||||||
ret = ret && a->PerformAction();
|
actionsExecutedSuccessfully =
|
||||||
|
actionsExecutedSuccessfully &&
|
||||||
|
action->PerformAction();
|
||||||
} else {
|
} else {
|
||||||
vblog(LOG_INFO, "skipping disabled action %s",
|
vblog(LOG_INFO, "skipping disabled action %s",
|
||||||
a->GetId().c_str());
|
action->GetId().c_str());
|
||||||
}
|
}
|
||||||
if (!ret || (_paused && !ignorePause) || _stop || _die) {
|
if (!actionsExecutedSuccessfully || (_paused && !ignorePause) ||
|
||||||
retVal = ret;
|
_stop || _die) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (a->Enabled()) {
|
if (action->Enabled()) {
|
||||||
a->SetHighlight();
|
action->SetHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_done = true;
|
_done = true;
|
||||||
|
return actionsExecutedSuccessfully;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Macro::RunActions(bool ignorePause)
|
bool Macro::RunActions(bool ignorePause)
|
||||||
{
|
{
|
||||||
bool unused;
|
vblog(LOG_INFO, "running actions of %s", _name.c_str());
|
||||||
RunActions(unused, ignorePause);
|
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
|
bool Macro::DockIsVisible() const
|
||||||
@@ -289,6 +319,11 @@ bool Macro::DockIsVisible() const
|
|||||||
return _dock && _dockAction && _dock->isVisible();
|
return _dock && _dockAction && _dock->isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Macro::SetMatchOnChange(bool onChange)
|
||||||
|
{
|
||||||
|
_performActionsOnChange = onChange;
|
||||||
|
}
|
||||||
|
|
||||||
void Macro::SetPaused(bool pause)
|
void Macro::SetPaused(bool pause)
|
||||||
{
|
{
|
||||||
if (_paused && !pause) {
|
if (_paused && !pause) {
|
||||||
@@ -332,22 +367,39 @@ std::deque<std::shared_ptr<MacroAction>> &Macro::Actions()
|
|||||||
return _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;
|
int idx = 0;
|
||||||
for (auto a : _actions) {
|
for (auto segment : list) {
|
||||||
a->SetIndex(idx);
|
segment->SetIndex(idx);
|
||||||
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()
|
void Macro::UpdateConditionIndices()
|
||||||
{
|
{
|
||||||
int idx = 0;
|
std::deque<std::shared_ptr<MacroSegment>> list(_conditions.begin(),
|
||||||
for (auto c : _conditions) {
|
_conditions.end());
|
||||||
c->SetIndex(idx);
|
updateIndicesHelper(list);
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Macro> Macro::Parent() const
|
std::shared_ptr<Macro> Macro::Parent() const
|
||||||
@@ -360,55 +412,57 @@ bool Macro::Save(obs_data_t *obj) const
|
|||||||
obs_data_set_string(obj, "name", _name.c_str());
|
obs_data_set_string(obj, "name", _name.c_str());
|
||||||
obs_data_set_bool(obj, "pause", _paused);
|
obs_data_set_bool(obj, "pause", _paused);
|
||||||
obs_data_set_bool(obj, "parallel", _runInParallel);
|
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);
|
obs_data_set_bool(obj, "group", _isGroup);
|
||||||
if (_isGroup) {
|
if (_isGroup) {
|
||||||
auto groupData = obs_data_create();
|
OBSDataAutoRelease groupData = obs_data_create();
|
||||||
obs_data_set_bool(groupData, "collapsed", _isCollapsed);
|
obs_data_set_bool(groupData, "collapsed", _isCollapsed);
|
||||||
obs_data_set_int(groupData, "size", _groupSize);
|
obs_data_set_int(groupData, "size", _groupSize);
|
||||||
obs_data_set_obj(obj, "groupData", groupData);
|
obs_data_set_obj(obj, "groupData", groupData);
|
||||||
obs_data_release(groupData);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveDockSettings(obj);
|
SaveDockSettings(obj);
|
||||||
|
|
||||||
|
SaveSplitterPos(_actionConditionSplitterPosition, obj,
|
||||||
|
"macroActionConditionSplitterPosition");
|
||||||
|
SaveSplitterPos(_elseActionSplitterPosition, obj,
|
||||||
|
"macroElseActionSplitterPosition");
|
||||||
|
|
||||||
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
|
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_set_array(obj, "pauseHotkey", pauseHotkey);
|
||||||
obs_data_array_release(pauseHotkey);
|
OBSDataArrayAutoRelease unpauseHotkey = obs_hotkey_save(_unpauseHotkey);
|
||||||
obs_data_array_t *unpauseHotkey = obs_hotkey_save(_unpauseHotkey);
|
|
||||||
obs_data_set_array(obj, "unpauseHotkey", unpauseHotkey);
|
obs_data_set_array(obj, "unpauseHotkey", unpauseHotkey);
|
||||||
obs_data_array_release(unpauseHotkey);
|
OBSDataArrayAutoRelease togglePauseHotkey =
|
||||||
obs_data_array_t *togglePauseHotkey =
|
|
||||||
obs_hotkey_save(_togglePauseHotkey);
|
obs_hotkey_save(_togglePauseHotkey);
|
||||||
obs_data_set_array(obj, "togglePauseHotkey", 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) {
|
for (auto &c : _conditions) {
|
||||||
obs_data_t *array_obj = obs_data_create();
|
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||||
|
c->Save(arrayObj);
|
||||||
c->Save(array_obj);
|
obs_data_array_push_back(conditions, arrayObj);
|
||||||
obs_data_array_push_back(conditions, array_obj);
|
|
||||||
|
|
||||||
obs_data_release(array_obj);
|
|
||||||
}
|
}
|
||||||
obs_data_set_array(obj, "conditions", conditions);
|
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) {
|
for (auto &a : _actions) {
|
||||||
obs_data_t *array_obj = obs_data_create();
|
OBSDataAutoRelease arrayObj = obs_data_create();
|
||||||
|
a->Save(arrayObj);
|
||||||
a->Save(array_obj);
|
obs_data_array_push_back(actions, arrayObj);
|
||||||
obs_data_array_push_back(actions, array_obj);
|
|
||||||
|
|
||||||
obs_data_release(array_obj);
|
|
||||||
}
|
}
|
||||||
obs_data_set_array(obj, "actions", actions);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -452,72 +506,69 @@ bool Macro::Load(obs_data_t *obj)
|
|||||||
_name = obs_data_get_string(obj, "name");
|
_name = obs_data_get_string(obj, "name");
|
||||||
_paused = obs_data_get_bool(obj, "pause");
|
_paused = obs_data_get_bool(obj, "pause");
|
||||||
_runInParallel = obs_data_get_bool(obj, "parallel");
|
_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");
|
_isGroup = obs_data_get_bool(obj, "group");
|
||||||
if (_isGroup) {
|
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");
|
_isCollapsed = obs_data_get_bool(groupData, "collapsed");
|
||||||
_groupSize = obs_data_get_int(groupData, "size");
|
_groupSize = obs_data_get_int(groupData, "size");
|
||||||
obs_data_release(groupData);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadDockSettings(obj);
|
LoadDockSettings(obj);
|
||||||
|
|
||||||
|
LoadSplitterPos(_actionConditionSplitterPosition, obj,
|
||||||
|
"macroActionConditionSplitterPosition");
|
||||||
|
LoadSplitterPos(_elseActionSplitterPosition, obj,
|
||||||
|
"macroElseActionSplitterPosition");
|
||||||
|
|
||||||
obs_data_set_default_bool(obj, "registerHotkeys", true);
|
obs_data_set_default_bool(obj, "registerHotkeys", true);
|
||||||
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
|
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
|
||||||
if (_registerHotkeys) {
|
if (_registerHotkeys) {
|
||||||
SetupHotkeys();
|
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_hotkey_load(_pauseHotkey, pauseHotkey);
|
||||||
obs_data_array_release(pauseHotkey);
|
OBSDataArrayAutoRelease unpauseHotkey =
|
||||||
obs_data_array_t *unpauseHotkey =
|
|
||||||
obs_data_get_array(obj, "unpauseHotkey");
|
obs_data_get_array(obj, "unpauseHotkey");
|
||||||
obs_hotkey_load(_unpauseHotkey, unpauseHotkey);
|
obs_hotkey_load(_unpauseHotkey, unpauseHotkey);
|
||||||
obs_data_array_release(unpauseHotkey);
|
OBSDataArrayAutoRelease togglePauseHotkey =
|
||||||
obs_data_array_t *togglePauseHotkey =
|
|
||||||
obs_data_get_array(obj, "togglePauseHotkey");
|
obs_data_get_array(obj, "togglePauseHotkey");
|
||||||
obs_hotkey_load(_togglePauseHotkey, togglePauseHotkey);
|
obs_hotkey_load(_togglePauseHotkey, togglePauseHotkey);
|
||||||
obs_data_array_release(togglePauseHotkey);
|
|
||||||
SetHotkeysDesc();
|
SetHotkeysDesc();
|
||||||
|
|
||||||
bool root = true;
|
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);
|
size_t count = obs_data_array_count(conditions);
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
obs_data_t *array_obj = obs_data_array_item(conditions, i);
|
OBSDataAutoRelease arrayObj =
|
||||||
|
obs_data_array_item(conditions, i);
|
||||||
std::string id = obs_data_get_string(array_obj, "id");
|
std::string id = obs_data_get_string(arrayObj, "id");
|
||||||
|
|
||||||
auto newEntry = MacroConditionFactory::Create(id, this);
|
auto newEntry = MacroConditionFactory::Create(id, this);
|
||||||
if (newEntry) {
|
if (newEntry) {
|
||||||
_conditions.emplace_back(newEntry);
|
_conditions.emplace_back(newEntry);
|
||||||
auto c = _conditions.back().get();
|
auto c = _conditions.back().get();
|
||||||
c->Load(array_obj);
|
c->Load(arrayObj);
|
||||||
setValidLogic(c, root, _name);
|
setValidLogic(c, root, _name);
|
||||||
} else {
|
} else {
|
||||||
blog(LOG_WARNING,
|
blog(LOG_WARNING,
|
||||||
"discarding condition entry with unknown id (%s) for macro %s",
|
"discarding condition entry with unknown id (%s) for macro %s",
|
||||||
id.c_str(), _name.c_str());
|
id.c_str(), _name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_release(array_obj);
|
|
||||||
root = false;
|
root = false;
|
||||||
}
|
}
|
||||||
obs_data_array_release(conditions);
|
|
||||||
UpdateConditionIndices();
|
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);
|
count = obs_data_array_count(actions);
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
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");
|
std::string id = obs_data_get_string(array_obj, "id");
|
||||||
|
|
||||||
auto newEntry = MacroActionFactory::Create(id, this);
|
auto newEntry = MacroActionFactory::Create(id, this);
|
||||||
if (newEntry) {
|
if (newEntry) {
|
||||||
_actions.emplace_back(newEntry);
|
_actions.emplace_back(newEntry);
|
||||||
@@ -527,11 +578,27 @@ bool Macro::Load(obs_data_t *obj)
|
|||||||
"discarding action entry with unknown id (%s) for macro %s",
|
"discarding action entry with unknown id (%s) for macro %s",
|
||||||
id.c_str(), _name.c_str());
|
id.c_str(), _name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_release(array_obj);
|
|
||||||
}
|
}
|
||||||
obs_data_array_release(actions);
|
|
||||||
UpdateActionIndices();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +617,12 @@ bool Macro::SwitchesScene() const
|
|||||||
{
|
{
|
||||||
MacroActionSwitchScene temp(nullptr);
|
MacroActionSwitchScene temp(nullptr);
|
||||||
auto sceneSwitchId = temp.GetId();
|
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) {
|
if (a->GetId() == sceneSwitchId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -558,18 +630,44 @@ bool Macro::SwitchesScene() const
|
|||||||
return false;
|
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()
|
bool Macro::OnChangePreventedActionsRecently()
|
||||||
{
|
{
|
||||||
if (_onChangeTriggered) {
|
if (_onPreventedActionExecution) {
|
||||||
_onChangeTriggered = false;
|
_onPreventedActionExecution = false;
|
||||||
return true;
|
return _matched ? _actions.size() > 0 : _elseActions.size() > 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Macro::ResetUIHelpers()
|
void Macro::ResetUIHelpers()
|
||||||
{
|
{
|
||||||
_onChangeTriggered = false;
|
_onPreventedActionExecution = false;
|
||||||
for (auto c : _conditions) {
|
for (auto c : _conditions) {
|
||||||
c->Highlight();
|
c->Highlight();
|
||||||
}
|
}
|
||||||
@@ -708,7 +806,8 @@ void Macro::EnableDock(bool value)
|
|||||||
// Create new dock widget
|
// Create new dock widget
|
||||||
auto window =
|
auto window =
|
||||||
static_cast<QMainWindow *>(obs_frontend_get_main_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,
|
_unpauseButtonText, _conditionsTrueStatusText,
|
||||||
_conditionsFalseStatusText, _dockHighlight);
|
_conditionsFalseStatusText, _dockHighlight);
|
||||||
SetDockWidgetName(); // Used by OBS to restore position
|
SetDockWidgetName(); // Used by OBS to restore position
|
||||||
@@ -1012,7 +1111,7 @@ bool SwitcherData::CheckMacros()
|
|||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
for (auto &m : macros) {
|
for (auto &m : macros) {
|
||||||
if (m->CeckMatch()) {
|
if (m->CeckMatch() || m->ElseActions().size() > 0) {
|
||||||
ret = true;
|
ret = true;
|
||||||
// This has to be performed here for now as actions are
|
// This has to be performed here for now as actions are
|
||||||
// not performed immediately after checking conditions.
|
// not performed immediately after checking conditions.
|
||||||
@@ -1026,7 +1125,7 @@ bool SwitcherData::CheckMacros()
|
|||||||
|
|
||||||
bool SwitcherData::RunMacros()
|
bool SwitcherData::RunMacros()
|
||||||
{
|
{
|
||||||
// Create copy of macor list as elements might be removed, inserted, or
|
// Create copy of macro list as elements might be removed, inserted, or
|
||||||
// reordered while macros are currently being executed.
|
// reordered while macros are currently being executed.
|
||||||
// For example, this can happen if a macro is performing a wait action,
|
// For example, this can happen if a macro is performing a wait action,
|
||||||
// as the main lock will be unlocked during this time.
|
// as the main lock will be unlocked during this time.
|
||||||
@@ -1047,12 +1146,18 @@ bool SwitcherData::RunMacros()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto &m : runPhaseMacros) {
|
for (auto &m : runPhaseMacros) {
|
||||||
if (m && m->Matched()) {
|
if (!m || !m->ShouldRunActions()) {
|
||||||
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
|
continue;
|
||||||
if (!m->PerformActions()) {
|
}
|
||||||
blog(LOG_WARNING, "abort macro: %s",
|
if (firstInterval && m->SkipExecOnStart()) {
|
||||||
m->Name().c_str());
|
blog(LOG_INFO,
|
||||||
}
|
"skip execution of macro \"%s\" at startup",
|
||||||
|
m->Name().c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
|
||||||
|
if (!m->PerformActions(m->Matched())) {
|
||||||
|
blog(LOG_WARNING, "abort macro: %s", m->Name().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (GetLock()) {
|
if (GetLock()) {
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ public:
|
|||||||
Macro(const std::string &name = "", const bool addHotkey = false);
|
Macro(const std::string &name = "", const bool addHotkey = false);
|
||||||
virtual ~Macro();
|
virtual ~Macro();
|
||||||
bool CeckMatch();
|
bool CeckMatch();
|
||||||
bool PerformActions(bool forceParallel = false,
|
bool PerformActions(bool match, bool forceParallel = false,
|
||||||
bool ignorePause = false);
|
bool ignorePause = false);
|
||||||
bool Matched() const { return _matched; }
|
bool Matched() const { return _matched; }
|
||||||
|
bool ShouldRunActions() const;
|
||||||
int64_t MsSinceLastCheck() const;
|
int64_t MsSinceLastCheck() const;
|
||||||
std::string Name() const { return _name; }
|
std::string Name() const { return _name; }
|
||||||
void SetName(const std::string &name);
|
void SetName(const std::string &name);
|
||||||
@@ -35,8 +36,10 @@ public:
|
|||||||
bool RunInParallel() const { return _runInParallel; }
|
bool RunInParallel() const { return _runInParallel; }
|
||||||
void SetPaused(bool pause = true);
|
void SetPaused(bool pause = true);
|
||||||
bool Paused() const { return _paused; }
|
bool Paused() const { return _paused; }
|
||||||
void SetMatchOnChange(bool onChange) { _matchOnChange = onChange; }
|
void SetMatchOnChange(bool onChange);
|
||||||
bool MatchOnChange() const { return _matchOnChange; }
|
bool MatchOnChange() const { return _performActionsOnChange; }
|
||||||
|
void SetSkipExecOnStart(bool skip) { _skipExecOnStart = skip; }
|
||||||
|
bool SkipExecOnStart() const { return _skipExecOnStart; }
|
||||||
int RunCount() const { return _runCount; };
|
int RunCount() const { return _runCount; };
|
||||||
void ResetRunCount() { _runCount = 0; };
|
void ResetRunCount() { _runCount = 0; };
|
||||||
void ResetTimers();
|
void ResetTimers();
|
||||||
@@ -46,7 +49,9 @@ public:
|
|||||||
|
|
||||||
std::deque<std::shared_ptr<MacroCondition>> &Conditions();
|
std::deque<std::shared_ptr<MacroCondition>> &Conditions();
|
||||||
std::deque<std::shared_ptr<MacroAction>> &Actions();
|
std::deque<std::shared_ptr<MacroAction>> &Actions();
|
||||||
|
std::deque<std::shared_ptr<MacroAction>> &ElseActions();
|
||||||
void UpdateActionIndices();
|
void UpdateActionIndices();
|
||||||
|
void UpdateElseActionIndices();
|
||||||
void UpdateConditionIndices();
|
void UpdateConditionIndices();
|
||||||
|
|
||||||
// Group controls
|
// Group controls
|
||||||
@@ -78,6 +83,11 @@ public:
|
|||||||
bool SwitchesScene() const;
|
bool SwitchesScene() const;
|
||||||
|
|
||||||
// UI helpers
|
// 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
|
bool
|
||||||
ExecutedSince(const std::chrono::high_resolution_clock::time_point &);
|
ExecutedSince(const std::chrono::high_resolution_clock::time_point &);
|
||||||
bool OnChangePreventedActionsRecently();
|
bool OnChangePreventedActionsRecently();
|
||||||
@@ -111,9 +121,11 @@ private:
|
|||||||
void SetupHotkeys();
|
void SetupHotkeys();
|
||||||
void ClearHotkeys() const;
|
void ClearHotkeys() const;
|
||||||
void SetHotkeysDesc() const;
|
void SetHotkeysDesc() const;
|
||||||
void RunActions(bool &ret, bool ignorePause);
|
bool RunActionsHelper(
|
||||||
void RunActions(bool ignorePause);
|
const std::deque<std::shared_ptr<MacroAction>> &actions,
|
||||||
void SetOnChangeHighlight();
|
bool ignorePause);
|
||||||
|
bool RunActions(bool ignorePause);
|
||||||
|
bool RunElseActions(bool ignorePause);
|
||||||
bool DockIsVisible() const;
|
bool DockIsVisible() const;
|
||||||
void SetDockWidgetName() const;
|
void SetDockWidgetName() const;
|
||||||
void SaveDockSettings(obs_data_t *obj) const;
|
void SaveDockSettings(obs_data_t *obj) const;
|
||||||
@@ -131,6 +143,7 @@ private:
|
|||||||
|
|
||||||
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
||||||
std::deque<std::shared_ptr<MacroAction>> _actions;
|
std::deque<std::shared_ptr<MacroAction>> _actions;
|
||||||
|
std::deque<std::shared_ptr<MacroAction>> _elseActions;
|
||||||
|
|
||||||
std::weak_ptr<Macro> _parent;
|
std::weak_ptr<Macro> _parent;
|
||||||
uint32_t _groupSize = 0;
|
uint32_t _groupSize = 0;
|
||||||
@@ -140,7 +153,9 @@ private:
|
|||||||
bool _runInParallel = false;
|
bool _runInParallel = false;
|
||||||
bool _matched = false;
|
bool _matched = false;
|
||||||
bool _lastMatched = false;
|
bool _lastMatched = false;
|
||||||
bool _matchOnChange = true;
|
bool _conditionSateChanged = false;
|
||||||
|
bool _performActionsOnChange = true;
|
||||||
|
bool _skipExecOnStart = false;
|
||||||
bool _paused = false;
|
bool _paused = false;
|
||||||
int _runCount = 0;
|
int _runCount = 0;
|
||||||
bool _registerHotkeys = true;
|
bool _registerHotkeys = true;
|
||||||
@@ -148,7 +163,11 @@ private:
|
|||||||
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
|
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||||
obs_hotkey_id _togglePauseHotkey = 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 _registerDock = false;
|
||||||
bool _dockHasRunButton = true;
|
bool _dockHasRunButton = true;
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ if(ENABLE_OPENVR_PLUGIN)
|
|||||||
add_subdirectory(openvr)
|
add_subdirectory(openvr)
|
||||||
endif()
|
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)
|
option(ENABLE_VIDEO_PLUGIN "Enable the video plugin" ON)
|
||||||
if(ENABLE_VIDEO_PLUGIN)
|
if(ENABLE_VIDEO_PLUGIN)
|
||||||
add_subdirectory(video)
|
add_subdirectory(video)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ project(advanced-scene-switcher-midi)
|
|||||||
|
|
||||||
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
||||||
set(LIBREMIDI_DIR "${ADVSS_SOURCE_DIR}/deps/libremidi")
|
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"
|
message(WARNING "libremidi directory \"${LIBREMIDI_DIR}\" not found!\n"
|
||||||
"MIDI support will be disabled!")
|
"MIDI support will be disabled!")
|
||||||
return()
|
return()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace advss {
|
|||||||
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
|
||||||
SetupMidiMessageVector()
|
SetupMidiMessageVector()
|
||||||
{
|
{
|
||||||
GetSwitcher()->AddResetForNextIntervalFunction(
|
GetSwitcher()->AddIntervalResetStep(
|
||||||
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
|
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
|
||||||
return {};
|
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
|
||||||
@@ -83,11 +83,6 @@ const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
|
|||||||
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
|
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
|
||||||
};
|
};
|
||||||
|
|
||||||
MacroConditionVideo::MacroConditionVideo(Macro *m) : MacroCondition(m, true)
|
|
||||||
{
|
|
||||||
SetupOpenCL();
|
|
||||||
}
|
|
||||||
|
|
||||||
cv::CascadeClassifier initObjectCascade(std::string &path)
|
cv::CascadeClassifier initObjectCascade(std::string &path)
|
||||||
{
|
{
|
||||||
cv::CascadeClassifier cascade;
|
cv::CascadeClassifier cascade;
|
||||||
@@ -109,7 +104,9 @@ static bool requiresFileInput(VideoCondition t)
|
|||||||
bool MacroConditionVideo::CheckShouldBeSkipped()
|
bool MacroConditionVideo::CheckShouldBeSkipped()
|
||||||
{
|
{
|
||||||
if (_condition != VideoCondition::PATTERN &&
|
if (_condition != VideoCondition::PATTERN &&
|
||||||
_condition != VideoCondition::OBJECT) {
|
_condition != VideoCondition::OBJECT &&
|
||||||
|
_condition != VideoCondition::HAS_CHANGED &&
|
||||||
|
_condition != VideoCondition::HAS_NOT_CHANGED) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +263,7 @@ bool MacroConditionVideo::SetLanguage(const std::string &language)
|
|||||||
|
|
||||||
bool MacroConditionVideo::ScreenshotContainsPattern()
|
bool MacroConditionVideo::ScreenshotContainsPattern()
|
||||||
{
|
{
|
||||||
cv::UMat result;
|
cv::Mat result;
|
||||||
MatchPattern(_screenshotData.image, _patternImageData,
|
MatchPattern(_screenshotData.image, _patternImageData,
|
||||||
_patternMatchParameters.threshold, result,
|
_patternMatchParameters.threshold, result,
|
||||||
_patternMatchParameters.useAlphaAsMask,
|
_patternMatchParameters.useAlphaAsMask,
|
||||||
@@ -279,16 +276,20 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
|
|||||||
|
|
||||||
bool MacroConditionVideo::OutputChanged()
|
bool MacroConditionVideo::OutputChanged()
|
||||||
{
|
{
|
||||||
if (_patternMatchParameters.useForChangedCheck) {
|
if (!_patternMatchParameters.useForChangedCheck) {
|
||||||
cv::UMat result;
|
return _screenshotData.image != _matchImage;
|
||||||
_patternImageData = CreatePatternData(_matchImage);
|
|
||||||
MatchPattern(_screenshotData.image, _patternImageData,
|
|
||||||
_patternMatchParameters.threshold, result,
|
|
||||||
_patternMatchParameters.useAlphaAsMask,
|
|
||||||
_patternMatchParameters.matchMode);
|
|
||||||
return countNonZero(result) == 0;
|
|
||||||
}
|
}
|
||||||
return _screenshotData.image != _matchImage;
|
|
||||||
|
cv::Mat result;
|
||||||
|
_patternImageData = CreatePatternData(_matchImage);
|
||||||
|
MatchPattern(_screenshotData.image, _patternImageData,
|
||||||
|
_patternMatchParameters.threshold, result,
|
||||||
|
_patternMatchParameters.useAlphaAsMask,
|
||||||
|
_patternMatchParameters.matchMode);
|
||||||
|
if (result.total() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return countNonZero(result) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionVideo::ScreenshotContainsObject()
|
bool MacroConditionVideo::ScreenshotContainsObject()
|
||||||
@@ -316,19 +317,17 @@ bool MacroConditionVideo::CheckOCR()
|
|||||||
|
|
||||||
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
|
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
|
||||||
_ocrParameters.color, _ocrParameters.colorThreshold);
|
_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);
|
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()
|
bool MacroConditionVideo::CheckColor()
|
||||||
@@ -1455,7 +1454,9 @@ static bool needsShowMatch(VideoCondition cond)
|
|||||||
static bool needsThrottleControls(VideoCondition cond)
|
static bool needsThrottleControls(VideoCondition cond)
|
||||||
{
|
{
|
||||||
return cond == VideoCondition::PATTERN ||
|
return cond == VideoCondition::PATTERN ||
|
||||||
cond == VideoCondition::OBJECT;
|
cond == VideoCondition::OBJECT ||
|
||||||
|
cond == VideoCondition::HAS_CHANGED ||
|
||||||
|
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool needsThreshold(VideoCondition cond)
|
static bool needsThreshold(VideoCondition cond)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class PreviewDialog;
|
|||||||
|
|
||||||
class MacroConditionVideo : public MacroCondition {
|
class MacroConditionVideo : public MacroCondition {
|
||||||
public:
|
public:
|
||||||
MacroConditionVideo(Macro *m);
|
MacroConditionVideo(Macro *m) : MacroCondition(m, true){};
|
||||||
bool CheckCondition();
|
bool CheckCondition();
|
||||||
bool Save(obs_data_t *obj) const;
|
bool Save(obs_data_t *obj) const;
|
||||||
bool Load(obs_data_t *obj);
|
bool Load(obs_data_t *obj);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#include "opencv-helpers.hpp"
|
#include "opencv-helpers.hpp"
|
||||||
#include "log-helper.hpp"
|
|
||||||
|
|
||||||
#include <opencv2/core/ocl.hpp>
|
#include <log-helper.hpp>
|
||||||
#include <opencv2/core/mat.hpp>
|
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -24,19 +22,17 @@ PatternImageData CreatePatternData(const QImage &pattern)
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void invertPatternMatchResult(cv::UMat &umat)
|
static void invertPatternMatchResult(cv::Mat &mat)
|
||||||
{
|
{
|
||||||
auto mat = umat.getMat(cv::ACCESS_RW);
|
|
||||||
for (int r = 0; r < mat.rows; r++) {
|
for (int r = 0; r < mat.rows; r++) {
|
||||||
for (int c = 0; c < mat.cols; c++) {
|
for (int c = 0; c < mat.cols; c++) {
|
||||||
mat.at<float>(r, c) = 1.0 - mat.at<float>(r, c);
|
mat.at<float>(r, c) = 1.0 - mat.at<float>(r, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
umat = mat.getUMat(cv::ACCESS_RW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatchPattern(QImage &img, const PatternImageData &patternData,
|
void MatchPattern(QImage &img, const PatternImageData &patternData,
|
||||||
double threshold, cv::UMat &result, bool useAlphaAsMask,
|
double threshold, cv::Mat &result, bool useAlphaAsMask,
|
||||||
cv::TemplateMatchModes matchMode)
|
cv::TemplateMatchModes matchMode)
|
||||||
{
|
{
|
||||||
if (img.isNull() || patternData.rgbaPattern.empty()) {
|
if (img.isNull() || patternData.rgbaPattern.empty()) {
|
||||||
@@ -55,12 +51,13 @@ void MatchPattern(QImage &img, const PatternImageData &patternData,
|
|||||||
// thus should not be used while matching the pattern as well
|
// thus should not be used while matching the pattern as well
|
||||||
//
|
//
|
||||||
// Input format is Format_RGBA8888 so discard the 4th channel
|
// Input format is Format_RGBA8888 so discard the 4th channel
|
||||||
std::vector<cv::UMat> inputChannels;
|
std::vector<cv::Mat1b> inputChannels;
|
||||||
cv::split(input, inputChannels);
|
cv::split(input, inputChannels);
|
||||||
std::vector<cv::UMat> rgbChanlesImage(
|
std::vector<cv::Mat1b> rgbChanlesImage(
|
||||||
inputChannels.begin(), inputChannels.begin() + 3);
|
inputChannels.begin(), inputChannels.begin() + 3);
|
||||||
cv::UMat rgbInput;
|
cv::Mat3b rgbInput;
|
||||||
cv::merge(rgbChanlesImage, rgbInput);
|
cv::merge(rgbChanlesImage, rgbInput);
|
||||||
|
|
||||||
cv::matchTemplate(rgbInput, patternData.rgbPattern, result,
|
cv::matchTemplate(rgbInput, patternData.rgbPattern, result,
|
||||||
matchMode, patternData.mask);
|
matchMode, patternData.mask);
|
||||||
} else {
|
} else {
|
||||||
@@ -79,7 +76,7 @@ void MatchPattern(QImage &img, const PatternImageData &patternData,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MatchPattern(QImage &img, QImage &pattern, double threshold,
|
void MatchPattern(QImage &img, QImage &pattern, double threshold,
|
||||||
cv::UMat &result, bool useAlphaAsMask,
|
cv::Mat &result, bool useAlphaAsMask,
|
||||||
cv::TemplateMatchModes matchColor)
|
cv::TemplateMatchModes matchColor)
|
||||||
{
|
{
|
||||||
auto data = CreatePatternData(pattern);
|
auto data = CreatePatternData(pattern);
|
||||||
@@ -96,12 +93,16 @@ std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto image = QImageToMat(img);
|
auto image = QImageToMat(img);
|
||||||
cv::UMat frameGray;
|
cv::Mat frameGray;
|
||||||
cv::cvtColor(image, frameGray, cv::COLOR_RGBA2GRAY);
|
cv::cvtColor(image, frameGray, cv::COLOR_RGBA2GRAY);
|
||||||
cv::equalizeHist(frameGray, frameGray);
|
cv::equalizeHist(frameGray, frameGray);
|
||||||
std::vector<cv::Rect> objects;
|
std::vector<cv::Rect> objects;
|
||||||
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
|
try {
|
||||||
0, minSize, maxSize);
|
cascade.detectMultiScale(frameGray, objects, scaleFactor,
|
||||||
|
minNeighbors, 0, minSize, maxSize);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
vblog(LOG_INFO, "detectMultiScale failed: %s", e.what());
|
||||||
|
}
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +112,9 @@ uchar GetAvgBrightness(QImage &img)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto i = QImageToMat(img);
|
auto image = QImageToMat(img);
|
||||||
cv::Mat hsvImage, rgbImage;
|
cv::Mat hsvImage, rgbImage;
|
||||||
cv::cvtColor(i, rgbImage, cv::COLOR_RGBA2RGB);
|
cv::cvtColor(image, rgbImage, cv::COLOR_RGBA2RGB);
|
||||||
cv::cvtColor(rgbImage, hsvImage, cv::COLOR_RGB2HSV);
|
cv::cvtColor(rgbImage, hsvImage, cv::COLOR_RGB2HSV);
|
||||||
long long brightnessSum = 0;
|
long long brightnessSum = 0;
|
||||||
for (int i = 0; i < hsvImage.rows; ++i) {
|
for (int i = 0; i < hsvImage.rows; ++i) {
|
||||||
@@ -138,8 +139,7 @@ static bool colorIsSimilar(const QColor &color1, const QColor &color2,
|
|||||||
cv::Mat PreprocessForOCR(const QImage &image, const QColor &textColor,
|
cv::Mat PreprocessForOCR(const QImage &image, const QColor &textColor,
|
||||||
double colorDiff)
|
double colorDiff)
|
||||||
{
|
{
|
||||||
auto umat = QImageToMat(image);
|
auto mat = QImageToMat(image);
|
||||||
auto mat = umat.getMat(cv::ACCESS_RW);
|
|
||||||
|
|
||||||
// Tesseract works best when matching black text on a white background,
|
// Tesseract works best when matching black text on a white background,
|
||||||
// so everything that matches the text color will be displayed black
|
// so everything that matches the text color will be displayed black
|
||||||
@@ -224,14 +224,13 @@ bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
|||||||
|
|
||||||
// Assumption is that QImage uses Format_RGBA8888.
|
// Assumption is that QImage uses Format_RGBA8888.
|
||||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||||
cv::UMat QImageToMat(const QImage &img)
|
cv::Mat QImageToMat(const QImage &img)
|
||||||
{
|
{
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
return cv::UMat();
|
return cv::Mat();
|
||||||
}
|
}
|
||||||
auto temp = cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
|
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
|
||||||
(uchar *)img.bits(), img.bytesPerLine());
|
(uchar *)img.bits(), img.bytesPerLine());
|
||||||
return temp.getUMat(cv::ACCESS_RW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage MatToQImage(const cv::Mat &mat)
|
QImage MatToQImage(const cv::Mat &mat)
|
||||||
@@ -243,12 +242,4 @@ QImage MatToQImage(const cv::Mat &mat)
|
|||||||
QImage::Format::Format_RGBA8888);
|
QImage::Format::Format_RGBA8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupOpenCL()
|
|
||||||
{
|
|
||||||
if (cv::ocl::haveOpenCL() && !cv::ocl::useOpenCL()) {
|
|
||||||
blog(LOG_INFO, "enabled OpenCL support for OpenCV");
|
|
||||||
cv::ocl::setUseOpenCL(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -42,17 +42,17 @@ constexpr int maxMinNeighbors = 6;
|
|||||||
constexpr double defaultScaleFactor = 1.1;
|
constexpr double defaultScaleFactor = 1.1;
|
||||||
|
|
||||||
struct PatternImageData {
|
struct PatternImageData {
|
||||||
cv::UMat rgbaPattern;
|
cv::Mat4b rgbaPattern;
|
||||||
cv::UMat rgbPattern;
|
cv::Mat3b rgbPattern;
|
||||||
cv::UMat mask;
|
cv::Mat1b mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
PatternImageData CreatePatternData(const QImage &pattern);
|
PatternImageData CreatePatternData(const QImage &pattern);
|
||||||
void MatchPattern(QImage &img, const PatternImageData &patternData,
|
void MatchPattern(QImage &img, const PatternImageData &patternData,
|
||||||
double threshold, cv::UMat &result, bool useAlphaAsMask,
|
double threshold, cv::Mat &result, bool useAlphaAsMask,
|
||||||
cv::TemplateMatchModes matchMode);
|
cv::TemplateMatchModes matchMode);
|
||||||
void MatchPattern(QImage &img, QImage &pattern, double threshold,
|
void MatchPattern(QImage &img, QImage &pattern, double threshold,
|
||||||
cv::UMat &result, bool useAlphaAsMask,
|
cv::Mat &result, bool useAlphaAsMask,
|
||||||
cv::TemplateMatchModes matchMode);
|
cv::TemplateMatchModes matchMode);
|
||||||
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||||
double scaleFactor, int minNeighbors,
|
double scaleFactor, int minNeighbors,
|
||||||
@@ -66,8 +66,7 @@ std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &,
|
|||||||
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
||||||
double colorDeviationThreshold,
|
double colorDeviationThreshold,
|
||||||
double totalPixelMatchThreshold);
|
double totalPixelMatchThreshold);
|
||||||
cv::UMat QImageToMat(const QImage &img);
|
cv::Mat QImageToMat(const QImage &img);
|
||||||
QImage MatToQImage(const cv::Mat &mat);
|
QImage MatToQImage(const cv::Mat &mat);
|
||||||
void SetupOpenCL();
|
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -252,7 +252,9 @@ OCRParameters::OCRParameters(const OCRParameters &other)
|
|||||||
colorThreshold(other.colorThreshold),
|
colorThreshold(other.colorThreshold),
|
||||||
pageSegMode(other.pageSegMode)
|
pageSegMode(other.pageSegMode)
|
||||||
{
|
{
|
||||||
Setup();
|
if (!initDone) {
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
if (initDone) {
|
if (initDone) {
|
||||||
ocr->SetPageSegMode(pageSegMode);
|
ocr->SetPageSegMode(pageSegMode);
|
||||||
}
|
}
|
||||||
@@ -265,7 +267,12 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
|
|||||||
color = other.color;
|
color = other.color;
|
||||||
colorThreshold = other.colorThreshold;
|
colorThreshold = other.colorThreshold;
|
||||||
pageSegMode = other.pageSegMode;
|
pageSegMode = other.pageSegMode;
|
||||||
ocr->SetPageSegMode(pageSegMode);
|
if (!initDone) {
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
if (initDone) {
|
||||||
|
ocr->SetPageSegMode(pageSegMode);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ void PreviewDialog::PatternMatchParametersChanged(
|
|||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_mtx);
|
std::unique_lock<std::mutex> lock(_mtx);
|
||||||
_patternMatchParams = params;
|
_patternMatchParams = params;
|
||||||
|
_patternImageData = CreatePatternData(_patternMatchParams.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters ¶ms)
|
void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters ¶ms)
|
||||||
@@ -169,8 +170,8 @@ void PreviewDialog::UpdateImage(const QPixmap &image)
|
|||||||
if (_type == PreviewType::SELECT_AREA && !_selectingArea) {
|
if (_type == PreviewType::SELECT_AREA && !_selectingArea) {
|
||||||
DrawFrame();
|
DrawFrame();
|
||||||
}
|
}
|
||||||
emit NeedImage(_video, _type, _patternMatchParams, _objDetectParams,
|
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
|
||||||
_ocrParams, _areaParams, _condition);
|
_objDetectParams, _ocrParams, _areaParams, _condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewDialog::Start()
|
void PreviewDialog::Start()
|
||||||
@@ -186,7 +187,7 @@ void PreviewDialog::Start()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PreviewImage *worker = new PreviewImage(_mtx);
|
auto worker = new PreviewImage(_mtx);
|
||||||
worker->moveToThread(&_thread);
|
worker->moveToThread(&_thread);
|
||||||
connect(&_thread, &QThread::finished, worker, &QObject::deleteLater);
|
connect(&_thread, &QThread::finished, worker, &QObject::deleteLater);
|
||||||
connect(worker, &PreviewImage::ImageReady, this,
|
connect(worker, &PreviewImage::ImageReady, this,
|
||||||
@@ -197,8 +198,8 @@ void PreviewDialog::Start()
|
|||||||
&PreviewImage::CreateImage);
|
&PreviewImage::CreateImage);
|
||||||
_thread.start();
|
_thread.start();
|
||||||
|
|
||||||
emit NeedImage(_video, _type, _patternMatchParams, _objDetectParams,
|
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
|
||||||
_ocrParams, _areaParams, _condition);
|
_objDetectParams, _ocrParams, _areaParams, _condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreviewDialog::DrawFrame()
|
void PreviewDialog::DrawFrame()
|
||||||
@@ -216,14 +217,13 @@ void PreviewDialog::DrawFrame()
|
|||||||
_rubberBand->show();
|
_rubberBand->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void markPatterns(cv::UMat &matchResult, QImage &image,
|
static void markPatterns(cv::Mat &matchResult, QImage &image,
|
||||||
const cv::UMat &pattern)
|
const cv::Mat &pattern)
|
||||||
{
|
{
|
||||||
auto temp = matchResult.getMat(cv::ACCESS_RW);
|
|
||||||
auto matchImg = QImageToMat(image);
|
auto matchImg = QImageToMat(image);
|
||||||
for (int row = 0; row < temp.rows - 1; row++) {
|
for (int row = 0; row < matchResult.rows - 1; row++) {
|
||||||
for (int col = 0; col < temp.cols - 1; col++) {
|
for (int col = 0; col < matchResult.cols - 1; col++) {
|
||||||
if (temp.at<float>(row, col) != 0.0) {
|
if (matchResult.at<float>(row, col) != 0.0) {
|
||||||
rectangle(matchImg, {col, row},
|
rectangle(matchImg, {col, row},
|
||||||
cv::Point(col + pattern.cols,
|
cv::Point(col + pattern.cols,
|
||||||
row + pattern.rows),
|
row + pattern.rows),
|
||||||
@@ -231,7 +231,6 @@ static void markPatterns(cv::UMat &matchResult, QImage &image,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matchResult = temp.getUMat(cv::ACCESS_RW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void markObjects(QImage &image, std::vector<cv::Rect> &objects)
|
static void markObjects(QImage &image, std::vector<cv::Rect> &objects)
|
||||||
@@ -249,6 +248,7 @@ PreviewImage::PreviewImage(std::mutex &mtx) : _mtx(mtx) {}
|
|||||||
|
|
||||||
void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
|
void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
|
||||||
const PatternMatchParameters &patternMatchParams,
|
const PatternMatchParameters &patternMatchParams,
|
||||||
|
const PatternImageData &patternImageData,
|
||||||
ObjDetectParameters objDetectParams,
|
ObjDetectParameters objDetectParams,
|
||||||
OCRParameters ocrParams,
|
OCRParameters ocrParams,
|
||||||
const AreaParameters &areaParams,
|
const AreaParameters &areaParams,
|
||||||
@@ -279,8 +279,6 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
|
|||||||
areaParams.area.x, areaParams.area.y,
|
areaParams.area.x, areaParams.area.y,
|
||||||
areaParams.area.width, areaParams.area.height);
|
areaParams.area.width, areaParams.area.height);
|
||||||
}
|
}
|
||||||
const auto patternImageData =
|
|
||||||
CreatePatternData(patternMatchParams.image);
|
|
||||||
// Will emit status label update
|
// Will emit status label update
|
||||||
MarkMatch(screenshot.image, patternMatchParams,
|
MarkMatch(screenshot.image, patternMatchParams,
|
||||||
patternImageData, objDetectParams, ocrParams,
|
patternImageData, objDetectParams, ocrParams,
|
||||||
@@ -299,7 +297,7 @@ void PreviewImage::MarkMatch(QImage &screenshot,
|
|||||||
VideoCondition condition)
|
VideoCondition condition)
|
||||||
{
|
{
|
||||||
if (condition == VideoCondition::PATTERN) {
|
if (condition == VideoCondition::PATTERN) {
|
||||||
cv::UMat result;
|
cv::Mat result;
|
||||||
MatchPattern(screenshot, patternImageData,
|
MatchPattern(screenshot, patternImageData,
|
||||||
patternMatchParams.threshold, result,
|
patternMatchParams.threshold, result,
|
||||||
patternMatchParams.useAlphaAsMask,
|
patternMatchParams.useAlphaAsMask,
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void CreateImage(const VideoInput &, PreviewType,
|
void CreateImage(const VideoInput &, PreviewType,
|
||||||
const PatternMatchParameters &, ObjDetectParameters,
|
const PatternMatchParameters &,
|
||||||
|
const PatternImageData &, ObjDetectParameters,
|
||||||
OCRParameters, const AreaParameters &, VideoCondition);
|
OCRParameters, const AreaParameters &, VideoCondition);
|
||||||
signals:
|
signals:
|
||||||
void ImageReady(const QPixmap &);
|
void ImageReady(const QPixmap &);
|
||||||
@@ -63,8 +64,9 @@ private slots:
|
|||||||
signals:
|
signals:
|
||||||
void SelectionAreaChanged(QRect area);
|
void SelectionAreaChanged(QRect area);
|
||||||
void NeedImage(const VideoInput &, PreviewType,
|
void NeedImage(const VideoInput &, PreviewType,
|
||||||
const PatternMatchParameters &, ObjDetectParameters,
|
const PatternMatchParameters &, const PatternImageData &,
|
||||||
OCRParameters, const AreaParameters &, VideoCondition);
|
ObjDetectParameters, OCRParameters,
|
||||||
|
const AreaParameters &, VideoCondition);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Start();
|
void Start();
|
||||||
@@ -76,6 +78,7 @@ private:
|
|||||||
|
|
||||||
VideoInput _video;
|
VideoInput _video;
|
||||||
PatternMatchParameters _patternMatchParams;
|
PatternMatchParameters _patternMatchParams;
|
||||||
|
PatternImageData _patternImageData;
|
||||||
ObjDetectParameters _objDetectParams;
|
ObjDetectParameters _objDetectParams;
|
||||||
OCRParameters _ocrParams;
|
OCRParameters _ocrParams;
|
||||||
AreaParameters _areaParams;
|
AreaParameters _areaParams;
|
||||||
|
|||||||
@@ -168,11 +168,22 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
|
|||||||
obs_data_set_string(obj, "version", currentVersion.c_str());
|
obs_data_set_string(obj, "version", currentVersion.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwitcherData::AddResetForNextIntervalFunction(
|
void SwitcherData::AddIntervalResetStep(std::function<void()> function)
|
||||||
std::function<void()> function)
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
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
|
} // namespace advss
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ public:
|
|||||||
|
|
||||||
void SetPreconditions();
|
void SetPreconditions();
|
||||||
void ResetForNextInterval();
|
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,
|
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
|
||||||
int &linger, bool &setPreviousSceneAsMatch,
|
int &linger, bool &setPreviousSceneAsMatch,
|
||||||
bool ¯oMatch);
|
bool ¯oMatch);
|
||||||
@@ -112,7 +114,9 @@ public:
|
|||||||
std::atomic_bool abortMacroWait = {false};
|
std::atomic_bool abortMacroWait = {false};
|
||||||
std::condition_variable macroTransitionCv;
|
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 firstBoot = true;
|
||||||
bool transitionActive = false;
|
bool transitionActive = false;
|
||||||
@@ -204,12 +208,12 @@ public:
|
|||||||
QStringList loadFailureLibs;
|
QStringList loadFailureLibs;
|
||||||
bool warnPluginLoadFailure = true;
|
bool warnPluginLoadFailure = true;
|
||||||
bool disableHints = false;
|
bool disableHints = false;
|
||||||
|
bool disableFilterComboboxFilter = false;
|
||||||
bool hideLegacyTabs = true;
|
bool hideLegacyTabs = true;
|
||||||
std::vector<int> tabOrder = std::vector<int>(tab_count);
|
std::vector<int> tabOrder = std::vector<int>(tab_count);
|
||||||
bool saveWindowGeo = false;
|
bool saveWindowGeo = false;
|
||||||
QPoint windowPos = {};
|
QPoint windowPos = {};
|
||||||
QSize windowSize = {};
|
QSize windowSize = {};
|
||||||
QList<int> macroActionConditionSplitterPosition;
|
|
||||||
QList<int> macroListMacroEditSplitterPosition;
|
QList<int> macroListMacroEditSplitterPosition;
|
||||||
|
|
||||||
/* --- End of UI section --- */
|
/* --- End of UI section --- */
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
|
|||||||
{
|
{
|
||||||
auto con = connection.lock();
|
auto con = connection.lock();
|
||||||
if (!con) {
|
if (!con) {
|
||||||
return "invalid connection selection";
|
return obs_module_text("AdvSceneSwitcher.connection.invalid");
|
||||||
}
|
}
|
||||||
return con->Name();
|
return con->Name();
|
||||||
}
|
}
|
||||||
@@ -248,6 +248,7 @@ ConnectionSelection::ConnectionSelection(QWidget *parent)
|
|||||||
AskForSettingsWrapper,
|
AskForSettingsWrapper,
|
||||||
"AdvSceneSwitcher.connection.select",
|
"AdvSceneSwitcher.connection.select",
|
||||||
"AdvSceneSwitcher.connection.add",
|
"AdvSceneSwitcher.connection.add",
|
||||||
|
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||||
"AdvSceneSwitcher.connection.configure", parent)
|
"AdvSceneSwitcher.connection.configure", parent)
|
||||||
{
|
{
|
||||||
// Connect to slots
|
// Connect to slots
|
||||||
@@ -297,7 +298,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
|||||||
const Connection &settings)
|
const Connection &settings)
|
||||||
: ItemSettingsDialog(settings, switcher->connections,
|
: ItemSettingsDialog(settings, switcher->connections,
|
||||||
"AdvSceneSwitcher.connection.select",
|
"AdvSceneSwitcher.connection.select",
|
||||||
"AdvSceneSwitcher.connection.add", parent),
|
"AdvSceneSwitcher.connection.add",
|
||||||
|
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
||||||
_useCustomURI(new QCheckBox()),
|
_useCustomURI(new QCheckBox()),
|
||||||
_customUri(new QLineEdit()),
|
_customUri(new QLineEdit()),
|
||||||
_address(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 {
|
namespace advss {
|
||||||
|
|
||||||
|
bool FilterComboBox::_filteringEnabled = false;
|
||||||
|
|
||||||
FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler)
|
FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler)
|
||||||
: QComboBox(parent)
|
: 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
|
// Allow edit for completer but don't add new entries on pressing enter
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
setInsertPolicy(InsertPolicy::NoInsert);
|
setInsertPolicy(InsertPolicy::NoInsert);
|
||||||
|
|
||||||
if (!placehodler.isEmpty()) {
|
if (!placehodler.isEmpty()) {
|
||||||
lineEdit()->setPlaceholderText(placehodler);
|
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);
|
setMaxVisibleItems(30);
|
||||||
@@ -31,6 +57,11 @@ FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler)
|
|||||||
&FilterComboBox::TextChagned);
|
&FilterComboBox::TextChagned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FilterComboBox::SetFilterBehaviourEnabled(bool value)
|
||||||
|
{
|
||||||
|
FilterComboBox::_filteringEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
void FilterComboBox::focusOutEvent(QFocusEvent *event)
|
void FilterComboBox::focusOutEvent(QFocusEvent *event)
|
||||||
{
|
{
|
||||||
// Reset on invalid selection
|
// Reset on invalid selection
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "export-symbol-helper.hpp"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
// Helper class which enables user to filter possible selections by typing
|
// Helper class which enables user to filter possible selections by typing
|
||||||
class FilterComboBox : public QComboBox {
|
class ADVSS_EXPORT FilterComboBox : public QComboBox {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FilterComboBox(QWidget *parent = nullptr,
|
FilterComboBox(QWidget *parent = nullptr,
|
||||||
const QString &placehodler = "");
|
const QString &placehodler = "");
|
||||||
|
static void SetFilterBehaviourEnabled(bool);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void focusOutEvent(QFocusEvent *event) override;
|
void focusOutEvent(QFocusEvent *event) override;
|
||||||
@@ -20,6 +23,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
int _lastCompleterHighlightRow = -1;
|
int _lastCompleterHighlightRow = -1;
|
||||||
|
static bool _filteringEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ static bool ItemNameAvailable(const std::string &name,
|
|||||||
ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
||||||
CreateItemFunc create, SettingsCallback callback,
|
CreateItemFunc create, SettingsCallback callback,
|
||||||
std::string_view select, std::string_view add,
|
std::string_view select, std::string_view add,
|
||||||
|
std::string_view conflict,
|
||||||
std::string_view configureTooltip, QWidget *parent)
|
std::string_view configureTooltip, QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
_selection(new FilterComboBox(this, obs_module_text(select.data()))),
|
_selection(new FilterComboBox(this, obs_module_text(select.data()))),
|
||||||
@@ -54,7 +55,8 @@ ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
|||||||
_askForSettings(callback),
|
_askForSettings(callback),
|
||||||
_items(items),
|
_items(items),
|
||||||
_selectStr(select),
|
_selectStr(select),
|
||||||
_addStr(add)
|
_addStr(add),
|
||||||
|
_conflictStr(conflict)
|
||||||
{
|
{
|
||||||
_modify->setMaximumWidth(22);
|
_modify->setMaximumWidth(22);
|
||||||
SetButtonIcon(_modify, ":/settings/images/settings/general.svg");
|
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)
|
void ItemSelection::ChangeSelection(const QString &sel)
|
||||||
{
|
{
|
||||||
if (sel == obs_module_text(_addStr.data())) {
|
if (sel == obs_module_text(_addStr.data())) {
|
||||||
@@ -139,12 +146,14 @@ void ItemSelection::ModifyButtonClicked()
|
|||||||
};
|
};
|
||||||
|
|
||||||
QMenu menu(this);
|
QMenu menu(this);
|
||||||
|
QAction *action;
|
||||||
QAction *action = new QAction(
|
if (_showRenameContextMenu) {
|
||||||
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
|
action = new QAction(
|
||||||
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
|
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
|
||||||
action->setProperty("connetion", QVariant::fromValue(item));
|
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
|
||||||
menu.addAction(action);
|
action->setProperty("item", QVariant::fromValue(item));
|
||||||
|
menu.addAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"),
|
action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"),
|
||||||
&menu);
|
&menu);
|
||||||
@@ -162,7 +171,7 @@ void ItemSelection::ModifyButtonClicked()
|
|||||||
void ItemSelection::RenameItem()
|
void ItemSelection::RenameItem()
|
||||||
{
|
{
|
||||||
QAction *action = reinterpret_cast<QAction *>(sender());
|
QAction *action = reinterpret_cast<QAction *>(sender());
|
||||||
QVariant variant = action->property("connetion");
|
QVariant variant = action->property("item");
|
||||||
Item *item = variant.value<Item *>();
|
Item *item = variant.value<Item *>();
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -174,12 +183,13 @@ void ItemSelection::RenameItem()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
DisplayMessage("AdvSceneSwitcher.item.emptyName");
|
DisplayMessage(
|
||||||
|
obs_module_text("AdvSceneSwitcher.item.emptyName"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_selection->currentText().toStdString() != name &&
|
if (_selection->currentText().toStdString() != name &&
|
||||||
!ItemNameAvailable(name, _items)) {
|
!ItemNameAvailable(name, _items)) {
|
||||||
DisplayMessage("AdvSceneSwitcher.item.nameNotAvailable");
|
DisplayMessage(obs_module_text(_conflictStr.data()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +255,9 @@ Item *ItemSelection::GetCurrentItem()
|
|||||||
ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
||||||
std::deque<std::shared_ptr<Item>> &items,
|
std::deque<std::shared_ptr<Item>> &items,
|
||||||
std::string_view select,
|
std::string_view select,
|
||||||
std::string_view add, QWidget *parent)
|
std::string_view add,
|
||||||
|
std::string_view nameConflict,
|
||||||
|
QWidget *parent)
|
||||||
: QDialog(parent),
|
: QDialog(parent),
|
||||||
_name(new QLineEdit()),
|
_name(new QLineEdit()),
|
||||||
_nameHint(new QLabel),
|
_nameHint(new QLabel),
|
||||||
@@ -253,18 +265,20 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
|||||||
QDialogButtonBox::Cancel)),
|
QDialogButtonBox::Cancel)),
|
||||||
_items(items),
|
_items(items),
|
||||||
_selectStr(select),
|
_selectStr(select),
|
||||||
_addStr(add)
|
_addStr(add),
|
||||||
|
_conflictStr(nameConflict)
|
||||||
{
|
{
|
||||||
setModal(true);
|
setModal(true);
|
||||||
setWindowModality(Qt::WindowModality::WindowModal);
|
setWindowModality(Qt::WindowModality::WindowModal);
|
||||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
setFixedWidth(555);
|
setMinimumWidth(555);
|
||||||
setMinimumHeight(100);
|
setMinimumHeight(100);
|
||||||
|
|
||||||
_buttonbox->setCenterButtons(true);
|
_buttonbox->setCenterButtons(true);
|
||||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(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,
|
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
|
||||||
SLOT(NameChanged(const QString &)));
|
SLOT(NameChanged(const QString &)));
|
||||||
@@ -279,9 +293,8 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
|||||||
void ItemSettingsDialog::NameChanged(const QString &text)
|
void ItemSettingsDialog::NameChanged(const QString &text)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (text != _name->text() && !ItemNameAvailable(text, _items)) {
|
if (text != _originalName && !ItemNameAvailable(text, _items)) {
|
||||||
SetNameWarning(obs_module_text(
|
SetNameWarning(obs_module_text(_conflictStr.data()));
|
||||||
"AdvSceneSwitcher.item.nameNotAvailable"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "filter-combo-box.hpp"
|
#include "filter-combo-box.hpp"
|
||||||
|
#include "export-symbol-helper.hpp"
|
||||||
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QCheckBox>
|
|
||||||
#include <QSpinBox>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QSpinBox>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <obs.hpp>
|
#include <obs-data.h>
|
||||||
#include <websocket-helpers.hpp>
|
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class ItemSelection;
|
class ADVSS_EXPORT ItemSelection;
|
||||||
class ItemSettingsDialog;
|
class ADVSS_EXPORT ItemSettingsDialog;
|
||||||
|
|
||||||
class Item {
|
class Item {
|
||||||
public:
|
public:
|
||||||
@@ -41,10 +36,13 @@ class ItemSettingsDialog : public QDialog {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ItemSettingsDialog(const Item &, std::deque<std::shared_ptr<Item>> &,
|
ItemSettingsDialog(
|
||||||
std::string_view = "AdvSceneSwitcher.item.select",
|
const Item &, std::deque<std::shared_ptr<Item>> &,
|
||||||
std::string_view = "AdvSceneSwitcher.item.select",
|
std::string_view selectString = "AdvSceneSwitcher.item.select",
|
||||||
QWidget *parent = 0);
|
std::string_view addString = "AdvSceneSwitcher.item.add",
|
||||||
|
std::string_view conflictString =
|
||||||
|
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||||
|
QWidget *parent = 0);
|
||||||
virtual ~ItemSettingsDialog() = default;
|
virtual ~ItemSettingsDialog() = default;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@@ -59,6 +57,8 @@ protected:
|
|||||||
std::deque<std::shared_ptr<Item>> &_items;
|
std::deque<std::shared_ptr<Item>> &_items;
|
||||||
std::string_view _selectStr;
|
std::string_view _selectStr;
|
||||||
std::string_view _addStr;
|
std::string_view _addStr;
|
||||||
|
std::string_view _conflictStr;
|
||||||
|
QString _originalName;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef bool (*SettingsCallback)(QWidget *, Item &);
|
typedef bool (*SettingsCallback)(QWidget *, Item &);
|
||||||
@@ -73,9 +73,12 @@ public:
|
|||||||
SettingsCallback,
|
SettingsCallback,
|
||||||
std::string_view selectString = "AdvSceneSwitcher.item.select",
|
std::string_view selectString = "AdvSceneSwitcher.item.select",
|
||||||
std::string_view addString = "AdvSceneSwitcher.item.add",
|
std::string_view addString = "AdvSceneSwitcher.item.add",
|
||||||
|
std::string_view conflictString =
|
||||||
|
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||||
std::string_view configureTooltip = "", QWidget *parent = 0);
|
std::string_view configureTooltip = "", QWidget *parent = 0);
|
||||||
virtual ~ItemSelection() = default;
|
virtual ~ItemSelection() = default;
|
||||||
void SetItem(const std::string &);
|
void SetItem(const std::string &);
|
||||||
|
void ShowRenameContextMenu(bool value);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ModifyButtonClicked();
|
void ModifyButtonClicked();
|
||||||
@@ -101,6 +104,8 @@ protected:
|
|||||||
std::deque<std::shared_ptr<Item>> &_items;
|
std::deque<std::shared_ptr<Item>> &_items;
|
||||||
std::string_view _selectStr;
|
std::string_view _selectStr;
|
||||||
std::string_view _addStr;
|
std::string_view _addStr;
|
||||||
|
std::string_view _conflictStr;
|
||||||
|
bool _showRenameContextMenu = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace advss
|
} // 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();
|
show();
|
||||||
|
|
||||||
// Trigger resize
|
// Trigger resize
|
||||||
_inputEdit->setPlainText("");
|
_inputEdit->setPlainText(_inputEdit->toPlainText());
|
||||||
|
|
||||||
exec();
|
exec();
|
||||||
this->deleteLater();
|
this->deleteLater();
|
||||||
@@ -85,6 +85,12 @@ std::optional<std::string> NonModalMessageDialog::GetInput()
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NonModalMessageDialog::SetInput(const QString &input)
|
||||||
|
{
|
||||||
|
assert(_type == Type::INPUT);
|
||||||
|
_inputEdit->setPlainText(input);
|
||||||
|
}
|
||||||
|
|
||||||
void NonModalMessageDialog::YesClicked()
|
void NonModalMessageDialog::YesClicked()
|
||||||
{
|
{
|
||||||
_answer = QMessageBox::Yes;
|
_answer = QMessageBox::Yes;
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ public:
|
|||||||
NonModalMessageDialog(const QString &message, bool question);
|
NonModalMessageDialog(const QString &message, bool question);
|
||||||
QMessageBox::StandardButton ShowMessage();
|
QMessageBox::StandardButton ShowMessage();
|
||||||
std::optional<std::string> GetInput();
|
std::optional<std::string> GetInput();
|
||||||
Type GetType() { return _type; }
|
Type GetType() const { return _type; }
|
||||||
|
void SetInput(const QString &);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void YesClicked();
|
void YesClicked();
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <winsock.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
std::unordered_map<size_t, OSCMessageElement::TypeInfo>
|
std::unordered_map<size_t, OSCMessageElement::TypeInfo>
|
||||||
@@ -360,7 +366,7 @@ OSCMessageElementEdit::OSCMessageElementEdit(QWidget *parent)
|
|||||||
_text->hide();
|
_text->hide();
|
||||||
_binaryText->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(
|
_type->addItem(obs_module_text(
|
||||||
OSCMessageElement::_typeNames.at(i).localizedName));
|
OSCMessageElement::_typeNames.at(i).localizedName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,29 +162,6 @@ static bool getSceneItemAtIdx(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
|||||||
return true;
|
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 {
|
struct GroupData {
|
||||||
std::string type;
|
std::string type;
|
||||||
std::vector<OBSSceneItem> items = {};
|
std::vector<OBSSceneItem> items = {};
|
||||||
@@ -367,7 +344,6 @@ std::vector<OBSSceneItem> SceneItemSelection::GetSceneItemsByName(
|
|||||||
} else {
|
} else {
|
||||||
name = GetWeakSourceName(_source);
|
name = GetWeakSourceName(_source);
|
||||||
}
|
}
|
||||||
int count = getCountOfSceneItemOccurance(sceneSelection, name, false);
|
|
||||||
auto items = getSceneItemsWithName(scene, name);
|
auto items = getSceneItemsWithName(scene, name);
|
||||||
ReduceBadedOnIndexSelection(items);
|
ReduceBadedOnIndexSelection(items);
|
||||||
return items;
|
return items;
|
||||||
@@ -409,7 +385,7 @@ std::vector<OBSSceneItem> SceneItemSelection::GetSceneItemsByIdx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto sceneWeakSource = sceneSelection.GetScene(false);
|
auto sceneWeakSource = sceneSelection.GetScene(false);
|
||||||
int count = getTotalSceneItemCountOnScene(sceneWeakSource);
|
int count = GetSceneItemCount(sceneWeakSource);
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -751,15 +727,17 @@ void SceneItemSelectionWidget::SetNameConflictVisibility()
|
|||||||
|
|
||||||
case SceneItemSelection::Type::SOURCE_NAME_PATTERN:
|
case SceneItemSelection::Type::SOURCE_NAME_PATTERN:
|
||||||
case SceneItemSelection::Type::SOURCE_GROUP:
|
case SceneItemSelection::Type::SOURCE_GROUP:
|
||||||
sceneItemCount =
|
sceneItemCount = GetSceneItemCount(_scene.GetScene(false));
|
||||||
getTotalSceneItemCountOnScene(_scene.GetScene(false));
|
break;
|
||||||
|
case SceneItemSelection::Type::INDEX:
|
||||||
|
case SceneItemSelection::Type::INDEX_RANGE:
|
||||||
|
case SceneItemSelection::Type::ALL:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentSelection._type ==
|
if (_currentSelection._type ==
|
||||||
SceneItemSelection::Type::SOURCE_NAME_PATTERN) {
|
SceneItemSelection::Type::SOURCE_NAME_PATTERN) {
|
||||||
int sceneItemCount =
|
int sceneItemCount = GetSceneItemCount(_scene.GetScene(false));
|
||||||
getTotalSceneItemCountOnScene(_scene.GetScene(false));
|
|
||||||
if (sceneItemCount == 0) {
|
if (sceneItemCount == 0) {
|
||||||
_nameConflictIndex->hide();
|
_nameConflictIndex->hide();
|
||||||
return;
|
return;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user